This website is an online learning platform for students to take courses in various fields.
What started as a university project quickly became a journey of learning and a passion project were all 5 team members put their best efforts in order to create an online learning platform that rivals the best ones out there in terms of ui and design.
This project is currently in build 1.0, with some bugs in connections from front end to back end.
- The code style is the standard style used in the MERN stack and visual studio code
- MVC(Model,View Controller) following the dry principle
- Spaces and indentations are used to identify loops,fuctions,conditions,..ets
https://drive.google.com/drive/folders/1mdeqfvAthBCS29tvZFpbaoFKMjAtVijp?usp=sharing
Client: Next.js(React Framework), TailwindCSS, Typescript
Server: Node, Express,Mongoose,Mongodb
- Exceptional UI
- Live previews
- Fullscreen mode
- Cross platform
- Adaptive to diffrent screen resolutions
- Efficient backend code
- All user data is encrypted and secure
- Handles credit card transactions securly
- A color pallet that is easy on the eyes
Footer of the website
import React from 'react' import classNames from 'classnames' import { FiFacebook, FiTwitter, FiInstagram, FiLinkedin } from 'react-icons/fi' import Link from 'next/link'
type Props = {}
const Footer = (props: Props) => {
return ( <div style={{backgroundColor: '#222222'}} className='text-white mx-0 fluid-container block relative z-50'>
const footerCol = classNames(col-6 col-md-3 p-4
);
const colHeader = classNames(w-fit relative right-3 border-b-[3px] border-canadian-red px-1 pb-1 skew-x-[40deg]
);
const colHeaderText = classNames(-skew-x-[40deg] ml-2
);
const colData = classNames(text-left mt-3 ml-1
);
const colItems = classNames(text-sm text-bright-gray py-1.5 hover:scale-105 hover:text-white transition-all duration-300 w-fit
);
const colIcons = classNames(text-sm p-1.25 flex border-canadian-red text-canadian-red hover:text-white hover:border-white items-center justify-center m-2 z-0 scale-125 rounded-full border-1.5 before:content-[""] before:inline-block before:absolute before:z-behind before:bottom-1 before:right-2.75 before:w-6 before:h-6 before:rounded-full hover:scale-135 transition-all duration-300
);
export default Footer
End of footer code
instructor add course function
async function addCourse(req, res, next) { try { var exists=await CourseTable.findOne({"title":req.body.title}); if(exists){ res.status(400).send("Course title already used"); } else{ var x = await User.find({ "_id": req.user._id }, { firstname: 1, lastname: 1, _id: 0 }); var y = Object.values(x)[0]; console.log(y); var name = y.firstname + " " + y.lastname; console.log(name);
const newCourse = new CourseTable({
instructorID: req.user._id,
title: req.body.title,
summary: req.body.summary,
subtitles: req.body.subtitles,
subject: req.body.subject,
price: req.body.price,
skills: req.body.skills,
level: req.body.level,
courseHours: req.body.courseHours,
//exercises: req.body.exercises,
courseVideo : req.body.courseVideo,
//rating: req.body.rating,
instructorName: name,
discountPrice: req.body.price,
//review:req.body.review,
courseImage:req.body.courseImage
});
newCourse.save();
// res.send(newCourse); console.log("Course added successfully."); if(req.body.exercises){ var z = Object.values(newCourse)[0] ; console.log(z._id); let exercises = req.body.exercises; for(var i = 0;i < exercises.length;i++){ var q = exercises[i] ; q.courseID = z._id; console.log("f:" + i); console.log(q); const newExercise = await new ExerciseTable(q); await newExercise.save(); console.log("added: ", i); } var courseid = z._id; const exe = await ExerciseTable.find({courseID:courseid}).select({"_id":1,"subtitleName":1,"exerciseTitle":1}); console.log("exe" +exe); //var z = Object.values(exe)[0] ; for(var i=0;i<exe.length;i++){ var z = exe[i] ; console.log(z.subtitleName); if(z.subtitleName){ await CourseTable.updateOne({ "_id": courseid,"subtitles.header": z.subtitleName }, { "$push": { "subtitles.$.exercise" :{ "exerciseID": z._id, "exerciseName": z.exerciseTitle }}} ); } else{ await CourseTable.updateOne({ "_id": courseid}, { "$set": { "finalExam" : z._id}} ); }
}
}
res.send("Course Added");
}
} catch (err) { res.status(400).json({error:err.message}) }; };
End of instructor add course function
Register Function
async function register(req, res) { console.log("req.body.password"); const saltHash = genPassword(req.body.password);
const salt = saltHash.salt; const hash = saltHash.hash; let EmailLowerCase = req.body.email.toLowerCase(); var exists1 = await User.findOne({ "email": EmailLowerCase });
var exists2 = await User.findOne({ "username": req.body.username }); if (exists1 || exists2) { if (exists1 && exists2) { res.status(400).send("username and email already used"); } else if (exists1) { res.status(400).send("email already used"); } else { res.status(400).send("username already used"); } } else { const newUser = new User({ username: req.body.username, hash: hash, salt: salt, gender: req.body.gender, email: EmailLowerCase, firstname: req.body.firstname, lastname: req.body.lastname, role: req.body.role });
newUser.save((err, newUser) => {
if (err) {
console.log(err);
res.status(400).send("Error registering new user please try again.");
}
else {
let token = CreateToken({ id: newUser._id, email: newUser.email });
MailValidate(newUser.email, "http://localhost:3000/Auth/FeedBack/EmailConfirmed", token);
res.status(200).send("Verify your email");
}
})
}
};
End of register function
Open the project is visual studio code npm install
PUT /user/ChangeEmail
allows the user to change the email of his account
Body | Type | Description |
---|---|---|
userMail |
string |
Required. |
POST /user/reportProblem
allows the user to report any error or bug encountered whilst using the website
the type is "financial","other","technical"
Body | Type | Description |
---|---|---|
user_id |
string |
Required. |
courseID |
string |
Required. |
type |
string |
Required. |
body |
string |
Required. |
startDate |
date |
Required. |
get /user/takeExam
allows the user to take the exam of the course
query | Type | Description |
---|---|---|
examID |
string |
Required. |
get /user/logout
allows the user to logout of his account
Parameter | Type | Description |
---|---|---|
logout |
string |
Required. |
clearCookie |
string |
Required. |
get /user/viewProfile
allows the user to view his profile data
Body | Type | Description |
---|---|---|
user_id |
string |
Required. |
courseID |
string |
Required. |
excerciseID |
string |
Required. |
answers |
array[string] |
Required. |
put /user/addNote
allows the user to add a note when whatching a video for future refrence
Body | Type | Description |
---|---|---|
user_id |
string |
Required. |
courseID |
string |
Required. |
content |
string |
Required. |
subtitle |
string |
Required. |
timestamp |
string |
Required. |
note |
string |
Required. |
subtitleName |
string |
Required. |
contentName |
string |
Required. |
subtitleIndex |
string |
Required. |
contentIndex |
string |
Required. |
put /user/viewNotes
allows the user to view all his notes
Body | Type | Description |
---|---|---|
user_id |
string |
Required. |
courseID |
string |
Required. |
Admin api
- GET admin/viewCourseRequests
Query | Type | Description |
---|---|---|
page |
Number |
Optional |
get all Course Requests with 5 Requests per page navigated by Parameter CurrentPage whose default is 1
- GET admin/viewRefunds
Query | Type | Description |
---|---|---|
page |
Number |
Optional |
get all efunds with 5 Refund per page navigated by Parameter CurrentPage whose default is 1
- PUT admin/grantAccess
Body | Type | Description |
---|---|---|
granted |
string |
Required |
allow or prevent CorporateTrainee from getting access to Course by sending body.granted = true or false
- PUT admin/refund
Body | Type | Description |
---|---|---|
refund |
string |
Required |
give or prevent CorporateTrainee from getting access to Course by sending body.refund = Accept or not
- PUT admin/givePromotion
Body | Type | Description |
---|---|---|
courseID |
[string] |
Required |
Promotion |
Integer |
Required |
StartDate |
Date |
Required |
EndDate |
Date |
Required |
set promotion to courses given in in courseID with with promotion as and set StartDate and endDate
- GET admin/viewReportedFunctions
Query | Type | Description |
---|---|---|
page |
Number |
Optional |
get all Report with 5 Report per page navigated by Parameter page whose default if 1
- PUT admin/markReportedProblem
Body | Type | Description |
---|---|---|
status |
string |
Required |
ProblemID |
string |
Required |
set problem.status with body.status
Instructor api
- GET instructor/viewCourses
Query | Type | Description |
---|---|---|
page |
Number |
Optional |
get Instructor Courses per page navigated by Parameter CurrentPage whose default is 1
- GET instructor/filterCourse
Query | Type | Description |
---|---|---|
price |
int |
Optional |
keyword |
string |
Optional |
subject |
string |
Optional |
filter Instructor Course by price or subject and search throught Instructor Course using course subject or title using keyword
- PUT instructor/updateBio
Body | Type | Description |
---|---|---|
newBio |
String |
Required |
update Instructor biography
- GET instructor/filterByRatings
| Query | Type | Description |
| :-------- | :------- | :------------------------- |
|
CurrentPage
|int
| Optional | |rating
|int
| Required |
Instructor filter his courses and his reviews by rating
-
PUT instructor/viewAmountOwned Instructor view Amount Owned from the site
-
POST instructor/addCourse
Body | Type | Description |
---|---|---|
title |
String |
Required |
summary |
string |
Required |
sutbtitles |
string |
Required |
subject |
string |
Required |
exercises |
string |
Required |
skills |
string |
Required |
level |
string |
Required |
courseVideo |
string |
Required |
courseHours |
int |
Required |
price |
int |
Required |
courseImage |
string |
Required |
Instructor add course
- POST instructor/discount
Body | Type | Description |
---|---|---|
courseID |
String |
Required |
discount |
int |
Required |
StartDate |
Date |
Required |
endDate |
Date |
Required |
Instructor add discount on course
- POST instructor/canceldiscount
Body | Type | Description |
---|---|---|
courseId |
String |
Required |
Instructor cancel discount on course
-
GET instructor/viewProfile view Instructor viewProfile
-
GET instructor/viewInstructorPopularCourses
Body | Type | Description |
---|---|---|
CurrentPage |
int |
Optional |
Instructor view his most popular courses
course api
- GET course/getPrice
Query | Type | Description |
---|---|---|
id |
string |
Required |
GET course price with his id
- GET course/
Query | Type | Description |
---|---|---|
price |
int |
Optional |
keyword |
string |
Optional |
subject |
string |
Optional |
rating |
string |
Optional |
page |
int |
Optional |
Course Search by keyboard or filter using price or rating or subject
- GET course/viewPopularCourses
Query | Type | Description |
---|---|---|
CurrentPageid |
int |
Optional |
coursesPerPage |
int |
Optional |
get courses ranking by number of buyers(most popular courses) |
User api
POST user/register
Body | Type | Description |
---|---|---|
password |
String |
Required |
username |
String |
Required |
firstname |
String |
Required |
lastname |
String |
Required |
gender |
String |
Required |
role |
String |
Required |
email |
String |
Required |
POST user/forgetPassword
Body | Type | Description |
---|---|---|
email |
String |
Required |
POST user/changePassword
Body | Type | Description |
---|---|---|
oldPassword |
String |
Required |
password |
String |
Required |
GET user/forgetPassword
Query | Type | Description |
---|---|---|
exerciseID |
String |
Required |
CourseID |
String |
Required |
PUT user/giveCourseRating
Body | Type | Description |
---|---|---|
rating |
int |
Required |
oldRating |
int |
Optional |
courseId |
String |
Required |
edit course rating if old rating is given else we give new rating to course
PUT user/giveCourseReview
Body | Type | Description |
---|---|---|
rating |
Number |
Required |
review |
String |
Required |
oldReview |
String |
Optional |
courseId |
String |
Required |
PUT user/addPaymentMethod
Body | Type | Description |
---|---|---|
creditCardNumber |
String |
Required |
ccv |
Number |
Required |
expiration |
String |
Required |
cardHolderName |
String |
Required |
check if card already saved if not save creditCard Details for user by creating stripe customer and saving it
PUT user/buyCourse
Body | Type | Description |
---|---|---|
creditCardNumber |
String |
Optional |
ccv |
Number |
Optional |
expiration |
String |
Optional |
cardHolderName |
String |
Optional |
courseID |
String |
Required |
Amount |
Number |
Required |
customerId |
String |
Optional |
user buy course | ||
if user choose already created card then we use customerId | ||
to get pay using stripe else user must enter card Details | ||
to pay for the course |
GET user/ViewMyCourses
Query | Type | Description |
---|---|---|
page |
Number |
Optional |
PUT user/selectCourse
Query | Type | Description |
---|---|---|
courseID |
String |
Required |
open course page |
PUT user/watchVideo
Query | Type | Description |
---|---|---|
courseID |
String |
Required |
videoURL |
String |
Required |
videotime |
Number |
Required |
when user or CorporateTrainee watch view it update progress | ||
of the student in the course.if video is watched before it | ||
will not increase the progress |
- Back end testing was done using postman to check the correctness of data and table updates
- front end testing was done by running the front end and seeing the look and functionality
- to run: open postman, import, link, paste any link
- collection 1: https://api.postman.com/collections/22942928-6bc72154-d208-4a13-bd38-61f621bbfec9?access_key=PMAT-01GP48Y1GHBN6JQZQ5G0WR9YD9
- collection 2: https://api.postman.com/collections/18649877-b4d6dcf5-8f86-4ebd-8b01-d85ff843aff8?access_key=PMAT-01GP4A0KHPNPMJW9507DGEVAPQ
- collection 3: https://api.postman.com/collections/24528147-1441a56e-fb32-403e-8d6e-ec912ff125bc?access_key=PMAT-01GP4AXC0WP2QSJY55QMSXTGX0
- after running npm i
- cd Frontend
- npm run dev / nodemon run dev
- open new terminal
- cd cd Backend
- node app.js / nodemon app.js
- Fixing Any missing connections from the frontend to the back end is appreciated
- new back end functions to improve quality of life for users
- Dr Mervat and her incredible Teaching Assistants (Nada,Hadwa,Noha)
- Dr. Angela yu for her amazing course (web development bootcamp)
- StackOverflow
- The 5 team members who created this project
Apache 2.0 license as stripe was used
https://www.apache.org/licenses/LICENSE-2.0.html
The MIT License (MIT)
Copyright (c) 2015 Chris Kibble
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.