Initial commit for aws_deployment
commit
f882f2cf56
@ -0,0 +1 @@
|
|||||||
|
REACT_APP_API_URL=http://<ec2-instance-Public-IPv4-DNS>:4000
|
@ -0,0 +1,23 @@
|
|||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"name": "s54",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@testing-library/jest-dom": "^5.16.5",
|
||||||
|
"@testing-library/react": "^13.4.0",
|
||||||
|
"@testing-library/user-event": "^13.5.0",
|
||||||
|
"bootstrap": "^5.3.0",
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-bootstrap": "^2.7.4",
|
||||||
|
"react-dom": "^18.2.0",
|
||||||
|
"react-router-dom": "^6.13.0",
|
||||||
|
"react-scripts": "5.0.1",
|
||||||
|
"sweetalert2": "^11.7.12",
|
||||||
|
"web-vitals": "^2.1.4"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "react-scripts start",
|
||||||
|
"build": "react-scripts build",
|
||||||
|
"test": "react-scripts test",
|
||||||
|
"eject": "react-scripts eject"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": [
|
||||||
|
"react-app",
|
||||||
|
"react-app/jest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"browserslist": {
|
||||||
|
"production": [
|
||||||
|
">0.2%",
|
||||||
|
"not dead",
|
||||||
|
"not op_mini all"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"last 1 chrome version",
|
||||||
|
"last 1 firefox version",
|
||||||
|
"last 1 safari version"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 3.8 KiB |
@ -0,0 +1,43 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta name="theme-color" content="#000000" />
|
||||||
|
<meta
|
||||||
|
name="description"
|
||||||
|
content="Web site created using create-react-app"
|
||||||
|
/>
|
||||||
|
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||||
|
<!--
|
||||||
|
manifest.json provides metadata used when your web app is installed on a
|
||||||
|
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||||
|
-->
|
||||||
|
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||||
|
<!--
|
||||||
|
Notice the use of %PUBLIC_URL% in the tags above.
|
||||||
|
It will be replaced with the URL of the `public` folder during the build.
|
||||||
|
Only files inside the `public` folder can be referenced from the HTML.
|
||||||
|
|
||||||
|
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||||
|
work correctly both with client-side routing and a non-root public URL.
|
||||||
|
Learn how to configure a non-root public URL by running `npm run build`.
|
||||||
|
-->
|
||||||
|
<title>React App</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
<div id="root"></div>
|
||||||
|
<!--
|
||||||
|
This HTML file is a template.
|
||||||
|
If you open it directly in the browser, you will see an empty page.
|
||||||
|
|
||||||
|
You can add webfonts, meta tags, or analytics to this file.
|
||||||
|
The build step will place the bundled scripts into the <body> tag.
|
||||||
|
|
||||||
|
To begin the development, run `npm start` or `yarn start`.
|
||||||
|
To create a production bundle, use `npm run build` or `yarn build`.
|
||||||
|
-->
|
||||||
|
</body>
|
||||||
|
</html>
|
Binary file not shown.
After Width: | Height: | Size: 5.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 9.4 KiB |
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"short_name": "React App",
|
||||||
|
"name": "Create React App Sample",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "favicon.ico",
|
||||||
|
"sizes": "64x64 32x32 24x24 16x16",
|
||||||
|
"type": "image/x-icon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "logo192.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "192x192"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "logo512.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "512x512"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start_url": ".",
|
||||||
|
"display": "standalone",
|
||||||
|
"theme_color": "#000000",
|
||||||
|
"background_color": "#ffffff"
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
# https://www.robotstxt.org/robotstxt.html
|
||||||
|
User-agent: *
|
||||||
|
Disallow:
|
@ -0,0 +1,9 @@
|
|||||||
|
*{
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cardHighlight {
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
@ -0,0 +1,101 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import { Container } from 'react-bootstrap';
|
||||||
|
import { BrowserRouter as Router } from 'react-router-dom';
|
||||||
|
import { Route, Routes } from 'react-router-dom';
|
||||||
|
import AppNavbar from './components/AppNavbar';
|
||||||
|
// import Banner from './components/Banner';
|
||||||
|
// import Highlights from './components/Highlights';
|
||||||
|
import Courses from './pages/Courses';
|
||||||
|
import CourseView from './pages/CourseView';
|
||||||
|
import Error from './pages/Error';
|
||||||
|
import Home from './pages/Home';
|
||||||
|
import Login from './pages/Login';
|
||||||
|
import Logout from './pages/Logout';
|
||||||
|
import Register from './pages/Register';
|
||||||
|
import AddCourse from './pages/AddCourse';
|
||||||
|
import Profile from './pages/Profile';
|
||||||
|
import './App.css';
|
||||||
|
import { UserProvider } from './UserContext';
|
||||||
|
|
||||||
|
// React JS is a single page application (SPA)
|
||||||
|
// Whenever a link is clicked, it functions as if the page is being reloaded but what it actually does is it goes through the process of rendering, mounting, rerendering and unmounting components
|
||||||
|
// When a link is clicked, React JS changes the url of the application to mirror how HTML accesses its urls
|
||||||
|
// It renders the component executing the function component and it's expressions
|
||||||
|
// After rendering it mounts the component displaying the elements
|
||||||
|
// Whenever a state is updated or changes are made with React JS, it rerenders the component
|
||||||
|
// Lastly, when a different page is loaded, it unmounts the component and repeats this process
|
||||||
|
// The updating of the user interface closely mirrors that of how HTML deals with page navigation with the exception that React JS does not reload the whole page
|
||||||
|
function App() {
|
||||||
|
|
||||||
|
// State hook for the user state that's defined here for a global scope
|
||||||
|
// Initialized as an object with properties from the localStorage
|
||||||
|
// This will be used to store the user information and will be used for validating if a user is logged in on the app or not
|
||||||
|
const [user, setUser] = useState({
|
||||||
|
id: null,
|
||||||
|
isAdmin: null
|
||||||
|
});
|
||||||
|
|
||||||
|
// Function for clearing localStorage on logout
|
||||||
|
const unsetUser = () => {
|
||||||
|
|
||||||
|
localStorage.clear();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
//Because our user state's values are reset to null every time the user reloads the page (thus logging the user out), we want to use React's useEffect hook to fetch the logged-in user's details when the page is reloaded. By using the token saved in localStorage when a user logs in, we can fetch the their data from the database, and re-set the user state values back to the user's details.
|
||||||
|
useEffect(() => {
|
||||||
|
|
||||||
|
// console.log(user);
|
||||||
|
fetch(`${process.env.REACT_APP_API_URL}/users/details`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${ localStorage.getItem('token') }`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
console.log(data)
|
||||||
|
// Set the user states values with the user details upon successful login.
|
||||||
|
if (typeof data._id !== "undefined") {
|
||||||
|
|
||||||
|
setUser({
|
||||||
|
id: data._id,
|
||||||
|
isAdmin: data.isAdmin
|
||||||
|
});
|
||||||
|
|
||||||
|
// Else set the user states to the initial values
|
||||||
|
} else {
|
||||||
|
|
||||||
|
setUser({
|
||||||
|
id: null,
|
||||||
|
isAdmin: null
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<UserProvider value={{user, setUser, unsetUser}}>
|
||||||
|
<Router>
|
||||||
|
<AppNavbar />
|
||||||
|
<Container>
|
||||||
|
<Routes>
|
||||||
|
<Route path="/" element={<Home />} />
|
||||||
|
<Route path="/courses" element={<Courses />} />
|
||||||
|
<Route path="/courses/:courseId" element={<CourseView />}/>
|
||||||
|
<Route path="/register" element={<Register />} />
|
||||||
|
<Route path="/login" element={<Login />} />
|
||||||
|
<Route path="/logout" element={<Logout />} />
|
||||||
|
<Route path="/addCourse" element={<AddCourse />} />
|
||||||
|
<Route path="/profile" element={<Profile />} />
|
||||||
|
<Route path="*" element={<Error />} />
|
||||||
|
</Routes>
|
||||||
|
</Container>
|
||||||
|
</Router>
|
||||||
|
</UserProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
@ -0,0 +1,11 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
// Creates a Context object
|
||||||
|
// A context object as the name states is a data type of an object that can be used to store information that can be shared to other components within the app
|
||||||
|
// The context object is a different approach to passing information between components and allows easier access by avoiding the use of prop-drilling
|
||||||
|
const UserContext = React.createContext();
|
||||||
|
|
||||||
|
// The "Provider" component allows other components to consume/use the context object and supply the necessary information needed to the context object
|
||||||
|
export const UserProvider = UserContext.Provider;
|
||||||
|
|
||||||
|
export default UserContext;
|
@ -0,0 +1,60 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import { Table } from 'react-bootstrap';
|
||||||
|
|
||||||
|
import EditCourse from './EditCourse';
|
||||||
|
import ArchiveCourse from './ArchiveCourse';
|
||||||
|
|
||||||
|
|
||||||
|
export default function AdminView({ coursesData, fetchData }) {
|
||||||
|
|
||||||
|
|
||||||
|
const [courses, setCourses] = useState([])
|
||||||
|
|
||||||
|
|
||||||
|
//Getting the coursesData from the courses page
|
||||||
|
useEffect(() => {
|
||||||
|
const coursesArr = coursesData.map(course => {
|
||||||
|
return (
|
||||||
|
<tr key={course._id}>
|
||||||
|
<td>{course._id}</td>
|
||||||
|
<td>{course.name}</td>
|
||||||
|
<td>{course.description}</td>
|
||||||
|
<td>{course.price}</td>
|
||||||
|
<td className={course.isActive ? "text-success" : "text-danger"}>
|
||||||
|
{course.isActive ? "Available" : "Unavailable"}
|
||||||
|
</td>
|
||||||
|
<td> <EditCourse course={course._id} fetchData={fetchData} /> </td>
|
||||||
|
<td><ArchiveCourse course={course._id} isActive={course.isActive} fetchData={fetchData}/></td>
|
||||||
|
</tr>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
setCourses(coursesArr)
|
||||||
|
|
||||||
|
}, [coursesData])
|
||||||
|
|
||||||
|
|
||||||
|
return(
|
||||||
|
<>
|
||||||
|
<h1 className="text-center my-4"> Admin Dashboard</h1>
|
||||||
|
|
||||||
|
<Table striped bordered hover responsive>
|
||||||
|
<thead>
|
||||||
|
<tr className="text-center">
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Description</th>
|
||||||
|
<th>Price</th>
|
||||||
|
<th>Availability</th>
|
||||||
|
<th colSpan="2">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
{courses}
|
||||||
|
</tbody>
|
||||||
|
</Table>
|
||||||
|
</>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
import { useState, useContext } from 'react';
|
||||||
|
import Container from 'react-bootstrap/Container';
|
||||||
|
import Navbar from 'react-bootstrap/Navbar';
|
||||||
|
import Nav from 'react-bootstrap/Nav';
|
||||||
|
|
||||||
|
import { Link, NavLink } from 'react-router-dom';
|
||||||
|
import UserContext from '../UserContext';
|
||||||
|
|
||||||
|
|
||||||
|
export default function AppNavbar() {
|
||||||
|
|
||||||
|
// State to store the user information stored in the login page.
|
||||||
|
// const [user, setUser] = useState(localStorage.getItem("access"));
|
||||||
|
// console.log(user);
|
||||||
|
|
||||||
|
const { user } = useContext(UserContext);
|
||||||
|
|
||||||
|
return(
|
||||||
|
<Navbar bg="light" expand="lg">
|
||||||
|
<Container fluid>
|
||||||
|
<Navbar.Brand as={Link} to="/">Zuitt</Navbar.Brand>
|
||||||
|
<Navbar.Toggle aria-controls="basic-navbar-nav" />
|
||||||
|
<Navbar.Collapse id="basic-navbar-nav">
|
||||||
|
<Nav className="ms-auto">
|
||||||
|
<Nav.Link as={NavLink} to="/" exact>Home</Nav.Link>
|
||||||
|
<Nav.Link as={NavLink} to="/courses" exact>Courses</Nav.Link>
|
||||||
|
{(user.id !== null) ?
|
||||||
|
|
||||||
|
user.isAdmin
|
||||||
|
?
|
||||||
|
<>
|
||||||
|
<Nav.Link as={Link} to="/addCourse">Add Course</Nav.Link>
|
||||||
|
<Nav.Link as={Link} to="/logout">Logout</Nav.Link>
|
||||||
|
</>
|
||||||
|
:
|
||||||
|
<>
|
||||||
|
<Nav.Link as={Link} to="/profile">Profile</Nav.Link>
|
||||||
|
<Nav.Link as={Link} to="/logout">Logout</Nav.Link>
|
||||||
|
</>
|
||||||
|
:
|
||||||
|
<>
|
||||||
|
<Nav.Link as={Link} to="/login">Login</Nav.Link>
|
||||||
|
<Nav.Link as={Link} to="/register">Register</Nav.Link>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</Nav>
|
||||||
|
</Navbar.Collapse>
|
||||||
|
</Container>
|
||||||
|
</Navbar>
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,87 @@
|
|||||||
|
import { Button } from 'react-bootstrap';
|
||||||
|
import Swal from 'sweetalert2';
|
||||||
|
|
||||||
|
export default function ArchiveCourse({course, isActive, fetchData}) {
|
||||||
|
|
||||||
|
const archiveToggle = (courseId) => {
|
||||||
|
fetch(`${process.env.REACT_APP_API_URL}/courses/${courseId}/archive`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${localStorage.getItem('token')}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
console.log(data)
|
||||||
|
if(data === true) {
|
||||||
|
Swal.fire({
|
||||||
|
title: 'Success',
|
||||||
|
icon: 'success',
|
||||||
|
text: 'Course successfully disabled'
|
||||||
|
})
|
||||||
|
fetchData();
|
||||||
|
|
||||||
|
}else {
|
||||||
|
Swal.fire({
|
||||||
|
title: 'Something Went Wrong',
|
||||||
|
icon: 'Error',
|
||||||
|
text: 'Please Try again'
|
||||||
|
})
|
||||||
|
fetchData();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const activateToggle = (courseId) => {
|
||||||
|
fetch(`${process.env.REACT_APP_API_URL}/courses/${courseId}/activate`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${localStorage.getItem('token')}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
console.log(data)
|
||||||
|
if(data === true) {
|
||||||
|
Swal.fire({
|
||||||
|
title: 'Success',
|
||||||
|
icon: 'success',
|
||||||
|
text: 'Course successfully enabled'
|
||||||
|
})
|
||||||
|
fetchData();
|
||||||
|
}else {
|
||||||
|
Swal.fire({
|
||||||
|
title: 'Something Went Wrong',
|
||||||
|
icon: 'Error',
|
||||||
|
text: 'Please Try again'
|
||||||
|
})
|
||||||
|
fetchData();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return(
|
||||||
|
<>
|
||||||
|
{isActive ?
|
||||||
|
|
||||||
|
<Button variant="danger" size="sm" onClick={() => archiveToggle(course)}>Archive</Button>
|
||||||
|
|
||||||
|
:
|
||||||
|
|
||||||
|
<Button variant="success" size="sm" onClick={() => activateToggle(course)}>Activate</Button>
|
||||||
|
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
import { Button, Row, Col } from 'react-bootstrap';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
export default function Banner({data}) {
|
||||||
|
|
||||||
|
console.log(data);
|
||||||
|
const {title, content, destination, label} = data;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Row>
|
||||||
|
<Col className="p-4 text-center">
|
||||||
|
<h1>{title}</h1>
|
||||||
|
<p>{content}</p>
|
||||||
|
<Link className="btn btn-primary" to={destination}>{label}</Link>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { Card, Button } from 'react-bootstrap';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
export default function CourseCard({courseProp}) {
|
||||||
|
|
||||||
|
// Checks to see if the data was successfully passed
|
||||||
|
// console.log(props);
|
||||||
|
// Every component recieves information in a form of an object
|
||||||
|
// console.log(typeof props);
|
||||||
|
|
||||||
|
// Deconstruct the course properties into their own variables
|
||||||
|
const { _id, name, description, price} = courseProp;
|
||||||
|
|
||||||
|
|
||||||
|
// const [count, setCount] = useState(0);
|
||||||
|
// console.log(useState(0));
|
||||||
|
|
||||||
|
// const [seats, setSeats] = useState(10);
|
||||||
|
|
||||||
|
// function enroll(){
|
||||||
|
// if (seats > 0) {
|
||||||
|
// setCount(count + 1);
|
||||||
|
// console.log('Enrollees: ' + count);
|
||||||
|
// setSeats(seats - 1);
|
||||||
|
// console.log('Seats: ' + seats)
|
||||||
|
// } else {
|
||||||
|
// alert("No more seats available");
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className="mt-3">
|
||||||
|
<Card.Body>
|
||||||
|
<Card.Title>{name}</Card.Title>
|
||||||
|
<Card.Subtitle>Description:</Card.Subtitle>
|
||||||
|
<Card.Text>{description}</Card.Text>
|
||||||
|
<Card.Subtitle>Price:</Card.Subtitle>
|
||||||
|
<Card.Text>PhP {price}</Card.Text>
|
||||||
|
<Link className="btn btn-primary" to={`/courses/${_id}`}>Details</Link>
|
||||||
|
</Card.Body>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
CourseCard.propTypes = {
|
||||||
|
// The "shape" method is used to check if a prop object conforms to a specific "shape"
|
||||||
|
course: PropTypes.shape({
|
||||||
|
// Define the properties and their expected types
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
description: PropTypes.string.isRequired,
|
||||||
|
price: PropTypes.number.isRequired
|
||||||
|
})
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import CourseCard from './CourseCard';
|
||||||
|
const CourseSearch = () => {
|
||||||
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
|
const [searchResults, setSearchResults] = useState([]);
|
||||||
|
|
||||||
|
const handleSearch = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${process.env.REACT_APP_API_URL}/courses/searchByName`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ courseName: searchQuery })
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
setSearchResults(data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error searching for courses:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='pt-5 container'>
|
||||||
|
<h2>Course Search</h2>
|
||||||
|
<div className="form-group">
|
||||||
|
<label htmlFor="courseName">Course Name:</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="courseName"
|
||||||
|
className="form-control"
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={event => setSearchQuery(event.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button className="btn btn-primary my-4" onClick={handleSearch}>
|
||||||
|
Search
|
||||||
|
</button>
|
||||||
|
<h3>Search Results:</h3>
|
||||||
|
<ul>
|
||||||
|
{searchResults.map(course => (
|
||||||
|
// <li key={course._id}>{course.name}</li>
|
||||||
|
<CourseCard courseProp={course} key={course._id}/>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CourseSearch;
|
@ -0,0 +1,135 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Button, Modal, Form } from 'react-bootstrap';
|
||||||
|
import Swal from 'sweetalert2';
|
||||||
|
|
||||||
|
export default function EditCourse({ course, fetchData }) {
|
||||||
|
|
||||||
|
//state for courseId for the fetch URL
|
||||||
|
const [courseId, setCourseId] = useState('');
|
||||||
|
|
||||||
|
//Forms state
|
||||||
|
//Add state for the forms of course
|
||||||
|
const [name, setName] = useState('');
|
||||||
|
const [description, setDescription] = useState('')
|
||||||
|
const [price, setPrice] = useState('')
|
||||||
|
|
||||||
|
//state for editCourse Modals to open/close
|
||||||
|
const [showEdit, setShowEdit] = useState(false)
|
||||||
|
|
||||||
|
//function for opening the modal
|
||||||
|
const openEdit = (courseId) => {
|
||||||
|
//to still get the actual data from the form
|
||||||
|
fetch(`${process.env.REACT_APP_API_URL}/courses/${ courseId }`)
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
//populate all the input values with course info that we fetched
|
||||||
|
setCourseId(data._id);
|
||||||
|
setName(data.name);
|
||||||
|
setDescription(data.description);
|
||||||
|
setPrice(data.price)
|
||||||
|
})
|
||||||
|
|
||||||
|
//Then, open the modal
|
||||||
|
setShowEdit(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeEdit = () => {
|
||||||
|
setShowEdit(false);
|
||||||
|
setName('')
|
||||||
|
setDescription('')
|
||||||
|
setPrice(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//function to update the course
|
||||||
|
const editCourse = (e, courseId) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
fetch(`${process.env.REACT_APP_API_URL}/courses/${ courseId }`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${localStorage.getItem('token')}`
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: name,
|
||||||
|
description: description,
|
||||||
|
price: price
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
console.log(data)
|
||||||
|
|
||||||
|
if(data === true) {
|
||||||
|
Swal.fire({
|
||||||
|
title: 'Success!',
|
||||||
|
icon: 'success',
|
||||||
|
text: 'Course Successfully Updated'
|
||||||
|
})
|
||||||
|
closeEdit();
|
||||||
|
fetchData();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
Swal.fire({
|
||||||
|
title: 'Error!',
|
||||||
|
icon: 'error',
|
||||||
|
text: 'Please try again'
|
||||||
|
})
|
||||||
|
closeEdit();
|
||||||
|
fetchData();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return(
|
||||||
|
<>
|
||||||
|
<Button variant="primary" size="sm" onClick={() => openEdit(course)}> Edit </Button>
|
||||||
|
|
||||||
|
{/*Edit Modal Forms*/}
|
||||||
|
<Modal show={showEdit} onHide={closeEdit}>
|
||||||
|
<Form onSubmit={e => editCourse(e, courseId)}>
|
||||||
|
<Modal.Header closeButton>
|
||||||
|
<Modal.Title>Edit Course</Modal.Title>
|
||||||
|
</Modal.Header>
|
||||||
|
|
||||||
|
<Modal.Body>
|
||||||
|
<Form.Group>
|
||||||
|
<Form.Label>Name</Form.Label>
|
||||||
|
<Form.Control
|
||||||
|
type="text"
|
||||||
|
value={name}
|
||||||
|
onChange={e => setName(e.target.value)}
|
||||||
|
required/>
|
||||||
|
</Form.Group>
|
||||||
|
|
||||||
|
<Form.Group>
|
||||||
|
<Form.Label>Description</Form.Label>
|
||||||
|
<Form.Control
|
||||||
|
type="text"
|
||||||
|
value={description}
|
||||||
|
onChange={e => setDescription(e.target.value)}
|
||||||
|
required/>
|
||||||
|
</Form.Group>
|
||||||
|
|
||||||
|
<Form.Group>
|
||||||
|
<Form.Label>Price</Form.Label>
|
||||||
|
<Form.Control
|
||||||
|
type="number"
|
||||||
|
value={price}
|
||||||
|
onChange={e => setPrice(e.target.value)}
|
||||||
|
required/>
|
||||||
|
</Form.Group>
|
||||||
|
</Modal.Body>
|
||||||
|
|
||||||
|
<Modal.Footer>
|
||||||
|
<Button variant="secondary" onClick={closeEdit}>Close</Button>
|
||||||
|
<Button variant="success" type="submit">Submit</Button>
|
||||||
|
</Modal.Footer>
|
||||||
|
</Form>
|
||||||
|
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
import { useState, useEffect } from 'react'
|
||||||
|
import { CardGroup } from 'react-bootstrap'
|
||||||
|
import PreviewCourses from './PreviewCourses'
|
||||||
|
|
||||||
|
export default function FeaturedCourses(){
|
||||||
|
|
||||||
|
const [previews, setPreviews] = useState([])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetch(`${ process.env.REACT_APP_API_URL}/courses/`)
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
console.log(data)
|
||||||
|
|
||||||
|
const numbers = []
|
||||||
|
const featured = []
|
||||||
|
|
||||||
|
const generateRandomNums = () => {
|
||||||
|
let randomNum = Math.floor(Math.random() * data.length)
|
||||||
|
|
||||||
|
if(numbers.indexOf(randomNum) === -1){
|
||||||
|
numbers.push(randomNum)
|
||||||
|
}else{
|
||||||
|
generateRandomNums()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(let i = 0; i < 5; i++){
|
||||||
|
generateRandomNums()
|
||||||
|
|
||||||
|
featured.push(
|
||||||
|
<PreviewCourses data={data[numbers[i]]} key={data[numbers[i]]._id} breakPoint={2} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
setPreviews(featured)
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return(
|
||||||
|
<>
|
||||||
|
<h2 className="text-center">Featured Courses</h2>
|
||||||
|
<CardGroup className="justify-content-center">
|
||||||
|
|
||||||
|
|
||||||
|
{previews}
|
||||||
|
|
||||||
|
</CardGroup>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
import { Row, Col, Card } from 'react-bootstrap';
|
||||||
|
|
||||||
|
export default function Highlights() {
|
||||||
|
return (
|
||||||
|
<Row className="mt-3 mb-3">
|
||||||
|
<Col xs={12} md={4}>
|
||||||
|
<Card className="cardHighlight p-3">
|
||||||
|
<Card.Body>
|
||||||
|
<Card.Title>
|
||||||
|
<h2>Learn from Home</h2>
|
||||||
|
</Card.Title>
|
||||||
|
<Card.Text>
|
||||||
|
Pariatur adipisicing aute do amet dolore cupidatat. Eu labore aliqua eiusmod commodo occaecat mollit ullamco labore minim. Minim irure fugiat anim ea sint consequat fugiat laboris id. Lorem elit irure mollit officia incididunt ea ullamco laboris excepteur amet. Cillum pariatur consequat adipisicing aute ex.
|
||||||
|
</Card.Text>
|
||||||
|
</Card.Body>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
<Col xs={12} md={4}>
|
||||||
|
<Card className="cardHighlight p-3">
|
||||||
|
<Card.Body>
|
||||||
|
<Card.Title>
|
||||||
|
<h2>Study Now, Pay Later</h2>
|
||||||
|
</Card.Title>
|
||||||
|
<Card.Text>
|
||||||
|
Ex Lorem cillum consequat ad. Consectetur enim sunt amet sit nulla dolor exercitation est pariatur aliquip minim. Commodo velit est in id anim deserunt ullamco sint aute amet. Adipisicing est Lorem aliquip anim occaecat consequat in magna nisi occaecat consequat et. Reprehenderit elit dolore sunt labore qui.
|
||||||
|
</Card.Text>
|
||||||
|
</Card.Body>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
<Col xs={12} md={4}>
|
||||||
|
<Card className="cardHighlight p-3">
|
||||||
|
<Card.Body>
|
||||||
|
<Card.Title>
|
||||||
|
<h2>Be Part of Our Community</h2>
|
||||||
|
</Card.Title>
|
||||||
|
<Card.Text>
|
||||||
|
Minim nostrud dolore consequat ullamco minim aliqua tempor velit amet. Officia occaecat non cillum sit incididunt id pariatur. Mollit tempor laboris commodo anim mollit magna ea reprehenderit fugiat et reprehenderit tempor. Qui ea Lorem dolor in ad nisi anim. Culpa adipisicing enim et officia exercitation adipisicing.
|
||||||
|
</Card.Text>
|
||||||
|
</Card.Body>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
import { Col, Card } from 'react-bootstrap'
|
||||||
|
import { Link } from 'react-router-dom'
|
||||||
|
|
||||||
|
export default function Product(props){
|
||||||
|
const { breakPoint, data } = props
|
||||||
|
|
||||||
|
const { _id, name, description, price } = data
|
||||||
|
|
||||||
|
return(
|
||||||
|
<Col xs={12} md={breakPoint}>
|
||||||
|
<Card className="cardHighlight mx-2">
|
||||||
|
<Card.Body>
|
||||||
|
<Card.Title className="text-center">
|
||||||
|
<Link to={`/courses/${ _id}`}>{name}</Link>
|
||||||
|
</Card.Title>
|
||||||
|
<Card.Text>{description}</Card.Text>
|
||||||
|
|
||||||
|
</Card.Body>
|
||||||
|
<Card.Footer>
|
||||||
|
<h5 className="text-center">₱{price}</h5>
|
||||||
|
<Link className="btn btn-primary d-block" to={`/courses/${ _id}`}>Details</Link>
|
||||||
|
</Card.Footer>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
|
const ResetPassword = () => {
|
||||||
|
const [password, setPassword] = useState('');
|
||||||
|
const [confirmPassword, setConfirmPassword] = useState('');
|
||||||
|
const [message, setMessage] = useState('');
|
||||||
|
|
||||||
|
const handleResetPassword = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (password !== confirmPassword) {
|
||||||
|
setMessage('Passwords do not match');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const token = localStorage.getItem('token'); // Replace with your actual JWT token
|
||||||
|
const response = await fetch(`${process.env.REACT_APP_API_URL}/users/reset-password`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ newPassword: password }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
setMessage('Password reset successfully');
|
||||||
|
setPassword('');
|
||||||
|
setConfirmPassword('');
|
||||||
|
} else {
|
||||||
|
const errorData = await response.json();
|
||||||
|
setMessage(errorData.message);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
setMessage('An error occurred. Please try again.');
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container">
|
||||||
|
<h2>Reset Password</h2>
|
||||||
|
<form onSubmit={handleResetPassword}>
|
||||||
|
<div className="mb-3">
|
||||||
|
<label htmlFor="password" className="form-label">
|
||||||
|
New Password
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
className="form-control"
|
||||||
|
id="password"
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mb-3">
|
||||||
|
<label htmlFor="confirmPassword" className="form-label">
|
||||||
|
Confirm Password
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
className="form-control"
|
||||||
|
id="confirmPassword"
|
||||||
|
value={confirmPassword}
|
||||||
|
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{message && <div className="alert alert-danger">{message}</div>}
|
||||||
|
<button type="submit" className="btn btn-primary">
|
||||||
|
Reset Password
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ResetPassword;
|
@ -0,0 +1,77 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
|
const SearchByPrice = () => {
|
||||||
|
const [minPrice, setMinPrice] = useState('');
|
||||||
|
const [maxPrice, setMaxPrice] = useState('');
|
||||||
|
const [courses, setCourses] = useState([]);
|
||||||
|
|
||||||
|
const handleMinPriceChange = (e) => {
|
||||||
|
setMinPrice(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMaxPriceChange = (e) => {
|
||||||
|
setMaxPrice(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const requestOptions = {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ minPrice, maxPrice }),
|
||||||
|
};
|
||||||
|
|
||||||
|
fetch(`${process.env.REACT_APP_API_URL}/courses/searchByPrice`, requestOptions)
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((data) => {
|
||||||
|
setCourses(data.courses);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container">
|
||||||
|
<h2>Search Courses by Price Range</h2>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<div className="mb-3">
|
||||||
|
<label htmlFor="minPrice" className="form-label">
|
||||||
|
Min Price:
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
className="form-control"
|
||||||
|
id="minPrice"
|
||||||
|
value={minPrice}
|
||||||
|
onChange={handleMinPriceChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mb-3">
|
||||||
|
<label htmlFor="maxPrice" className="form-label">
|
||||||
|
Max Price:
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
className="form-control"
|
||||||
|
id="maxPrice"
|
||||||
|
value={maxPrice}
|
||||||
|
onChange={handleMaxPriceChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button type="submit" className="btn btn-primary">
|
||||||
|
Search
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<h3>Search Results:</h3>
|
||||||
|
<ul>
|
||||||
|
{courses.map((course) => (
|
||||||
|
<li key={course.id}>{course.name}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SearchByPrice;
|
@ -0,0 +1,90 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
|
const UpdateProfileForm = () => {
|
||||||
|
const [firstName, setFirstName] = useState('');
|
||||||
|
const [lastName, setLastName] = useState('');
|
||||||
|
const [mobileNo, setMobileNo] = useState('');
|
||||||
|
|
||||||
|
const handleSubmit = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const token = localStorage.getItem('token');
|
||||||
|
|
||||||
|
const profileData = {
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
mobileNo,
|
||||||
|
};
|
||||||
|
|
||||||
|
fetch('http://localhost:4000/users/profile', {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify(profileData),
|
||||||
|
})
|
||||||
|
.then(res=>res.json())
|
||||||
|
.then((response) => {
|
||||||
|
if (response._id) {
|
||||||
|
// Profile update was successful, refresh the page
|
||||||
|
window.location.reload();
|
||||||
|
} else {
|
||||||
|
throw new Error('Profile update failed');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
// Handle error here
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='container'>
|
||||||
|
<h2 className='my-4'>Update Profile</h2>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<div className="mb-3">
|
||||||
|
<label htmlFor="firstName" className="form-label">
|
||||||
|
First Name
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="form-control"
|
||||||
|
id="firstName"
|
||||||
|
value={firstName}
|
||||||
|
onChange={(e) => setFirstName(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mb-3">
|
||||||
|
<label htmlFor="lastName" className="form-label">
|
||||||
|
Last Name
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="form-control"
|
||||||
|
id="lastName"
|
||||||
|
value={lastName}
|
||||||
|
onChange={(e) => setLastName(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mb-3">
|
||||||
|
<label htmlFor="mobileNo" className="form-label">
|
||||||
|
Mobile Number
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="form-control"
|
||||||
|
id="mobileNo"
|
||||||
|
value={mobileNo}
|
||||||
|
onChange={(e) => setMobileNo(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button type="submit" className="btn btn-primary">
|
||||||
|
Update Profile
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UpdateProfileForm;
|
@ -0,0 +1,33 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import CourseCard from './CourseCard';
|
||||||
|
import CourseSearch from './CourseSearch';
|
||||||
|
|
||||||
|
|
||||||
|
export default function UserView({coursesData}) {
|
||||||
|
|
||||||
|
const [courses, setCourses] = useState([])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const coursesArr = coursesData.map(course => {
|
||||||
|
//only render the active courses
|
||||||
|
if(course.isActive === true) {
|
||||||
|
return (
|
||||||
|
<CourseCard courseProp={course} key={course._id}/>
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
//set the courses state to the result of our map function, to bring our returned course component outside of the scope of our useEffect where our return statement below can see.
|
||||||
|
setCourses(coursesArr)
|
||||||
|
|
||||||
|
}, [coursesData])
|
||||||
|
|
||||||
|
return(
|
||||||
|
<>
|
||||||
|
<CourseSearch />
|
||||||
|
{ courses }
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
const coursesData = [
|
||||||
|
{
|
||||||
|
id: "wdc001",
|
||||||
|
name: "PHP - Laravel",
|
||||||
|
description: "Nostrud velit dolor excepteur ullamco consectetur aliquip tempor. Consectetur occaecat laborum exercitation sint reprehenderit irure nulla mollit. Do dolore sint deserunt quis ut sunt ad nulla est consectetur culpa. Est esse dolore nisi consequat nostrud id nostrud sint sint deserunt dolore.",
|
||||||
|
price: 45000,
|
||||||
|
onOffer: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "wdc002",
|
||||||
|
name: "Python - Django",
|
||||||
|
description: "Eu non commodo et eu ex incididunt minim aliquip anim. Aliquip voluptate ut velit fugiat laborum. Laborum dolore anim pariatur pariatur commodo minim ut officia mollit ad ipsum ex. Laborum veniam cupidatat veniam minim occaecat veniam deserunt nulla irure. Enim elit sint magna incididunt occaecat in dolor amet dolore consectetur ad mollit. Exercitation sunt occaecat labore irure proident consectetur commodo ad anim ea tempor irure.",
|
||||||
|
price: 50000,
|
||||||
|
onOffer: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "wdc003",
|
||||||
|
name: "Java - Springboot",
|
||||||
|
description: "Proident est adipisicing est deserunt cillum dolore. Fugiat incididunt quis aliquip ut aliquip est mollit officia dolor ea cupidatat velit. Consectetur aute velit aute ipsum quis. Eiusmod dolor exercitation dolor mollit duis velit aliquip dolor proident ex exercitation labore cupidatat. Eu aliquip mollit labore do.",
|
||||||
|
price: 55000,
|
||||||
|
onOffer: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
export default coursesData;
|
@ -0,0 +1,29 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom/client';
|
||||||
|
import App from './App';
|
||||||
|
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||||
|
|
||||||
|
|
||||||
|
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||||
|
root.render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<App />
|
||||||
|
</React.StrictMode>
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
// const name = 'John Smith';
|
||||||
|
// const user = {
|
||||||
|
// firstName: 'Jane',
|
||||||
|
// lastName: 'Smith'
|
||||||
|
// }
|
||||||
|
|
||||||
|
// function formatName(user) {
|
||||||
|
// return user.firstName + ' ' + user.lastName;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const element = <h1>Hello, {formatName(user)}</h1>
|
||||||
|
|
||||||
|
// const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||||
|
|
||||||
|
// root.render(element)
|
@ -0,0 +1,101 @@
|
|||||||
|
import {useState,useEffect, useContext} from 'react';
|
||||||
|
import {Form,Button} from 'react-bootstrap';
|
||||||
|
import { Navigate, useNavigate } from 'react-router-dom';
|
||||||
|
import Swal from 'sweetalert2';
|
||||||
|
import UserContext from '../UserContext';
|
||||||
|
|
||||||
|
export default function AddCourse(){
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const {user} = useContext(UserContext);
|
||||||
|
|
||||||
|
//input states
|
||||||
|
const [name,setName] = useState("");
|
||||||
|
const [description,setDescription] = useState("");
|
||||||
|
const [price,setPrice] = useState("");
|
||||||
|
|
||||||
|
function createCourse(e){
|
||||||
|
|
||||||
|
//prevent submit event's default behavior
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
let token = localStorage.getItem('token');
|
||||||
|
console.log(token);
|
||||||
|
|
||||||
|
fetch(`${process.env.REACT_APP_API_URL}/courses/`,{
|
||||||
|
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
|
||||||
|
name: name,
|
||||||
|
description: description,
|
||||||
|
price: price
|
||||||
|
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
|
||||||
|
//data is the response of the api/server after it's been process as JS object through our res.json() method.
|
||||||
|
console.log(data);
|
||||||
|
|
||||||
|
if(data){
|
||||||
|
Swal.fire({
|
||||||
|
|
||||||
|
icon:"success",
|
||||||
|
title: "Course Added"
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
navigate("/courses");
|
||||||
|
} else {
|
||||||
|
Swal.fire({
|
||||||
|
|
||||||
|
icon: "error",
|
||||||
|
title: "Unsuccessful Course Creation",
|
||||||
|
text: data.message
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
setName("")
|
||||||
|
setDescription("")
|
||||||
|
setPrice(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
|
||||||
|
(user.isAdmin === true)
|
||||||
|
?
|
||||||
|
<>
|
||||||
|
<h1 className="my-5 text-center">Add Course</h1>
|
||||||
|
<Form onSubmit={e => createCourse(e)}>
|
||||||
|
<Form.Group>
|
||||||
|
<Form.Label>Name:</Form.Label>
|
||||||
|
<Form.Control type="text" placeholder="Enter Name" required value={name} onChange={e => {setName(e.target.value)}}/>
|
||||||
|
</Form.Group>
|
||||||
|
<Form.Group>
|
||||||
|
<Form.Label>Description:</Form.Label>
|
||||||
|
<Form.Control type="text" placeholder="Enter Description" required value={description} onChange={e => {setDescription(e.target.value)}}/>
|
||||||
|
</Form.Group>
|
||||||
|
<Form.Group>
|
||||||
|
<Form.Label>Price:</Form.Label>
|
||||||
|
<Form.Control type="number" placeholder="Enter Price" required value={price} onChange={e => {setPrice(e.target.value)}}/>
|
||||||
|
</Form.Group>
|
||||||
|
<Button variant="primary" type="submit" className="my-5">Submit</Button>
|
||||||
|
</Form>
|
||||||
|
</>
|
||||||
|
:
|
||||||
|
<Navigate to="/courses" />
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,105 @@
|
|||||||
|
import { useState, useEffect, useContext } from 'react';
|
||||||
|
import { Container, Card, Button, Row, Col } from 'react-bootstrap';
|
||||||
|
import { useParams, useNavigate, Link } from 'react-router-dom';
|
||||||
|
import Swal from 'sweetalert2';
|
||||||
|
import UserContext from '../UserContext';
|
||||||
|
|
||||||
|
export default function CourseView() {
|
||||||
|
|
||||||
|
// The "useParams" hook allows us to retrieve any parameter or the courseId passed via the URL
|
||||||
|
const { courseId } = useParams();
|
||||||
|
const { user } = useContext(UserContext);
|
||||||
|
// Allows us to gain access to methods that will allow us to redirect a user to a different page after enrolling a course
|
||||||
|
|
||||||
|
//an object with methods to redirect the user
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const [name, setName] = useState("");
|
||||||
|
const [description, setDescription] = useState("");
|
||||||
|
const [price, setPrice] = useState(0);
|
||||||
|
|
||||||
|
const enroll = (courseId) => {
|
||||||
|
|
||||||
|
fetch(`${process.env.REACT_APP_API_URL}/users/enroll`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: `Bearer ${ localStorage.getItem('access') }`
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
courseId: courseId
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
|
||||||
|
console.log(data.message);
|
||||||
|
|
||||||
|
if (data.message === 'Enrolled Successfully.') {
|
||||||
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "Successfully enrolled",
|
||||||
|
icon: 'success',
|
||||||
|
text: "You have successfully enrolled for this course."
|
||||||
|
});
|
||||||
|
|
||||||
|
// The "navigate" method allows us to redirect the user to a different page and is an easier approach rather than using the "Navigate" component
|
||||||
|
navigate("/courses");
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "Something went wrong",
|
||||||
|
icon: "error",
|
||||||
|
text: "Please try again."
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(()=> {
|
||||||
|
|
||||||
|
console.log(courseId);
|
||||||
|
|
||||||
|
fetch(`${process.env.REACT_APP_API_URL}/courses/${courseId}`)
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
|
||||||
|
console.log(data);
|
||||||
|
|
||||||
|
setName(data.name);
|
||||||
|
setDescription(data.description);
|
||||||
|
setPrice(data.price);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}, [courseId]);
|
||||||
|
|
||||||
|
return(
|
||||||
|
<Container className="mt-5">
|
||||||
|
<Row>
|
||||||
|
<Col lg={{ span: 6, offset: 3 }}>
|
||||||
|
<Card>
|
||||||
|
<Card.Body className="text-center">
|
||||||
|
<Card.Title>{name}</Card.Title>
|
||||||
|
<Card.Subtitle>Description:</Card.Subtitle>
|
||||||
|
<Card.Text>{description}</Card.Text>
|
||||||
|
<Card.Subtitle>Price:</Card.Subtitle>
|
||||||
|
<Card.Text>PhP {price}</Card.Text>
|
||||||
|
<Card.Subtitle>Class Schedule</Card.Subtitle>
|
||||||
|
<Card.Text>8 am - 5 pm</Card.Text>
|
||||||
|
{ user.id !== null ?
|
||||||
|
<Button variant="primary" block onClick={() => enroll(courseId)}>Enroll</Button>
|
||||||
|
:
|
||||||
|
<Link className="btn btn-danger btn-block" to="/login">Log in to Enroll</Link>
|
||||||
|
}
|
||||||
|
</Card.Body>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
import { useEffect, useState, useContext } from 'react';
|
||||||
|
import CourseCard from '../components/CourseCard';
|
||||||
|
// import coursesData from '../data/coursesData';
|
||||||
|
import UserContext from '../UserContext';
|
||||||
|
import UserView from '../components/UserView';
|
||||||
|
import AdminView from '../components/AdminView';
|
||||||
|
|
||||||
|
export default function Courses() {
|
||||||
|
|
||||||
|
const { user } = useContext(UserContext);
|
||||||
|
|
||||||
|
// Checks to see if the mock data was captured
|
||||||
|
// console.log(coursesData);
|
||||||
|
// console.log(coursesData[0]);
|
||||||
|
|
||||||
|
// State that will be used to store the courses retrieved from the database
|
||||||
|
const [courses, setCourses] = useState([]);
|
||||||
|
|
||||||
|
|
||||||
|
const fetchData = () => {
|
||||||
|
fetch(`${process.env.REACT_APP_API_URL}/courses/all`)
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
|
||||||
|
console.log(data);
|
||||||
|
|
||||||
|
// Sets the "courses" state to map the data retrieved from the fetch request into several "CourseCard" components
|
||||||
|
setCourses(data);
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Retrieves the courses from the database upon initial render of the "Courses" component
|
||||||
|
useEffect(() => {
|
||||||
|
|
||||||
|
fetchData()
|
||||||
|
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// The "map" method loops through the individual course objects in our array and returns a component for each course
|
||||||
|
// Multiple components created through the map method must have a unique key that will help React JS identify which components/elements have been changed, added or removed
|
||||||
|
// Everytime the map method loops through the data, it creates a "CourseCard" component and then passes the current element in our coursesData array using the courseProp
|
||||||
|
// const courses = coursesData.map(course => {
|
||||||
|
// return (
|
||||||
|
// <CourseCard key={course.id} courseProp={course}/>
|
||||||
|
// );
|
||||||
|
// })
|
||||||
|
|
||||||
|
return(
|
||||||
|
<>
|
||||||
|
{
|
||||||
|
(user.isAdmin === true) ?
|
||||||
|
<AdminView coursesData={courses} fetchData={fetchData} />
|
||||||
|
|
||||||
|
:
|
||||||
|
|
||||||
|
<UserView coursesData={courses} />
|
||||||
|
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
|||||||
|
import Banner from '../components/Banner';
|
||||||
|
|
||||||
|
export default function Error() {
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
title: "404 - Not found",
|
||||||
|
content: "The page you are looking for cannot be found",
|
||||||
|
destination: "/",
|
||||||
|
label: "Back home"
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Banner data={data}/>
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
import Banner from '../components/Banner';
|
||||||
|
import Highlights from '../components/Highlights';
|
||||||
|
import FeaturedCourses from '../components/FeaturedCourses';
|
||||||
|
// import CourseCard from '../components/CourseCard';
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
title: "Zuitt Coding Bootcamp",
|
||||||
|
content: "Opportunities for everyone, everywhere",
|
||||||
|
destination: "/courses",
|
||||||
|
label: "Enroll now!"
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Banner data={data}/>
|
||||||
|
<FeaturedCourses />
|
||||||
|
<Highlights />
|
||||||
|
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,152 @@
|
|||||||
|
import { useState, useEffect, useContext } from 'react';
|
||||||
|
import { Form, Button } from 'react-bootstrap';
|
||||||
|
import { Navigate } from 'react-router-dom';
|
||||||
|
import Swal from 'sweetalert2';
|
||||||
|
import UserContext from '../UserContext';
|
||||||
|
|
||||||
|
export default function Login() {
|
||||||
|
|
||||||
|
const { user, setUser } = useContext(UserContext);
|
||||||
|
|
||||||
|
// State hooks to store the values of the input fields
|
||||||
|
const [email, setEmail] = useState('');
|
||||||
|
const [password, setPassword] = useState('');
|
||||||
|
// State to determine whether submit button is enabled or not
|
||||||
|
const [isActive, setIsActive] = useState(true);
|
||||||
|
|
||||||
|
|
||||||
|
function authenticate(e) {
|
||||||
|
|
||||||
|
// Prevents page redirection via form submission
|
||||||
|
e.preventDefault();
|
||||||
|
fetch(`${process.env.REACT_APP_API_URL}/users/login`,{
|
||||||
|
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
|
||||||
|
email: email,
|
||||||
|
password: password
|
||||||
|
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
|
||||||
|
if(data.access){
|
||||||
|
|
||||||
|
// Set the email of the authenticated user in the local storage
|
||||||
|
// Syntax
|
||||||
|
// localStorage.setItem('propertyName', value);
|
||||||
|
localStorage.setItem('token', data.access);
|
||||||
|
retrieveUserDetails(data.access);
|
||||||
|
|
||||||
|
setUser({
|
||||||
|
access: localStorage.getItem('token')
|
||||||
|
})
|
||||||
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "Login Successful",
|
||||||
|
icon: "success",
|
||||||
|
text: "Welcome to Zuitt!"
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "Authentication failed",
|
||||||
|
icon: "error",
|
||||||
|
text: "Check your login details and try again."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// Clear input fields after submission
|
||||||
|
setEmail('');
|
||||||
|
setPassword('');
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const retrieveUserDetails = (token) => {
|
||||||
|
|
||||||
|
// The token will be sent as part of the request's header information
|
||||||
|
// We put "Bearer" in front of the token to follow implementation standards for JWTs
|
||||||
|
fetch(`${process.env.REACT_APP_API_URL}/users/details`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${ token }`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
|
||||||
|
console.log(data);
|
||||||
|
|
||||||
|
// localStorage.setItem('token', data.access);
|
||||||
|
|
||||||
|
// Changes the global "user" state to store the "id" and the "isAdmin" property of the user which will be used for validation across the whole application
|
||||||
|
setUser({
|
||||||
|
id: data._id,
|
||||||
|
isAdmin: data.isAdmin
|
||||||
|
});
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
|
||||||
|
// Validation to enable submit button when all fields are populated and both passwords match
|
||||||
|
if(email !== '' && password !== ''){
|
||||||
|
setIsActive(true);
|
||||||
|
}else{
|
||||||
|
setIsActive(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
}, [email, password]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
(user.id !== null) ?
|
||||||
|
|
||||||
|
<Navigate to="/courses" />
|
||||||
|
|
||||||
|
:
|
||||||
|
|
||||||
|
<Form onSubmit={(e) => authenticate(e)}>
|
||||||
|
<h1 className="my-5 text-center">Login</h1>
|
||||||
|
<Form.Group controlId="userEmail">
|
||||||
|
<Form.Label>Email address</Form.Label>
|
||||||
|
<Form.Control
|
||||||
|
type="email"
|
||||||
|
placeholder="Enter email"
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</Form.Group>
|
||||||
|
|
||||||
|
<Form.Group controlId="password">
|
||||||
|
<Form.Label>Password</Form.Label>
|
||||||
|
<Form.Control
|
||||||
|
type="password"
|
||||||
|
placeholder="Password"
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</Form.Group>
|
||||||
|
|
||||||
|
{ isActive ?
|
||||||
|
<Button variant="primary" type="submit" id="submitBtn">
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
:
|
||||||
|
<Button variant="danger" type="submit" id="submitBtn" disabled>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
</Form>
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
import { useContext, useEffect } from 'react';
|
||||||
|
import { Navigate } from 'react-router-dom';
|
||||||
|
import UserContext from '../UserContext';
|
||||||
|
|
||||||
|
export default function Logout() {
|
||||||
|
|
||||||
|
// Consume the UserContext object and destructure it to access the user state and unsetUser function from the context provider
|
||||||
|
const { unsetUser, setUser } = useContext(UserContext);
|
||||||
|
|
||||||
|
// Clear the localStorage of the user's information
|
||||||
|
unsetUser();
|
||||||
|
|
||||||
|
// Placing the "setUser" setter function inside of a useEffect is necessary because of updates within React JS that a state of another component cannot be updated while trying to render a different component
|
||||||
|
// By adding the useEffect, this will allow the Logout page to render first before triggering the useEffect which changes the state of our user
|
||||||
|
useEffect(() => {
|
||||||
|
// Set the user state back to it's original value
|
||||||
|
setUser({
|
||||||
|
id: null,
|
||||||
|
isAdmin: null
|
||||||
|
});
|
||||||
|
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// Navigate back to login
|
||||||
|
return (
|
||||||
|
<Navigate to='/login' />
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,71 @@
|
|||||||
|
import {useState,useEffect, useContext} from 'react';
|
||||||
|
import {Row, Col} from 'react-bootstrap';
|
||||||
|
import UserContext from '../UserContext';
|
||||||
|
import { useNavigate,Navigate } from 'react-router-dom';
|
||||||
|
import ResetPassword from '../components/ResetPassword';
|
||||||
|
import UpdateProfileForm from '../components/UpdateProfile';
|
||||||
|
export default function Profile(){
|
||||||
|
|
||||||
|
const {user} = useContext(UserContext);
|
||||||
|
|
||||||
|
const [details,setDetails] = useState({})
|
||||||
|
|
||||||
|
useEffect(()=>{
|
||||||
|
|
||||||
|
fetch(`${process.env.REACT_APP_API_URL}/users/details`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${ localStorage.getItem('token') }`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
console.log(data)
|
||||||
|
// Set the user states values with the user details upon successful login.
|
||||||
|
if (typeof data._id !== "undefined") {
|
||||||
|
|
||||||
|
setDetails(data);
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
},[])
|
||||||
|
|
||||||
|
return (
|
||||||
|
// (user.email === null) ?
|
||||||
|
// <Navigate to="/courses" />
|
||||||
|
// :
|
||||||
|
(user.id === null) ?
|
||||||
|
<Navigate to="/courses" />
|
||||||
|
:
|
||||||
|
<>
|
||||||
|
<Row>
|
||||||
|
<Col className="p-5 bg-primary text-white">
|
||||||
|
<h1 className="my-5 ">Profile</h1>
|
||||||
|
{/* <h2 className="mt-3">James Dela Cruz</h2> */}
|
||||||
|
<h2 className="mt-3">{`${details.firstName} ${details.lastName}`}</h2>
|
||||||
|
<hr />
|
||||||
|
<h4>Contacts</h4>
|
||||||
|
<ul>
|
||||||
|
{/* <li>Email: {user.email}</li> */}
|
||||||
|
<li>Email: {details.email}</li>
|
||||||
|
{/* <li>Mobile No: 09266772411</li> */}
|
||||||
|
<li>Mobile No: {details.mobileNo}</li>
|
||||||
|
</ul>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row className='pt-4 mt-4'>
|
||||||
|
<Col>
|
||||||
|
<ResetPassword />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row className='pt-4 mt-4'>
|
||||||
|
<Col>
|
||||||
|
<UpdateProfileForm />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</>
|
||||||
|
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,164 @@
|
|||||||
|
import { useState, useEffect, useContext } from 'react';
|
||||||
|
import { Form, Button } from 'react-bootstrap';
|
||||||
|
import { Navigate } from 'react-router-dom';
|
||||||
|
import UserContext from '../UserContext';
|
||||||
|
|
||||||
|
export default function Register() {
|
||||||
|
|
||||||
|
const {user} = useContext(UserContext);
|
||||||
|
|
||||||
|
// State hooks to store the values of the input fields
|
||||||
|
const [firstName,setFirstName] = useState("");
|
||||||
|
const [lastName,setLastName] = useState("");
|
||||||
|
const [email,setEmail] = useState("");
|
||||||
|
const [mobileNo,setMobileNo] = useState("");
|
||||||
|
const [password,setPassword] = useState("");
|
||||||
|
const [confirmPassword, setConfirmPassword] = useState("");
|
||||||
|
// State to determine whether submit button is enabled or not
|
||||||
|
const [isActive, setIsActive] = useState(false);
|
||||||
|
|
||||||
|
// Check if values are successfully binded
|
||||||
|
console.log(firstName);
|
||||||
|
console.log(lastName);
|
||||||
|
console.log(email);
|
||||||
|
console.log(mobileNo);
|
||||||
|
console.log(password);
|
||||||
|
console.log(confirmPassword)
|
||||||
|
|
||||||
|
|
||||||
|
function registerUser(e) {
|
||||||
|
|
||||||
|
// Prevents page redirection via form submission
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
fetch(`${process.env.REACT_APP_API_URL}/users/register`,{
|
||||||
|
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
|
||||||
|
firstName: firstName,
|
||||||
|
lastName: lastName,
|
||||||
|
email: email,
|
||||||
|
mobileNo: mobileNo,
|
||||||
|
password: password
|
||||||
|
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
|
||||||
|
//data is the response of the api/server after it's been process as JS object through our res.json() method.
|
||||||
|
console.log(data);
|
||||||
|
//data will only contain an email property if we can properly save our user.
|
||||||
|
if(data){
|
||||||
|
|
||||||
|
setFirstName('');
|
||||||
|
setLastName('');
|
||||||
|
setEmail('');
|
||||||
|
setMobileNo('');
|
||||||
|
setPassword('');
|
||||||
|
setConfirmPassword('');
|
||||||
|
|
||||||
|
|
||||||
|
alert("Thank you for registering!")
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
alert("Please try again later.")
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(()=>{
|
||||||
|
|
||||||
|
if((firstName !== "" && lastName !== "" && email !== "" && mobileNo !== "" && password !=="" && confirmPassword !=="") && (password === confirmPassword) && (mobileNo.length === 11)){
|
||||||
|
|
||||||
|
setIsActive(true)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
setIsActive(false)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
},[firstName,lastName,email,mobileNo,password,confirmPassword])
|
||||||
|
|
||||||
|
return (
|
||||||
|
|
||||||
|
(user.id !== null) ?
|
||||||
|
<Navigate to="/courses" />
|
||||||
|
:
|
||||||
|
|
||||||
|
<Form onSubmit={(e) => registerUser(e)}>
|
||||||
|
<h1 className="my-5 text-center">Register</h1>
|
||||||
|
<Form.Group>
|
||||||
|
<Form.Label>First Name:</Form.Label>
|
||||||
|
<Form.Control
|
||||||
|
type="text"
|
||||||
|
placeholder="Enter First Name"
|
||||||
|
required
|
||||||
|
value={firstName}
|
||||||
|
onChange={e => {setFirstName(e.target.value)}}
|
||||||
|
/>
|
||||||
|
</Form.Group>
|
||||||
|
<Form.Group>
|
||||||
|
<Form.Label>Last Name:</Form.Label>
|
||||||
|
<Form.Control
|
||||||
|
type="text"
|
||||||
|
placeholder="Enter Last Name"
|
||||||
|
required
|
||||||
|
value={lastName}
|
||||||
|
onChange={e => {setLastName(e.target.value)}}/>
|
||||||
|
</Form.Group>
|
||||||
|
<Form.Group>
|
||||||
|
<Form.Label>Email:</Form.Label>
|
||||||
|
<Form.Control
|
||||||
|
type="email"
|
||||||
|
placeholder="Enter Email"
|
||||||
|
required
|
||||||
|
value={email}
|
||||||
|
onChange={e => {setEmail(e.target.value)}}/>
|
||||||
|
</Form.Group>
|
||||||
|
<Form.Group>
|
||||||
|
<Form.Label>Mobile No:</Form.Label>
|
||||||
|
<Form.Control
|
||||||
|
type="number"
|
||||||
|
placeholder="Enter 11 Digit No."
|
||||||
|
required
|
||||||
|
value={mobileNo}
|
||||||
|
onChange={e => {setMobileNo(e.target.value)}}/>
|
||||||
|
</Form.Group>
|
||||||
|
<Form.Group>
|
||||||
|
<Form.Label>Password:</Form.Label>
|
||||||
|
<Form.Control
|
||||||
|
type="password"
|
||||||
|
placeholder="Enter Password"
|
||||||
|
required
|
||||||
|
value={password}
|
||||||
|
onChange={e => {setPassword(e.target.value)}}/>
|
||||||
|
</Form.Group>
|
||||||
|
<Form.Group>
|
||||||
|
<Form.Label>Confirm Password:</Form.Label>
|
||||||
|
<Form.Control
|
||||||
|
type="password"
|
||||||
|
placeholder="Confirm Password"
|
||||||
|
required
|
||||||
|
value={confirmPassword}
|
||||||
|
onChange={e => {setConfirmPassword(e.target.value)}}/>
|
||||||
|
</Form.Group>
|
||||||
|
{
|
||||||
|
isActive
|
||||||
|
|
||||||
|
? <Button variant="primary" type="submit">Submit</Button>
|
||||||
|
: <Button variant="primary" disabled>Submit</Button>
|
||||||
|
}
|
||||||
|
</Form>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,2 @@
|
|||||||
|
PORT=4000
|
||||||
|
CONNECTION_STRING=<Mongo-DB-Atlas-connection-string>/bookingAPI?retryWrites=true&w=majority
|
@ -0,0 +1 @@
|
|||||||
|
/node_modules
|
@ -0,0 +1,98 @@
|
|||||||
|
// [Section] JSON Web Tokens
|
||||||
|
const jwt = require("jsonwebtoken");
|
||||||
|
|
||||||
|
// [Section] Secret Keyword
|
||||||
|
const secret = "CourseBookingAPI";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// [Section] Token creation
|
||||||
|
module.exports.createAccessToken = (user) => {
|
||||||
|
const data = {
|
||||||
|
id : user._id,
|
||||||
|
email : user.email,
|
||||||
|
isAdmin : user.isAdmin
|
||||||
|
};
|
||||||
|
return jwt.sign(data, secret, {});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//[Section] Token Verification
|
||||||
|
/*
|
||||||
|
- Analogy
|
||||||
|
Receive the gift and open the lock to verify if the the sender is legitimate and the gift was not tampered with
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports.verify = (req, res, next) => {
|
||||||
|
console.log(req.headers.authorization);
|
||||||
|
|
||||||
|
//req.headers.authorization contains sensitive data and especially our token
|
||||||
|
let token = req.headers.authorization;
|
||||||
|
|
||||||
|
//This if statement will first check IF token variable contains undefined or a proper jwt. If it is undefined, we will check token's data type with typeof, then send a message to the client.
|
||||||
|
if(typeof token === "undefined"){
|
||||||
|
return res.send({auth: "Failed. No Token"});
|
||||||
|
} else {
|
||||||
|
console.log(token);
|
||||||
|
token = token.slice(7, token.length);
|
||||||
|
console.log(token);
|
||||||
|
|
||||||
|
//[SECTION] Token decryption
|
||||||
|
/*
|
||||||
|
- Analogy
|
||||||
|
Open the gift and get the content
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Validate the token using the "verify" method decrypting the token using the secret code
|
||||||
|
jwt.verify(token, secret, function(err, decodedToken){
|
||||||
|
if(err){
|
||||||
|
return res.send({
|
||||||
|
auth: "Failed",
|
||||||
|
message: err.message
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log(decodedToken);//contains the data from our token
|
||||||
|
req.user = decodedToken
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//[Section] verifyAdmin will also be used a middleware.
|
||||||
|
module.exports.verifyAdmin = (req, res, next) => {
|
||||||
|
if(req.user.isAdmin){
|
||||||
|
next();
|
||||||
|
} else {
|
||||||
|
return res.send({
|
||||||
|
auth: "Failed",
|
||||||
|
message: "Action Forbidden"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,245 @@
|
|||||||
|
//[SECTION] Dependencies and Modules
|
||||||
|
const Course = require("../models/Course");
|
||||||
|
const User = require("../models/User");
|
||||||
|
|
||||||
|
|
||||||
|
//[SECTION] Create a new course
|
||||||
|
|
||||||
|
module.exports.addCourse = (req, res) => {
|
||||||
|
let newCourse = new Course({
|
||||||
|
name : req.body.name,
|
||||||
|
description : req.body.description,
|
||||||
|
price : req.body.price
|
||||||
|
});
|
||||||
|
|
||||||
|
return newCourse.save().then((course, error) => {
|
||||||
|
// Course creation successful
|
||||||
|
if (error) {
|
||||||
|
return res.send(false);
|
||||||
|
|
||||||
|
// Course creation failed
|
||||||
|
} else {
|
||||||
|
return res.send(true);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => res.send(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//[SECTION] Retrieve all courses
|
||||||
|
module.exports.getAllCourses = (req, res) => {
|
||||||
|
return Course.find({}).then(result => {
|
||||||
|
return res.send(result);
|
||||||
|
})
|
||||||
|
.catch(err => res.send(err))
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//[SECTION] Retrieve all ACTIVE courses
|
||||||
|
module.exports.getAllActive = (req, res) => {
|
||||||
|
return Course.find({ isActive : true }).then(result => {
|
||||||
|
return res.send(result);
|
||||||
|
})
|
||||||
|
.catch(err => res.send(err))
|
||||||
|
};
|
||||||
|
|
||||||
|
//[SECTION] Retrieving a specific course
|
||||||
|
module.exports.getCourse = (req, res) => {
|
||||||
|
return Course.findById(req.params.courseId).then(result => {
|
||||||
|
return res.send(result);
|
||||||
|
})
|
||||||
|
.catch(err => res.send(err))
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//[SECTION] Update a course
|
||||||
|
module.exports.updateCourse = (req, res) => {
|
||||||
|
// Specify the fields/properties of the document to be updated
|
||||||
|
let updatedCourse = {
|
||||||
|
name : req.body.name,
|
||||||
|
description : req.body.description,
|
||||||
|
price : req.body.price
|
||||||
|
};
|
||||||
|
|
||||||
|
// Syntax
|
||||||
|
// findByIdAndUpdate(document ID, updatesToBeApplied)
|
||||||
|
return Course.findByIdAndUpdate(req.params.courseId, updatedCourse).then((course, error) => {
|
||||||
|
|
||||||
|
// Course not updated
|
||||||
|
if (error) {
|
||||||
|
return res.send(false);
|
||||||
|
|
||||||
|
// Course updated successfully
|
||||||
|
} else {
|
||||||
|
return res.send(true);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => res.send(err))
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//SECTION] Archive a course
|
||||||
|
module.exports.archiveCourse = (req, res) => {
|
||||||
|
|
||||||
|
let updateActiveField = {
|
||||||
|
isActive: false
|
||||||
|
}
|
||||||
|
|
||||||
|
return Course.findByIdAndUpdate(req.params.courseId, updateActiveField)
|
||||||
|
.then((course, error) => {
|
||||||
|
|
||||||
|
//course archived successfully
|
||||||
|
if(error){
|
||||||
|
return res.send(false)
|
||||||
|
|
||||||
|
// failed
|
||||||
|
} else {
|
||||||
|
return res.send(true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => res.send(err))
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//[SECTION] Activate a course
|
||||||
|
module.exports.activateCourse = (req, res) => {
|
||||||
|
|
||||||
|
let updateActiveField = {
|
||||||
|
isActive: true
|
||||||
|
}
|
||||||
|
|
||||||
|
return Course.findByIdAndUpdate(req.params.courseId, updateActiveField)
|
||||||
|
.then((course, error) => {
|
||||||
|
|
||||||
|
//course archived successfully
|
||||||
|
if(error){
|
||||||
|
return res.send(false)
|
||||||
|
|
||||||
|
// failed
|
||||||
|
} else {
|
||||||
|
return res.send(true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => res.send(err))
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
//ChatGPT Generated Code
|
||||||
|
|
||||||
|
module.exports.searchCoursesByName = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { courseName } = req.body;
|
||||||
|
|
||||||
|
// Use a regular expression to perform a case-insensitive search
|
||||||
|
const courses = await Course.find({
|
||||||
|
name: { $regex: courseName, $options: 'i' }
|
||||||
|
});
|
||||||
|
|
||||||
|
res.json(courses);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
res.status(500).json({ error: 'Internal Server Error' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Controller to get the list of emails of users enrolled in a course
|
||||||
|
//Solution
|
||||||
|
module.exports.getEmailsOfEnrolledUsers = async (req, res) => {
|
||||||
|
const courseId = req.params.courseId;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Find the course by courseId
|
||||||
|
const course = await Course.findById(courseId);
|
||||||
|
|
||||||
|
if (!course) {
|
||||||
|
return res.status(404).json({ message: 'Course not found' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the userIds of enrolled users from the course
|
||||||
|
const userIds = course.enrollees.map(enrollee => enrollee.userId);
|
||||||
|
|
||||||
|
// Find the users with matching userIds
|
||||||
|
const enrolledUsers = await User.find({ _id: { $in: userIds } });
|
||||||
|
|
||||||
|
// Extract the emails from the enrolled users
|
||||||
|
const emails = enrolledUsers.map(user => user.email);
|
||||||
|
|
||||||
|
res.status(200).json({ emails });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ message: 'An error occurred while retrieving enrolled users' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//Given to students:
|
||||||
|
/*
|
||||||
|
const getEmailsOfEnrolledUsers = async (req, res) => {
|
||||||
|
const courseId = req.body.courseId;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Find the course by courseId
|
||||||
|
const course = await Course.findById(courseId);
|
||||||
|
|
||||||
|
if (!course) {
|
||||||
|
return res.status(404).json({ message: 'Course not found' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the userIds of enrolled users from the course
|
||||||
|
const userIds = course.enrollees.map(enrollee => enrollee.userId);
|
||||||
|
|
||||||
|
// Find the users with matching userIds
|
||||||
|
const enrolledUsers = await User.find({ _id: { $in: users } });
|
||||||
|
|
||||||
|
// Extract the emails from the enrolled users
|
||||||
|
const emails = enrolledStudents.forEach(user => user.email);
|
||||||
|
|
||||||
|
res.status(200).json({ userEmails });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ message: 'An error occurred while retrieving enrolled users' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
//[ACTIVITY]
|
||||||
|
|
||||||
|
exports.searchCoursesByPriceRange = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { minPrice, maxPrice } = req.body;
|
||||||
|
|
||||||
|
// Find courses within the price range
|
||||||
|
const courses = await Course.find({
|
||||||
|
price: { $gte: minPrice, $lte: maxPrice }
|
||||||
|
});
|
||||||
|
|
||||||
|
res.status(200).json({ courses });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ error: 'An error occurred while searching for courses' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,279 @@
|
|||||||
|
//[SECTION] Dependencies and Modules
|
||||||
|
const User = require("../models/User");
|
||||||
|
const Course = require("../models/Course");
|
||||||
|
const bcrypt = require('bcrypt');
|
||||||
|
const auth = require("../auth");
|
||||||
|
|
||||||
|
|
||||||
|
//[SECTION] Check if the email already exists
|
||||||
|
|
||||||
|
module.exports.checkEmailExists = (reqBody) => {
|
||||||
|
|
||||||
|
// The result is sent back to the frontend via the "then" method found in the route file
|
||||||
|
return User.find({email : reqBody.email}).then(result => {
|
||||||
|
|
||||||
|
// The "find" method returns a record if a match is found
|
||||||
|
if (result.length > 0) {
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// No duplicate email found
|
||||||
|
// The user is not yet registered in the database
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => res.send(err))
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//[SECTION] User registration
|
||||||
|
module.exports.registerUser = (reqBody) => {
|
||||||
|
|
||||||
|
// Creates a variable "newUser" and instantiates a new "User" object using the mongoose model
|
||||||
|
// Uses the information from the request body to provide all the necessary information
|
||||||
|
let newUser = new User({
|
||||||
|
firstName : reqBody.firstName,
|
||||||
|
lastName : reqBody.lastName,
|
||||||
|
email : reqBody.email,
|
||||||
|
mobileNo : reqBody.mobileNo,
|
||||||
|
password : bcrypt.hashSync(reqBody.password, 10)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Saves the created object to our database
|
||||||
|
return newUser.save().then((user, error) => {
|
||||||
|
|
||||||
|
// User registration failed
|
||||||
|
if (error) {
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// User registration successful
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => err)
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//[SECTION] User authentication
|
||||||
|
|
||||||
|
module.exports.loginUser = (req, res) => {
|
||||||
|
User.findOne({ email : req.body.email} ).then(result => {
|
||||||
|
|
||||||
|
console.log(result);
|
||||||
|
|
||||||
|
// User does not exist
|
||||||
|
if(result == null){
|
||||||
|
|
||||||
|
return res.send(false);
|
||||||
|
|
||||||
|
// User exists
|
||||||
|
} else {
|
||||||
|
|
||||||
|
const isPasswordCorrect = bcrypt.compareSync(req.body.password, result.password);
|
||||||
|
// If the passwords match/result of the above code is true
|
||||||
|
if (isPasswordCorrect) {
|
||||||
|
|
||||||
|
return res.send({ access : auth.createAccessToken(result) })
|
||||||
|
|
||||||
|
// Passwords do not match
|
||||||
|
} else {
|
||||||
|
|
||||||
|
return res.send(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => res.send(err))
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//[SECTION] Retrieve user details
|
||||||
|
|
||||||
|
module.exports.getProfile = (req, res) => {
|
||||||
|
|
||||||
|
return User.findById(req.user.id)
|
||||||
|
.then(result => {
|
||||||
|
result.password = "";
|
||||||
|
return res.send(result);
|
||||||
|
})
|
||||||
|
.catch(err => res.send(err))
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//[SECTION] Enroll a registered User
|
||||||
|
|
||||||
|
module.exports.enroll = async (req, res) => {
|
||||||
|
|
||||||
|
console.log(req.user.id) //the user's id from the decoded token after verify()
|
||||||
|
console.log(req.body.courseId) //the course from our request body
|
||||||
|
|
||||||
|
//process stops here and sends response IF user is an admin
|
||||||
|
if(req.user.isAdmin){
|
||||||
|
return res.send("Action Forbidden")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let isUserUpdated = await User.findById(req.user.id).then(user => {
|
||||||
|
let newEnrollment = {
|
||||||
|
courseId: req.body.courseId,
|
||||||
|
courseName: req.body.courseName,
|
||||||
|
courseDescription: req.body.courseDescription,
|
||||||
|
coursePrice: req.body.coursePrice
|
||||||
|
}
|
||||||
|
user.enrollments.push(newEnrollment);
|
||||||
|
|
||||||
|
return user.save().then(user => true).catch(err => err.message)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
if(isUserUpdated !== true) {
|
||||||
|
return res.send({message: isUserUpdated})
|
||||||
|
}
|
||||||
|
|
||||||
|
let isCourseUpdated = await Course.findById(req.body.courseId).then(course => {
|
||||||
|
|
||||||
|
let enrollee = {
|
||||||
|
userId: req.user.id
|
||||||
|
}
|
||||||
|
|
||||||
|
course.enrollees.push(enrollee);
|
||||||
|
|
||||||
|
return course.save().then(course => true).catch(err => err.message)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
if(isCourseUpdated !== true) {
|
||||||
|
return res.send({ message: isCourseUpdated})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if(isUserUpdated && isCourseUpdated) {
|
||||||
|
return res.send({ message: "Enrolled Successfully."})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//[ACTIVITY] Getting user's enrolled courses
|
||||||
|
module.exports.getEnrollments = (req, res) => {
|
||||||
|
User.findById(req.user.id)
|
||||||
|
.then(result => res.send(result.enrollments))
|
||||||
|
.catch(err => res.send(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//ChatGPT Generated
|
||||||
|
|
||||||
|
//[SECTION] Reset Password
|
||||||
|
|
||||||
|
module.exports.resetPassword = async (req, res) => {
|
||||||
|
try {
|
||||||
|
|
||||||
|
//console.log(req.user)
|
||||||
|
//console.log(req.body)
|
||||||
|
|
||||||
|
const { newPassword } = req.body;
|
||||||
|
const { id } = req.user; // Extracting user ID from the authorization header
|
||||||
|
|
||||||
|
// Hashing the new password
|
||||||
|
const hashedPassword = await bcrypt.hash(newPassword, 10);
|
||||||
|
|
||||||
|
// Updating the user's password in the database
|
||||||
|
await User.findByIdAndUpdate(id, { password: hashedPassword });
|
||||||
|
|
||||||
|
// Sending a success response
|
||||||
|
res.status(200).send({ message: 'Password reset successfully' });
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
res.status(500).send({ message: 'Internal server error' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//[SECTION] Reset Profile
|
||||||
|
module.exports.updateProfile = async (req, res) => {
|
||||||
|
try {
|
||||||
|
|
||||||
|
console.log(req.user);
|
||||||
|
console.log(req.body);
|
||||||
|
|
||||||
|
// Get the user ID from the authenticated token
|
||||||
|
const userId = req.user.id;
|
||||||
|
|
||||||
|
// Retrieve the updated profile information from the request body
|
||||||
|
const { firstName, lastName, mobileNo } = req.body;
|
||||||
|
|
||||||
|
// Update the user's profile in the database
|
||||||
|
const updatedUser = await User.findByIdAndUpdate(
|
||||||
|
userId,
|
||||||
|
{ firstName, lastName, mobileNo },
|
||||||
|
{ new: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
res.send(updatedUser);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
res.status(500).send({ message: 'Failed to update profile' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//[ACTIVITY] Update Enrollment Status
|
||||||
|
module.exports.updateEnrollmentStatus = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { userId, courseId, status } = req.body;
|
||||||
|
|
||||||
|
// Find the user and update the enrollment status
|
||||||
|
const user = await User.findById(userId);
|
||||||
|
|
||||||
|
// Find the enrollment for the course in the user's enrollments array
|
||||||
|
const enrollment = user.enrollments.find((enrollment) => enrollment.courseId === courseId);
|
||||||
|
|
||||||
|
if (!enrollment) {
|
||||||
|
return res.status(404).json({ error: 'Enrollment not found' });
|
||||||
|
}
|
||||||
|
|
||||||
|
enrollment.status = status;
|
||||||
|
|
||||||
|
// Save the updated user document
|
||||||
|
await user.save();
|
||||||
|
|
||||||
|
res.status(200).json({ message: 'Enrollment status updated successfully' });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ error: 'An error occurred while updating the enrollment status' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//[ACTIVITY] Update user as admin controller
|
||||||
|
exports.updateUserAsAdmin = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { userId } = req.body;
|
||||||
|
|
||||||
|
// Find the user and update isAdmin flag
|
||||||
|
const user = await User.findById(userId);
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return res.status(404).json({ error: 'User not found' });
|
||||||
|
}
|
||||||
|
|
||||||
|
user.isAdmin = true;
|
||||||
|
|
||||||
|
// Save the updated user document
|
||||||
|
await user.save();
|
||||||
|
|
||||||
|
res.status(200).json({ message: 'User updated as admin successfully' });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ error: 'An error occurred while updating the user as admin' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,45 @@
|
|||||||
|
//[SECTION] Dependencies and Modules
|
||||||
|
const dotenv = require('dotenv').config();
|
||||||
|
const express = require("express");
|
||||||
|
const mongoose = require("mongoose");
|
||||||
|
const cors = require("cors");
|
||||||
|
const userRoutes = require("./routes/user");
|
||||||
|
const courseRoutes = require("./routes/course");
|
||||||
|
|
||||||
|
|
||||||
|
//[SECTION] Environment Setup
|
||||||
|
const port = process.env.PORT;
|
||||||
|
|
||||||
|
//[SECTION] Server Setup
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
app.use(cors())
|
||||||
|
app.use(express.json());
|
||||||
|
app.use(express.urlencoded({ extended: true }));
|
||||||
|
|
||||||
|
|
||||||
|
//[SECTION] Database Connection
|
||||||
|
mongoose.connect(process.env.CONNECTION_STRING, {
|
||||||
|
useNewUrlParser: true,
|
||||||
|
useUnifiedTopology: true
|
||||||
|
});
|
||||||
|
|
||||||
|
mongoose.connection.once('open', () => console.log('Now connected to MongoDB Atlas.'));
|
||||||
|
|
||||||
|
|
||||||
|
//[SECTION] Backend Routes
|
||||||
|
//http://localhost:4000/users
|
||||||
|
app.use("/users", userRoutes);
|
||||||
|
//http://localhost:4000/courses
|
||||||
|
app.use("/courses", courseRoutes);
|
||||||
|
|
||||||
|
|
||||||
|
//[SECTION] Server Gateway Response
|
||||||
|
if(require.main === module) {
|
||||||
|
app.listen( process.env.PORT || port, () => {
|
||||||
|
console.log(`API is now online on port ${ process.env.PORT || port }`)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = app;
|
@ -0,0 +1,41 @@
|
|||||||
|
//[SECTION] Dependencies and Modules
|
||||||
|
const mongoose = require('mongoose');
|
||||||
|
|
||||||
|
//[SECTION] Schema/Blueprint
|
||||||
|
const courseSchema = new mongoose.Schema({
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
required: [true, 'is Required']
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
type: String,
|
||||||
|
required: [true, 'is Required']
|
||||||
|
},
|
||||||
|
price: {
|
||||||
|
type: Number,
|
||||||
|
required: [true, 'Course Price is Required']
|
||||||
|
},
|
||||||
|
isActive: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
createdOn: {
|
||||||
|
type: Date,
|
||||||
|
default: new Date()
|
||||||
|
},
|
||||||
|
enrollees: [
|
||||||
|
{
|
||||||
|
userId: {
|
||||||
|
type: String,
|
||||||
|
required: [true, 'Student ID is Required']
|
||||||
|
},
|
||||||
|
enrolledOn: {
|
||||||
|
type: Date,
|
||||||
|
default: new Date()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
//[SECTION] Model
|
||||||
|
module.exports = mongoose.model('Course', courseSchema);
|
@ -0,0 +1,49 @@
|
|||||||
|
//[SECTION] Modules and Dependencies
|
||||||
|
const mongoose = require('mongoose');
|
||||||
|
|
||||||
|
//[SECTION] Schema/Blueprint
|
||||||
|
const userSchema = new mongoose.Schema({
|
||||||
|
firstName: {
|
||||||
|
type: String,
|
||||||
|
required: [true, 'First Name is Required']
|
||||||
|
},
|
||||||
|
lastName: {
|
||||||
|
type: String,
|
||||||
|
required: [true, 'Last Name is Required']
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
type: String,
|
||||||
|
required: [true, 'Email is Required']
|
||||||
|
},
|
||||||
|
password: {
|
||||||
|
type: String,
|
||||||
|
required: [true, 'Password is Required']
|
||||||
|
},
|
||||||
|
isAdmin: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
mobileNo: {
|
||||||
|
type: String,
|
||||||
|
required: [true, 'Mobile Number is Required']
|
||||||
|
},
|
||||||
|
enrollments: [
|
||||||
|
{
|
||||||
|
courseId: {
|
||||||
|
type: String,
|
||||||
|
required: [true, 'Subject ID is Required']
|
||||||
|
},
|
||||||
|
enrolledOn: {
|
||||||
|
type: Date,
|
||||||
|
default: new Date()
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
type: String,
|
||||||
|
default: 'Enrolled'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
//[SECTION] Model
|
||||||
|
module.exports = mongoose.model('User', userSchema);
|
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "s43-47",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"start": "node index",
|
||||||
|
"dev": "nodemon index"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"bcrypt": "^5.1.0",
|
||||||
|
"cors": "^2.8.5",
|
||||||
|
"dotenv": "^16.3.1",
|
||||||
|
"express": "^4.18.2",
|
||||||
|
"jsonwebtoken": "^9.0.0",
|
||||||
|
"mongoose": "^7.2.0",
|
||||||
|
"nodemon": "^2.0.22"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
//[SECTION] Dependencies and Modules
|
||||||
|
const express = require('express');
|
||||||
|
const courseController = require("../controllers/course");
|
||||||
|
const auth = require("../auth")
|
||||||
|
|
||||||
|
const {verify, verifyAdmin} = auth;
|
||||||
|
|
||||||
|
//[SECTION] Routing Component
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
|
||||||
|
//[ACTIVITY] create a course POST
|
||||||
|
router.post("/", verify, verifyAdmin, courseController.addCourse);
|
||||||
|
|
||||||
|
|
||||||
|
//[SECTION] Route for retrieving all the courses
|
||||||
|
router.get("/all", courseController.getAllCourses);
|
||||||
|
|
||||||
|
//[SECTION] Route for retrieving all the ACTIVE courses for all users
|
||||||
|
// Middleware for verifying JWT is not required because users who aren't logged in should also be able to view the courses
|
||||||
|
router.get("/", courseController.getAllActive);
|
||||||
|
|
||||||
|
|
||||||
|
//[SECTION] Route for Search Course by Name
|
||||||
|
router.post('/searchByName', courseController.searchCoursesByName);
|
||||||
|
|
||||||
|
//[ACTIVITY] Search Courses By Price Range
|
||||||
|
router.post('/searchByPrice', courseController.searchCoursesByPriceRange);
|
||||||
|
|
||||||
|
//[SECTION] Route for retrieving a specific course
|
||||||
|
router.get("/:courseId", courseController.getCourse);
|
||||||
|
|
||||||
|
//[SECTION] Route for updating a course (Admin)
|
||||||
|
router.put("/:courseId", verify, verifyAdmin, courseController.updateCourse);
|
||||||
|
|
||||||
|
// [SECTION]Route to get the emails of users enrolled in a course
|
||||||
|
|
||||||
|
// Solution
|
||||||
|
router.get('/:courseId/enrolled-users', courseController.getEmailsOfEnrolledUsers);
|
||||||
|
|
||||||
|
//Given to students:
|
||||||
|
// router.post('/:courseId/enrolled-users', getEmailsOfEnrolledUsers;
|
||||||
|
|
||||||
|
//[ACTIVITY] Route to archiving a course (Admin)
|
||||||
|
router.put("/:courseId/archive", verify, verifyAdmin, courseController.archiveCourse);
|
||||||
|
|
||||||
|
//[ACTIVITY] Route to activating a course (Admin)
|
||||||
|
router.put("/:courseId/activate", verify, verifyAdmin, courseController.activateCourse);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Allows us to export the "router" object that will be accessed in our "index.js" file
|
||||||
|
module.exports = router;
|
||||||
|
|
@ -0,0 +1,71 @@
|
|||||||
|
//[SECTION] Dependencies and Modules
|
||||||
|
const express = require('express');
|
||||||
|
const userController = require("../controllers/user");
|
||||||
|
const auth = require("../auth")
|
||||||
|
|
||||||
|
const {verify, verifyAdmin} = auth;
|
||||||
|
|
||||||
|
//[SECTION] Routing Component
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//[SECTION] Routes - POST
|
||||||
|
router.post("/checkEmail", (req, res) => {
|
||||||
|
userController.checkEmailExists(req.body).then(resultFromController => res.send(resultFromController));
|
||||||
|
})
|
||||||
|
|
||||||
|
//[SECTION] Route for user registration
|
||||||
|
router.post("/register", (req, res) => {
|
||||||
|
userController.registerUser(req.body).then(resultFromController => res.send(resultFromController));
|
||||||
|
});
|
||||||
|
|
||||||
|
//[SECTION] Route for user authentication
|
||||||
|
router.post("/login", userController.loginUser);
|
||||||
|
|
||||||
|
|
||||||
|
//[ACTIVITY] Route for retrieving user details
|
||||||
|
//router.post("/details", verify, userController.getProfile);
|
||||||
|
|
||||||
|
//Refactor
|
||||||
|
router.get("/details", verify, userController.getProfile);
|
||||||
|
|
||||||
|
|
||||||
|
//[SECTION] Route to enroll user to a course
|
||||||
|
router.post('/enroll', verify, userController.enroll);
|
||||||
|
|
||||||
|
//[ACTIVITY] Get Logged User's Enrollments
|
||||||
|
router.get('/getEnrollments', verify, userController.getEnrollments)
|
||||||
|
|
||||||
|
//ChatGPT Generated Codes
|
||||||
|
|
||||||
|
//[SECTION] Reset Password
|
||||||
|
router.put('/reset-password', verify, userController.resetPassword);
|
||||||
|
|
||||||
|
//[SECTION] Update Profile
|
||||||
|
router.put('/profile', verify, userController.updateProfile);
|
||||||
|
|
||||||
|
//[ACTIVITY] Update enrollment status route
|
||||||
|
router.put('/enrollmentStatusUpdate', userController.updateEnrollmentStatus);
|
||||||
|
|
||||||
|
//[ACTIVITY] Update Admin route
|
||||||
|
router.put('/updateAdmin', verify, verifyAdmin, userController.updateUserAsAdmin);
|
||||||
|
|
||||||
|
//[SECTION] Export Route System
|
||||||
|
module.exports = router;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue