6n%9eW+:83wd
7 | MAILING_SERVICE_CLIENT_ID = 406743098508-3kucpc3tkfloeqnv7tftff0gp8hmn0ru.apps.googleusercontent.com
8 | MAILING_SERVICE_CLIENT_SECRET = f381ppWoIrGGDOhNPbo8pCc_
9 | MAILING_SERVICE_REFRESH_TOKEN = 1//04kYwCqVxCzImCgYIARAAGAQSNwF-L9IrQL3wwbnpLiCJ5l5lDaUHYw2ZnnS_JxaEjNbVsKqTsr-xmroq0NCQ0i51KvFYSpcd7hM
10 | SENDER_EMAIL_ADDRESS = eduspace4reply@gmail.com
11 | PORT = 4000
12 | CLOUD_NAME = eduspace
13 | CLOUD_API_KEY = 838867848496279
14 | CLOUD_API_SECRET = H9rl4L8kFQWbqG4k7mL-D6nhw0c
--------------------------------------------------------------------------------
/frontend/src/components/CourseCard/CourseCard.js:
--------------------------------------------------------------------------------
1 | import { Card, Rate } from 'antd'
2 | import React from 'react'
3 | import {Link} from 'react-router-dom'
4 | const CourseCard = ({ course }) => {
5 |
6 | return (
7 |
8 |
}
11 | >
12 |
13 |
14 | {course.name}
15 |
16 |
17 |
{course.user.name}
18 |
${course.price}
19 |
20 |
21 |
22 |
23 |
24 |
25 | )
26 | }
27 |
28 | export default CourseCard
--------------------------------------------------------------------------------
/frontend/src/pages/Coursepage/Rating.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {AiFillStar, BsStarHalf, BsStar} from 'react-icons/all'
3 |
4 | const Rating = ({value}) => {
5 |
6 | return (
7 |
8 |
9 |
{value >=1 ? : value >= 0.5 ? : }
10 |
{value >=2 ? : value >= 1.5 ? : }
11 |
{value >=3 ? : value >= 2.5 ? : }
12 |
{value >=4 ? : value >= 3.5 ? : }
13 |
{value >=5 ? : value >= 4.5 ? : }
14 | {/*
{value} ({text && text}) */}
15 |
16 |
17 | )
18 | }
19 |
20 | export default Rating
21 |
--------------------------------------------------------------------------------
/backend/middleware/uploadImage.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | module.exports = async function (req,res,next) {
3 | try {
4 | if(!req.files || Object.keys(req.files).length === 0)
5 | return res.status(400).json({msg :"No Files were uploaded !"})
6 | const file = req.files.file;
7 |
8 | // console.log(file);
9 | if(file.size > 1024 * 1024){
10 | removeTmp(file.tempFilePath)
11 | return res.status(400).json({msg: "Size too large."})
12 | } // 1mb
13 |
14 | if(file.mimetype !== 'image/jpeg' && file.mimetype !== 'image/png'){
15 | removeTmp(file.tempFilePath)
16 | return res.status(400).json({msg: "File format is incorrect."})
17 | }
18 | next()
19 | } catch (err) {
20 | return res.status(500).json({msg :err.message})
21 | }
22 | }
23 | const removeTmp = (path) => {
24 | fs.unlink(path, err => {
25 | if(err) throw err
26 | })
27 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # E-learning Platform Using MERN
2 | ## MAIN GOAL
3 | #### The main of this project was to built a platform that enable insctructors to sell their knowledge and teach people a lot of skills.
4 | ## DEMO
5 | ##### here is the demo : [EDUSPACE](https://e-duspace.herokuapp.com/)
6 |
7 | ## TECHS
8 | In this project we have used these technologies :
9 | + React js
10 | + Nodejs (Express js)
11 | + [Ant D](https://github.com/ant-design/ant-design)
12 | + Mongo DB
13 | + Redux
14 | + Google apis
15 | + Cloudinary
16 | ## Usage
17 | To run this project you can simply type in the terminal :
18 | ```bash
19 | npm run dev
20 | ```
21 | And it will run a script named dev which runs both server(nodemon) and client(react start script) apps.
22 |
23 | ## Preview
24 | Here is some hidden parts of the project which only the admin or the teach can acess it :
25 | + Admin profile
26 | 
27 | + Teacher profile
28 | 
29 | + Edit a course:
30 | 
31 |
--------------------------------------------------------------------------------
/frontend/src/redux/constants/orderconstants.js:
--------------------------------------------------------------------------------
1 | export const ORDER_CREATE_REQUEST = 'ORDER_CREATE_REQUEST'
2 | export const ORDER_CREATE_SUCCESS = 'ORDER_CREATE_SUCCESS'
3 | export const ORDER_CREATE_FAIL = 'ORDER_CREATE_FAIL'
4 |
5 | export const ORDER_DETAILS_REQUEST = 'ORDER_DETAILS_REQUEST'
6 | export const ORDER_DETAILS_SUCCESS = 'ORDER_DETAILS_SUCCESS'
7 | export const ORDER_DETAILS_FAIL = 'ORDER_DETAILS_FAIL'
8 |
9 | export const ORDER_PAY_REQUEST = 'ORDER_PAY_REQUEST'
10 | export const ORDER_PAY_SUCCESS = 'ORDER_PAY_SUCCESS'
11 | export const ORDER_PAY_FAIL = 'ORDER_PAY_FAIL'
12 | export const ORDER_PAY_RESET = 'ORDER_PAY_RESET'
13 |
14 | export const ORDER_LIST_MY_REQUEST = 'ORDER_LIST_MY_REQUEST'
15 | export const ORDER_LIST_MY_SUCCESS = 'ORDER_LIST_MY_SUCCESS'
16 | export const ORDER_LIST_MY_FAIL = 'ORDER_LIST_MY_FAIL'
17 | export const ORDER_LIST_MY_RESET = 'ORDER_LIST_MY_RESET'
18 |
19 | export const ORDER_LIST_REQUEST = 'ORDER_LIST_REQUEST'
20 | export const ORDER_LIST_SUCCESS = 'ORDER_LIST_SUCCESS'
21 | export const ORDER_LIST_FAIL = 'ORDER_LIST_FAIL'
22 |
23 |
--------------------------------------------------------------------------------
/frontend/src/pages/CourseFilter/CollapsibleFilter.css:
--------------------------------------------------------------------------------
1 | .courseContentFilter {
2 | position: relative;
3 |
4 | color: rgb(51, 51, 51);
5 | }
6 | .courseContentFilter b {
7 | font-size: 18px;
8 | }
9 | .contentCFilter {
10 | display: flex;
11 | justify-content: space-between;
12 | align-items: center;
13 | padding: 15px 30px;
14 | cursor: pointer;
15 | background-color: rgb(255, 255, 255);
16 | border-top: 1px solid rgb(207, 207, 207);
17 | }
18 |
19 | .iconArrowDownFilter {
20 | margin-right: 15px;
21 | }
22 |
23 | .iconCourseCFilter {
24 | display: flex;
25 | align-items: center;
26 | }
27 |
28 | .allCourseContentFilter {
29 | margin-top: 20px;
30 | }
31 | .sessionNameFilter div {
32 | display: flex;
33 | margin-bottom: 20px;
34 | }
35 |
36 | .sessionNameFilter .session {
37 | display: block;
38 | margin-bottom: 10px;
39 | }
40 |
41 | .playIconFilter {
42 | margin-right: 15px;
43 | }
44 |
45 | .sessionsContentFilter {
46 | border-top: 1px solid rgb(207, 207, 207);
47 |
48 | padding: 15px 30px;
49 | }
50 | .start {
51 | margin: 0 7px;
52 | color: rgb(245, 197, 39);
53 | }
54 |
--------------------------------------------------------------------------------
/backend/models/userModel.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose')
2 | // import mongoose from 'mongoose';
3 |
4 |
5 | const userSchema = new mongoose.Schema({
6 | name: {
7 | type: String,
8 | required: [true, "Please enter your name!"],
9 | trim: true
10 | },
11 | email: {
12 | type: String,
13 | required: [true, "Please enter your email!"],
14 | trim: true,
15 | unique: true
16 | },
17 | password: {
18 | type: String,
19 | required: [true, "Please enter your password!"]
20 | },
21 | role: {
22 | type: Number,
23 | default: 0 // 0 = user, 1 = admin
24 | },
25 | Teacher:{
26 | type : Boolean,
27 | required : true,
28 | default : false
29 | },
30 | avatar: {
31 | type: String,
32 | default: "https://i.imgur.com/tJOSejv.png"
33 | },description:{
34 | type : String
35 | },
36 | headline: {
37 | type : String
38 | }
39 | }, {
40 | timestamps: true
41 | })
42 |
43 | module.exports = mongoose.model("Users", userSchema)
--------------------------------------------------------------------------------
/frontend/src/pages/Coursepage/Comments.js:
--------------------------------------------------------------------------------
1 | import React, { useRef, useState } from "react";
2 | import Rating from "./Rating";
3 |
4 | import { BiLike, BiDislike } from "react-icons/all";
5 | const Comments = ({ commentPerson, commentMessage, rating, date }) => {
6 | const [backgroundColor, setBackgroundColor] = useState("white");
7 | const [backgroundDisColor, setBackgroundDisColor] = useState("white");
8 |
9 | return (
10 |
11 |
12 |
13 |
{commentPerson}
14 |
15 |
16 |
17 | ( {date} )
18 |
19 |
20 |
{commentMessage}
21 |
22 |
23 |
24 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | );
35 | };
36 |
37 | export default Comments;
38 |
--------------------------------------------------------------------------------
/frontend/src/components/body/auth/ActivationEmail.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import { useParams } from "react-router-dom";
3 | import { Helmet } from "react-helmet";
4 |
5 | import axios from "axios";
6 | import {
7 | showErrMsg,
8 | showSuccessMsg,
9 | } from "../../utils/notification/Notification";
10 |
11 | const ActivationEmail = () => {
12 | const { activation_token } = useParams();
13 | const [err, setErr] = useState("");
14 | const [success, setSuccess] = useState("");
15 | useEffect(() => {
16 | if (activation_token) {
17 | const activationEmail = async () => {
18 | try {
19 | const res = await axios.post("/user/activation", {
20 | activation_token,
21 | });
22 | console.log(res);
23 | setSuccess(res.data.msg);
24 | } catch (error) {
25 | err.response.data.msg && setErr(err.response.data.msg);
26 | }
27 | };
28 | activationEmail();
29 | }
30 | }, [activation_token]);
31 | return (
32 | <>
33 |
34 | Activate your account
35 |
36 |
37 | {err && showErrMsg(err)}
38 | {success && showSuccessMsg(success)}
39 |
40 | >
41 | );
42 | };
43 |
44 | export default ActivationEmail;
45 |
--------------------------------------------------------------------------------
/backend/models/orderModel.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | const orderSchema = mongoose.Schema(
4 | {
5 | user: {
6 | type: mongoose.Schema.Types.ObjectId,
7 | required: true,
8 | ref: "Users",
9 | },
10 | orderItems: [
11 | {
12 | name: { type: String, required: true },
13 | image: {
14 | type: String,
15 | },
16 | price: { type: Number, required: true },
17 | course: {
18 | type: mongoose.Schema.Types.ObjectId,
19 | required: true,
20 | ref: "Course",
21 | },
22 | },
23 | ],
24 |
25 | countryCustomer: {
26 | country: {
27 | type: String,
28 | required: true,
29 | },
30 | },
31 | paymentMethod: {
32 | type: String,
33 | required: true,
34 | },
35 | paymentResult: {
36 | id: { type: String },
37 | status: { type: String },
38 | update_time: { type: String },
39 | email_adress: { type: String },
40 | },
41 |
42 | totalPrice: {
43 | type: Number,
44 | required: true,
45 | default: 0.0,
46 | },
47 | isPaid: {
48 | type: Boolean,
49 | required: true,
50 | default: false,
51 | },
52 | paidAt: {
53 | type: Date,
54 | },
55 | },
56 | {
57 | timestamps: true,
58 | }
59 | );
60 |
61 | module.exports = mongoose.model("Order", orderSchema);
62 |
--------------------------------------------------------------------------------
/backend/routes/userRouter.js:
--------------------------------------------------------------------------------
1 | const router = require('express').Router()
2 | const userCtrl = require('../controllers/userCtrl')
3 | const auth = require('../middleware/auth')
4 | const authAdmin = require('../middleware/authAdmin')
5 | const isTeacher = require('../middleware/isTeacher')
6 | // http://localhost:5000/user/register not http://localhost:5000/signin cuz of rout in server.js
7 |
8 | /*Next, we go for the POST request. We create a
9 | new user in the database and then return the
10 | created user as a response. */
11 |
12 | router.post('/register', userCtrl.register)
13 | router.post('/activation', userCtrl.activateEmail)
14 | router.post('/login', userCtrl.login)
15 | router.post('/refresh_token', userCtrl.getAccessToken)
16 | router.post('/forgot', userCtrl.forgotPassword)
17 | router.post('/reset', auth, userCtrl.resetPassword)
18 | // login as normal user -> refresh_token -> grtUserInfo
19 | router.get('/infor', auth, userCtrl.getUserInfor)
20 | // login as admin -> refresh_token -> grtAllUsersInfo
21 | router.get('/all_infor', auth, authAdmin, userCtrl.getUsersAllInfor)
22 | router.get('/logout', userCtrl.logout)
23 | // login as normal user -> refresh_token -> update
24 | router.put('/update', auth, userCtrl.updateUser)
25 | // login as admin -> refresh_token -> update
26 | router.patch('/update_role/:id', auth, authAdmin, userCtrl.updateUsersRole)
27 | router.delete('/delete/:id', auth, authAdmin, userCtrl.deleteUser)
28 |
29 |
30 | module.exports = router
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1.0.0",
3 | "description": "\"# E-learning MERN\"",
4 | "main": "index.js",
5 | "scripts": {
6 | "start": "node backend/server",
7 | "server": "nodemon backend/server",
8 | "client": "npm start --prefix frontend",
9 | "test": "echo \"Error: no test specified\" && exit 1",
10 | "dev": "concurrently \"npm run server\" \"npm run client\" ",
11 | "heroku-postbuild": "NPM_CONFIG_PRODUCTION=false npm install --prefix frontend && npm run build --prefix frontend"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "git+https://github.com/Bourhjoul/E-learning-platform-Mern.git"
16 | },
17 | "keywords": [],
18 | "author": "ABDESSAMAD BOURHJOUL & SOUHAIL OUABOU & SOUFIAN ZAAM",
19 | "license": "ISC",
20 | "bugs": {
21 | "url": "https://github.com/Bourhjoul/E-learning-platform-Mern/issues"
22 | },
23 | "homepage": "https://github.com/Bourhjoul/E-learning-platform-Mern#readme",
24 | "dependencies": {
25 | "concurrently": "^6.1.0",
26 | "bcrypt": "^5.0.1",
27 | "cloudinary": "^1.25.1",
28 | "cookie-parser": "^1.4.5",
29 | "cors": "^2.8.5",
30 | "dotenv": "^9.0.2",
31 | "express": "^4.17.1",
32 | "express-fileupload": "^1.2.1",
33 | "googleapis": "^73.0.0",
34 | "jsonwebtoken": "^8.5.1",
35 | "mongoose": "^5.12.8",
36 | "node-fetch": "^2.6.1",
37 | "nodemailer": "^6.6.0",
38 | "redux-devtools-extension": "^2.13.9",
39 | "redux-thunk": "^2.3.0",
40 | "nodemon": "^2.0.7"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/frontend/src/pages/Placeorderscreen/Paypal.js:
--------------------------------------------------------------------------------
1 | import React, { useRef, useEffect } from 'react'
2 | import {useDispatch,useSelector} from 'react-redux'
3 | const Paypal = () => {
4 |
5 | const cartReducer = useSelector(state => state.cartReducer)
6 | const {cartItems} = cartReducer
7 |
8 |
9 | const paypal = useRef()
10 |
11 | useEffect(() => {
12 | window.paypal.Buttons({
13 | createOrder: (data, actions, err) => {
14 | return actions.order.create({
15 |
16 | intent: 'CAPTURE',
17 | purchase_units: [
18 | {
19 | description: "Cool looking table",
20 | amount: {
21 | currency_code: 'USD',
22 | value: cartItems.reduce((acc,item )=> acc + item.price,0).toFixed(2)
23 |
24 | }
25 | }
26 | ]
27 | })
28 | },
29 | onApprove: async(data, actions) => {
30 | const order = await actions.order.capture()
31 | console.log(order)
32 | },
33 | onError: (err) => {
34 | console.log(err)
35 | }
36 | }).render(paypal.current)
37 | }, [])
38 | return (
39 |
44 | )
45 | }
46 |
47 | export default Paypal
48 |
--------------------------------------------------------------------------------
/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "e-learning",
3 | "version": "0.1.0",
4 | "private": true,
5 | "proxy": "http://localhost:4000",
6 | "dependencies": {
7 | "@react-icons/all-files": "^4.1.0",
8 | "@testing-library/jest-dom": "^5.12.0",
9 | "@testing-library/react": "^11.2.6",
10 | "@testing-library/user-event": "^12.8.3",
11 | "antd": "^4.15.4",
12 | "axios": "^0.21.1",
13 | "immer": "^8.0.1",
14 | "immutability-helper": "^3.1.1",
15 | "react": "^17.0.2",
16 | "react-dom": "^17.0.2",
17 | "react-helmet": "^6.1.0",
18 | "react-icons": "^4.2.0",
19 | "react-paypal-button-v2": "^2.6.3",
20 | "react-redux": "^7.2.4",
21 | "react-router-dom": "^5.2.0",
22 | "react-scripts": "4.0.3",
23 | "react-slick": "^0.28.1",
24 | "react-spinners": "^0.11.0",
25 | "reactjs-media": "^1.5.1",
26 | "redux": "^4.1.0",
27 | "redux-devtools-extension": "^2.13.9",
28 | "redux-thunk": "^2.3.0",
29 | "slick-carousel": "^1.8.1",
30 | "web-vitals": "^1.1.1"
31 | },
32 | "scripts": {
33 | "start": "react-scripts start",
34 | "build": "react-scripts build",
35 | "test": "react-scripts test",
36 | "eject": "react-scripts eject"
37 | },
38 | "eslintConfig": {
39 | "extends": [
40 | "react-app",
41 | "react-app/jest"
42 | ]
43 | },
44 | "browserslist": {
45 | "production": [
46 | ">0.2%",
47 | "not dead",
48 | "not op_mini all"
49 | ],
50 | "development": [
51 | "last 1 chrome version",
52 | "last 1 firefox version",
53 | "last 1 safari version"
54 | ]
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/backend/controllers/uploadCtrl.js:
--------------------------------------------------------------------------------
1 | const cloudinary = require('cloudinary')
2 | const fs = require('fs')
3 |
4 | cloudinary.config({
5 | cloud_name :process.env.CLOUD_NAME,
6 | api_key :process.env.CLOUD_API_KEY,
7 | api_secret :process.env.CLOUD_API_SECRET
8 | })
9 |
10 | const uploadCtrl = {
11 | uploadAvatar: async (req, res) => {
12 | try {
13 | const file = req.files.file;
14 | cloudinary.v2.uploader.upload(file.tempFilePath, {
15 | folder: 'avatar', width: 150, height: 150, crop: "fill"
16 | }, async(err, result) => {
17 | if(err) throw err;
18 | removeTmp(file.tempFilePath)
19 | res.json({url: result.secure_url})
20 | })
21 |
22 | } catch (err) {
23 | return res.status(500).json({msg: err.message})
24 | }
25 | },
26 | uploadCourseimage: async (req, res) => {
27 | try {
28 | const file = req.files.file;
29 |
30 | cloudinary.v2.uploader.upload(file.tempFilePath, {
31 | folder: 'avatar', width: 1920, height: 1280
32 | }, async(err, result) => {
33 | if(err) throw err;
34 |
35 | removeTmp(file.tempFilePath)
36 | //console.log({result});
37 | res.json({url: result.secure_url})
38 |
39 | })
40 | } catch (err) {
41 | return res.status(500).json({msg: err.message})
42 | }
43 | }
44 | }
45 | const removeTmp = (path) => {
46 | fs.unlink(path, err => {
47 | if(err) throw err
48 | })
49 | }
50 |
51 | module.exports = uploadCtrl
--------------------------------------------------------------------------------
/frontend/src/pages/CourseFilter/Coursesblock.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Rating from '../Coursepage/Rating'
3 | import {Link } from 'react-router-dom'
4 | const Coursesblock = ({course}) => {
5 | return (
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
{course.name}
16 |
{course.user.name}
17 |
18 | {course.rating}
19 |
20 |
21 |
22 |
63 total hours . All levels
23 |
24 |
25 |
26 |
27 | ${course.price}
28 | $94.99
29 |
30 |
31 |
32 | )
33 | }
34 |
35 | export default Coursesblock
36 |
--------------------------------------------------------------------------------
/frontend/src/pages/Cart/Productcart.js:
--------------------------------------------------------------------------------
1 |
2 | import React,{useRef,useState,useEffect} from 'react'
3 | import {useDispatch} from 'react-redux'
4 |
5 | import { VscChromeClose } from "react-icons/all";
6 | import './Cart.css'
7 | import { removeFromCart } from '../../redux/actions/cartActions';
8 |
9 | import { Link } from 'react-router-dom';
10 | const Productcart = ({course}) => {
11 |
12 | const dispatch = useDispatch()
13 | const removeFromCartHandler = (id) =>{
14 | dispatch(removeFromCart(id))
15 | }
16 | return (
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
{course.name}
25 |
{course.shortdescription}
26 |
By {course.user.name}, {course.user.headline}
27 |
28 |
29 |
30 | {course.price}$
31 |
32 | 300,00 $US
33 |
34 |
35 | removeFromCartHandler(course.course)} />
36 |
37 |
38 |
39 |
40 |
41 | )
42 | }
43 |
44 | export default Productcart
45 |
--------------------------------------------------------------------------------
/frontend/src/redux/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from "redux";
2 | import auth from "./authReducer";
3 | import token from "./tokenReducer";
4 | import {
5 | ListMyCoursesReducer,
6 | ListCoursesReducer,
7 | ListAllCoursesReducer,
8 | ListCoursesbyPobularityReducer,
9 | GetCourseDetailsReducer,
10 | ListNewCoursesReducer,
11 | courseUpdateReducer,
12 | GetSubCategorysReducer,
13 | GetCoursesbysubcg,
14 | courseCreateReducer,
15 | courseDeleteReducer,
16 | CheckStudentReducer,
17 | Createcoursereviewreducer,
18 | listCoursespurshasedreducer,
19 | listCourseSearchedreducer,
20 | ListCoursesbyrating,
21 | ListCoursesbyprice,
22 | } from "./courseReducer";
23 | import { cartReducer } from "./cartReducer";
24 | import {
25 | CreateOrderReducers,
26 | OrderDetailsreducer,
27 | OrderListreducer,
28 | OrderListMyreducer,
29 | OrderPayreducer,
30 | } from "./orderReducers";
31 | import usersInfo from "./usersInfoReducer";
32 | export default combineReducers({
33 | auth,
34 | token,
35 | ListMyCoursesReducer,
36 | ListCoursesReducer,
37 | ListCoursesbyPobularityReducer,
38 | ListAllCoursesReducer,
39 | GetCourseDetailsReducer,
40 | GetSubCategorysReducer,
41 | GetCoursesbysubcg,
42 | cartReducer,
43 | usersInfo,
44 | ListNewCoursesReducer,
45 | courseUpdateReducer,
46 | courseCreateReducer,
47 | courseDeleteReducer,
48 | CheckStudentReducer,
49 | listCoursespurshasedreducer,
50 | Createcoursereviewreducer,
51 | listCourseSearchedreducer,
52 | ListCoursesbyrating,
53 | ListCoursesbyprice,
54 | orderCreate: CreateOrderReducers,
55 | orderDetails: OrderDetailsreducer,
56 | orderPay: OrderPayreducer,
57 | orderMylist: OrderListMyreducer,
58 | orderList: OrderListreducer,
59 | });
60 |
--------------------------------------------------------------------------------
/frontend/src/components/body/auth/ForgotPassword.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import axios from "axios";
3 | import {
4 | showErrMsg,
5 | showSuccessMsg,
6 | } from "../../utils/notification/Notification";
7 | import { isEmail } from "../../utils/validation/Validation";
8 | import { Input, Button } from "antd";
9 |
10 | const initialState = {
11 | email: "",
12 | success: "",
13 | err: "",
14 | };
15 | const ForgotPassword = () => {
16 | const [data, setData] = useState(initialState);
17 | const { email, success, err } = data;
18 | const handleChange = (e) => {
19 | setData({ ...data, [e.target.name]: e.target.value, err: "", success: "" });
20 | };
21 | const forgotPassword = async () => {
22 | if (!isEmail(email))
23 | return setData({ ...data, err: "Invalid email", success: "" });
24 | try {
25 | console.log("test");
26 | const res = await axios.post("/user/forgot", { email });
27 |
28 | return setData({ ...data, err: "", success: res.data.msg });
29 | } catch (err) {
30 | err.response.data.msg &&
31 | setData({ ...data, err: err.response.data.msg, success: "" });
32 | }
33 | };
34 | return (
35 |
36 |
37 |
Forgot Your Password ?
38 | {err && showErrMsg(err)}
39 | {success && showSuccessMsg(success)}
40 | Enter your email address
41 |
49 | Verify your email
50 |
51 |
52 | );
53 | };
54 | export default ForgotPassword;
55 |
--------------------------------------------------------------------------------
/backend/routes/courseRouter.js:
--------------------------------------------------------------------------------
1 | const router = require("express").Router();
2 | const coursesCtrl = require("../controllers/coursesCtrl");
3 | const auth = require("../middleware/auth");
4 | const admin = require("../middleware/authAdmin");
5 | const isTeacher = require("../middleware/isTeacher");
6 | //visit -> list 6 courses by cg
7 | router.get("/topic", coursesCtrl.getallcoursesbycategory);
8 | //visit -> list 6 courses sorted by rating
9 | router.get("/pobular", coursesCtrl.getcoursesbypob);
10 | router.get("/searched", coursesCtrl.getcoursesSearched);
11 | //login as a teacher -> refresh_token -> getallmycourses
12 | router.get("/Mycourses", auth, isTeacher, coursesCtrl.getMycourses);
13 | //login as an admin -> refresh_token -> getallcourses
14 | router.get("/Allcourses", auth, admin, coursesCtrl.getAllcourses);
15 | //visit -> check the membership of student in a course
16 | router.get("/checkmembership", auth, coursesCtrl.studentMembership);
17 | //login as a student -> refresh_token -> getallCoursespurshased
18 | router.get("/Coursespurshased", auth, coursesCtrl.getcoursespurshased);
19 |
20 | router.get("/subcategory/:Topic", coursesCtrl.getsubcategorys);
21 | router.post("/rate/:Topic", coursesCtrl.getcoursesbyrate);
22 | router.post("/price/:Topic", coursesCtrl.getcoursesbyprice);
23 |
24 | router.get("/subcg/:subcg", coursesCtrl.getcoursesbysubcg);
25 | //login as a student -> refresh_token -> getallCoursespurshased
26 | router.post("/createreview/:id", auth, coursesCtrl.createcoursereview);
27 | //visit -> Get course details
28 | router.get("/details/:id", coursesCtrl.getcoursedetails);
29 | router.put("/updatecourse/:id", coursesCtrl.updateCourse);
30 | router.delete("/deletecourse/:id", coursesCtrl.deleteCourse);
31 | router.post("/addcourse", auth, isTeacher, coursesCtrl.addCourse);
32 |
33 | module.exports = router;
34 |
--------------------------------------------------------------------------------
/frontend/src/App.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap');
2 | @import "slick-carousel/slick/slick.css";
3 | @import "slick-carousel/slick/slick-theme.css";
4 |
5 | *{
6 | margin: 0;
7 | padding: 0;
8 | box-sizing: border-box;
9 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
10 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
11 | sans-serif;
12 | -webkit-font-smoothing: antialiased;
13 | -moz-osx-font-smoothing: grayscale;
14 | }
15 | .Btn {
16 | cursor: pointer;
17 | position: relative;
18 | background-color: transparent;
19 | border: none;
20 | height : 35px;
21 | width: 100px;
22 | font-weight: 600;
23 | transition: all 0.2s linear 0.2s;
24 | border-radius: 0px;
25 | overflow: hidden;
26 | align-self: center;
27 | justify-self: center;
28 | }
29 | .main {
30 | scroll-behavior: smooth;
31 | /* min-height: 100vh; */
32 | }
33 |
34 | .lined {
35 | color: rgb(255, 255, 255);
36 | opacity: 1;
37 | list-style: none;
38 | width: fit-content;
39 | cursor: pointer;
40 | transition: all 0.3s linear;
41 | }
42 | .lined:hover{
43 | opacity: 1;
44 | }
45 | .lined::after {
46 | position: relative;
47 | content: "";
48 | display: block;
49 | right: 10px;
50 | bottom: 20px;
51 | width: 3px;
52 | height: 0px;
53 | background: #fff;
54 | transition: height 0.3s linear;
55 | }
56 | .lined:hover::after {
57 | height: 20px;
58 | /* width: 100%; */
59 | transition: height 0.3s linear;
60 | }
61 |
62 | /* ----------------- NAVBAR ------------------- */
63 | /* @import url("./components/nav/Navbar.css");*/
64 | /* ----------------- AUTH ------------------- */
65 | /*@import url("./components/body/auth/auth.css");*/
--------------------------------------------------------------------------------
/backend/seeder.js:
--------------------------------------------------------------------------------
1 | const dotenv = require("dotenv");
2 | const mongoose = require('mongoose')
3 | const Users = require('./models/userModel')
4 | const Courses = require('./models/CourseModel')
5 | const samplecourses = require ('./Data/Courses')
6 | dotenv.config()
7 | const CONNECTION_URL = 'mongodb+srv://souhail:souhail2001@cluster0.bnzut.mongodb.net/myDataBase?retryWrites=true&w=majority';
8 |
9 | const connectDB = async () => {
10 | try{
11 | const conn = await mongoose.connect(CONNECTION_URL,{
12 | useUnifiedTopology: true,
13 | useNewUrlParser: true,
14 | useCreateIndex: true
15 | })
16 | console.log(`MongoDB Connected: ${conn.connection.host}`)
17 | } catch (error) {
18 | console.error(`Error: ${error.message}`)
19 | process.exit(1)
20 | }
21 | }
22 |
23 | const importdata = async () => {
24 | connectDB()
25 | try {
26 | const Teacher = await Users.findById('60b420cc9462a83244b17db1')
27 | const sampletcourses = samplecourses.map(course => {
28 | return{...course, user: Teacher}
29 | })
30 | console.log(sampletcourses)
31 | await Courses.insertMany(sampletcourses)
32 | console.log('Data Imported')
33 |
34 | } catch (error) {
35 | console.log(`${error}`)
36 | process.exit(1)
37 | }
38 | }
39 |
40 |
41 | const destroyData = async () => {
42 | connectDB()
43 |
44 | try {
45 | // empty all models
46 | await Courses.deleteMany()
47 |
48 | console.log(`Data Destroyed !`)
49 | } catch (error) {
50 | console.log(`${error}`)
51 | process.exit(1)
52 | }
53 | }
54 |
55 |
56 |
57 | if (process.argv[2] === '-i') {
58 | importdata()
59 |
60 | }else if(process.argv[2] === '-d'){
61 | destroyData()
62 | } else {
63 | console.log('nothing to run')
64 | }
--------------------------------------------------------------------------------
/frontend/src/pages/CourseFilter/Subcategory.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { useDispatch, useSelector } from "react-redux";
3 | import { useParams } from "react-router-dom";
4 | import { GetCoursesbysubcg } from "../../redux/actions/courseActions";
5 | import Coursesblock from "./Coursesblock";
6 | import { Helmet } from "react-helmet";
7 |
8 | import { Button, Skeleton, Pagination } from "antd";
9 | import { Empty } from "antd";
10 | import Error from "../../components/utils/Error";
11 | import { ArrowLeftOutlined } from "@ant-design/icons";
12 |
13 | const Subcategory = ({ history }) => {
14 | const [page, setpage] = useState(1);
15 | const dispatch = useDispatch();
16 | const Coursesbysubcg = useSelector((state) => state.GetCoursesbysubcg);
17 | const { loading, courses, totalcourses, error } = Coursesbysubcg;
18 | let { subcategory } = useParams();
19 | useEffect(() => {
20 | dispatch(GetCoursesbysubcg(subcategory, page));
21 | return () => {};
22 | }, [dispatch, subcategory, page]);
23 | return (
24 |
25 |
26 | {subcategory}
27 |
28 |
history.goBack()}
31 | size="middle"
32 | type="primary"
33 | shape="round"
34 | icon={ }
35 | >
36 | Go Back
37 |
38 |
{subcategory} Courses
39 | {loading ? (
40 |
41 | ) : error ? (
42 |
43 | ) : courses.length === 0 ? (
44 |
45 | ) : (
46 | courses.map((course, index) => (
47 |
48 | ))
49 | )}
50 |
setpage(current)}
54 | total={totalcourses}
55 | />
56 |
57 | );
58 | };
59 |
60 | export default Subcategory;
61 |
--------------------------------------------------------------------------------
/frontend/src/pages/CourseSearch/CourseSeacrh.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { Helmet } from "react-helmet";
3 |
4 | import { useParams } from "react-router-dom";
5 | import { useDispatch, useSelector } from "react-redux";
6 | import { Button, Pagination, Skeleton, Empty } from "antd";
7 | import Error from "../../components/utils/Error";
8 | import Coursesblock from "../CourseFilter/Coursesblock";
9 | import { ListcoursesSearched } from "../../redux/actions/courseActions";
10 | import { ArrowLeftOutlined } from "@ant-design/icons";
11 | const CourseSeacrh = ({ history }) => {
12 | const [page, setpage] = useState(1);
13 | let { keyword } = useParams();
14 | const dispatch = useDispatch();
15 | const listCourseSearchedreducer = useSelector(
16 | (state) => state.listCourseSearchedreducer
17 | );
18 | const { loading, courses, totalcourses, error } = listCourseSearchedreducer;
19 | useEffect(() => {
20 | dispatch(ListcoursesSearched(keyword, page));
21 | return () => {};
22 | }, [dispatch, keyword, page]);
23 | return (
24 |
25 |
26 | {keyword} Results
27 |
28 |
history.goBack()}
31 | size="middle"
32 | type="primary"
33 | shape="round"
34 | icon={ }
35 | >
36 | Go Back
37 |
38 |
Find "{keyword}" Courses
39 | {loading ? (
40 |
41 | ) : error ? (
42 |
43 | ) : courses.length === 0 ? (
44 |
45 | ) : (
46 | courses.map((course, index) => (
47 |
48 | ))
49 | )}
50 |
setpage(current)}
54 | total={totalcourses}
55 | />
56 |
57 | );
58 | };
59 |
60 | export default CourseSeacrh;
61 |
--------------------------------------------------------------------------------
/backend/server.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | require("dotenv").config({ silent: process.env.NODE_ENV === "production" });
3 | const express = require("express");
4 | const mongoose = require("mongoose");
5 | const cors = require("cors");
6 | const cookieParser = require("cookie-parser");
7 | const fileUpoad = require("express-fileupload");
8 | const app = express();
9 | app.use(express.json());
10 | app.use(cors());
11 | app.use(cookieParser());
12 | app.use(fileUpoad({ useTempFiles: true }));
13 |
14 | //Routes
15 | app.use("/user", require("./routes/userRouter"));
16 | app.use("/api", require("./routes/upload"));
17 | app.use("/courses", require("./routes/courseRouter"));
18 | app.use("/orders", require("./routes/orderRoutes"));
19 | app.get("/api/config/paypal", (req, res) =>
20 | res.send(process.env.PAYPAL_CLIENT_ID)
21 | );
22 | app.use((err, req, res, next) => {
23 | // because err.status is undefined
24 | res.status(404).json({
25 | error: {
26 | message: err.message,
27 | },
28 | });
29 | });
30 | __dirname = path.resolve();
31 | console.log(__dirname);
32 | if (process.env.NODE_ENV === "production") {
33 | app.use(express.static(path.join(__dirname, "/frontend/build")));
34 | app.get("*", (req, res) =>
35 | res.sendFile(path.resolve(__dirname, "frontend", "build", "index.html"))
36 | );
37 | } else {
38 | app.get("/", (req, res) => {
39 | res.send("API is Runn....");
40 | });
41 | }
42 |
43 | const PORT = process.env.PORT || 4000;
44 |
45 | const CONNECTION_URL =
46 | "mongodb+srv://souhail:souhail2001@cluster0.bnzut.mongodb.net/myDataBase?retryWrites=true&w=majority";
47 |
48 | mongoose
49 | .connect(CONNECTION_URL, {
50 | useNewUrlParser: true,
51 | useFindAndModify: false,
52 | useCreateIndex: true,
53 | useUnifiedTopology: true,
54 | })
55 | .then(() =>
56 | app.listen(PORT, () =>
57 | console.log(
58 | `Server MongoDb Connected Running on Port: http://localhost:${PORT}`
59 | )
60 | )
61 | )
62 | .catch((error) => console.log(`${error} did not connect`));
63 |
64 | mongoose.set("useFindAndModify", false);
65 |
--------------------------------------------------------------------------------
/frontend/src/components/body/auth/img/avatare.svg:
--------------------------------------------------------------------------------
1 | profile pic
--------------------------------------------------------------------------------
/frontend/src/redux/actions/cartActions.js:
--------------------------------------------------------------------------------
1 | import {
2 | ADD_ITEM_CART,
3 | CART_NAME_PAYMENT,
4 | CART_SAVE_COUNTRY_CUSTOMER,
5 | CART_SAVE_PAYMENT,
6 | REMOVE_ALL_FROM_CART,
7 | REMOVE_ITEM_CART,
8 | } from "../constants/cartconstants";
9 | import axios from "axios";
10 |
11 | export const addToCart = (id) => async (dispatch, getState) => {
12 | const { data } = await axios.get(`/courses/details/${id}`);
13 | console.log("add to cart : ", data);
14 | dispatch({
15 | type: ADD_ITEM_CART,
16 | payload: {
17 | course: data._id,
18 | name: data.name,
19 | user: data.user,
20 | shortdescription: data.shortdescription,
21 | image: data.image,
22 | price: data.price,
23 | rating: data.rating,
24 | numStudents: data.numStudents,
25 | category: data.category,
26 | },
27 | });
28 | localStorage.setItem(
29 | "cartItems",
30 | JSON.stringify(getState().cartReducer.cartItems)
31 | );
32 | };
33 |
34 | export const removeFromCart = (id) => (dispatch, getState) => {
35 | dispatch({
36 | type: REMOVE_ITEM_CART,
37 | payload: id,
38 | });
39 | localStorage.setItem(
40 | "cartItems",
41 | JSON.stringify(getState().cartReducer.cartItems)
42 | );
43 | };
44 |
45 | export const removeAllFromCart = () => (dispatch, getState) => {
46 | dispatch({
47 | type: REMOVE_ALL_FROM_CART,
48 | });
49 | localStorage.removeItem(
50 | "cartItems",
51 | JSON.stringify(getState().cartReducer.cartItems)
52 | );
53 | };
54 |
55 | export const saveCountryCustomer = (data) => (dispatch, getState) => {
56 | dispatch({
57 | type: CART_SAVE_COUNTRY_CUSTOMER,
58 | payload: data,
59 | });
60 | localStorage.setItem("countryCustomer", JSON.stringify(data));
61 | };
62 |
63 | export const saveCartPayment = (data) => (dispatch) => {
64 | dispatch({
65 | type: CART_SAVE_PAYMENT,
66 | payload: data,
67 | });
68 | localStorage.setItem("MethodOfPayment", JSON.stringify(data));
69 | };
70 |
71 | export const saveNamePayment = (data) => (dispatch) => {
72 | dispatch({
73 | type: CART_NAME_PAYMENT,
74 | payload: data,
75 | });
76 | localStorage.setItem("NameOnCard", JSON.stringify(data));
77 | };
78 |
--------------------------------------------------------------------------------
/frontend/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
13 |
14 |
18 |
19 |
22 |
23 |
32 | Eduspace
33 |
34 |
35 | You need to enable JavaScript to run this app.
36 |
37 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/frontend/src/pages/Coursepage/Collapsible.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useRef } from "react";
2 | import { RiArrowUpSLine, RiArrowDownSLine } from "react-icons/all";
3 | const Collapsible = ({ section, isaccessable }) => {
4 | const [isOpen, setIsOpen] = useState(false);
5 | const parentRef = useRef();
6 | if (parentRef.current) console.log(parentRef.current.scrollHeight);
7 |
8 | const disableclick = (e) => {
9 | e.preventDefault();
10 | };
11 |
12 | return (
13 |
14 |
setIsOpen(!isOpen)}>
15 |
16 | {!isOpen ? (
17 |
18 | ) : (
19 |
20 | )}{" "}
21 | {section.name}
22 |
23 |
24 | x sessions / x min
25 |
26 |
27 |
28 |
39 |
40 |
41 |
42 | {section.lectures.map((lecture, index) => {
43 | if (isaccessable) {
44 | return (
45 |
51 | {index} - {lecture.name}
52 |
53 | );
54 | } else {
55 | return (
56 |
57 | {index} - {lecture.name}
58 |
59 | );
60 | }
61 | })}
62 |
63 |
64 |
65 |
66 |
67 | );
68 | };
69 |
70 | export default Collapsible;
71 |
--------------------------------------------------------------------------------
/frontend/src/pages/Cart/Cart.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import "antd/dist/antd.css"; // or 'antd/dist/antd.less'
3 | import Productcart from "./Productcart";
4 | import { Helmet } from "react-helmet";
5 | import { useParams } from "react-router";
6 |
7 | import { Input } from "antd";
8 | import "./Cart.css";
9 | import { addToCart } from "../../redux/actions/cartActions";
10 | import { useDispatch, useSelector } from "react-redux";
11 | import Empty from "./Empty";
12 |
13 | const Cart = ({ match, history }) => {
14 | const dispatch = useDispatch();
15 | const cartReducer = useSelector((state) => state.cartReducer);
16 | const { cartItems } = cartReducer;
17 | const { id } = useParams();
18 | useEffect(() => {
19 | if (id) {
20 | console.log(id);
21 | dispatch(addToCart(id));
22 | }
23 | }, [id, dispatch]);
24 |
25 | const { Search } = Input;
26 |
27 | const checkoutHandler = () => {
28 | history.push("/checkout");
29 | };
30 | return (
31 | <>
32 |
33 | CART
34 |
35 | {cartItems.length === 0 ? (
36 |
37 | ) : (
38 |
39 |
40 |
41 | {cartItems.map((course) => (
42 |
43 | ))}
44 |
45 |
46 |
47 |
Subtotal ({cartItems.length} items)
48 |
49 | {cartItems.reduce((acc, item) => acc + item.price, 0).toFixed(2)}$
50 |
51 |
52 | 600,00 $US
53 |
54 |
55 | VALIDATION
56 |
57 |
58 |
59 |
60 |
66 |
67 |
68 | )}
69 | >
70 | );
71 | };
72 |
73 | export default Cart;
74 |
--------------------------------------------------------------------------------
/frontend/src/components/Footer/Footer.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import './footer.css'
3 |
4 | const Footer = () => {
5 | let d = new Date();
6 | let n = d.getFullYear();
7 | return (
8 |
9 |
10 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
Get in Contact
23 |
24 | ABOUT US
25 | CONTACT US
26 | DISCOVER LIFE MAKERS ACADEMY
27 |
28 |
29 |
30 |
31 |
Follow Us
32 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | Copyright © {n} EDUSPACE All Rights Reserved.
44 |
45 |
46 |
47 |
48 | )
49 | }
50 |
51 | export default Footer
52 |
--------------------------------------------------------------------------------
/frontend/src/redux/reducers/cartReducer.js:
--------------------------------------------------------------------------------
1 | import { ADD_ITEM_CART, CART_NAME_PAYMENT, CART_SAVE_COUNTRY_CUSTOMER, CART_SAVE_PAYMENT, REMOVE_ALL_FROM_CART, REMOVE_ITEM_CART} from "../constants/cartconstants"
2 |
3 | const cartItemsFromStorage = localStorage.getItem('cartItems') ? JSON.parse(localStorage.getItem('cartItems')) : []
4 |
5 | const countryCustomerFromStorage = localStorage.getItem('countryCustomer') ? JSON.parse(localStorage.getItem('countryCustomer')) : {}
6 |
7 | const paymentShippingFromStorage = localStorage.getItem('MethodOfPayment') ? JSON.parse(localStorage.getItem('MethodOfPayment')) : {}
8 |
9 | const nameOnCardFromStorage = localStorage.getItem('NameOnCard') ? JSON.parse(localStorage.getItem('NameOnCard')) : {}
10 |
11 | const initalState = {
12 | cartItems: cartItemsFromStorage,
13 | countryCustomer: countryCustomerFromStorage,
14 | MethodOfPayment: paymentShippingFromStorage,
15 | NameOnCard: nameOnCardFromStorage
16 | }
17 | export const cartReducer = (state = initalState, action) => {
18 | switch(action.type) {
19 | case ADD_ITEM_CART :
20 | const item = action.payload//the new course added
21 | // x.course it's the ID
22 | const existingItem = state.cartItems.find((x) => x.course === item.course)
23 | if (existingItem){
24 | return {
25 | ...state,
26 | cartItems : state.cartItems.map((x) =>
27 | x.course === existingItem.course ? item : x)
28 | }
29 | }
30 | else {
31 | return{
32 | ...state,
33 | cartItems: [...state.cartItems,item]
34 | }}
35 | case REMOVE_ITEM_CART :
36 | return {
37 | ...state,
38 | cartItems : state.cartItems.filter((x) =>
39 | x.course !== action.payload)
40 | }
41 |
42 | case REMOVE_ALL_FROM_CART:
43 |
44 | return {
45 | ...state
46 |
47 | }
48 |
49 | case CART_SAVE_COUNTRY_CUSTOMER:
50 | return {
51 | ...state,
52 | countryCustomer: action.payload,
53 | }
54 |
55 |
56 | case CART_SAVE_PAYMENT:
57 | return {
58 | ...state,
59 | MethodOfPayment: action.payload
60 | }
61 |
62 | case CART_NAME_PAYMENT:
63 | return {
64 | ...state,
65 | NameOnCard: action.payload
66 | }
67 | default : return state
68 | }
69 | }
--------------------------------------------------------------------------------
/frontend/src/components/body/auth/ResetPassword.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { isMatch, isLength } from "../../utils/validation/Validation";
3 | import { useParams } from "react-router-dom";
4 | import {
5 | showErrMsg,
6 | showSuccessMsg,
7 | } from "../../utils/notification/Notification";
8 | import { Button, Input } from "antd";
9 | import { Helmet } from "react-helmet";
10 |
11 | import axios from "axios";
12 | const initialState = {
13 | password: "",
14 | cf_password: "",
15 | success: "",
16 | err: "",
17 | };
18 |
19 | const ResetPassword = () => {
20 | const [data, setData] = useState(initialState);
21 | const { token } = useParams();
22 | const { password, cf_password, err, success } = data;
23 | const handleChange = (e) => {
24 | setData({ ...data, [e.target.name]: e.target.value, err: "", success: "" });
25 | };
26 | const resetPassword = async () => {
27 | if (isLength(password))
28 | return setData({
29 | ...data,
30 | err: "Password must be at least 6 characters.",
31 | success: "",
32 | });
33 |
34 | if (!isMatch(password, cf_password))
35 | return setData({ ...data, err: "Password did not match", success: "" });
36 | try {
37 | const res = await axios.post(
38 | "/user/reset",
39 | { password },
40 | {
41 | headers: { Authorization: token },
42 | }
43 | );
44 | return setData({ ...data, err: "", success: res.data.msg });
45 | } catch (err) {
46 | return setData({ ...data, err: err.response.data.msg, success: "" });
47 | }
48 | };
49 | return (
50 | <>
51 |
52 | Reset Password
53 |
54 |
83 | >
84 | );
85 | };
86 |
87 | export default ResetPassword;
88 |
--------------------------------------------------------------------------------
/backend/controllers/sendMail.js:
--------------------------------------------------------------------------------
1 | const nodemailer = require("nodemailer");
2 | const { google } = require("googleapis");
3 | const { OAuth2 } = google.auth;
4 | const dotenv = require("dotenv");
5 | dotenv.config();
6 | const OAUTH_PLAYGROUND = "https://developers.google.com/oauthplayground";
7 |
8 | const {
9 | MAILING_SERVICE_CLIENT_ID,
10 | MAILING_SERVICE_CLIENT_SECRET,
11 | MAILING_SERVICE_REFRESH_TOKEN,
12 | SENDER_EMAIL_ADDRESS,
13 | } = process.env;
14 |
15 | const oauth2Client = new OAuth2(
16 | MAILING_SERVICE_CLIENT_ID,
17 | MAILING_SERVICE_CLIENT_SECRET,
18 | MAILING_SERVICE_REFRESH_TOKEN,
19 | OAUTH_PLAYGROUND
20 | );
21 |
22 | // send mail
23 | const sendEmail = (to, url, name, txt) => {
24 | console.log("send mail");
25 | oauth2Client.setCredentials({
26 | // access_token : MAILING_SERVICE_CLIENT_SECRET,
27 | refresh_token: MAILING_SERVICE_REFRESH_TOKEN,
28 | });
29 |
30 | const accessToken = oauth2Client.getAccessToken();
31 | const smtpTransport = nodemailer.createTransport({
32 | service: "gmail",
33 | auth: {
34 | type: "OAuth2",
35 | user: SENDER_EMAIL_ADDRESS,
36 | clientId: MAILING_SERVICE_CLIENT_ID,
37 | clientSecret: MAILING_SERVICE_CLIENT_SECRET,
38 | refreshToken: MAILING_SERVICE_REFRESH_TOKEN,
39 | accessToken,
40 | },
41 | });
42 |
43 | const mailOptions = {
44 | from: SENDER_EMAIL_ADDRESS,
45 | to: to,
46 | subject: "EduSpace Activation mail",
47 | html: `
48 |
49 |
50 |
Welcome to EduSpace.
51 | Hey
${name},
52 | Thanks for signing up at EduSpace.
53 | To complete your registration, please confirm your email
${to} by clicking the following button:
54 |
55 |
58 |
If the button doesn't work for any reason, you can also click on the link below:
59 |
60 |
${url}
61 |
62 | `,
63 | };
64 |
65 | smtpTransport.sendMail(mailOptions, (err, infor) => {
66 | console.log(err);
67 | if (err) return err;
68 | return infor;
69 | });
70 | };
71 |
72 | module.exports = sendEmail;
73 |
--------------------------------------------------------------------------------
/backend/controllers/orderControler.js:
--------------------------------------------------------------------------------
1 | const Order = require("../models/orderModel");
2 | const Course = require("../models/CourseModel");
3 |
4 | const coursesCtrl = {
5 | // @desc Create new order
6 | // @route POST /api/orders
7 | // @access Private
8 | addorderitems: async (req, res) => {
9 | const {
10 | orderItems,
11 | countryCustomer,
12 | paymentMethod,
13 | itemsPrice,
14 | totalPrice,
15 | } = req.body;
16 | if (orderItems && orderItems.length === 0) {
17 | res.status(400);
18 | throw new Error("There is no orders");
19 | return;
20 | } else {
21 | const order = new Order({
22 | user: req.user.id,
23 | orderItems,
24 | countryCustomer,
25 | paymentMethod,
26 | itemsPrice,
27 | totalPrice,
28 | });
29 | const createdOrder = await order.save();
30 | res.status(201).json(createdOrder);
31 | }
32 | },
33 | // @desc get order by id
34 | // @route GET /api/orders/:id
35 | // @access Private
36 | getOrderById: async (req, res) => {
37 | const order = await Order.findById(req.params.id).populate(
38 | "user",
39 | "name email"
40 | );
41 | if (order) {
42 | res.json(order);
43 | } else {
44 | res.status(404);
45 | throw new Error("Order Not found");
46 | }
47 | },
48 | // @desc update order to paid
49 | // @route update /api/orders/:id/pay
50 | // @access Private
51 | updateOrderToPaid: async (req, res) => {
52 | const order = await Order.findById(req.params.id);
53 | const userid = req.query.id;
54 | if (order) {
55 | order.isPaid = true;
56 | order.paidAt = Date.now();
57 | order.paymentResult = {
58 | id: req.body.id,
59 | status: req.body.status,
60 | update_time: req.body.update_time,
61 | email: req.body.payer.email,
62 | };
63 | order.orderItems.forEach(async (order) => {
64 | const course = await Course.findById(order.course._id);
65 | course.students.push(userid);
66 | course.numStudents = course.students.length;
67 | await course.save();
68 | });
69 | const updatedOrder = await order.save();
70 | res.json(updatedOrder);
71 | } else {
72 | res.status(404);
73 | throw new Error("Order Not found");
74 | }
75 | },
76 |
77 | // @desc get logged in user orders
78 | // @route GET /api/orders/myorders
79 | // @access Private
80 | GetMyOrders: async (req, res) => {
81 | const orders = await Order.find({ user: req.user._id });
82 | res.json(orders);
83 | },
84 |
85 | // @desc get orders
86 | // @route GET /api/admin/orders
87 | // @access Private/admin
88 | GetOrders: async (req, res) => {
89 | const orders = await Order.find({}).populate("user", "id name");
90 | res.json(orders);
91 | },
92 | };
93 | module.exports = coursesCtrl;
94 |
--------------------------------------------------------------------------------
/frontend/src/pages/Mycourses/Mycourses.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import { Table, Button, Skeleton } from "antd";
3 | import { Link } from "react-router-dom";
4 | import { listMyCourses } from "../../redux/actions/courseActions";
5 | import { AiOutlineEdit, TiDeleteOutline } from "react-icons/all";
6 | import { useDispatch, useSelector } from "react-redux";
7 | import Error from "../../components/utils/Error";
8 | const Mycourses = ({ history }) => {
9 | const { Column } = Table;
10 | let crs = [];
11 | const dispatch = useDispatch();
12 | const ListMyCoursesReducer = useSelector(
13 | (state) => state.ListMyCoursesReducer
14 | );
15 | const { loading, courses, error } = ListMyCoursesReducer;
16 | const auth = useSelector((state) => state.auth);
17 | const { user, isLogged } = auth;
18 |
19 | useEffect(() => {
20 | if (isLogged && user.Teacher) {
21 | dispatch(listMyCourses());
22 | }
23 | }, [dispatch, isLogged, user.Teacher]);
24 |
25 | if (!loading && !error) {
26 | courses.forEach((element, index) => {
27 | crs.push({
28 | key: index,
29 | name: element.name,
30 | price: element.price,
31 | rating: element.rating,
32 | nmbr_stu: element.numStudents,
33 | category: element.category,
34 | });
35 | });
36 | }
37 | return (
38 | <>
39 | {loading ? (
40 |
41 | ) : error ? (
42 |
43 | ) : (
44 |
45 |
46 |
47 |
48 |
49 |
50 | (
55 |
56 |
57 | }
62 | size="small"
63 | >
64 | EDIT
65 |
66 |
67 | }
72 | size="small"
73 | >
74 | DELETE
75 |
76 |
77 | )}
78 | />
79 |
80 | )}
81 | >
82 | );
83 | };
84 |
85 | export default Mycourses;
86 |
--------------------------------------------------------------------------------
/frontend/src/components/Footer/footer.css:
--------------------------------------------------------------------------------
1 | .footer {
2 | margin-top: 10%;
3 | position: relative;
4 | /* top: 600px; */
5 | height: fit-content;
6 | display: grid;
7 | background-color: #373737;
8 | }
9 | .subfooter {
10 | font-weight: 600;
11 | height: fit-content;
12 | background: #252525;
13 | color: #fff;
14 | padding: 20px;
15 | display: grid;
16 | grid-template-columns: 50% 50%;
17 | /* justify-items: center; */
18 | text-align: center;
19 | }
20 | .Payment_methods {
21 | font-weight: 600;
22 | border-right: 1px #fff solid;
23 | justify-self: center;
24 | padding-right: 85px;
25 | /* margin :10px 30px; */
26 | }
27 | .payement_method {
28 | object-fit: contain;
29 | width: 90px;
30 | height: 35px;
31 | background-color: rgba(255, 255, 255, 0.5);
32 | margin-left: 5px;
33 | filter: gray; /* IE6-9 */
34 | -webkit-filter: grayscale(60%);
35 | transition: filter 0.3s linear 0.3s;
36 | cursor: pointer;
37 | }
38 |
39 | .main_footer {
40 | padding: 20px;
41 | display: grid;
42 | grid-template-columns: 50% 50%;
43 | }
44 | .footer_logo {
45 | cursor: pointer;
46 | object-fit: contain;
47 | width: 120px;
48 | height: 35px;
49 | justify-self: center;
50 | filter: gray;
51 | -webkit-filter: grayscale(100%);
52 | transition: filter 0.3s linear 0.3s;
53 | }
54 | .footer_logo:hover,
55 | .payement_method:hover {
56 | filter: none; /* IE6-9 */
57 | -webkit-filter: grayscale(0%);
58 | }
59 | .footer ul {
60 | margin: 10px 30px;
61 | list-style: none;
62 | font-size: 15px;
63 | font-weight: 500;
64 | border-right: 1px #fff solid;
65 | color: #fff;
66 | }
67 | li {
68 | margin: 20px 00px;
69 | }
70 | .socialmedia_links {
71 | text-align: center;
72 | }
73 | .socialmedia_links div {
74 | margin-top: 20px;
75 | }
76 | .social_icons {
77 | cursor: pointer;
78 | object-fit: contain;
79 | height: 30px;
80 | margin-left: 20px;
81 | filter: gray; /* IE6-9 */
82 | -webkit-filter: grayscale(30%);
83 | transition: filter 0.3s linear 0.3s;
84 | }
85 | .social_icons:hover {
86 | filter: none; /* IE6-9 */
87 | -webkit-filter: grayscale(0%);
88 | }
89 |
90 | .get_in_contact h1,
91 | .socialmedia_links h1 {
92 | color: #fff;
93 | font-weight: 600;
94 | font-size: 19px;
95 | text-align: center;
96 | }
97 |
98 | .logo_copyright {
99 | display: grid;
100 | grid-template-columns: 50% 50%;
101 | color: #fff;
102 | }
103 | .copyright {
104 | align-self: center;
105 | text-align: center;
106 | }
107 | @media screen and (max-width: 768px) {
108 | .Payment_methods {
109 | border: none;
110 | border-bottom: 1px #fff solid;
111 | padding-bottom: 5px;
112 | padding-right: 0;
113 | }
114 | .subfooter {
115 | padding: 4px 0px;
116 | grid-template-columns: 1fr;
117 | }
118 | .main_footer {
119 | padding: 5px 0;
120 | }
121 | .payement_method {
122 | margin-bottom: 2px;
123 | width: 80px;
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/backend/models/CourseModel.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | const reviewSchema = mongoose.Schema(
4 | {
5 | name: { type: String, required: true },
6 | rating: { type: Number, required: true },
7 | comment: { type: String, required: true },
8 | user: {
9 | type: mongoose.Schema.Types.ObjectId,
10 | required: true,
11 | ref: "Users", //relation betwen the review and the user
12 | },
13 | Likes: { type: Number, default: 0 },
14 | Dislikes: { type: Number, default: 0 },
15 | },
16 | {
17 | timestamps: true,
18 | }
19 | );
20 | const sectionSchema = mongoose.Schema(
21 | {
22 | name: { type: String, required: true },
23 | lectures: [
24 | {
25 | name: { type: String, required: true },
26 | link: {
27 | type: String,
28 | default: "https://meet.google.com/",
29 | required: true,
30 | },
31 | isStreamed: { type: Boolean, default: false },
32 | streamedAt: Date,
33 | },
34 | ],
35 | },
36 | {
37 | timestamps: true,
38 | }
39 | );
40 |
41 | const courseSchema = mongoose.Schema(
42 | {
43 | user: {
44 | type: mongoose.Schema.Types.ObjectId,
45 | required: true,
46 | ref: "Users", //relation betwen the course and the teacher
47 | },
48 | name: {
49 | type: String,
50 | required: true,
51 | },
52 | image: {
53 | type: String,
54 | default: "https://i.imgur.com/ouOr3VY.jpg",
55 | },
56 | shortdescription: {
57 | type: String,
58 | required: true,
59 | maxLength: 50,
60 | },
61 | description: {
62 | type: String,
63 | required: true,
64 | },
65 | goals: [
66 | {
67 | type: String,
68 | required: true,
69 | },
70 | ],
71 | students: [
72 | {
73 | type: mongoose.Schema.Types.ObjectId,
74 | ref: "Users",
75 | },
76 | ],
77 | content: [sectionSchema],
78 | Prerequisites: [
79 | {
80 | type: String,
81 | required: true,
82 | },
83 | ],
84 | audience: [
85 | {
86 | type: String,
87 | required: true,
88 | },
89 | ],
90 | category: {
91 | type: String,
92 | required: true,
93 | },
94 | subcategorys: [
95 | {
96 | type: String,
97 | required: true,
98 | },
99 | ],
100 | reviews: [reviewSchema],
101 | rating: {
102 | type: Number,
103 | required: true,
104 | default: 0,
105 | },
106 | numReviews: {
107 | type: Number,
108 | required: true,
109 | default: 0,
110 | },
111 | price: {
112 | type: Number,
113 | required: true,
114 | default: 0,
115 | },
116 | numStudents: {
117 | type: Number,
118 | default: 0,
119 | },
120 | },
121 | {
122 | timestamps: true,
123 | }
124 | );
125 |
126 | module.exports = mongoose.model("Course", courseSchema);
127 |
--------------------------------------------------------------------------------
/frontend/src/components/body/profile/editcourse.css:
--------------------------------------------------------------------------------
1 | .edit-course-page {
2 | width: 100%;
3 | height: 120vh;
4 | }
5 | .edit-course-page .wrapper {
6 | width: 100%;
7 | }
8 | .edit-course-page .inner-wrapper {
9 | width: 96vw;
10 | height: 100vh;
11 | display: grid;
12 | grid-template-rows: auto;
13 | row-gap: 10px;
14 | margin: auto;
15 | margin-top: 3px;
16 | padding: 0 10px;
17 | }
18 | .inner-wrapper-image h2 {
19 | text-align: center;
20 | font-size: 1.7rem;
21 | text-transform: uppercase;
22 | color: #333;
23 | margin: 10px 0px;
24 | }
25 | .inner-wrapper-image a p {
26 | text-align: center;
27 | color: crimson;
28 | }
29 |
30 | .inner-wrapper-form {
31 | width: fit-content;
32 | height: 100vh;
33 | display: grid;
34 | grid-template-columns: repeat(2, minmax(160px, 600px));
35 | column-gap: 50px;
36 | }
37 | .edit-course-page .inner-wrapper .avatar {
38 | width: 350px;
39 | height: 150px;
40 | overflow: hidden;
41 | cursor: pointer;
42 | border: 1px solid #c5def5;
43 | border-radius: 5%;
44 | margin: 15px auto;
45 | position: relative;
46 | }
47 | .edit-course-page .wrapper .disable-avatar {
48 | display: none;
49 | }
50 | .edit-course-page .wrapper img {
51 | width: 100%;
52 | height: 100%;
53 | display: block;
54 | object-fit: contain;
55 | }
56 | .edit-course-page .wrapper .avatar span {
57 | position: absolute;
58 | bottom: -100%;
59 | left: 0;
60 | width: 100%;
61 | height: 50%;
62 | background-color: #2583d6b6;
63 | text-align: center;
64 | font-weight: 400;
65 | text-transform: uppercase;
66 | color: white;
67 | transition: 0.6s ease-in-out;
68 | }
69 | .edit-course-page .wrapper .avatar:hover span {
70 | bottom: -15%;
71 | }
72 |
73 | .edit-course-page .wrapper #file_up {
74 | position: absolute;
75 | top: 0;
76 | left: 0;
77 | width: 100%;
78 | height: 100%;
79 | cursor: pointer;
80 | opacity: 0;
81 | }
82 |
83 | .edit-course-page .wrapper label {
84 | display: block;
85 | margin-top: 5px;
86 | }
87 | .edit-course-page .wrapper input,
88 | textarea {
89 | width: 90%;
90 | height: 40px;
91 | margin: 5px 0;
92 | outline: none;
93 | background-color: rgb(238, 246, 255);
94 | padding: 0 5px;
95 | border: 2px solid rgba(27, 120, 202, 0.191);
96 | border-radius: 4px;
97 | }
98 | .edit-course-page .wrapper textarea {
99 | height: 110px;
100 | }
101 | .btn-update-course {
102 | color: white;
103 | text-transform: uppercase;
104 | letter-spacing: 1.4px;
105 | margin: 10px 200px;
106 | cursor: pointer;
107 | }
108 | .btn-crs {
109 | align-items: center;
110 | }
111 | .btn-add-item {
112 | color: white;
113 | text-transform: uppercase;
114 | letter-spacing: 1.4px;
115 | margin: 10px 0;
116 | cursor: pointer;
117 | }
118 |
119 | .form-group-center h2 {
120 | text-align: center;
121 | font-size: 1.5rem;
122 | color: #333;
123 | margin: 10px 0px;
124 | }
125 | .form-group-center,
126 | .form-group-left,
127 | .form-group-right {
128 | text-align: center;
129 | overflow-y: auto;
130 | margin-bottom: 20%;
131 | }
132 |
133 | .ant-tabs-content-holder {
134 | display: flex;
135 | }
136 |
137 | @media screen and (max-width: 1050px) {
138 | .inner-wrapper-form {
139 | grid-template-columns: 1fr;
140 | }
141 | .edit-course-page {
142 | margin-bottom: 50%;
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/frontend/src/components/body/auth/img/avatarRegister.svg:
--------------------------------------------------------------------------------
1 | male_avatar
--------------------------------------------------------------------------------
/frontend/src/pages/Cart/Cart.css:
--------------------------------------------------------------------------------
1 | .cartfull {
2 | display: grid;
3 | position: relative;
4 | margin: 20px 100px;
5 | grid-template-columns: 70% 30%;
6 | }
7 | .totalName {
8 | color: rgb(90, 90, 90);
9 | font-weight: 500;
10 | font-size: 23px;
11 | }
12 | .totalcart {
13 | height: auto;
14 | width: 100%;
15 | margin-top: 40px;
16 | }
17 | .totalcart .totalprice {
18 | color: rgb(46, 46, 46);
19 | font-size: 40px;
20 |
21 | font-weight: 700;
22 | }
23 | .validationBtn {
24 | display: grid;
25 | width: 100%;
26 | grid-column: 1/3;
27 | background-color: #1890ff;
28 | color: rgb(255, 255, 255);
29 | border: none;
30 | padding: 30px 10px;
31 | align-content: center;
32 | font-size: 16px;
33 | outline-style: none;
34 | height: 40px;
35 | cursor: pointer;
36 | font-weight: 500;
37 | transition: all 0.2s linear;
38 | }
39 | .validationBtn:hover {
40 | background-color: #0b2f50;
41 | }
42 |
43 | .cart h1 {
44 | font-size: 23px;
45 | }
46 | .productsoncart {
47 | margin-right: 30px;
48 | }
49 |
50 | .imageCart {
51 | width: 110px;
52 | height: 60px;
53 | }
54 | .productoncart {
55 | display: grid;
56 | position: relative;
57 | align-items: center;
58 | grid-template-columns: 20% 60% 16% 4%;
59 |
60 | border: 1px solid rgba(233, 233, 233, 0.534);
61 | padding: 20px;
62 | box-shadow: 0.2px 0.2px 0.2px 0.2px rgb(218, 215, 215);
63 | border-radius: 2px;
64 | transition: all 0.5s linear 0.1s;
65 | margin-bottom: 10px;
66 | }
67 |
68 | .productNameAFounder b {
69 | font-size: 18px;
70 | transition: all 0.2s linear;
71 | }
72 | .productNameAFounder b:hover {
73 | color: #1890ff;
74 | cursor: pointer;
75 | }
76 |
77 | .productNameAFounder p {
78 | color: gray;
79 | }
80 | .totalpriceCart {
81 | color: rgb(46, 46, 46);
82 | font-size: 35px;
83 |
84 | font-weight: 700;
85 | }
86 |
87 | .deletecart {
88 | justify-self: flex-end;
89 |
90 | cursor: pointer;
91 | transition: all 0.3s linear;
92 | }
93 | .deletecart:hover {
94 | color: #1890ff;
95 | transform: scale(0.8);
96 | }
97 |
98 | .priceOfCourseb {
99 | color: #dd4141;
100 | text-decoration: line-through;
101 | }
102 | .productNameAFounder {
103 | margin-left: 15px;
104 | }
105 | .productImgCart img {
106 | object-fit: contain;
107 | height: auto;
108 | width: 100%;
109 | overflow: hidden;
110 | transition: all 0.5s linear 0.1s;
111 | }
112 | .productImgCart img:hover {
113 | transform: scale(1.1);
114 | }
115 |
116 | .pricebefore {
117 | font-size: 20px;
118 | text-decoration: line-through;
119 | color: #dd4141;
120 | }
121 | /* empty cart */
122 |
123 | .Emptycart {
124 | position: relative;
125 | display: grid;
126 | grid-template-columns: 40% 60%;
127 | height: 600px;
128 | }
129 | .illustration {
130 | position: relative;
131 | right: 50px;
132 | transform: scale(0.5);
133 | }
134 | .arrow {
135 | display: inline;
136 | }
137 | .textempty {
138 | align-self: center;
139 | justify-self: center;
140 | font-size: 30px;
141 | }
142 | .textempty h1 {
143 | font-weight: 600;
144 | font-size: 40px;
145 | }
146 | .textempty .goshop {
147 | transition: all 0.5s linear;
148 | opacity: 0.7;
149 | }
150 | .textempty .goshop:hover {
151 | opacity: 1;
152 | }
153 | @media screen and (max-width: 1000px) {
154 | .cartfull {
155 | grid-template-columns: 100%;
156 | margin: 20px 20px;
157 | }
158 | .productsoncart {
159 | margin-right: 0px;
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/frontend/src/pages/checkout/CheckoutScreen.css:
--------------------------------------------------------------------------------
1 | .checkoutPage {
2 | display: grid;
3 | grid-template-columns: repeat(auto-fill, minmax(460px, 1fr));
4 | column-gap: 50px;
5 | margin: 35px 40px;
6 | }
7 | #country {
8 | padding: 30px;
9 | }
10 | .select-wrap {
11 | border: 1px solid #777;
12 | border-radius: 4px;
13 | margin-bottom: 10px;
14 | padding: 6px 7px 15px;
15 | width: 320px;
16 | background-color: white;
17 | }
18 | .imgRadio {
19 | display: flex;
20 | align-items: center;
21 | }
22 |
23 | .select-wrap label {
24 | font-size: 10px;
25 | text-transform: uppercase;
26 | color: rgb(0, 0, 0);
27 | padding: 2px 8px 0;
28 | }
29 |
30 | .cardPayment {
31 | display: grid;
32 | position: relative;
33 | grid-template-rows: repeat(auto-fill, minmax(auto, 1fr));
34 | row-gap: 20px;
35 | margin-top: 20px;
36 | border: 15px;
37 | border-radius: 15px;
38 | background-color: rgb(240, 240, 240);
39 | padding: 30px;
40 | }
41 |
42 | .moisSecurity {
43 | display: grid;
44 | grid-template-columns: repeat(auto-fill, minmax(190px, 3fr));
45 | column-gap: 3px;
46 | }
47 | .imageShippi {
48 | object-fit: cover;
49 | width: 100%;
50 | }
51 | .orderDetails {
52 | margin-top: 20px;
53 | }
54 | .orderDetailsInfo {
55 | display: grid;
56 | grid-template-columns: 20% 60% 20%;
57 | grid-template-rows: repeat(auto-fill, minmax(65px, 1fr));
58 |
59 | position: relative;
60 | }
61 | .orderDetailsInfo .orderName {
62 | font-weight: 500;
63 | }
64 | .orderPrice {
65 | justify-self: flex-end;
66 | font-weight: 650;
67 | font-size: 13px;
68 | }
69 |
70 | .messageError {
71 | font-size: 18px;
72 | margin-top: 10px;
73 | color: red;
74 | font-weight: 500;
75 | text-align: center;
76 | letter-spacing: 2px;
77 | }
78 |
79 | .priceOfP {
80 | display: grid;
81 | grid-template-columns: repeat(auto-fill, minmax(315px, 1fr));
82 | font-size: 20px;
83 | }
84 | .priceOfP b {
85 | font-weight: 500;
86 | color: #295d91;
87 | }
88 | .priceOfP h3 {
89 | justify-self: flex-end;
90 | }
91 |
92 | .priceOfTotal {
93 | display: grid;
94 | grid-template-columns: repeat(auto-fill, minmax(315px, 1fr));
95 | font-size: 20px;
96 | }
97 | .priceOfTotal b {
98 | color: #295d91;
99 | }
100 | .priceOfTotal h3 {
101 | justify-self: flex-end;
102 | }
103 | .cardPaymentDisabled {
104 | display: none;
105 | }
106 |
107 | .summary {
108 | padding: 50px 50px;
109 | border: 3px solid rgb(209, 209, 209);
110 | }
111 |
112 | .checkoutPage h1 {
113 | font-weight: 700;
114 | }
115 |
116 | .countryPayment {
117 | border: 3px solid rgb(209, 209, 209);
118 | padding: 20px 20px;
119 | }
120 |
121 | .countryPayment b {
122 | letter-spacing: 4px;
123 | color: #295d91;
124 | font-weight: 400;
125 | }
126 |
127 | .countryPayment span {
128 | font-size: 16px;
129 | font-weight: 500;
130 | }
131 |
132 | .countryPayment div {
133 | display: grid;
134 | grid-template-columns: 30% 70%;
135 | }
136 |
137 | .payParagraph {
138 | text-decoration: underline;
139 | font-size: 16px;
140 | }
141 |
142 | .keepOrderId {
143 | border: 3px solid rgb(209, 209, 209);
144 | padding: 20px 20px;
145 | }
146 |
147 | .keepOrderId p {
148 | font-size: 15px;
149 | font-weight: 500;
150 | }
151 |
152 | .keepOrderId h2 {
153 | text-align: center;
154 | color: rgb(190, 49, 49);
155 | font-weight: 600;
156 | }
157 |
158 | .totalPriceOrder {
159 | display: flex;
160 | justify-content: space-between;
161 | }
162 |
163 | .totalPriceNumberOrder {
164 | font-size: 18px;
165 | }
166 |
167 | .lastStepPurchase {
168 | text-align: center;
169 | }
170 |
--------------------------------------------------------------------------------
/frontend/README.md:
--------------------------------------------------------------------------------
1 | # Getting Started with Create React App
2 |
3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
4 |
5 | ## Available Scripts
6 |
7 | In the project directory, you can run:
8 |
9 | ### `npm start`
10 |
11 | Runs the app in the development mode.\
12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
13 |
14 | The page will reload if you make edits.\
15 | You will also see any lint errors in the console.
16 |
17 | ### `npm test`
18 |
19 | Launches the test runner in the interactive watch mode.\
20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
21 |
22 | ### `npm run build`
23 |
24 | Builds the app for production to the `build` folder.\
25 | It correctly bundles React in production mode and optimizes the build for the best performance.
26 |
27 | The build is minified and the filenames include the hashes.\
28 | Your app is ready to be deployed!
29 |
30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
31 |
32 | ### `npm run eject`
33 |
34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
35 |
36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
37 |
38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
39 |
40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
41 |
42 | ## Learn More
43 |
44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
45 |
46 | To learn React, check out the [React documentation](https://reactjs.org/).
47 |
48 | ### Code Splitting
49 |
50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
51 |
52 | ### Analyzing the Bundle Size
53 |
54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
55 |
56 | ### Making a Progressive Web App
57 |
58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
59 |
60 | ### Advanced Configuration
61 |
62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
63 |
64 | ### Deployment
65 |
66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
67 |
68 | ### `npm run build` fails to minify
69 |
70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
71 |
--------------------------------------------------------------------------------
/frontend/src/redux/reducers/orderReducers.js:
--------------------------------------------------------------------------------
1 | import { ORDER_CREATE_FAIL,ORDER_CREATE_REQUEST,ORDER_CREATE_SUCCESS ,ORDER_DETAILS_REQUEST , ORDER_DETAILS_FAIL,ORDER_DETAILS_SUCCESS, ORDER_PAY_RESET, ORDER_PAY_FAIL, ORDER_PAY_SUCCESS, ORDER_PAY_REQUEST, ORDER_LIST_MY_FAIL, ORDER_LIST_MY_SUCCESS, ORDER_LIST_MY_REQUEST, ORDER_LIST_MY_RESET, ORDER_LIST_REQUEST, ORDER_LIST_SUCCESS, ORDER_LIST_FAIL} from "../constants/orderconstants";
2 |
3 | export const CreateOrderReducers = (state = {} , action) => {
4 | switch (action.type) {
5 | case ORDER_CREATE_REQUEST:
6 | return {
7 | loading : true,
8 | }
9 | case ORDER_CREATE_SUCCESS:
10 | return {
11 | loading : false,
12 | success: true,
13 | order: action.payload
14 | }
15 | case ORDER_CREATE_FAIL :
16 | return{
17 | loading : false,
18 | error : action.payload,
19 |
20 | }
21 | default:
22 | return state
23 | }
24 | }
25 |
26 | export const OrderDetailsreducer = (state = {loading : true,orderItems : [], countryCustomer: {}} , action) => {
27 | switch (action.type) {
28 | case ORDER_DETAILS_REQUEST:
29 | return {
30 | ...state,
31 | loading : true,
32 | }
33 | case ORDER_DETAILS_SUCCESS:
34 | return {
35 | loading : false,
36 | order: action.payload
37 | }
38 | case ORDER_DETAILS_FAIL :
39 | return{
40 | loading : false,
41 | error : action.payload,
42 |
43 | }
44 | default:
45 | return state
46 | }
47 | }
48 | export const OrderPayreducer = (state = {} , action) => {
49 | switch (action.type) {
50 | case ORDER_PAY_REQUEST:
51 | return {
52 | loading : true,
53 | }
54 | case ORDER_PAY_SUCCESS:
55 | return {
56 | loading : false,
57 | success : true
58 | }
59 | case ORDER_PAY_FAIL :
60 | return{
61 | loading : false,
62 | error : action.payload,
63 |
64 | }
65 | case ORDER_PAY_RESET:
66 | return{}
67 | default:
68 | return state
69 | }
70 | }
71 |
72 |
73 | export const OrderListMyreducer = (state = {orders : []} , action) => {
74 | switch (action.type) {
75 | case ORDER_LIST_MY_REQUEST:
76 | return {
77 | loading : true,
78 | }
79 | case ORDER_LIST_MY_SUCCESS:
80 | return {
81 | loading : false,
82 | orders : action.payload
83 | }
84 | case ORDER_LIST_MY_FAIL :
85 | return{
86 | loading : false,
87 | error : action.payload,
88 |
89 | }
90 | case ORDER_LIST_MY_RESET :
91 | return{orders : []}
92 | default:
93 | return state
94 | }
95 | }
96 |
97 |
98 |
99 | export const OrderListreducer = (state = {orders : []} , action) => {
100 | switch (action.type) {
101 | case ORDER_LIST_REQUEST:
102 | return {
103 | loading : true,
104 | }
105 | case ORDER_LIST_SUCCESS:
106 | return {
107 | loading : false,
108 | orders : action.payload
109 | }
110 | case ORDER_LIST_FAIL :
111 | return{
112 | loading : false,
113 | error : action.payload,
114 |
115 | }
116 | default:
117 | return state
118 | }
119 | }
--------------------------------------------------------------------------------
/frontend/src/components/body/profile/EditUser.js:
--------------------------------------------------------------------------------
1 | import { useParams, useHistory } from "react-router-dom";
2 | import { React, useState, useEffect } from "react";
3 | import { useSelector } from "react-redux";
4 | import axios from "axios";
5 | import { Helmet } from "react-helmet";
6 |
7 | import { ArrowLeftOutlined, RetweetOutlined } from "@ant-design/icons";
8 | import { Button, Input, Checkbox } from "antd";
9 | import {
10 | showSuccessMsg,
11 | showErrMsg,
12 | } from "../../utils/notification/Notification";
13 |
14 | const EditUser = () => {
15 | const [size, setSize] = useState("middle");
16 |
17 | const { id } = useParams();
18 | const history = useHistory();
19 | const [editUser, setEditUser] = useState([]);
20 | const [num, setNum] = useState(0);
21 |
22 | const usersInfo = useSelector((state) => state.usersInfo);
23 | const { users } = usersInfo;
24 | const token = useSelector((state) => state.token);
25 |
26 | const [err, setErr] = useState(false);
27 | const [success, setSuccess] = useState(false);
28 |
29 | const [checkAdmin, setCheckAdmin] = useState(false);
30 |
31 | const handleCheck = () => {
32 | setSuccess("");
33 | setErr("");
34 | setCheckAdmin(!checkAdmin);
35 | setNum(num + 1);
36 | };
37 | const handleUpdate = async () => {
38 | try {
39 | if (num % 2 !== 0) {
40 | const res = await axios.patch(
41 | `/user/update_role/${editUser._id}`,
42 | {
43 | role: checkAdmin ? 1 : 0,
44 | },
45 | {
46 | headers: { Authorization: token },
47 | }
48 | );
49 |
50 | setSuccess(res.data.msg);
51 | setNum(0);
52 | }
53 | } catch (err) {
54 | err.response.data.msg && setErr(err.response.data.msg);
55 | }
56 | };
57 |
58 | useEffect(() => {
59 | if (users.length !== 0) {
60 | users.forEach((user) => {
61 | if (user._id === id) {
62 | setEditUser(user);
63 | setCheckAdmin(user.role === 1 ? true : false);
64 | }
65 | });
66 | } else {
67 | history.push("/profile");
68 | }
69 | }, [users, id, history]);
70 |
71 | return (
72 |
73 |
74 | EDIT USER
75 |
76 | {err && showErrMsg(err)}
77 | {success && showSuccessMsg(success)}
78 |
79 |
80 |
history.goBack()}
83 | size={size}
84 | type="primary"
85 | shape="round"
86 | icon={ }
87 | >
88 | Go Back
89 |
90 |
91 |
92 |
Edit User
93 |
94 | Name
95 |
102 |
103 |
104 | Email
105 |
112 |
113 |
114 |
115 | isAdmin
116 |
117 |
118 | <>
119 | {" "}
120 |
127 | Update
128 |
129 | >
130 |
131 |
132 |
133 | );
134 | };
135 |
136 | export default EditUser;
137 |
--------------------------------------------------------------------------------
/frontend/src/components/body/profile/profile.css:
--------------------------------------------------------------------------------
1 | .profile-page {
2 | width: 100%;
3 | display: grid;
4 | grid-template-columns: 40% 60%;
5 | }
6 | .ant-table {
7 | overflow-x: scroll;
8 | }
9 | .profile-page .col-left {
10 | max-width: 400px;
11 | min-width: 300px;
12 | margin: auto;
13 | margin-top: 3px;
14 | /* padding: 0 10px; */
15 | }
16 | .profile-page .col-left .avatar {
17 | width: 150px;
18 | height: 150px;
19 | overflow: hidden;
20 | cursor: pointer;
21 | border: 1px solid gray;
22 | border-radius: 50%;
23 | margin: 15px auto;
24 | position: relative;
25 | }
26 | .profile-page .col-left .disable-avatar {
27 | display: none;
28 | }
29 | .profile-page .col-left img {
30 | width: 100%;
31 | height: 100%;
32 | display: block;
33 | object-fit: cover;
34 | }
35 | .profile-page .col-left .avatar span {
36 | position: absolute;
37 | bottom: -100%;
38 | left: 0;
39 | width: 100%;
40 | height: 50%;
41 | background-color: #2583d6b6;
42 | text-align: center;
43 | font-weight: 400;
44 | text-transform: uppercase;
45 | color: white;
46 | transition: 0.6s ease-in-out;
47 | }
48 | .profile-page .col-left .avatar:hover span {
49 | bottom: -15%;
50 | }
51 |
52 | .profile-page .col-left #file_up {
53 | position: absolute;
54 | top: 0;
55 | left: 0;
56 | width: 100%;
57 | height: 100%;
58 | cursor: pointer;
59 | opacity: 0;
60 | }
61 | .profile-page .col-left h2 {
62 | text-align: center;
63 | font-size: 1.7rem;
64 | text-transform: uppercase;
65 | color: #333;
66 | margin: 10px 0px;
67 | }
68 | .profile-page .col-left label {
69 | display: block;
70 | margin-top: 5px;
71 | }
72 | .profile-page .col-left input {
73 | width: 100%;
74 | height: 40px;
75 | margin: 5px 0;
76 | outline: none;
77 | background-color: rgb(238, 246, 255);
78 | padding: 0 5px;
79 | border: 2px solid rgba(27, 120, 202, 0.191);
80 | border-radius: 4px;
81 | }
82 | .profile-page .col-left textarea {
83 | height: 70px;
84 | }
85 |
86 | .btn-update-profile {
87 | color: white;
88 | text-transform: uppercase;
89 | letter-spacing: 1.4px;
90 | margin: 10px 130px;
91 | cursor: pointer;
92 | }
93 |
94 | /* ----------- Col Right----------- */
95 | .profile-page .col-right {
96 | flex: 1;
97 | min-width: 300px;
98 | }
99 | .profile-page .col-right h2 {
100 | font-size: 1.7rem;
101 | text-transform: uppercase;
102 | color: #333;
103 | margin: 10px 0;
104 | }
105 | .btn-edit {
106 | display: block;
107 | width: 100%;
108 | height: 50px;
109 | text-transform: uppercase;
110 | margin: 1rem 0;
111 | }
112 | .btn-delete {
113 | display: block;
114 | width: 100%;
115 | height: 50px;
116 | text-transform: uppercase;
117 | }
118 |
119 | .admin {
120 | color: rgb(29, 223, 45);
121 | font-weight: 800;
122 | }
123 | .notadmin {
124 | color: brown;
125 | font-weight: 800;
126 | }
127 | .loading {
128 | z-index: 20;
129 | text-align: center;
130 | background-color: #fff;
131 | display: flex;
132 | justify-content: center;
133 | align-items: center;
134 | width: 100%;
135 | height: 300px;
136 | }
137 |
138 | /* -------------- Edit User ----------------- */
139 | .edit-page {
140 | width: 100%;
141 | display: grid;
142 | }
143 | .edit-page .col-left {
144 | margin-top: 50px;
145 | width: 100%;
146 | display: grid;
147 | justify-content: center;
148 | }
149 | .edit-page .col-left label {
150 | display: block;
151 | margin-top: 5px;
152 | }
153 | .edit-page .col-left input {
154 | width: 300px;
155 | height: 40px;
156 | margin: 5px 0;
157 | outline: none;
158 | background-color: rgb(238, 246, 255);
159 | padding: 0 5px;
160 | border: 2px solid rgba(27, 120, 202, 0.191);
161 | border-radius: 4px;
162 | }
163 |
164 | .btn-back {
165 | margin: 10px;
166 | }
167 | .btn-add {
168 | text-align: center;
169 | }
170 | .btn-update-user {
171 | width: 200px;
172 | height: 40px;
173 | margin-top: 10px;
174 | justify-self: center;
175 | }
176 | .edit-page .col-left .isAdmin {
177 | width: 20px;
178 | height: 20px;
179 | transform: translateY(3px);
180 | margin: 10px 0;
181 | }
182 | .edit-page .col-left .isAdmin {
183 | margin-left: 115px;
184 | }
185 | .ant-checkbox {
186 | display: inline-block;
187 | margin-right: 8px;
188 | }
189 |
190 | @media screen and (max-width: 1050px) {
191 | .profile-page {
192 | grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
193 | }
194 | }
195 |
--------------------------------------------------------------------------------
/frontend/src/redux/actions/orderActions.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import {
3 | ORDER_CREATE_FAIL,
4 | ORDER_CREATE_REQUEST,
5 | ORDER_CREATE_SUCCESS,
6 | ORDER_DETAILS_FAIL,
7 | ORDER_DETAILS_REQUEST,
8 | ORDER_DETAILS_SUCCESS,
9 | ORDER_LIST_FAIL,
10 | ORDER_LIST_MY_FAIL,
11 | ORDER_LIST_MY_REQUEST,
12 | ORDER_LIST_MY_SUCCESS,
13 | ORDER_LIST_REQUEST,
14 | ORDER_LIST_SUCCESS,
15 | ORDER_PAY_FAIL,
16 | ORDER_PAY_REQUEST,
17 | ORDER_PAY_SUCCESS,
18 | } from "../constants/orderconstants";
19 |
20 | export const CreateOrder = (order) => async (dispatch, getState) => {
21 | try {
22 | dispatch({
23 | type: ORDER_CREATE_REQUEST,
24 | });
25 |
26 | const { token } = getState();
27 |
28 | const config = {
29 | headers: {
30 | Authorization: token,
31 | },
32 | };
33 |
34 | const { data } = await axios.post(`/orders`, order, config);
35 | dispatch({
36 | type: ORDER_CREATE_SUCCESS,
37 | payload: data,
38 | });
39 | } catch (error) {
40 | dispatch({
41 | type: ORDER_CREATE_FAIL,
42 | payload:
43 | error.response && error.response.data.message
44 | ? error.response.data.message
45 | : error.message,
46 | });
47 | }
48 | };
49 |
50 | export const getOrderDetails = (id) => async (dispatch, getState) => {
51 | try {
52 | dispatch({
53 | type: ORDER_DETAILS_REQUEST,
54 | });
55 |
56 | const { token } = getState();
57 |
58 | const config = {
59 | headers: {
60 | Authorization: token,
61 | },
62 | };
63 |
64 | const { data } = await axios.get(`/orders/${id}`, config);
65 | dispatch({
66 | type: ORDER_DETAILS_SUCCESS,
67 | payload: data,
68 | });
69 | } catch (error) {
70 | dispatch({
71 | type: ORDER_DETAILS_FAIL,
72 | payload:
73 | error.response && error.response.data.message
74 | ? error.response.data.message
75 | : error.message,
76 | });
77 | }
78 | };
79 | export const payOrder =
80 | (orderId, paymentResult, userid) => async (dispatch, getState) => {
81 | try {
82 | dispatch({
83 | type: ORDER_PAY_REQUEST,
84 | });
85 |
86 | const { token } = getState();
87 |
88 | const config = {
89 | headers: {
90 | Authorization: token,
91 | },
92 | };
93 |
94 | const { data } = await axios.put(
95 | `/orders/${orderId}/pay?id=${userid}`,
96 | paymentResult,
97 | config
98 | );
99 | dispatch({
100 | type: ORDER_PAY_SUCCESS,
101 | payload: data,
102 | });
103 | } catch (error) {
104 | dispatch({
105 | type: ORDER_PAY_FAIL,
106 | payload:
107 | error.response && error.response.data.message
108 | ? error.response.data.message
109 | : error.message,
110 | });
111 | }
112 | };
113 |
114 | export const listMyOrders = () => async (dispatch, getState) => {
115 | try {
116 | dispatch({
117 | type: ORDER_LIST_MY_REQUEST,
118 | });
119 |
120 | const { token } = getState();
121 |
122 | const config = {
123 | headers: {
124 | Authorization: token,
125 | },
126 | };
127 |
128 | const { data } = await axios.get(`/orders/myorders`, config);
129 | dispatch({
130 | type: ORDER_LIST_MY_SUCCESS,
131 | payload: data,
132 | });
133 | } catch (error) {
134 | dispatch({
135 | type: ORDER_LIST_MY_FAIL,
136 | payload:
137 | error.response && error.response.data.message
138 | ? error.response.data.message
139 | : error.message,
140 | });
141 | }
142 | };
143 |
144 | export const listOrders = () => async (dispatch, getState) => {
145 | try {
146 | dispatch({
147 | type: ORDER_LIST_REQUEST,
148 | });
149 |
150 | const { token } = getState();
151 |
152 | const config = {
153 | headers: {
154 | Authorization: token,
155 | },
156 | };
157 |
158 | const { data } = await axios.get(`/orders/`, config);
159 | dispatch({
160 | type: ORDER_LIST_SUCCESS,
161 | payload: data,
162 | });
163 | } catch (error) {
164 | dispatch({
165 | type: ORDER_LIST_FAIL,
166 | payload:
167 | error.response && error.response.data.message
168 | ? error.response.data.message
169 | : error.message,
170 | });
171 | }
172 | };
173 |
--------------------------------------------------------------------------------
/frontend/src/redux/constants/courseconstants.js:
--------------------------------------------------------------------------------
1 | export const MY_COURSES_REQUEST = "MY_COURSES_REQUEST";
2 | export const MY_COURSES_SUCCESS = "MY_COURSES_SUCCESS";
3 | export const MY_COURSES_FAIL = "MY_COURSES_FAIL";
4 | export const MY_COURSES_RESET = "MY_COURSES_RESET";
5 |
6 | export const ALL_COURSES_REQUEST = "ALL_COURSES_REQUEST";
7 | export const ALL_COURSES_SUCCESS = "ALL_COURSES_SUCCESS";
8 | export const ALL_COURSES_FAIL = "ALL_COURSES_FAIL";
9 | export const ALL_COURSES_RESET = "ALL_COURSES_RESET";
10 |
11 | export const LIST_COURSES_REQUEST = "LIST_COURSES_REQUEST";
12 | export const LIST_COURSES_SUCCESS = "LIST_COURSES_SUCCESS";
13 | export const LIST_COURSES_FAIL = "LIST_COURSES_FAIL";
14 | export const LIST_COURSES_RESET = "LIST_COURSES_RESET";
15 |
16 | export const GET_SUBCATEGORYS_REQUEST = "GET_SUBCATEGORYS_REQUEST";
17 | export const GET_SUBCATEGORYS_SUCCESS = "GET_SUBCATEGORYS_SUCCESS";
18 | export const GET_SUBCATEGORYS_FAIL = "GET_SUBCATEGORYS_FAIL";
19 | export const GET_SUBCATEGORYS_RESET = "GET_SUBCATEGORYS_RESET";
20 |
21 | export const LIST_BYSUBCATEGORYS_REQUEST = "LIST_BYSUBCATEGORYS_REQUEST";
22 | export const LIST_BYSUBCATEGORYS_SUCCESS = "LIST_BYSUBCATEGORYS_SUCCESS";
23 | export const LIST_BYSUBCATEGORYS_FAIL = "LIST_BYSUBCATEGORYS_FAIL";
24 | export const LIST_BYSUBCATEGORYS_RESET = "LIST_BYSUBCATEGORYS_RESET";
25 |
26 | export const LIST_NEW_COURSES_REQUEST = "LIST_NEW_COURSES_REQUEST";
27 | export const LIST_NEW_COURSES_SUCCESS = "LIST_NEW_COURSES_SUCCESS";
28 | export const LIST_NEW_COURSES_FAIL = "LIST_NEW_COURSES_FAIL";
29 | export const LIST_NEW_COURSES_RESET = "LIST_NEW_COURSES_RESET";
30 |
31 | export const LIST_COURSES_POBULAR_REQUEST = "LIST_COURSES_POBULAR_REQUEST";
32 | export const LIST_COURSES_POBULAR_SUCCESS = "LIST_COURSES_POBULAR_SUCCESS";
33 | export const LIST_COURSES_POBULAR_FAIL = "LIST_COURSES_POBULAR_FAIL";
34 | export const LIST_COURSES_POBULAR_RESET = "LIST_COURSES_POBULAR_RESET";
35 |
36 | export const LIST_COURSE_DETAILS_REQUEST = "LIST_COURSE_DETAILS_REQUEST";
37 | export const LIST_COURSE_DETAILS_SUCCESS = "LIST_COURSE_DETAILS_SUCCESS";
38 | export const LIST_COURSE_DETAILS_FAIL = "LIST_COURSE_DETAILS_FAIL";
39 | export const LIST_COURSE_DETAILS_RESET = "LIST_COURSE_DETAILS_RESET";
40 |
41 | export const COURSE_UPDATE_REQUEST = "COURSE_UPDATE_REQUEST";
42 | export const COURSE_UPDATE_SUCCESS = "COURSE_UPDATE_SUCCESS";
43 | export const COURSE_UPDATE_FAIL = "COURSE_UPDATE_FAIL";
44 | export const COURSE_UPDATE_RESET = "COURSE_UPDATE_RESET";
45 |
46 | export const COURSE_DELETE_REQUEST = "COURSE_DELETE_REQUEST";
47 | export const COURSE_DELETE_SUCCESS = "COURSE_DELETE_SUCCESS";
48 | export const COURSE_DELETE_FAIL = "COURSE_DELETE_FAIL";
49 |
50 | export const COURSE_CREATE_REQUEST = "COURSE_CREATE_REQUEST";
51 | export const COURSE_CREATE_SUCCESS = "COURSE_CREATE_SUCCESS";
52 | export const COURSE_CREATE_FAIL = "COURSE_CREATE_FAIL";
53 | export const COURSE_CREATE_RESET = "COURSE_CREATE_RESET";
54 |
55 | export const CREATE_REVIEW_REQUEST = "CREATE_REVIEW_REQUEST";
56 | export const CREATE_REVIEW_SUCCESS = "CREATE_REVIEW_SUCCESS";
57 | export const CREATE_REVIEW_FAIL = "CREATE_REVIEW_FAIL";
58 | export const CREATE_REVIEW_RESET = "CREATE_REVIEW_RESET";
59 |
60 | export const CHECK_STUDENT_REQUEST = "CHECK_STUDENT_REQUEST";
61 | export const CHECK_STUDENT_SUCCESS = "CHECK_STUDENT_SUCCESS";
62 | export const CHECK_STUDENT_FAIL = "CHECK_STUDENT_FAIL";
63 | export const CHECK_STUDENT_RESET = "CHECK_STUDENT_RESET";
64 |
65 | export const LIST_COURSES_PURCHASED_REQUEST = "LIST_COURSES_PURCHASED_REQUEST";
66 | export const LIST_COURSES_PURCHASED_SUCCESS = "LIST_COURSES_PURCHASED_SUCCESS";
67 | export const LIST_COURSES_PURCHASED_FAIL = "LIST_COURSES_PURCHASED_FAIL";
68 | export const LIST_COURSES_PURCHASED_RESET = "LIST_COURSES_PURCHASED_RESET";
69 |
70 | export const LIST_COURSES_SEARCH_REQUEST = "LIST_COURSES_SEARCH_REQUEST";
71 | export const LIST_COURSES_SEARCH_SUCCESS = "LIST_COURSES_SEARCH_SUCCESS";
72 | export const LIST_COURSES_SEARCH_FAIL = "LIST_COURSES_SEARCH_FAIL";
73 | export const LIST_COURSES_SEARCH_RESET = "LIST_COURSES_SEARCH_RESET";
74 |
75 | export const GET_CRSRATING_SUCCESS = "GET_CRSRATING_SUCCESS";
76 | export const GET_CRSRATING_REQUEST = "GET_CRSRATING_REQUEST";
77 | export const GET_CRSRATING_FAIL = "GET_CRSRATING_FAIL";
78 | export const GET_CRSRATING_RESET = "GET_CRSRATING_RESET";
79 |
80 | export const GET_CRSPRICE_SUCCESS = "GET_CRSPRICE_SUCCESS";
81 | export const GET_CRSPRICE_REQUEST = "GET_CRSPRICE_REQUEST";
82 | export const GET_CRSPRICE_FAIL = "GET_CRSPRICE_FAIL";
83 | export const GET_CRSPRICE_RESET = "GET_CRSPRICE_RESET";
84 |
--------------------------------------------------------------------------------
/frontend/src/pages/Placeorderscreen/PlaceOrder.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import "../checkout/CheckoutScreen.css";
3 | import { Helmet } from "react-helmet";
4 |
5 | import { Button, Modal } from "antd";
6 | import { useDispatch, useSelector } from "react-redux";
7 |
8 | import { CreateOrder } from "../../redux/actions/orderActions";
9 |
10 | const PlaceOrder = ({ history }) => {
11 | const { confirm } = Modal;
12 | const dispatch = useDispatch();
13 | const cartReducer = useSelector((state) => state.cartReducer);
14 | const addDecimals = (num) => {
15 | return (Math.round(num * 100) / 100).toFixed(2);
16 | };
17 | const nameCard = cartReducer.NameOnCard.nameCard.replace(/.(?=.{4})/g, "x");
18 | const cardNumber = cartReducer.NameOnCard.cardNumber.replace(
19 | /.(?=.{4})/g,
20 | "x"
21 | );
22 | const securityCode = cartReducer.NameOnCard.security.replace(
23 | /.(?=.{1})/g,
24 | "x"
25 | );
26 | const { cartItems } = cartReducer;
27 |
28 | cartReducer.itemsPrice = addDecimals(
29 | cartReducer.cartItems.reduce((acc, item) => acc + item.price, 0)
30 | );
31 |
32 | cartReducer.totalPrice = Number(cartReducer.itemsPrice).toFixed(2);
33 |
34 | const orderCreate = useSelector((state) => state.orderCreate);
35 | const { order, success, error } = orderCreate;
36 | const Placeorderhanlder = () => {
37 | dispatch(
38 | CreateOrder({
39 | orderItems: cartReducer.cartItems,
40 | countryCustomer: cartReducer.countryCustomer,
41 | paymentMethod: cartReducer.MethodOfPayment,
42 | itemsPrice: cartReducer.itemsPrice,
43 |
44 | totalPrice: cartReducer.totalPrice,
45 | })
46 | );
47 | };
48 | useEffect(() => {
49 | if (success) {
50 | history.push(`/order/${order._id}`);
51 | }
52 | return () => {};
53 | //eslint-disable-next-line
54 | }, [history, success]);
55 |
56 | function showConfirm() {
57 | confirm({
58 | title: "Do you confirm this informations ?",
59 |
60 | content: "Please Verify The Total Price And Payment Method",
61 | onOk() {
62 | Placeorderhanlder();
63 | console.log("OK");
64 | },
65 | onCancel() {
66 | console.log("Cancel");
67 | },
68 | });
69 | }
70 | return (
71 |
72 |
73 | Place Order
74 |
75 |
76 |
77 |
Your Informations:
78 |
79 |
80 |
81 | Your Country:
82 | {cartReducer.countryCustomer.country}
83 |
84 |
85 |
86 | Payment Method:
87 | {cartReducer.MethodOfPayment}
88 |
89 |
90 |
91 | {cartReducer.MethodOfPayment === "Card" && (
92 |
93 |
94 | Name On Card:
95 | {nameCard}
96 |
97 |
98 |
99 | Card Number:
100 | {cardNumber}
101 |
102 |
103 |
104 | Expiration Date:
105 | {cartReducer.NameOnCard.dateExp}
106 |
107 |
108 |
109 | Security Code:
110 | {securityCode}
111 |
112 |
113 |
114 | Zip/Postal Code:
115 | {cartReducer.NameOnCard.zip}
116 |
117 |
118 | )}
119 |
120 |
121 |
122 |
123 |
Summary
124 |
125 | Original price:
126 |
${cartReducer.totalPrice}
127 |
128 |
129 | Coupon discounts:
130 |
$189,33
131 |
132 |
133 |
134 | Total:
135 |
${cartReducer.totalPrice}
136 |
137 |
138 |
139 |
140 | Next
141 |
142 | {error && error}
143 |
144 |
145 |
146 | );
147 | };
148 |
149 | export default PlaceOrder;
150 |
--------------------------------------------------------------------------------
/frontend/src/components/Navbar/Navbar.css:
--------------------------------------------------------------------------------
1 | .navbar {
2 | display: grid;
3 | background-color: #fafafa;
4 | grid-template-columns: 20% 40% 40%;
5 | height: 70px;
6 | align-content: center;
7 | justify-content: center;
8 | align-items: center;
9 | padding: 0 10px;
10 | box-shadow: 0 2px 8px -2px rgba(0, 0, 0, 0.6);
11 | }
12 | .ant-input-group {
13 | z-index: 10;
14 | }
15 | .logo_header {
16 | object-fit: contain;
17 | width: 45%;
18 | }
19 | .burger {
20 | display: none;
21 | cursor: pointer;
22 | /* position: absolute; */
23 | /* left: 0px; */
24 | /* margin-right: 12px; */
25 | /* display: inline-block; */
26 | }
27 |
28 | .burger div {
29 | border-radius: 99px;
30 | width: 25px;
31 | height: 2px;
32 | background-color: #0f6ab9;
33 | margin: 6px;
34 | transition: all 0.3s ease;
35 | }
36 | .Categoriespobover {
37 | width: 220px;
38 | font-size: 16px;
39 | color: #2f2f2f;
40 | display: grid;
41 | row-gap: 0.5px;
42 | }
43 | .search_box_container {
44 | display: flex;
45 | flex-direction: row;
46 | }
47 | .icon_search {
48 | position: relative;
49 | left: 95%;
50 | top: 10px;
51 | }
52 | .Onright {
53 | height: 100%;
54 | margin-left: 20px;
55 | display: grid;
56 | align-content: center;
57 | align-items: center;
58 | grid-template-columns: 30% 20% 10% 40%;
59 | }
60 | .carticon {
61 | color: #2f2f2f;
62 | align-self: center;
63 | justify-self: center;
64 | }
65 | .logo {
66 | font-size: 20px;
67 | font-weight: 800;
68 | text-align: center;
69 | }
70 | .logo a {
71 | color: #2f2f2f;
72 | }
73 | .Navbarbtns {
74 | background-color: transparent;
75 | border: none;
76 | color: #2f2f2f;
77 | font-weight: 500;
78 | transition: all 0.5s linear 0.5s;
79 | cursor: pointer;
80 | }
81 | .Navbarbtns:hover {
82 | color: #0f6ab9;
83 | }
84 | .ant-btn-primary {
85 | background: #1b78ca !important;
86 | border-color: #0f6ab9 !important;
87 | }
88 | .ant-btn-primary:hover {
89 | background: #2584d6 !important;
90 | border-color: #2584d6 !important;
91 | }
92 | #SignInbtn {
93 | color: #0f6ab9;
94 | border: 1px #0f6ab9 solid;
95 | border-radius: 2px;
96 | margin: 0px 20px;
97 | }
98 |
99 | #spin {
100 | width: 100%;
101 | height: 100%;
102 | left: -200px;
103 | background: #0f6ab9;
104 | position: absolute;
105 | transition: all 0.35s ease-Out;
106 | bottom: 0;
107 | }
108 | #SignInbtn #spin {
109 | background: #0f6ab9;
110 | }
111 | #SignInbtn .linkinbtn {
112 | color: #0f6ab9;
113 | }
114 | #SignUpbtn:hover #spin {
115 | left: 0;
116 | }
117 | #SignInbtn:hover #spin {
118 | left: 0;
119 | }
120 | .linkinbtn {
121 | color: #0f6ab9;
122 | }
123 | #SignUpbtn:hover .linkinbtn,
124 | #SignInbtn:hover .linkinbtn {
125 | color: rgb(245, 245, 245);
126 | position: relative;
127 | }
128 | nav ul li {
129 | display: inline-block;
130 | width: 190px;
131 | padding: 10px 20px;
132 | transform: translateY(13px);
133 | text-decoration: none;
134 | }
135 | nav ul li img {
136 | width: 30px;
137 | height: 30px;
138 | }
139 | .Profilepobover {
140 | width: 100px;
141 | font-size: 16px;
142 | color: #2f2f2f;
143 | display: grid;
144 | /* row-gap: 0.5px; */
145 | }
146 | .profile_pic {
147 | object-fit: cover;
148 | height: 30px;
149 | width: 30px;
150 | border-radius: 50%;
151 | }
152 | .phonedropdown {
153 | margin-left: 50px;
154 | background-color: rgb(255, 255, 255);
155 | box-shadow: 0 2px 8px -2px rgba(0, 0, 0, 0.6);
156 | padding: 20px;
157 | }
158 | .dropdownic {
159 | display: grid;
160 | grid-template-columns: 20% 50% 30%;
161 | justify-content: center;
162 | align-items: center;
163 | cursor: pointer;
164 | }
165 | @keyframes moving {
166 | from {
167 | opacity: 0;
168 | transform: translateX(-50px);
169 | }
170 | to {
171 | opacity: 1;
172 | transform: translateX(0px);
173 | }
174 | }
175 | .Phoneonright {
176 | display: grid;
177 | grid-template-columns: 50% 50%;
178 | }
179 | .loadingNav {
180 | text-align: center;
181 | display: flex;
182 | justify-content: center;
183 | align-items: center;
184 | width: 100%;
185 | height: 300px;
186 | margin-left: 30px;
187 | }
188 |
189 | @media screen and (max-width: 768px) {
190 | .burger {
191 | /* position: absolute; */
192 | /* left: 0px; */
193 | margin-right: 12px;
194 | display: inline-block;
195 | }
196 |
197 | .navbar {
198 | grid-template-columns: 10% 75% 15%;
199 | }
200 | .search_box {
201 | display: none;
202 | }
203 | .searchactive {
204 | position: absolute;
205 | right: 0;
206 | top: 80px;
207 | opacity: 1;
208 | transition: all 0.2s linear 0.2s;
209 | }
210 | .searchphone {
211 | position: absolute;
212 | right: 0;
213 | top: -30px;
214 | opacity: 0;
215 | transition: all 0.5s linear 0.5s;
216 | }
217 |
218 | .onRightphone {
219 | align-items: center;
220 | display: grid;
221 | row-gap: 10px;
222 | grid-template-columns: 50% 50%;
223 | transition: all 0.5s linear 0.5s;
224 | }
225 |
226 | .onRightphone h4 {
227 | color: #5a5a5a;
228 | }
229 | .drop-nav {
230 | list-style: none;
231 | }
232 | }
233 |
--------------------------------------------------------------------------------
/frontend/src/pages/CourseFilter/CollapsibleFilter.js:
--------------------------------------------------------------------------------
1 | import React,{useState, useRef, useEffect} from 'react'
2 | import { Radio, Checkbox } from 'antd';
3 | import { useDispatch } from 'react-redux';
4 | import './CollapsibleFilter.css'
5 | import { Image } from 'antd';
6 | import { Link, useParams } from "react-router-dom";
7 | import { IoIosArrowDown, AiFillPlayCircle, RiArrowUpSLine, RiArrowDownSLine,BsStarFill, BsStarHalf, BsStar} from 'react-icons/all'
8 | import { LisCoursesbyrating, ListCoursesbyprice } from '../../redux/actions/courseActions';
9 | import {GET_CRSRATING_RESET,GET_CRSPRICE_RESET} from "../../redux/constants/courseconstants";
10 | const CollapsibleFilter = (props) => {
11 | let { topic } = useParams();
12 | const [isOpen, setIsOpen] = useState(false)
13 | const parentRef = useRef();
14 | if(parentRef.current) console.log(parentRef.current.scrollHeight)
15 |
16 | var i = 1;
17 |
18 | const dispatch = useDispatch()
19 | const [price, setPrice] = useState();
20 | const [rating, setRating] = useState();
21 |
22 | const handleRating = async (e) => {
23 | console.log('Rate Value', e.target.value);
24 | setRating(e.target.value);
25 | }
26 | const handlepric = async (e) => {
27 | console.log('Price Value', e.target.value);
28 | setPrice(e.target.value);
29 | }
30 |
31 | useEffect(() => {
32 | if(rating){
33 | dispatch(LisCoursesbyrating(topic,{rating}))
34 | console.log("Topic - rating dispatch : ",topic,rating);
35 | dispatch({type : GET_CRSPRICE_RESET});
36 | }
37 | if(price){
38 | dispatch(ListCoursesbyprice(topic,{price}))
39 | dispatch({type : GET_CRSRATING_RESET});
40 | }
41 | return () => {};
42 | }, [dispatch,rating,price]);
43 | return (
44 |
45 |
46 |
47 |
setIsOpen(!isOpen)}>
48 |
49 | {!isOpen ? : } {props.title}
50 |
51 |
52 |
53 |
54 |
59 |
60 |
61 |
62 |
63 | {props.value == 1 ?
64 |
65 |
71 |
72 |
73 | : null}
74 |
75 |
76 |
77 |
78 | {props.value == 2 ?
79 |
84 | : null}
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 | )
101 | }
102 |
103 | export default CollapsibleFilter
104 |
--------------------------------------------------------------------------------
/frontend/src/components/body/auth/Login.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { Image, Form, Button } from "antd";
3 | import { Link, NavLink, useHistory } from "react-router-dom";
4 | import axios from "axios";
5 | import { Helmet } from "react-helmet";
6 | import { FiUser, AiFillLock } from "react-icons/all";
7 | import {
8 | showErrMsg,
9 | showSuccessMsg,
10 | } from "../../utils/notification/Notification";
11 | import "./auth.css";
12 | import { dispatchLogin } from "../../../redux/actions/authAction";
13 | import { useDispatch } from "react-redux";
14 | import { BiRightArrowAlt } from "react-icons/all";
15 | import { isEmpty, isEmail } from "../../utils/validation/Validation";
16 | const initialState = {
17 | email: "",
18 | password: "",
19 | err: "",
20 | success: "",
21 | };
22 |
23 | const Login = () => {
24 | const [formDataUser, setFormDataUser] = useState(initialState);
25 | const dispatch = useDispatch();
26 | const history = useHistory();
27 | const { email, password, err, success } = formDataUser;
28 | const handleChange = (e) => {
29 | //place of do that onChange={(e) => setEmail(e.target.value) for each field (input) we do that
30 | setFormDataUser({
31 | ...formDataUser,
32 | [e.target.name]: e.target.value,
33 | err: "",
34 | success: "",
35 | });
36 | };
37 | const handleSubmit = async (e) => {
38 | e.preventDefault();
39 | if (isEmpty(email) | isEmpty(password))
40 | return setFormDataUser({
41 | ...formDataUser,
42 | err: "Please fill in all fields",
43 | success: "",
44 | });
45 |
46 | if (!isEmail(email))
47 | return setFormDataUser({
48 | ...formDataUser,
49 | err: "Invalid email",
50 | success: "",
51 | });
52 | try {
53 | const res = await axios.post("/user/login", { email, password });
54 | setFormDataUser({ ...formDataUser, err: "", success: res.data.msg }); // true
55 | dispatch(dispatchLogin());
56 | localStorage.setItem("firstLogin", true);
57 |
58 | history.push("/");
59 | } catch (err) {
60 | err.response.data.msg &&
61 | setFormDataUser({
62 | ...formDataUser,
63 | err: err.response.data.msg,
64 | success: "",
65 | });
66 | }
67 | };
68 |
69 | const inputs = document.querySelectorAll(".input");
70 |
71 | function addcl() {
72 | let parent = this.parentNode.parentNode;
73 | parent.classList.add("focus");
74 | }
75 |
76 | function remcl() {
77 | let parent = this.parentNode.parentNode;
78 | if (this.value == "") {
79 | parent.classList.remove("focus");
80 | }
81 | }
82 |
83 | inputs.forEach((inputa) => {
84 | inputa.addEventListener("focus", addcl);
85 | inputa.addEventListener("blur", remcl);
86 | });
87 |
88 | return (
89 | <>
90 |
91 | login
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
153 |
154 |
155 | >
156 | );
157 | };
158 |
159 | export default Login;
160 |
--------------------------------------------------------------------------------
/frontend/src/App.js:
--------------------------------------------------------------------------------
1 | import "./App.css";
2 | import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
3 | import React, { useEffect } from "react";
4 | import { useDispatch, useSelector } from "react-redux";
5 | import Home from "./pages/Home/Home";
6 | import "antd/dist/antd.css";
7 | import "slick-carousel/slick/slick.css";
8 | import "slick-carousel/slick/slick-theme.css";
9 | import Navbar from "./components/Navbar/Navbar";
10 | import Subcategory from "./pages/CourseFilter/Subcategory";
11 |
12 | import Footer from "./components/Footer/Footer";
13 |
14 | import Login from "./components/body/auth/Login";
15 | import Register from "./components/body/auth/Register";
16 | import ActivationEmail from "./components/body/auth/ActivationEmail";
17 | import axios from "axios";
18 | import {
19 | dispatchLogin,
20 | dispatchGetUser,
21 | fetchUser,
22 | } from "./redux/actions/authAction";
23 |
24 | import NotFound from "./components/utils/NotFound/NotFound";
25 | import ForgotPass from "./components/body/auth/ForgotPassword";
26 | import ResetPass from "./components/body/auth/ResetPassword";
27 | import Coursepage from "./pages/Coursepage/Coursepage";
28 | import Cart from "./pages/Cart/Cart";
29 | import Mycourses from "./pages/Mycourses/Mycourses";
30 | import CourseFilter from "./pages/CourseFilter/CourseFilter";
31 | import CheckoutScreen from "./pages/checkout/CheckoutScreen";
32 | import Profile from "./components/body/profile/Profile";
33 | import EditUser from "./components/body/profile/EditUser";
34 | import PlaceOrder from "./pages/Placeorderscreen/PlaceOrder";
35 | import EditCourse from "./components/body/profile/EditCourse";
36 | import OrderScreen from "./pages/Orderscreen/OrderScreen";
37 | import CourseSeacrh from "./pages/CourseSearch/CourseSeacrh";
38 | function App() {
39 | //Get Acces token
40 | const dispatch = useDispatch();
41 | const token = useSelector((state) => state.token);
42 | const auth = useSelector((state) => state.auth);
43 | const { isLogged, user, isAdmin } = auth;
44 |
45 | useEffect(() => {
46 | const firstLogin = localStorage.getItem("firstLogin");
47 | if (firstLogin) {
48 | const getToken = async () => {
49 | // make post request : hey db get me some data and return it to me
50 | const res = await axios.post("/user/refresh_token", null);
51 | dispatch({ type: "GET_TOKEN", payload: res.data.access_token });
52 | };
53 | getToken();
54 | }
55 | }, [auth.isLogged, dispatch]);
56 | // when refresh the token exsit but the logged change to false that's we do that
57 |
58 | useEffect(() => {
59 | if (token) {
60 | const getUser = () => {
61 | dispatch(dispatchLogin());
62 | //Get user infor
63 | return fetchUser(token).then((res) => {
64 | dispatch(dispatchGetUser(res));
65 | });
66 | };
67 | getUser();
68 | }
69 | }, [token, dispatch]);
70 | return (
71 | <>
72 |
73 |
74 | <>
75 |
76 |
77 |
78 |
79 |
80 |
81 |
85 |
89 |
93 |
97 |
102 |
107 |
112 |
117 |
122 |
127 |
132 |
137 |
138 |
139 |
140 |
141 |
142 | >
143 |
144 |
145 |
146 | >
147 | );
148 | }
149 |
150 | export default App;
151 |
--------------------------------------------------------------------------------
/frontend/src/components/body/auth/auth.css:
--------------------------------------------------------------------------------
1 | .teacherdetails {
2 | margin-top: 10px;
3 | display: grid;
4 | text-align: left;
5 | font-weight: 600;
6 | }
7 | .container_login {
8 | display: table;
9 | }
10 | .wave {
11 | position: fixed;
12 | bottom: 0;
13 | left: 0;
14 | height: 100%;
15 | z-index: -1;
16 | }
17 | .activate {
18 | height: 900px;
19 | }
20 | .container {
21 | margin-bottom: 100px;
22 | margin-top: 100px;
23 | width: 96vw;
24 | display: grid;
25 | grid-template-columns: repeat(2, 1fr);
26 | grid-gap: 7rem;
27 | padding: 0 2rem;
28 | }
29 | .img {
30 | display: flex;
31 | justify-content: flex-end;
32 | align-items: center;
33 | }
34 |
35 | .login-content {
36 | display: flex;
37 | justify-content: center;
38 | align-items: center;
39 | text-align: center;
40 | }
41 |
42 | .img img {
43 | width: 500px;
44 | }
45 |
46 | form {
47 | width: 400px;
48 | }
49 |
50 | .login-content img {
51 | height: 100px;
52 | }
53 |
54 | .login-content h2 {
55 | margin: 15px 0;
56 | color: #333;
57 | text-transform: uppercase;
58 | font-size: 2.9rem;
59 | }
60 |
61 | .login-content .input-div {
62 | position: relative;
63 | display: grid;
64 | grid-template-columns: 7% 93%;
65 | margin-bottom: 0px;
66 | margin: 25px 0;
67 | padding: 5px 0;
68 | border-bottom: 2px solid #d9d9d9;
69 | }
70 |
71 | .login-content .input-div.one {
72 | margin-top: 0;
73 | }
74 |
75 | .i {
76 | color: #d9d9d9;
77 | display: flex;
78 | justify-content: center;
79 | align-items: center;
80 | }
81 |
82 | .i i {
83 | transition: 0.3s;
84 | }
85 |
86 | .input-div > div {
87 | position: relative;
88 | height: 45px;
89 | }
90 |
91 | .input-div > div > h5 {
92 | position: absolute;
93 | left: 10px;
94 | top: 50%;
95 | transform: translateY(-50%);
96 | color: #999;
97 | font-size: 18px;
98 | transition: 0.3s;
99 | }
100 |
101 | .input-div:before,
102 | .input-div:after {
103 | content: "";
104 | position: absolute;
105 | bottom: -2px;
106 | width: 0%;
107 | height: 2px;
108 | background-color: #9283f8;
109 | transition: 0.4s;
110 | }
111 |
112 | .input-div:before {
113 | right: 50%;
114 | }
115 |
116 | .input-div:after {
117 | left: 50%;
118 | }
119 |
120 | .input-div.focus:before,
121 | .input-div.focus:after {
122 | width: 50%;
123 | }
124 |
125 | .input-div.focus > div > h5 {
126 | top: -5px;
127 | font-size: 15px;
128 | }
129 |
130 | .input-div.focus > .i > i {
131 | color: #9283f8;
132 | }
133 |
134 | .input-div > div > input {
135 | position: absolute;
136 | left: 0;
137 | top: 0;
138 | width: 100%;
139 | height: 100%;
140 | border: none;
141 | outline: none;
142 | background: none;
143 | padding: 0.5rem 0.7rem;
144 | font-size: 1.2rem;
145 | color: #555;
146 | font-family: "poppins", sans-serif;
147 | }
148 |
149 | .input-div.pass {
150 | margin-bottom: 4px;
151 | }
152 |
153 | .forgot {
154 | display: block;
155 | text-align: right;
156 | text-decoration: none;
157 | color: #999;
158 | font-size: 0.9rem;
159 | transition: 0.3s;
160 | }
161 | .register {
162 | text-decoration: none;
163 | color: #999;
164 | }
165 |
166 | a:hover {
167 | color: #6567d8;
168 | }
169 |
170 | .btn {
171 | display: block;
172 | width: 100%;
173 | height: 50px;
174 | border-radius: 25px;
175 | outline: none;
176 | border: none;
177 | background-image: linear-gradient(to right, #1277cf, #0f6ab9, #434882);
178 | background-size: 200%;
179 | font-size: 1.2rem;
180 | color: #fff;
181 | font-family: "Poppins", sans-serif;
182 | text-transform: uppercase;
183 | margin: 1rem 0;
184 | cursor: pointer;
185 | transition: 0.5s;
186 | }
187 | .btn:hover {
188 | background-position: right;
189 | }
190 |
191 | @media screen and (max-width: 1050px) {
192 | .container {
193 | grid-gap: 5rem;
194 | }
195 | }
196 |
197 | @media screen and (max-width: 1000px) {
198 | .container_login {
199 | /* display: block; */
200 | /* margin-bottom: 80%; */
201 | }
202 | form {
203 | width: 290px;
204 | }
205 |
206 | .login-content h2 {
207 | font-size: 2.4rem;
208 | margin: 8px 0;
209 | }
210 |
211 | .img img {
212 | width: 400px;
213 | }
214 | }
215 |
216 | @media screen and (max-width: 900px) {
217 | .container {
218 | grid-template-columns: 1fr;
219 | display: table;
220 | }
221 |
222 | .img {
223 | display: none;
224 | }
225 |
226 | .wave {
227 | display: none;
228 | }
229 |
230 | .login-content {
231 | justify-content: center;
232 | }
233 | }
234 |
235 | /* ------------- Forgot Password --------------- */
236 | .fg_pass {
237 | width: 96vw;
238 | height: 0px;
239 | display: inline-block;
240 | grid-template-rows: repeat(2, 1fr);
241 | grid-gap: 7rem;
242 | padding: 0 2rem;
243 | }
244 | .fg_pass h2 {
245 | color: #555;
246 | text-transform: uppercase;
247 | text-align: center;
248 | font-size: 2rem;
249 | margin: 50px 0;
250 | letter-spacing: 1.3px;
251 | }
252 |
253 | .fg_pass .row {
254 | max-width: 500px;
255 | margin: auto;
256 | padding: 0 10px;
257 | }
258 | .fg_pass .row input {
259 | width: 100%;
260 | height: 45px;
261 | border: 1px solid #ccc;
262 | outline: none;
263 | padding: 0 15px;
264 | border-radius: 3px;
265 | margin: 5px 0;
266 | }
267 | .fg_pass .row button {
268 | background: #2584d6;
269 | color: white;
270 | padding: 10px 30px;
271 | height: 100%;
272 | text-transform: uppercase;
273 | letter-spacing: 1.3px;
274 | border-radius: 3px;
275 | margin-top: 15px;
276 | }
277 | /* ---------- Facebook / Google ---------------- */
278 | .login_page .hr {
279 | margin: 15px 0;
280 | color: crimson;
281 | }
282 |
283 | .login_page .social button {
284 | width: 100%;
285 | margin: 10px 0;
286 | height: 50px;
287 | font-size: 14px;
288 | font-weight: 200;
289 | text-transform: capitalize;
290 | letter-spacing: 1.3px;
291 | box-shadow: 0 2px 4px #777;
292 | }
293 |
--------------------------------------------------------------------------------
/frontend/src/components/body/auth/img/login.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/src/pages/Orderscreen/OrderScreen.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import "../checkout/CheckoutScreen.css";
3 | import { Helmet } from "react-helmet";
4 |
5 | import { Image, Alert } from "antd";
6 | import { PayPalButton } from "react-paypal-button-v2";
7 | import { useDispatch, useSelector } from "react-redux";
8 | import { Skeleton } from "antd";
9 | import Error from "../../components/utils/Error";
10 | import { removeAllFromCart } from "../../redux/actions/cartActions";
11 |
12 | import axios from "axios";
13 |
14 | import { getOrderDetails, payOrder } from "../../redux/actions/orderActions";
15 |
16 | import { ORDER_PAY_RESET } from "../../redux/constants/orderconstants";
17 |
18 | const OrderScreen = ({ history, match }) => {
19 | const [sdkReady, setsdkReady] = useState(false);
20 | const orderId = match.params.id;
21 | const dispatch = useDispatch();
22 | const orderDetails = useSelector((state) => state.orderDetails);
23 | const { order, loading, error } = orderDetails;
24 |
25 | const orderPay = useSelector((state) => state.orderPay);
26 | const { loading: loadingpay, success: successPay } = orderPay;
27 |
28 | const auth = useSelector((state) => state.auth);
29 | const { user, isLogged } = auth;
30 |
31 | useEffect(() => {
32 | if (!isLogged) {
33 | history.push("/login");
34 | }
35 | const addPaypalscript = async () => {
36 | const { data: clientId } = await axios.get("/api/config/paypal ");
37 | const script = document.createElement("script");
38 | script.type = "text/javascript";
39 | script.async = true;
40 | script.src = `https://www.paypal.com/sdk/js?client-id=${clientId}`;
41 | script.onload = () => {
42 | setsdkReady(true);
43 | };
44 | document.body.appendChild(script);
45 | };
46 | if (!order || successPay) {
47 | dispatch({
48 | type: ORDER_PAY_RESET,
49 | });
50 |
51 | dispatch(getOrderDetails(orderId));
52 | } else if (!order.isPaid) {
53 | if (!window.paypal) {
54 | addPaypalscript();
55 | } else {
56 | setsdkReady(true);
57 | }
58 | }
59 |
60 | console.log(order);
61 | }, [dispatch, orderId, successPay, orderPay, isLogged]);
62 | const successpaymenthandler = (paymentResult) => {
63 | console.log(paymentResult);
64 | dispatch(payOrder(orderId, paymentResult, user._id));
65 | dispatch(removeAllFromCart());
66 | // history.push('/')
67 | };
68 | return (
69 |
70 |
71 | Order
72 |
73 | {loading ? (
74 |
75 | ) : error ? (
76 |
77 | ) : (
78 |
79 |
80 | {order.isPaid ? (
81 |
87 | ) : (
88 |
94 | )}
95 |
Your Informations:
96 |
97 |
98 |
99 | Name:
100 | {order.user.name.toUpperCase()}
101 |
102 |
103 | E-mail:
104 | {order.user.email}
105 |
106 |
107 | Country:
108 | {order.countryCustomer.country}
109 |
110 |
111 | Payment Method:
112 | {order.paymentMethod}
113 |
114 | {order.isPaid && (
115 |
116 | Paid At:
117 | {order.paidAt}
118 |
119 | )}
120 |
121 |
122 |
123 |
124 |
125 |
Order Details
126 |
127 | {order.orderItems.map((item, index) => (
128 |
129 |
130 |
136 |
137 |
140 |
141 |
${item.price}
142 |
143 |
144 | ))}
145 |
146 |
147 |
Total =
148 | ${order.totalPrice}
149 |
150 |
151 |
152 |
153 |
154 | {!order.isPaid && (
155 |
156 |
Last Step
157 |
158 | By completing your purchase you agree to{" "}
159 | Terms of Service.
160 |
161 |
162 | Now You Have Just To Pay To Have Access To The Course
163 |
164 |
169 |
170 | )}
171 |
172 |
Warning !
173 |
174 | For Security And Purchase Success, You Should Keep The Order ID
175 | Saved And If You Faced Any Problem Just Send Us Short
176 | Description With The Order ID
177 |
178 |
Order ID: {order._id}
179 |
180 |
181 |
182 | )}
183 |
184 | );
185 | };
186 |
187 | export default OrderScreen;
188 |
--------------------------------------------------------------------------------
/frontend/src/pages/Home/Home.css:
--------------------------------------------------------------------------------
1 |
2 | .Home_image {
3 | position: relative;
4 | height: 500px;
5 | object-fit: cover;
6 | width: 100%;
7 | z-index: -1;
8 | }
9 |
10 | .ant-card-body{
11 | height: 220px;
12 | }
13 | .Banner_Card{
14 | width: auto;
15 | position: absolute;
16 | top: 30%;
17 | left: 20px;
18 | border-radius: 10px;
19 | box-shadow: 0 2px 8px -2px rgba(0, 0, 0, 0.3);
20 | z-index: 1;
21 |
22 | }
23 | .Banner_Card h1{
24 | font-weight: 600;
25 |
26 | }
27 | .Banner_Card p{
28 | margin-top: 20px;
29 | font-size: 16px;
30 |
31 | }
32 | #discover_btn{
33 | color: rgb(233, 231, 231);
34 | border: none;
35 | font-size: 16px;
36 | background-color: #0f6ab9;
37 | width: 150px;
38 | height: 40px;
39 | border-radius:3px;
40 |
41 | }
42 | .ant-tabs-ink-bar{
43 | background:#0f6ab9 !important;
44 | }
45 | .ant-tabs-tab.ant-tabs-tab-active .ant-tabs-tab-btn{
46 | color:#0f6ab9 !important;
47 | }
48 | #discover_btn:hover{
49 | background-color: #2278c3;
50 | box-shadow: 0 2px 8px -2px rgba(0, 0, 0, 0.3);
51 | }
52 | .Menu1{
53 | margin: 20px 0 ;
54 | margin-left: 20px;
55 | margin-right: 30px;
56 | position: relative;
57 | /* top: 500px; */
58 | }
59 | .Menu1 > h2{
60 | font-weight: 700;
61 | }
62 | .Tab_Content{
63 | display:grid;
64 | width:100%;
65 | border: 0.2px #c7c7c7 solid;
66 |
67 | box-shadow: 0 2px 8px -2px rgba(0, 0, 0, 0.2);
68 | padding: 20px;
69 | /* align-items: center; */
70 |
71 |
72 | }
73 | .ant-drawer-close {
74 | color: #0f6ab9 !important;
75 | }
76 | .Tab_Content #paragraphbtn {
77 | text-align: justify;
78 | margin-bottom: 20px;
79 | width:70%;
80 | }
81 |
82 | .coursecards{
83 | padding: 8px;
84 | width: 100%;
85 | }
86 | .slick-list{
87 | display:grid;
88 | grid-template-columns: repeat(auto-fill,minmax(350px,1fr));
89 | height: 400px;
90 | }
91 |
92 | .coursecard{
93 | width: 252px;
94 | border: 1px #dfdfdf solid;
95 |
96 | }
97 |
98 | .slick-active{
99 | margin: 0 10px;
100 | margin-left: 20px;
101 | }
102 | .slick-prev::before,.slick-next::before{
103 | font-size: 25px;
104 | color: #000a13;
105 | z-index: 8;
106 |
107 | }
108 | .slick-prev{
109 | z-index: 8;
110 | }
111 | .coursecard h3 {
112 | width: 230px;
113 | cursor: pointer;
114 | position: absolute;
115 | top: 170px;
116 | right: 10px;
117 | height: 50px;
118 | text-overflow: ellipsis;
119 | overflow: hidden;
120 | white-space: normal;
121 | display: -webkit-box!important;
122 | -webkit-line-clamp: 2;
123 | -webkit-box-orient: vertical;
124 | }
125 | .coursecards h4{
126 | position: absolute;
127 | top: 230px;
128 | font-size: 16px;
129 | left: 10px;
130 | }
131 | .coursecard h5{
132 | position: absolute;
133 | top: 260px;
134 | font-size: 15px;
135 | left: 10px;
136 |
137 | }
138 | .coursecards h4{
139 | color: #5a5a5a;
140 | }
141 | .rating{
142 | position: absolute;
143 | bottom: 10px;
144 | left: 10px;
145 | }
146 | #ReadMorebtn{
147 | width: fit-content;
148 | background-color : #0f6ab9;
149 | padding: 0px 10px;
150 | color: #eeeded;
151 | border-radius: 2px;
152 | }
153 | #ReadMorebtn:hover{
154 | background-color : #0960ac;
155 | }
156 | .ant-rate-star ,.ant-rate-star-full{
157 | width:15px
158 | }
159 | .Courses_Popular{
160 | margin-left: 30px;
161 | margin-right: 30px;
162 | position: relative;
163 | /* top: 550px; */
164 | }
165 | .Courses_Popular h2{
166 | font-weight: 700;
167 | }
168 | .Categorys_Popular{
169 | /* text-align: center; */
170 | /* margin-left: 30px;
171 | margin-right: 30px; */
172 | position: relative;
173 | /* top: 550px; */
174 | margin-bottom: 10%;
175 | }
176 | .Categorys_Popular h2{
177 | font-weight: 700;
178 | margin-left: 30px;
179 | margin-right: 30px;
180 | }
181 | .Become_Teacher{
182 | position: relative;
183 | /* top: 100px; */
184 |
185 | /* margin-bottom: 198px; */
186 |
187 | }
188 | .background{
189 | position: relative;
190 | width: 100%;
191 | height: 400px;
192 | background-color: rgba(218, 218, 218, 0.2);
193 | display: grid;
194 | grid-template-columns: 50% 50%;
195 | }
196 | .Become_Teacher img{
197 | margin: 30px 270px;
198 | /* position: relative; */
199 | object-fit: cover;
200 | height: 400px;
201 | justify-self: center;
202 | width: 400px;
203 | box-shadow: 0 2px 8px -2px rgba(0, 0, 0, 0.2);
204 | }
205 | .Become_Teacher h2{
206 | font-size: 23px;
207 | }
208 | .Become_Teacher div{
209 | /* justify-self: center; */
210 | align-self: center;
211 | z-index:8;
212 | }
213 |
214 | /* .ant-card.ant-card-bordered{
215 | cursor: pointer;
216 | } */
217 | .Become_Teacher p{
218 | font-size: 16px;
219 | }
220 |
221 | #Joinusbtn{
222 | position: relative;
223 | color: rgb(233, 231, 231);
224 | border: none;
225 | font-size: 16px;
226 | background-color: #0f6ab9;
227 | width: 150px;
228 | height: 40px;
229 | border-radius:3px;
230 | cursor: pointer;
231 | }
232 | #Joinusbtn:hover{
233 | background-color: rgb(8, 75, 134);
234 | }
235 | .ant-card-cover img{
236 | transition: all 0.3s linear 0.3s;
237 |
238 | }
239 | .ant-card-cover img:hover{
240 | filter: rgb(92, 92, 92);
241 | -webkit-filter: grayscale(70%);
242 | }
243 | .Categorycards{
244 | /* margin : 0 20px; */
245 | display: grid;
246 | text-align: center;
247 | row-gap: 20px;
248 | grid-template-columns:repeat(auto-fill,minmax(300px,1fr))
249 | }
250 | /* .ant-card-body{
251 | height: fit-content;
252 | } */
253 | @media screen and (max-width:768px) {
254 | #paragraphbtn p{
255 | padding-right: 30%;
256 | width: fit-content;
257 | /* height: 500px; */
258 | }
259 | .Become_Teacher img{
260 | margin-left: 10px;
261 | margin-right : 90px;
262 | height: 300px;
263 | width: 300px;
264 | }
265 | .Become_Teacher .paragraph{
266 | /* grid-column:1/1; */
267 | margin-left: 20px;
268 | }
269 | .Home_image{
270 | object-position: 80%;
271 |
272 | }
273 | .Banner_Card{
274 | left: 5px;
275 | width: 80%;
276 | top: 26%;
277 | }
278 | .slick-list{
279 | width:100%;
280 | grid-template-columns: repeat(auto-fill,550px);
281 | }
282 | .Courses_Popular{
283 | margin-left: 0;
284 | margin-right: 0;
285 |
286 | }
287 | .Courses_Popular h2{
288 | margin-left: 20px;
289 | }
290 |
291 | }
--------------------------------------------------------------------------------
/frontend/src/pages/CourseFilter/CourseFilter.css:
--------------------------------------------------------------------------------
1 | .cartPage {
2 | position: relative;
3 | margin: 10px 30px;
4 | }
5 | .subcg_container {
6 | width: 60%;
7 | margin-left: 50px;
8 | }
9 | .subcg_container h2 {
10 | font-weight: 700;
11 | margin: 30px 0;
12 | }
13 | .courseSurSujet {
14 | display: grid;
15 | grid-template-columns: 100%;
16 | position: relative;
17 | }
18 | .courseSurSujet h3 {
19 | color: gray;
20 | font-size: 15px;
21 | }
22 | .courseSurSujet h1 {
23 | font-size: 30px;
24 | color: rgb(56, 56, 56);
25 | font-weight: 600;
26 | }
27 | .courseSurSujet h3 a {
28 | color: rgb(62, 112, 179);
29 | }
30 |
31 | .courseSurSujet h3 a:hover {
32 | color: rgb(0, 62, 197);
33 | transition: color 0.2s linear;
34 | }
35 |
36 | .courseSurSujet p {
37 | font-size: 16px;
38 | }
39 | .slick-list {
40 | display: grid;
41 | grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
42 | height: 400px;
43 | }
44 | .iconPeople {
45 | margin-right: 5px;
46 | }
47 |
48 | .coursePourCommencer h2 {
49 | font-size: 24px;
50 | font-weight: 550;
51 | color: rgb(56, 56, 56);
52 | }
53 | .ant-tabs-ink-bar {
54 | background: #000000 !important;
55 | }
56 |
57 | .ant-tabs-tab.ant-tabs-tab-active .ant-tabs-tab-btn {
58 | color: #000000 !important;
59 | }
60 |
61 | .ant-tabs-tab {
62 | color: #303030 !important;
63 | }
64 |
65 | .antd-tabs-tab:hover {
66 | color: black !important;
67 | }
68 |
69 | .coursecards {
70 | width: 100%;
71 | padding: 10px;
72 | }
73 |
74 | .TabContent {
75 | padding: 10px 20px;
76 | }
77 | .slick-active {
78 | margin: 0 10px;
79 | margin-left: 20px;
80 | }
81 | .slick-prev::before,
82 | .slick-next::before {
83 | font-size: 25px;
84 | color: #000a13;
85 | z-index: 8;
86 | }
87 | .slick-prev {
88 | z-index: 8;
89 | }
90 |
91 | .courseOfSite h2 {
92 | font-size: 24px;
93 | font-weight: 550;
94 | color: rgb(56, 56, 56);
95 | }
96 |
97 | .courseOfSite div a {
98 | border: 1px solid #dcdacb;
99 | border-radius: 4px;
100 | color: #0f7c90;
101 | display: flex;
102 | justify-content: center;
103 | align-items: center;
104 | text-align: center;
105 | height: 100%;
106 | min-height: 3.9rem;
107 | width: 100%;
108 | color: rgb(9, 94, 204);
109 | }
110 |
111 | .courseOfSite div a:hover {
112 | color: rgb(0, 35, 110);
113 | transition: color 0.1s linear;
114 | }
115 |
116 | .courseOfSiteSlider div {
117 | margin: 5px 5px;
118 | font-size: 15px;
119 | text-decoration: underline;
120 | }
121 |
122 | .courseOfSiteSlider {
123 | display: grid;
124 |
125 | grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
126 | justify-content: center;
127 | align-items: center;
128 | }
129 |
130 | .filterCourses h2 {
131 | font-size: 24px;
132 | font-weight: 550;
133 | color: rgb(56, 56, 56);
134 | }
135 |
136 | .filterButton {
137 | align-items: center;
138 | border: 1px solid rgb(0, 0, 0);
139 | background-color: transparent;
140 | color: #0f7c90;
141 | border-radius: 4px;
142 | border: 1px solid #0f7c90;
143 | font-size: 15px;
144 | outline: none;
145 | font-weight: 600;
146 | margin: 10px 5px;
147 | padding: 11px 20px;
148 | cursor: pointer;
149 | height: 43%;
150 | }
151 |
152 | .filterButton:hover {
153 | color: #156b7a;
154 | }
155 | .fl-btn{
156 | margin-left: 50px;
157 | }
158 | .filterIcon {
159 | margin-right: 5px;
160 | float: left;
161 | vertical-align: bottom;
162 | }
163 |
164 | .filterHere {
165 | display: grid;
166 | grid-template-columns: 25% auto;
167 |
168 | margin-top: 20px;
169 | }
170 |
171 | .filterHereWithout {
172 | display: grid;
173 | grid-template-columns: 100%;
174 | margin-top: 20px;
175 | }
176 | .coursesToBuy {
177 | display: grid;
178 | grid-template-columns: 20% 60% 15%;
179 | column-gap: 10px;
180 | border-bottom: 1px solid rgba(211, 211, 211, 0.658);
181 | padding-bottom: 15px;
182 | cursor: pointer;
183 |
184 | margin-bottom: 15px;
185 | }
186 |
187 | .courseTitle {
188 | font-size: 20px;
189 | color: rgb(59, 59, 59);
190 | }
191 |
192 | .courseOwner {
193 | font-size: 13px;
194 | color: #73726c;
195 | font-weight: 500;
196 | }
197 |
198 | .coursesToBuy img {
199 | position: relative;
200 | width: 100%;
201 | height: auto;
202 | object-fit: cover;
203 | }
204 |
205 | .coursesInformation p {
206 | color: #73726c;
207 | }
208 |
209 | .coursesToBuy:hover .coursesPhoto1 {
210 | filter: rgb(92, 92, 92);
211 | -webkit-filter: grayscale(60%);
212 | transition: all 0.2s linear;
213 | }
214 |
215 | .coursesPrice {
216 | display: grid;
217 | font-size: 18px;
218 | font-weight: 600;
219 | font-style: oblique;
220 | justify-content: end;
221 | grid-template-rows: 25%;
222 | }
223 |
224 | .nouveauPrice {
225 | font-weight: 700;
226 | }
227 |
228 | .ancienPrice {
229 | font-size: 14px;
230 | font-weight: 500;
231 | text-decoration: line-through;
232 | color: rgb(107, 107, 107);
233 | }
234 |
235 | .filterBy {
236 | opacity: 0;
237 | margin-right: 30px;
238 | margin-bottom: 30px;
239 | height: auto;
240 | }
241 |
242 | .filterr {
243 | transform: translateX(0px);
244 | height: auto;
245 | opacity: 1;
246 | transition: all 1s ease;
247 | margin: 0;
248 | }
249 | .filterroff {
250 | transform: translateX(-400px);
251 | height: 0px;
252 |
253 | opacity: 0;
254 | transition: all 1s ease;
255 | }
256 |
257 | .filterByWithout {
258 | transform: translateX(-400px);
259 | transition: transform 6s ease-in-out;
260 | }
261 |
262 | .coursesToBuy img {
263 | border-radius: 10%;
264 | }
265 |
266 | .filterHere h2 {
267 | margin-top: 20px;
268 | }
269 |
270 | .teachOnUdemy {
271 | margin-top: 2px;
272 | width: auto;
273 | font-size: 16px;
274 | padding: 9px 10px;
275 | color: #0f7c90;
276 | border-radius: 5px;
277 | border: 0.5px solid #0f7c90;
278 | background-color: rgb(255, 255, 255);
279 | display: block;
280 |
281 | cursor: pointer;
282 | left: -220px;
283 | opacity: 0.4;
284 | transition: all 0.5s ease-in-out;
285 | }
286 |
287 | .teacherUdemy {
288 | display: grid;
289 | justify-items: center;
290 | align-items: center;
291 | }
292 |
293 | .teachOnUdemy:hover {
294 | opacity: 1;
295 | border: 2px solid #0f7c90;
296 | transition: all 0.5s ease-in-out;
297 | }
298 |
299 | .clearFiltersBtn {
300 | background: transparent;
301 | color: #0f7c90;
302 | border: none;
303 | font-size: 20px;
304 | cursor: pointer;
305 | margin-left: 10px;
306 | border-bottom: 1px solid #0f7c90;
307 | transition: all 0.3s ease-in-out;
308 | }
309 |
310 | .clearFiltersBtn:hover {
311 | color: rgb(11, 82, 82);
312 | transition: all 0.3s ease-in-out;
313 | }
314 |
315 | .coursesVideos {
316 | position: relative;
317 | width: 100%;
318 | }
319 |
320 | @media screen and (max-width: 1008px) {
321 | .subcg_container {
322 | width: 100%;
323 | margin-left: 0px;
324 | }
325 | .slick-list {
326 | width: 100%;
327 | grid-template-columns: repeat(auto-fill, 450px);
328 | }
329 |
330 | .coursecards {
331 | width: 100%;
332 | padding: 0px;
333 | }
334 |
335 | .filterHere {
336 | grid-template-columns: 100%;
337 | row-gap: 40px;
338 | }
339 |
340 | .courseTitle {
341 | font-size: 16px;
342 | }
343 | }
344 |
--------------------------------------------------------------------------------
/frontend/src/components/body/auth/Register.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { Image, Input } from "antd";
3 | import { Link } from "react-router-dom";
4 | import { Helmet } from "react-helmet";
5 | import { FiUser, AiFillLock, MdEmail } from "react-icons/all";
6 |
7 | import axios from "axios";
8 | import {
9 | showErrMsg,
10 | showSuccessMsg,
11 | } from "../../utils/notification/Notification";
12 | import { useDispatch } from "react-redux";
13 | import {
14 | isEmpty,
15 | isEmail,
16 | isMatch,
17 | isLength,
18 | } from "../../utils/validation/Validation";
19 | import { Checkbox } from "antd";
20 | import { useEffect } from "react";
21 |
22 | const initialState = {
23 | name: "",
24 | email: "",
25 | password: "",
26 | cf_password: "",
27 | description: "",
28 | headline: "",
29 | err: "",
30 | success: "",
31 | };
32 |
33 | const Register = () => {
34 | const { TextArea } = Input;
35 | const [isTeacher, setisTeacher] = useState(false);
36 | const [formDataUser, setFormDataUser] = useState(initialState);
37 | const dispatch = useDispatch();
38 |
39 | const {
40 | name,
41 | email,
42 | password,
43 | cf_password,
44 | description,
45 | headline,
46 | err,
47 | success,
48 | } = formDataUser;
49 | const handleChange = (e) => {
50 | //place of do that onChange={(e) => setEmail(e.target.value) for each field (input) we do that
51 | setFormDataUser({
52 | ...formDataUser,
53 | [e.target.name]: e.target.value,
54 | err: "",
55 | success: "",
56 | });
57 | };
58 | const handleSubmit = async (e) => {
59 | e.preventDefault();
60 | if (isEmpty(name) | isEmpty(password))
61 | return setFormDataUser({
62 | ...formDataUser,
63 | err: "Please fill in all fields",
64 | success: "",
65 | });
66 |
67 | if (!isEmail(email))
68 | return setFormDataUser({
69 | ...formDataUser,
70 | err: "Invalid email",
71 | success: "",
72 | });
73 |
74 | if (isLength(password))
75 | return setFormDataUser({
76 | ...formDataUser,
77 | err: "Password must be at least 6 characters.",
78 | success: "",
79 | });
80 |
81 | if (!isMatch(password, cf_password))
82 | return setFormDataUser({
83 | ...formDataUser,
84 | err: "Password did not match",
85 | success: "",
86 | });
87 | try {
88 | if (isTeacher) {
89 | const res = await axios.post("/user/register", {
90 | name,
91 | email,
92 | password,
93 | isTeacher,
94 | description,
95 | headline,
96 | });
97 | setFormDataUser({ ...formDataUser, err: "", success: res.data.msg });
98 | } else {
99 | const res = await axios.post("/user/register", {
100 | name,
101 | email,
102 | password,
103 | });
104 | setFormDataUser({ ...formDataUser, err: "", success: res.data.msg });
105 | }
106 | } catch (err) {
107 | err.response.data.msg &&
108 | setFormDataUser({
109 | ...formDataUser,
110 | err: err.response.data.msg,
111 | success: "",
112 | });
113 | }
114 | };
115 |
116 | const inputs = document.querySelectorAll(".input");
117 |
118 | function addcl() {
119 | let parent = this.parentNode.parentNode;
120 | parent.classList.add("focus");
121 | }
122 |
123 | function remcl() {
124 | let parent = this.parentNode.parentNode;
125 | if (this.value === "") {
126 | parent.classList.remove("focus");
127 | }
128 | }
129 |
130 | inputs.forEach((inputa) => {
131 | inputa.addEventListener("focus", addcl);
132 | inputa.addEventListener("blur", remcl);
133 | });
134 |
135 | useEffect(() => {
136 | console.log(isTeacher);
137 |
138 | return () => {};
139 | }, [isTeacher]);
140 |
141 | return (
142 |
143 |
144 | Sign Up
145 |
146 |
147 |
153 |
154 |
155 |
156 |
157 |
259 |
260 |
261 |
262 | );
263 | };
264 |
265 | export default Register;
266 |
--------------------------------------------------------------------------------
/frontend/src/components/Navbar/Navbar.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import "./Navbar.css";
3 | import { withRouter } from "react-router-dom";
4 | import { Input, Popover, Drawer, Badge, Button, Dropdown } from "antd";
5 | import {
6 | CgShoppingCart,
7 | AiOutlineSearch,
8 | AiOutlineClose,
9 | RiArrowDropDownLine,
10 | } from "react-icons/all";
11 | import { Link } from "react-router-dom";
12 | import useWindowDimensions from "../../useWindowDimensions";
13 | import axios from "axios";
14 | import PropagateLoader from "react-spinners/PropagateLoader";
15 | import { useSelector, useDispatch } from "react-redux";
16 | const Navbar = ({ match, history }) => {
17 | const { height, width } = useWindowDimensions();
18 | const [showsearch, setshowsearch] = useState(false);
19 | const [keyword, setKeyword] = useState("");
20 | const [showicons, setshowicons] = useState(false);
21 | const { Search } = Input;
22 | const auth = useSelector((state) => state.auth);
23 | const { user, isLogged, loading } = auth;
24 |
25 | const cartReducer = useSelector((state) => state.cartReducer);
26 | const { cartItems } = cartReducer;
27 |
28 | const handleSearch = () => {
29 | if (keyword) {
30 | history.push(`/search/${keyword}`);
31 | }
32 | };
33 | const handleLogout = async () => {
34 | try {
35 | await axios.get("/user/logout");
36 | localStorage.removeItem("firstLogin");
37 | window.location.href = "/";
38 | } catch (err) {
39 | window.location.href = "/";
40 | }
41 | };
42 | const contentProfile = (
43 |
44 | Profile
45 |
46 | Logout
47 |
48 |
49 |
50 | );
51 | const contentProfilephone = (
52 |
53 | Profile
54 |
55 | Logout
56 |
57 |
58 | );
59 | const userLink = () => {
60 | return (
61 |
62 |
63 |
64 | {loading ? (
65 |
68 | ) : (
69 | <>
70 | {" "}
71 |
72 | {user.name}
73 | >
74 | )}
75 |
76 |
77 |
78 | );
79 | };
80 | const userLinkDrawer = () => {
81 | return (
82 |
83 |
84 |
{" "}
85 | {user.name}
86 |
87 |
88 |
89 | );
90 | };
91 |
92 | const [visbile, setvisbile] = useState(false);
93 | const showDrawer = () => {
94 | setvisbile(true);
95 | };
96 |
97 | const onClose = () => {
98 | setvisbile(false);
99 | };
100 |
101 | const content = (
102 |
103 | Development
104 | Marketing
105 |
106 | Design
107 |
108 | Education
109 |
110 | Photography
111 |
112 | Music
113 |
114 | Self Devlopement
115 |
116 | Business
117 |
118 |
119 | );
120 | useEffect(() => {
121 | if (width < 788) {
122 | setshowicons(true);
123 | } else {
124 | setvisbile(false);
125 | setshowicons(false);
126 | }
127 | return () => {};
128 | }, [width]);
129 | const Activateburger = () => {
130 | showDrawer();
131 | };
132 | return (
133 | <>
134 |
135 |
140 |
141 |
142 |
147 |
148 |
149 | {showicons && (
150 |
151 | {showsearch ? (
152 |
setshowsearch(!showsearch)}
156 | />
157 | ) : (
158 | setshowsearch(!showsearch)}
162 | />
163 | )}
164 |
165 |
170 |
171 |
172 |
173 |
174 | setKeyword(e.target.value)}
182 | />
183 |
184 |
185 | )}
186 |
187 | setKeyword(e.target.value)}
195 | />
196 |
197 | {!showicons && (
198 |
199 |
200 |
205 | Become a Teacher
206 |
207 |
208 |
209 |
210 | Categories
211 |
212 |
213 |
214 |
220 |
221 |
222 |
223 |
224 | {isLogged ? (
225 | userLink()
226 | ) : (
227 |
228 |
229 |
230 | {" "}
231 | Log in
232 |
233 |
234 | )}
235 | {/* */}
236 |
237 | )}
238 |
239 | {showicons && (
240 |
248 |
249 |
250 |
255 | Become a Teacher
256 |
257 |
258 |
259 | {isLogged ? (
260 | userLinkDrawer()
261 | ) : (
262 |
263 |
264 |
265 | {" "}
266 | Log in
267 |
268 |
269 | )}
270 |
271 |
Most visited :
272 | {content}
273 |
274 |
275 | )}
276 | >
277 | );
278 | };
279 |
280 | export default withRouter(Navbar);
281 |
--------------------------------------------------------------------------------
/frontend/src/pages/CourseFilter/CourseFilter.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import { BsFillPeopleFill, MdFilterList } from "react-icons/all";
3 | import { Tabs, Pagination } from "antd";
4 | import { Helmet } from "react-helmet";
5 |
6 | import Slider from "react-slick";
7 | import "./CourseFilter.css";
8 | import { Link, useParams } from "react-router-dom";
9 | import CourseCard from "../../components/CourseCard/CourseCard";
10 |
11 | import CollapsibleFilter from "./CollapsibleFilter";
12 | import { Empty, Skeleton } from "antd";
13 | import { useDispatch, useSelector } from "react-redux";
14 | import {
15 | GetSubCategorys,
16 | Listcoursesbypobularity,
17 | ListcoursesbyTopic,
18 | ListnewCourses,
19 | } from "../../redux/actions/courseActions";
20 | import {
21 | GET_CRSRATING_RESET,
22 | GET_CRSPRICE_RESET,
23 | } from "../../redux/constants/courseconstants";
24 | import Coursesblock from "./Coursesblock";
25 | import Error from "../../components/utils/Error";
26 | const CourseFilter = ({ history }) => {
27 | const [page, setpage] = useState(1);
28 | const [isFilter, setIsFilter] = useState(false);
29 | const dispatch = useDispatch();
30 | const [optionValue, setOptionValue] = useState("");
31 | const ListCoursesReducer = useSelector((state) => state.ListCoursesReducer);
32 | const { loading, courses, totalcourses, error } = ListCoursesReducer;
33 |
34 | const GetSubCategorysReducer = useSelector(
35 | (state) => state.GetSubCategorysReducer
36 | );
37 | const {
38 | loading: loadingsubcg,
39 | Subcategorys,
40 | error: errorcg,
41 | } = GetSubCategorysReducer;
42 |
43 | const ListCoursesbyPobularityReducer = useSelector(
44 | (state) => state.ListCoursesbyPobularityReducer
45 | );
46 | const {
47 | loading: loadingpobular,
48 | courses: coursespobular,
49 | error: errorpobular,
50 | } = ListCoursesbyPobularityReducer;
51 |
52 | const ListCoursesbyrating = useSelector((state) => state.ListCoursesbyrating);
53 | const {
54 | loading: loadingRate,
55 | courses: coursesRate,
56 | error: errorRate,
57 | } = ListCoursesbyrating;
58 |
59 | const ListCoursesbyprice = useSelector((state) => state.ListCoursesbyprice);
60 | const {
61 | loading: loadingPrice,
62 | courses: coursesPrice,
63 | error: errorPrice,
64 | } = ListCoursesbyprice;
65 |
66 | const ListNewCoursesReducer = useSelector(
67 | (state) => state.ListNewCoursesReducer
68 | );
69 | const {
70 | loading: loadingNew,
71 | courses: coursesNew,
72 | error: errorNew,
73 | } = ListNewCoursesReducer;
74 |
75 | const { TabPane } = Tabs;
76 | var settings = {
77 | dots: false,
78 | infinite: true,
79 | slidesToShow: 5,
80 | slidesToScroll: 1,
81 |
82 | responsive: [
83 | {
84 | breakpoint: 1024,
85 | settings: {
86 | slidesToShow: 3,
87 | slidesToScroll: 3,
88 | infinite: true,
89 | arrows: false,
90 | },
91 | },
92 | {
93 | breakpoint: 600,
94 | settings: {
95 | slidesToShow: 2,
96 | slidesToScroll: 2,
97 | initialSlide: 2,
98 | arrows: false,
99 | },
100 | },
101 | {
102 | breakpoint: 480,
103 | settings: {
104 | slidesToShow: 1,
105 | slidesToScroll: 1,
106 | arrows: false,
107 | },
108 | },
109 | ],
110 | };
111 |
112 | let { topic } = useParams();
113 | const changeTab = (key) => {
114 | switch (key) {
115 | case "1":
116 | dispatch(Listcoursesbypobularity(topic));
117 | break;
118 | case "2":
119 | dispatch(ListnewCourses(topic));
120 | break;
121 | default:
122 | break;
123 | }
124 | };
125 | const handleClear = () => {
126 | dispatch({ type: GET_CRSRATING_RESET });
127 | dispatch({ type: GET_CRSPRICE_RESET });
128 | };
129 | useEffect(() => {
130 | dispatch(GetSubCategorys(topic));
131 | dispatch({ type: GET_CRSRATING_RESET });
132 | dispatch({ type: GET_CRSPRICE_RESET });
133 | dispatch(Listcoursesbypobularity(topic));
134 | dispatch(ListcoursesbyTopic(topic, true, page));
135 | return () => {};
136 | }, [dispatch, topic, page, history]);
137 | if (!loading && totalcourses === 0) {
138 | history.push("/notfound");
139 | }
140 |
141 | return (
142 |
143 |
144 | {topic} Courses
145 |
146 |
147 |
{topic.charAt(0).toUpperCase() + topic.slice(1)} Courses
148 |
149 | {topic} relates to {topic},
150 | IT & Software
151 |
152 |
153 | {" "}
154 | 9 000 002 learners
155 |
156 |
157 |
158 |
Courses to get you started
159 |
160 |
161 |
162 |
163 | {loadingpobular ? (
164 |
165 | ) : errorpobular ? (
166 |
167 | ) : coursespobular.length === 0 ? (
168 |
169 | ) : (
170 |
171 | {coursespobular.map((course, index) => (
172 | <>
173 |
178 | >
179 | ))}
180 |
181 | )}
182 |
183 |
184 |
185 |
186 |
187 |
188 | {loadingNew ? (
189 |
190 | ) : errorNew ? (
191 |
192 | ) : coursesNew.length === 0 ? (
193 |
194 | ) : (
195 |
196 | {coursesNew.map((course, index) => (
197 | <>
198 |
203 | >
204 | ))}
205 |
206 | )}
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
{topic.charAt(0).toUpperCase() + topic.slice(1)} students also learn
215 |
216 | {loadingsubcg ? (
217 |
218 | ) : errorcg ? (
219 |
220 | ) : Subcategorys.length === 0 ? (
221 |
222 | ) : (
223 | Subcategorys.map((sub) => (
224 |
225 | {sub}
226 |
227 | ))
228 | )}
229 |
230 |
231 |
232 |
233 |
234 |
235 |
All {topic.charAt(0).toUpperCase() + topic.slice(1)} courses
236 |
237 | setIsFilter(!isFilter)}
240 | >
241 |
242 | Filter
243 |
244 | handleClear()}>
245 | Most Popular
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
Teach the world online
259 |
260 | Create an online video course, reach students across the globe,
261 | and earn money
262 |
263 |
Teach on Udemy
264 |
265 |
266 |
267 | {loading || loadingRate || loadingPrice ? (
268 |
269 | ) : error || errorRate || errorPrice ? (
270 |
271 | ) : courses.length === 0 ? (
272 |
273 | ) : coursesRate ? (
274 | coursesRate.map((course, index) => (
275 |
280 | ))
281 | ) : coursesPrice ? (
282 | coursesPrice.map((course, index) => (
283 |
288 | ))
289 | ) : (
290 | courses.map((course, index) => (
291 |
296 | ))
297 | )}
298 |
setpage(current)}
302 | total={totalcourses}
303 | />
304 |
305 |
306 |
307 |
308 | );
309 | };
310 |
311 | export default CourseFilter;
312 |
--------------------------------------------------------------------------------