diff --git a/individual/backend/csp2-reciproco/Procfile b/individual/backend/csp2-reciproco/Procfile deleted file mode 100644 index 5ec9cc2..0000000 --- a/individual/backend/csp2-reciproco/Procfile +++ /dev/null @@ -1 +0,0 @@ -web: node index.js \ No newline at end of file diff --git a/individual/backend/csp2-reciproco/csp2-postman.json b/individual/backend/csp2-reciproco/csp2-postman.json index 931f9d4..cf9bada 100644 --- a/individual/backend/csp2-reciproco/csp2-postman.json +++ b/individual/backend/csp2-reciproco/csp2-postman.json @@ -16,7 +16,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\r\n \"email\": \"renzo@email.com\",\r\n \"password\": \"renzo\"\r\n}\r\n", + "raw": "{\r\n \"email\": \"ronron@email.com\",\r\n \"password\": \"ronron\"\r\n}\r\n", "options": { "raw": { "language": "json" @@ -24,13 +24,17 @@ } }, "url": { - "raw": "http://localhost:3000/user/register", + "raw": "http://ec2-3-15-94-47.us-east-2.compute.amazonaws.com/b5/user/register", "protocol": "http", "host": [ - "localhost" + "ec2-3-15-94-47", + "us-east-2", + "compute", + "amazonaws", + "com" ], - "port": "3000", "path": [ + "b5", "user", "register" ] @@ -55,7 +59,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\r\n \"email\": \"wapatu@email.com\",\r\n \"password\": \"wapatu\"\r\n}\r\n", + "raw": "{\r\n \"email\": \"ronron@email.com\",\r\n \"password\": \"ronron\"\r\n}\r\n", "options": { "raw": { "language": "json" @@ -63,13 +67,17 @@ } }, "url": { - "raw": "http://localhost:3000/user/authenticate", + "raw": "http://ec2-3-15-94-47.us-east-2.compute.amazonaws.com/b5/user/authenticate", "protocol": "http", "host": [ - "localhost" + "ec2-3-15-94-47", + "us-east-2", + "compute", + "amazonaws", + "com" ], - "port": "3000", "path": [ + "b5", "user", "authenticate" ] @@ -105,13 +113,17 @@ } }, "url": { - "raw": "http://localhost:3000/user/update", + "raw": "http://ec2-3-15-94-47.us-east-2.compute.amazonaws.com/b5/user/update", "protocol": "http", "host": [ - "localhost" + "ec2-3-15-94-47", + "us-east-2", + "compute", + "amazonaws", + "com" ], - "port": "3000", "path": [ + "b5", "user", "update" ] @@ -125,14 +137,17 @@ "method": "GET", "header": [], "url": { - "raw": "http://localhost:3000/product/active", + "raw": "http://ec2-3-15-94-47.us-east-2.compute.amazonaws.com/b5/active", "protocol": "http", "host": [ - "localhost" + "ec2-3-15-94-47", + "us-east-2", + "compute", + "amazonaws", + "com" ], - "port": "3000", "path": [ - "product", + "b5", "active" ] } @@ -145,13 +160,17 @@ "method": "GET", "header": [], "url": { - "raw": "http://localhost:3000/product/products/65545619b88d0a48f00ae778", + "raw": "http://ec2-3-15-94-47.us-east-2.compute.amazonaws.com/b5/product/products/65545619b88d0a48f00ae778", "protocol": "http", "host": [ - "localhost" + "ec2-3-15-94-47", + "us-east-2", + "compute", + "amazonaws", + "com" ], - "port": "3000", "path": [ + "b5", "product", "products", "65545619b88d0a48f00ae778" @@ -167,7 +186,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\r\n \"userId\": \"65544d9be5c01f6c0ca79200\", // Replace with a valid user ID from your database\r\n \"products\": [\r\n {\r\n \"productId\": \"6553a4e897ac8ac9462f96c4\", // Replace with a valid product ID from your database\r\n \"productName\": \"Mastering Card\",\r\n \"quantity\": 1\r\n }\r\n ],\r\n \"totalAmount\": 500\r\n}\r\n", + "raw": "{\r\n \"userId\": \"65535cb526b586a3e2fd56cc\", // Replace with a valid user ID from your database\r\n \"products\": [\r\n {\r\n \"productId\": \"6553a4e897ac8ac9462f96c4\", // Replace with a valid product ID from your database\r\n \"productName\": \"Mastering Card\",\r\n \"quantity\": 1\r\n }\r\n ],\r\n \"totalAmount\": 500\r\n}\r\n", "options": { "raw": { "language": "json" @@ -175,13 +194,17 @@ } }, "url": { - "raw": "http://localhost:3000/user/order", + "raw": "http://ec2-3-15-94-47.us-east-2.compute.amazonaws.com/b5/user/order", "protocol": "http", "host": [ - "localhost" + "ec2-3-15-94-47", + "us-east-2", + "compute", + "amazonaws", + "com" ], - "port": "3000", "path": [ + "b5", "user", "order" ] @@ -217,13 +240,17 @@ } }, "url": { - "raw": "http://localhost:3000/user/retrieveUser", + "raw": "http://ec2-3-15-94-47.us-east-2.compute.amazonaws.com/b5/user/retrieveUser", "protocol": "http", "host": [ - "localhost" + "ec2-3-15-94-47", + "us-east-2", + "compute", + "amazonaws", + "com" ], - "port": "3000", "path": [ + "b5", "user", "retrieveUser" ] @@ -239,16 +266,30 @@ { "name": "Retrieve All Products", "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI2NTUzNWNiNTI2YjU4NmEzZTJmZDU2Y2MiLCJlbWFpbCI6ImFkbWluQGVtYWlsLmNvbSIsImlzQWRtaW4iOnRydWUsImlhdCI6MTcwMDIxNTgwNSwiZXhwIjoxNzAwMjE5NDA1fQ.tYjbNBbBsg71V7fPXk68ccEJ3svDiyAL_8O0CPZDKYo", + "type": "string" + } + ] + }, "method": "GET", "header": [], "url": { - "raw": "http://localhost:3000/product/all", + "raw": "http://ec2-3-15-94-47.us-east-2.compute.amazonaws.com/b5/product/all", "protocol": "http", "host": [ - "localhost" + "ec2-3-15-94-47", + "us-east-2", + "compute", + "amazonaws", + "com" ], - "port": "3000", "path": [ + "b5", "product", "all" ] @@ -281,13 +322,17 @@ } }, "url": { - "raw": "http://localhost:3000/product/create", + "raw": "http://ec2-3-15-94-47.us-east-2.compute.amazonaws.com/b5/product/create", "protocol": "http", "host": [ - "localhost" + "ec2-3-15-94-47", + "us-east-2", + "compute", + "amazonaws", + "com" ], - "port": "3000", "path": [ + "b5", "product", "create" ] @@ -320,13 +365,17 @@ } }, "url": { - "raw": "http://localhost:3000/product/products/65545a1e6fa9d841e1518d1d", + "raw": "http://ec2-3-15-94-47.us-east-2.compute.amazonaws.com/b5/product/products/65545a1e6fa9d841e1518d1d", "protocol": "http", "host": [ - "localhost" + "ec2-3-15-94-47", + "us-east-2", + "compute", + "amazonaws", + "com" ], - "port": "3000", "path": [ + "b5", "product", "products", "65545a1e6fa9d841e1518d1d" @@ -351,13 +400,17 @@ "method": "PUT", "header": [], "url": { - "raw": "http://localhost:3000/product/products/6554634e5cac4bcd6f2394ed/activate", + "raw": "http://ec2-3-15-94-47.us-east-2.compute.amazonaws.com/b5/product/products/6554634e5cac4bcd6f2394ed/activate", "protocol": "http", "host": [ - "localhost" + "ec2-3-15-94-47", + "us-east-2", + "compute", + "amazonaws", + "com" ], - "port": "3000", "path": [ + "b5", "product", "products", "6554634e5cac4bcd6f2394ed", @@ -373,13 +426,17 @@ "method": "PUT", "header": [], "url": { - "raw": "http://localhost:3000/product/products/6554634e5cac4bcd6f2394ed/archive", + "raw": "http://ec2-3-15-94-47.us-east-2.compute.amazonaws.com/b5/product/products/6554634e5cac4bcd6f2394ed/archive", "protocol": "http", "host": [ - "localhost" + "ec2-3-15-94-47", + "us-east-2", + "compute", + "amazonaws", + "com" ], - "port": "3000", "path": [ + "b5", "product", "products", "6554634e5cac4bcd6f2394ed", @@ -392,16 +449,39 @@ { "name": "Set User to Admin", "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI2NTUzNWNiNTI2YjU4NmEzZTJmZDU2Y2MiLCJlbWFpbCI6ImFkbWluQGVtYWlsLmNvbSIsImlzQWRtaW4iOnRydWUsImlhdCI6MTcwMDA0MzgyOSwiZXhwIjoxNzAwMDQ3NDI5fQ.Mwkp161yHtw11mWL6oNnSDjyobOLHCb8qrFqgvF4bqI", + "type": "string" + } + ] + }, "method": "POST", "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"userId\": \"65535cb526b586a3e2fd56cc\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { - "raw": "http://localhost:3000/user/set-admin/", + "raw": "http://ec2-3-15-94-47.us-east-2.compute.amazonaws.com/b5/user/set-admin/", "protocol": "http", "host": [ - "localhost" + "ec2-3-15-94-47", + "us-east-2", + "compute", + "amazonaws", + "com" ], - "port": "3000", "path": [ + "b5", "user", "set-admin", "" @@ -418,7 +498,7 @@ "bearer": [ { "key": "token", - "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI2NTU0YWM4ZGQ3ZmJmOWVlOTAyMTdlNzciLCJlbWFpbCI6Im1hc3RlckBlbWFpbC5jb20iLCJpc0FkbWluIjpmYWxzZSwiaWF0IjoxNzAwMjEyNjkzLCJleHAiOjE3MDAyMTYyOTN9.J908JWFjN5dKRw0-XJPHa4kD6QAW4M7tv1LOVmbtM_E", + "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI2NTUzNWNiNTI2YjU4NmEzZTJmZDU2Y2MiLCJlbWFpbCI6ImFkbWluQGVtYWlsLmNvbSIsImlzQWRtaW4iOnRydWUsImlhdCI6MTcwMDIxNTgwNSwiZXhwIjoxNzAwMjE5NDA1fQ.tYjbNBbBsg71V7fPXk68ccEJ3svDiyAL_8O0CPZDKYo", "type": "string" } ] @@ -426,13 +506,17 @@ "method": "GET", "header": [], "url": { - "raw": "http://localhost:3000/user/orders-all", + "raw": "http://ec2-3-15-94-47.us-east-2.compute.amazonaws.com/b5/user/orders-all", "protocol": "http", "host": [ - "localhost" + "ec2-3-15-94-47", + "us-east-2", + "compute", + "amazonaws", + "com" ], - "port": "3000", "path": [ + "b5", "user", "orders-all" ] @@ -470,13 +554,17 @@ } }, "url": { - "raw": "http://localhost:3000/cart/add-to-cart", + "raw": "http://ec2-3-15-94-47.us-east-2.compute.amazonaws.com/b5/cart/add-to-cart", "protocol": "http", "host": [ - "localhost" + "ec2-3-15-94-47", + "us-east-2", + "compute", + "amazonaws", + "com" ], - "port": "3000", "path": [ + "b5", "cart", "add-to-cart" ] @@ -509,13 +597,17 @@ } }, "url": { - "raw": "http://localhost:3000/cart/remove-from-cart", + "raw": "http://ec2-3-15-94-47.us-east-2.compute.amazonaws.com/b5/cart/remove-from-cart", "protocol": "http", "host": [ - "localhost" + "ec2-3-15-94-47", + "us-east-2", + "compute", + "amazonaws", + "com" ], - "port": "3000", "path": [ + "b5", "cart", "remove-from-cart" ] @@ -548,15 +640,18 @@ } }, "url": { - "raw": "http://localhost:3000/cart//update-quantity", + "raw": "http://ec2-3-15-94-47.us-east-2.compute.amazonaws.com/b5/cart/update-quantity", "protocol": "http", "host": [ - "localhost" + "ec2-3-15-94-47", + "us-east-2", + "compute", + "amazonaws", + "com" ], - "port": "3000", "path": [ + "b5", "cart", - "", "update-quantity" ] } @@ -565,20 +660,45 @@ }, { "name": "Cart Total Price", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI2NTU0YWM4ZGQ3ZmJmOWVlOTAyMTdlNzciLCJlbWFpbCI6Im1hc3RlckBlbWFpbC5jb20iLCJpc0FkbWluIjpmYWxzZSwiaWF0IjoxNzAwMDU0NDk2LCJleHAiOjE3MDAwNTgwOTZ9.ytA5zJ3gqEefw7lRAgvVXlWD3-iANaEjjkYgBqMWc4I", + "type": "string" + } + ] + }, "method": "GET", "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"userId\": \"655396dcc8ea29f42422e214\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { - "raw": "http://localhost:3000/cart/6554cc5a4446b63dcf2d7fc0/655396dcc8ea29f42422e214", + "raw": "http://ec2-3-15-94-47.us-east-2.compute.amazonaws.com/b5/cart/cart-details", "protocol": "http", "host": [ - "localhost" + "ec2-3-15-94-47", + "us-east-2", + "compute", + "amazonaws", + "com" ], - "port": "3000", "path": [ + "b5", "cart", - "6554cc5a4446b63dcf2d7fc0", - "655396dcc8ea29f42422e214" + "cart-details" ] } }, diff --git a/individual/fullstack/s54-s62/package-lock.json b/individual/fullstack/s54-s62/package-lock.json index ada4dfd..461ef2e 100644 --- a/individual/fullstack/s54-s62/package-lock.json +++ b/individual/fullstack/s54-s62/package-lock.json @@ -12,10 +12,13 @@ "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "bootstrap": "^4.6.0", + "dotenv": "^16.3.1", "react": "^18.2.0", "react-bootstrap": "^1.5.2", "react-dom": "^18.2.0", + "react-router-dom": "^6.20.0", "react-scripts": "5.0.1", + "sweetalert2": "^11.10.1", "web-vitals": "^2.1.4" } }, @@ -3272,6 +3275,14 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@remix-run/router": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.13.0.tgz", + "integrity": "sha512-5dMOnVnefRsl4uRnAdoWjtVTdh8e6aZqgM4puy9nmEADH72ck+uXwzpJLEKE9Q6F8ZljNewLgmTfkxUrBdv4WA==", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@restart/context": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/@restart/context/-/context-2.1.4.tgz", @@ -7120,11 +7131,14 @@ } }, "node_modules/dotenv": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", - "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", + "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", "engines": { - "node": ">=10" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/motdotla/dotenv?sponsor=1" } }, "node_modules/dotenv-expand": { @@ -14929,6 +14943,36 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.20.0.tgz", + "integrity": "sha512-pVvzsSsgUxxtuNfTHC4IxjATs10UaAtvLGVSA1tbUE4GDaOSU1Esu2xF5nWLz7KPiMuW8BJWuPFdlGYJ7/rW0w==", + "dependencies": { + "@remix-run/router": "1.13.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.20.0.tgz", + "integrity": "sha512-CbcKjEyiSVpA6UtCHOIYLUYn/UJfwzp55va4yEfpk7JBN3GPqWfHrdLkAvNCcpXr8QoihcDMuk0dzWZxtlB/mQ==", + "dependencies": { + "@remix-run/router": "1.13.0", + "react-router": "6.20.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, "node_modules/react-scripts": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", @@ -15001,6 +15045,14 @@ } } }, + "node_modules/react-scripts/node_modules/dotenv": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", + "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", + "engines": { + "node": ">=10" + } + }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", @@ -16488,6 +16540,15 @@ "boolbase": "~1.0.0" } }, + "node_modules/sweetalert2": { + "version": "11.10.1", + "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.10.1.tgz", + "integrity": "sha512-qu145oBuFfjYr5yZW9OSdG6YmRxDf8CnkgT/sXMfrXGe+asFy2imC2vlaLQ/L/naZ/JZna1MPAY56G4qYM0VUQ==", + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/limonte" + } + }, "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", diff --git a/individual/fullstack/s54-s62/package.json b/individual/fullstack/s54-s62/package.json index aaa19f4..5d395c5 100644 --- a/individual/fullstack/s54-s62/package.json +++ b/individual/fullstack/s54-s62/package.json @@ -7,10 +7,13 @@ "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "bootstrap": "^4.6.0", + "dotenv": "^16.3.1", "react": "^18.2.0", "react-bootstrap": "^1.5.2", "react-dom": "^18.2.0", + "react-router-dom": "^6.20.0", "react-scripts": "5.0.1", + "sweetalert2": "^11.10.1", "web-vitals": "^2.1.4" }, "scripts": { diff --git a/individual/fullstack/s54-s62/src/App.js b/individual/fullstack/s54-s62/src/App.js index bd37b2b..31126ed 100644 --- a/individual/fullstack/s54-s62/src/App.js +++ b/individual/fullstack/s54-s62/src/App.js @@ -1,26 +1,102 @@ -import AppNavBar from "./components/AppNavBar"; -import { Container } from "react-bootstrap"; -import "./App.css"; +import { useState, useEffect } from 'react'; +import AppNavBar from './components/AppNavBar'; +import { Container } from 'react-bootstrap'; +import { BrowserRouter as Router } from 'react-router-dom'; +import { Route, Routes } from 'react-router-dom'; +import './App.css'; +import { UserProvider } from './UserContext'; // Pages -// import Home from "./pages/Home"; -// import Courses from "./pages/Courses"; -import Register from "./pages/Register"; -import Login from "./pages/Login"; +import Home from './pages/Home'; +import Courses from './pages/Courses'; +import Register from './pages/Register'; +import Login from './pages/Login'; +import Logout from './pages/Logout'; +import Profile from './pages/Profile'; +import CourseView from './pages/CourseView'; +import AddCourse from './pages/AddCourse'; +import NotFound from './pages/Error'; function App() { + + // let user = { token: localStorage.getItem('token') } + const [user, setUser] = useState({ + id: null, + isAdmin: null + }) + + const unsetUser = () => { + localStorage.clear() + } + + useEffect(() => { + + // console.log(localStorage); + + fetch(`${process.env.REACT_APP_API_URL}/users/details`, { + headers: { + Authorization: `Bearer ${ localStorage.getItem('token') }` + } + }) + .then(res => res.json()) + .then(data => { + + // console.log(data); + + if(typeof data._id !== "undefined"){ + + setUser({ + id: data._id, + isAdmin: data.isAdmin + }); + + } else { + + setUser({ + id: null, + isAdmin: null + }); + + } + + }) + + }, []) + + console.log(user); + return ( - // JSX - JavaScript XML Code - - // Fragments - <> - - - {/* */} - - - + // JSX - JavaScript XML code + + // <> - Fragments + + + + + + } /> + } /> + {/*MINI ACTIVITY - continue completing the routes*/} + } /> + } /> + } /> + } /> + } /> + } /> + + } /> + + + + ); } export default App; + +/** + * Mini Activity 2 + * Import the Highlights component + * Add the Highlights component in the JSX inside the Container and after the Banner Component + * 2 mins + */ \ No newline at end of file diff --git a/individual/fullstack/s54-s62/src/UserContext.js b/individual/fullstack/s54-s62/src/UserContext.js new file mode 100644 index 0000000..3eb9121 --- /dev/null +++ b/individual/fullstack/s54-s62/src/UserContext.js @@ -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; \ No newline at end of file diff --git a/individual/fullstack/s54-s62/src/components/AdminView.js b/individual/fullstack/s54-s62/src/components/AdminView.js new file mode 100644 index 0000000..d79ac53 --- /dev/null +++ b/individual/fullstack/s54-s62/src/components/AdminView.js @@ -0,0 +1,84 @@ +// AdminView.js + +import { useState, useEffect } from 'react'; +import { Table } from 'react-bootstrap'; +import EditCourse from './EditCourse'; +import ArchiveCourse from './ArchiveCourse'; + +const fetchDataFromDatabase = async () => { + try { + const response = await fetch(`${process.env.REACT_APP_API_URL}/courses/all`); + const data = await response.json(); + return data; + } catch (error) { + throw new Error('Error fetching data from the database'); + } +}; + +export default function AdminView() { + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [courses, setCourses] = useState([]); + + useEffect(() => { + const fetchData = async () => { + try { + setLoading(true); + const data = await fetchDataFromDatabase(); + setCourses(data.map(course => ( + + {course._id} + {course.name} + {course.description} + {course.price} + + {course.isActive ? 'Available' : 'Unavailable'} + + + + + + + ))); + } catch (error) { + setError(error.message || 'An error occurred'); + } finally { + setLoading(false); + } + }; + + fetchData(); + }, []); + + return ( + <> +

Admin Dashboard

+ + {loading &&

Loading...

} + {error &&

{error}

} + + {!loading && !error && ( + + + + + + + + + + + + + + {courses} + +
IDNameDescriptionPriceAvailabilityActions
+ )} + + ); +} diff --git a/individual/fullstack/s54-s62/src/components/AppNavBar.js b/individual/fullstack/s54-s62/src/components/AppNavBar.js index ba4fe78..f23b915 100644 --- a/individual/fullstack/s54-s62/src/components/AppNavBar.js +++ b/individual/fullstack/s54-s62/src/components/AppNavBar.js @@ -1,25 +1,59 @@ // React Bootstrap Component /* - -Syntax - import moduleName from 'filePath'; + -Syntax + import moduleName from 'filePath'; */ +import { useContext } from 'react'; import Container from 'react-bootstrap/Container'; -import NavBar from 'react-bootstrap/NavBar'; +import Navbar from 'react-bootstrap/Navbar'; import Nav from 'react-bootstrap/Nav'; +import { NavLink } from 'react-router-dom'; +import UserContext from '../UserContext'; export default function AppNavBar() { - return ( - - - Zuitt Booking - - - - - - - ) -} \ No newline at end of file + const { user } = useContext(UserContext); + + return ( + + + Zuitt Booking + + + + + + + ); + } + diff --git a/individual/fullstack/s54-s62/src/components/ArchiveCourse.js b/individual/fullstack/s54-s62/src/components/ArchiveCourse.js new file mode 100644 index 0000000..76c1e1f --- /dev/null +++ b/individual/fullstack/s54-s62/src/components/ArchiveCourse.js @@ -0,0 +1,82 @@ +import React from 'react'; +import { Button } from 'react-bootstrap'; +import Swal from 'sweetalert2'; + +export default function ArchiveCourse({ course, fetchData, isActive }) { + + + 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.success) { + Swal.fire({ + title: 'Success!', + icon: 'success', + text: 'Course Successfully Archived' + }); + fetchData(); // Ensure it fetches the updated data + } else { + Swal.fire({ + title: 'Error!', + 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.success) { + Swal.fire({ + title: 'Success!', + icon: 'success', + text: 'Course Successfully Activated' + }); + fetchData(); + } else { + Swal.fire({ + title: 'Error!', + icon: 'error', + text: ' Please try again' + }); + fetchData(); + } + }); + }; + + return ( + <> + {isActive ? ( + + ) : ( + + )} + + ); +} diff --git a/individual/fullstack/s54-s62/src/components/Banner.js b/individual/fullstack/s54-s62/src/components/Banner.js index 8f49628..314fe38 100644 --- a/individual/fullstack/s54-s62/src/components/Banner.js +++ b/individual/fullstack/s54-s62/src/components/Banner.js @@ -1,13 +1,19 @@ import { Button, Row, Col } from 'react-bootstrap'; +import { Link } from 'react-router-dom'; -export default function Banner() { - return ( - - -

Zuitt Coding Bootcamp

-

Opportunites for everyone, everywhere.

- - -
- ) +export default function Banner({data}) { + + console.log(data); + + const {title, content, destination, label} = data; + + return ( + + +

{title}

+

{content}

+ {label} + +
+ ) } \ No newline at end of file diff --git a/individual/fullstack/s54-s62/src/components/CourseCard.js b/individual/fullstack/s54-s62/src/components/CourseCard.js index 9851037..051595a 100644 --- a/individual/fullstack/s54-s62/src/components/CourseCard.js +++ b/individual/fullstack/s54-s62/src/components/CourseCard.js @@ -1,54 +1,55 @@ -import { Button, Card } from "react-bootstrap"; -import PropTypes from 'prop-types'; +import { Card, Button } from 'react-bootstrap'; import { useState } from 'react' +import { Link } from 'react-router-dom'; +import PropTypes from 'prop-types'; export default function CourseCard({ courseProp }) { - - const { name, description, price } = courseProp; - // Use the state hook to be able to store state (data) in this component - // States are used to keep track of information related to individual component - // Syntax: - /** - * const [ getter, setterFunction ] = useState(initialGetterValue) - */ + // Deconstruct the course properties into their own variables + const { _id, name, description, price } = courseProp; - const [ count, setCount ] = useState(0); - const enroll = () => { - if (count < 30) { - setCount(count + 1); - } else { - alert('No More Seats'); - } - }; + // Use the state hook to be able to store state (data) in this component + // States are used to keep track of information related to individual component + // Syntax: + /* + const [getter, setterFunction] = useState(initialGetterValue) + */ + // const [count, setCount] = useState(0); + // const [seats, setSeats] = useState(10); + // function enroll(){ + // if(seats > 0) { + // setCount(count + 1) + // setSeats(seats - 1); + // } else { + // alert("No more seats available") + // } + + // } - return ( - - - {name} - Description - {description} - Price: - Php {price} - Enrollees: {count} - - - - ); + return ( + + + {name} + Description: + {description} + Price: + Php {price} + Details + + + ) } // Check if the CourseCard component is getting the correct prop types // PropTypes are used for validating information passed to a component. CourseCard.propTypes = { - // The 'shape' method is used to check if a prop object conforms to a specific "shape" - courseProp: PropTypes.shape({ - // Define the properties and their expected types - name: PropTypes.string.isRequired, - description: PropTypes.string.isRequired, - price: PropTypes.number.isRequired - }).isRequired // Make sure the entire courseProp object is required -}; + // The 'shape' method is used to check if a prop object comforms to a specific "shape" + courseProp: PropTypes.shape({ + // Define the properties and their expected types + name: PropTypes.string.isRequired, + description: PropTypes.string.isRequired, + price: PropTypes.number.isRequired + }) +} \ No newline at end of file diff --git a/individual/fullstack/s54-s62/src/components/CourseSearch.js b/individual/fullstack/s54-s62/src/components/CourseSearch.js new file mode 100644 index 0000000..9b6ca93 --- /dev/null +++ b/individual/fullstack/s54-s62/src/components/CourseSearch.js @@ -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/search`, { + 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 ( +
+

Course Search

+
+ + setSearchQuery(event.target.value)} + /> +
+ +

Search Results:

+
    + {searchResults.map(course => ( + + ))} +
+
+ ); +}; + +export default CourseSearch; diff --git a/individual/fullstack/s54-s62/src/components/EditCourse.js b/individual/fullstack/s54-s62/src/components/EditCourse.js new file mode 100644 index 0000000..abab472 --- /dev/null +++ b/individual/fullstack/s54-s62/src/components/EditCourse.js @@ -0,0 +1,127 @@ +import React, { useState } from 'react'; +import { Button, Modal, Form } from 'react-bootstrap'; +import Swal from 'sweetalert2'; + +// Add the props course to get the specific id of the course +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) => { + fetch(`http://localhost:4000/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) + }) + + // 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(`http://localhost:4000/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( + <> + + + {/*Edit Modal*/} + +
editCourse(e, courseId)}> + + Edit Course + + + + Name + setName(e.target.value)} + required/> + + + Description + setDescription(e.target.value)} + required/> + + + Price + setPrice(e.target.value)} + required/> + + + + + + +
+
+ + ) +} \ No newline at end of file diff --git a/individual/fullstack/s54-s62/src/components/FeaturedCourses.js b/individual/fullstack/s54-s62/src/components/FeaturedCourses.js new file mode 100644 index 0000000..bfddc0a --- /dev/null +++ b/individual/fullstack/s54-s62/src/components/FeaturedCourses.js @@ -0,0 +1,60 @@ +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) + + // Create two empty arrays to be used to store random numbers and featured course data. + const numbers = [] + const featured = [] + + + // This function generates a random number between 0 and the length of the data array (the fetched course data). It checks if the random number has already been added to the numbers array. If not, it adds the random number to the numbers array. If the random number already exists in the numbers array, it recursively calls itself to generate a new random number. + const generateRandomNums = () => { + let randomNum = Math.floor(Math.random() * data.length) + + if(numbers.indexOf(randomNum) === -1){ + numbers.push(randomNum) + }else{ + generateRandomNums() + } + } + + // A loop is used to iterate five times (from 0 to 4). Inside the loop, the generateRandomNums function is called to generate a random number and push it into the numbers array. + for(let i = 0; i < 5; i++){ + generateRandomNums() + + + // For each iteration of the loop, the PreviewCourses component is rendered with the corresponding course data from the data array based on the random number. The key prop is set to the _id of the course for React's reconciliation to track components efficiently. The resulting JSX is pushed into the featured array. + featured.push( + + // the breakPoint here is for columns + + ) + } + + // After the loop finishes, the setPreviews function is called to update the state of the component with the featured array. This state update triggers a re-render, and the PreviewCourses components are displayed on the page. + setPreviews(featured) + }) + }, []) + + return( + <> +

Featured Courses

+ + {/*add the state here*/} + {previews} + + + ) + } + diff --git a/individual/fullstack/s54-s62/src/components/Highlights.js b/individual/fullstack/s54-s62/src/components/Highlights.js index 78a8828..31c0b13 100644 --- a/individual/fullstack/s54-s62/src/components/Highlights.js +++ b/individual/fullstack/s54-s62/src/components/Highlights.js @@ -1,44 +1,45 @@ -import { Row, Col, Card } from "react-bootstrap"; +import { Row, Col, Card } from 'react-bootstrap'; export default function Highlights() { - return ( - - - - - -

Learn from Home

-
- - 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. - -
-
- - - - - -

Study Now, Pay Later

-
- - 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. - -
-
- - - - - -

Be Part of Our Community

-
- - 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. - -
-
- -
- ) -} + + return ( + + + + + +

Learn from Home

+
+ + 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. + +
+
+ + + + + +

Study Now, Pay Later

+
+ + 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. + +
+
+ + + + + +

Be Part of Our Community

+
+ + 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. + +
+
+ +
+ ) +} \ No newline at end of file diff --git a/individual/fullstack/s54-s62/src/components/PreviewCourses.js b/individual/fullstack/s54-s62/src/components/PreviewCourses.js new file mode 100644 index 0000000..ca64efc --- /dev/null +++ b/individual/fullstack/s54-s62/src/components/PreviewCourses.js @@ -0,0 +1,34 @@ +import { Col, Card } from 'react-bootstrap' +import { Link } from 'react-router-dom' + +export default function Product(props){ + +// props is used here to get the data and breakPoint from the FeaturedCourses.js + const { breakPoint, data } = props + + // Destructure the courses data here + const { _id, name, description, price } = data + + return( + + + {/*add spacing for x-axis*/} + + + + + {/*Add the specific details of course link*/} + { name } + + { description } + + + +
₱{ price }
+ Details +
+
+ + ) +} + diff --git a/individual/fullstack/s54-s62/src/components/ResetPassword.js b/individual/fullstack/s54-s62/src/components/ResetPassword.js new file mode 100644 index 0000000..9ca1160 --- /dev/null +++ b/individual/fullstack/s54-s62/src/components/ResetPassword.js @@ -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 ( +
+

Reset Password

+
+
+ + setPassword(e.target.value)} + required + /> +
+
+ + setConfirmPassword(e.target.value)} + required + /> +
+ {message &&
{message}
} + +
+
+ ); +}; + +export default ResetPassword; diff --git a/individual/fullstack/s54-s62/src/components/UserView.js b/individual/fullstack/s54-s62/src/components/UserView.js new file mode 100644 index 0000000..9125c53 --- /dev/null +++ b/individual/fullstack/s54-s62/src/components/UserView.js @@ -0,0 +1,34 @@ +import { 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 since the route used is /all from Course.js page + if(course.isActive === true) { + return ( + + ) + } 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( + <> + + { courses } + + ) +} \ No newline at end of file diff --git a/individual/fullstack/s54-s62/src/data/courseData.js b/individual/fullstack/s54-s62/src/data/courseData.js index 9539178..f60c9af 100644 --- a/individual/fullstack/s54-s62/src/data/courseData.js +++ b/individual/fullstack/s54-s62/src/data/courseData.js @@ -21,17 +21,17 @@ const coursesData = [ onOffer: true }, { - id: "wdc003", - name: "Java - React", + id: "wdc004", + name: "JavaScript - React", 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, + price: 60000, onOffer: true }, { - id: "wdc003", - name: "Java - Express", + id: "wdc005", + name: "JavaScript - Express", 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, + price: 50000, onOffer: true } ] diff --git a/individual/fullstack/s54-s62/src/index.js b/individual/fullstack/s54-s62/src/index.js index e4730ba..954d37f 100644 --- a/individual/fullstack/s54-s62/src/index.js +++ b/individual/fullstack/s54-s62/src/index.js @@ -12,19 +12,3 @@ root.render( ); - -const name = 'John Smith'; -const user = { - firstName: "Jane", - lastName: "Lovelace" -} - -function formatName(user) { - return user.firstName + ' ' + user.lastName -} -const element =

Hello there, {formatName(user)}

; // JSX - Javascript XML - -// const root = ReactDOM.createRoot(document.getElementById('root')); -// root.render(element); - - diff --git a/individual/fullstack/s54-s62/src/pages/AddCourse.js b/individual/fullstack/s54-s62/src/pages/AddCourse.js new file mode 100644 index 0000000..68a63c1 --- /dev/null +++ b/individual/fullstack/s54-s62/src/pages/AddCourse.js @@ -0,0 +1,121 @@ +import React, { useContext, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import Container from 'react-bootstrap/Container'; +import Form from 'react-bootstrap/Form'; +import Button from 'react-bootstrap/Button'; +import UserContext from '../UserContext'; +import Swal from 'sweetalert2'; + +const AddCourse = () => { + const { user } = useContext(UserContext); + const navigate = useNavigate(); + const [courseData, setCourseData] = useState({ + name: '', + description: '', + price: 0, + // Add other course-related fields here + }); + const [isCourseAdded, setCourseAdded] = useState(false); + + const handleInputChange = (e) => { + const { name, value } = e.target; + setCourseData((prevData) => ({ + ...prevData, + [name]: value, + })); + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + + try { + const response = await fetch(`${process.env.REACT_APP_API_URL}/courses/add-course/`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${localStorage.getItem('token')}`, // Assuming you have a token-based authentication + }, + body: JSON.stringify(courseData), + }); + + if (response.ok) { + setCourseAdded(true); + Swal.fire({ + icon: "success", + title: "Courses added" + }) + } else { + console.error('Failed to add the course'); + // Optionally handle error response here + Swal.fire({ + icon: "error", + title: "Failed to add the course" + }) + } + } catch (error) { + console.error('Error adding course:', error); + // Optionally handle network or other errors here + } + }; + + if (!user || !user.isAdmin) { + navigate('/'); + return null; + } + + if (isCourseAdded) { + navigate('/courses'); + return null; + } + + return ( + +

Add New Course

+
+ + Course Name + + + + + Description + + + + + Price + + + + {/* Add other form fields for additional course information */} + + +
+
+ ); +}; + +export default AddCourse; diff --git a/individual/fullstack/s54-s62/src/pages/CourseView.js b/individual/fullstack/s54-s62/src/pages/CourseView.js new file mode 100644 index 0000000..c778365 --- /dev/null +++ b/individual/fullstack/s54-s62/src/pages/CourseView.js @@ -0,0 +1,96 @@ +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() { + + // useParams hook allows us to retrieve the courseId passed via the URL + const { courseId } = useParams(); + + const { user } = useContext(UserContext); + + // Allow us to redirect to different pages in our functions + const navigate = useNavigate(); + + const [name, setName] = useState(""); + const [description, setDescription] = useState(""); + const [price, setPrice] = useState(0); + + const enroll = (coursId) => { + + fetch(`${ process.env.REACT_APP_API_URL }/users/enroll`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${localStorage.getItem('token')}` + }, + body: JSON.stringify({ + courseId: courseId + }) + }) + .then(res => res.json()) + .then(data => { + console.log(data) + + if(data.message === 'Enrolled Successfully!') { + + Swal.fire({ + title: "Successfully Enrolled", + icon: "success", + text: "You have successfully enrolled for this course." + }) + + navigate("/courses"); + } else { + + Swal.fire({ + title: "Something went wrong", + icon: "error", + text: "Please try again." + }) + + } + + }) + } + + useEffect(() => { + + fetch(`${ process.env.REACT_APP_API_URL }/courses/${courseId}`) + .then(res => res.json()) + .then(data => { + + setName(data.name); + setDescription(data.description); + setPrice(data.price) + }) + + }, [courseId]); + + return ( + + + + + + {name} + Description: + {description} + Price: + PhP {price} + Class Schedule + 8 am - 5 pm + {(user.id !== null) ? + + : + Log in to Enroll + } + + + + + + ) +} \ No newline at end of file diff --git a/individual/fullstack/s54-s62/src/pages/Courses.js b/individual/fullstack/s54-s62/src/pages/Courses.js index fa54640..a37c85a 100644 --- a/individual/fullstack/s54-s62/src/pages/Courses.js +++ b/individual/fullstack/s54-s62/src/pages/Courses.js @@ -1,21 +1,67 @@ +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'; + -import CourseCard from "../components/CourseCard" -import coursesData from "../data/courseData" export default function Courses() { - - console.log(coursesData); - console.log(coursesData[0]); - - const courses = coursesData.map(course => { - return ( - - ) - }) - - // Component Body - return ( - <> - {courses} - - ) -} \ No newline at end of file + + // console.log(coursesData); + // console.log(coursesData[0]) + + const { user } = useContext(UserContext); + + + 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 => { + // // The "courseProp" in the CourseCard component is called a "prop" which is a shorthand for "property" since components are considered as objects in React JS + // // The curly braces ({}) are used for props to signify that we are providing information using JavaScript expressions rather than hard coded values which use double quotes ("") + // // We can pass information from one component to another using props. This is referred to as "props drilling" + // return ( + // + // ) + // }) + + // Component Body + return ( + <> + { + (user.isAdmin === true) ? + + + : + + + } + + + ) +} diff --git a/individual/fullstack/s54-s62/src/pages/Error.js b/individual/fullstack/s54-s62/src/pages/Error.js new file mode 100644 index 0000000..204fa85 --- /dev/null +++ b/individual/fullstack/s54-s62/src/pages/Error.js @@ -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 ( + + ) +} \ No newline at end of file diff --git a/individual/fullstack/s54-s62/src/pages/Home.js b/individual/fullstack/s54-s62/src/pages/Home.js index ed8a663..f22245e 100644 --- a/individual/fullstack/s54-s62/src/pages/Home.js +++ b/individual/fullstack/s54-s62/src/pages/Home.js @@ -1,11 +1,20 @@ -import Banner from "../components/Banner"; -import Highlights from "../components/Highlights"; +import Banner from '../components/Banner'; +import FeaturedCourses from '../components/FeaturedCourses'; +import Highlights from '../components/Highlights'; export default function Home() { - return ( - <> - - - - ); -} + const data = { + title: "Zuitt Coding Bootcamp", + content: "Opportunities for everyone, everywhere", + destination: "/courses", + label: "Enroll now!" + } + + return ( + <> + + + + + ) +} \ No newline at end of file diff --git a/individual/fullstack/s54-s62/src/pages/Login.js b/individual/fullstack/s54-s62/src/pages/Login.js index 0e9ee13..4c31719 100644 --- a/individual/fullstack/s54-s62/src/pages/Login.js +++ b/individual/fullstack/s54-s62/src/pages/Login.js @@ -1,97 +1,140 @@ -import React, { useState } from "react"; -import { Form, Button } from "react-bootstrap"; - -const Login = () => { - const [email, setEmail] = useState(""); - const [password, setPassword] = useState(""); - const [isValid, setIsValid] = useState(false); - - const checkFormValidity = () => { - // Simple validation for demonstration purposes - const isValid = email !== "" && password !== ""; - setIsValid(isValid); - }; - - const handleInputChange = (e) => { - const { name, value } = e.target; - // Update state based on the input field name - switch (name) { - case "email": - setEmail(value); - break; - case "password": - setPassword(value); - break; - default: - break; - } - }; - - const handleSubmit = async (e) => { - e.preventDefault(); - - try { - const response = await fetch("http://localhost:4000/users/login", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - email, - password, - }), - }); - - if (response.ok) { - alert("Thank you for logging in"); - console.log("Login success") - // Handle successful login, e.g., redirect to another page - } else { - alert("Unsucessful login"); - // Handle invalid credentials, e.g., show an error message - } - } catch (error) { - console.error("Error during login:", error); - // Handle other errors, e.g., network issues - } - }; - - return ( -
-

Login

- - Email Address: - { - handleInputChange(e); - checkFormValidity(); - }} - /> - - - Password: - { - handleInputChange(e); - checkFormValidity(); - }} - /> - - -
- ); -}; - -export default Login; +import { useState, useEffect, useContext } from 'react'; +import { Form, Button } from 'react-bootstrap'; +import Swal from 'sweetalert2'; +import UserContext from '../UserContext'; +import { Navigate } from 'react-router-dom'; + +export default function Login() { + + // Allows us to consume the User context and it's properties to use for validation + const { user, setUser } = useContext(UserContext); + + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + + const [isActive, setIsActive] = useState(false); + + function authenticate(e) { + + // Prevent 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 token of the authenticated user in the local storage + /* + Syntax: + localStorage.setItem('propertyName', value); + */ + localStorage.setItem("token", data.access); + + // function for retrieving user details + retrieveUserDetails(data.access); + + 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" + }); + } + }) + + // Clearing our input fields after submission + setEmail(''); + setPassword(''); + + } + + const retrieveUserDetails = (token) => { + + fetch(`${process.env.REACT_APP_API_URL}/users/details`, { + headers: { + Authorization: `Bearer ${token}` + } + }) + .then(res => res.json()) + .then(data => { + + console.log(data); + + setUser({ + id: data._id, + isAdmin: data.isAdmin + }) + + }) + + } + + + useEffect(() => { + + if(email !== '' && password !== '') { + setIsActive(true); + } else { + setIsActive(false); + } + + }, [email, password]) + + return ( + (user.id !== null) ? + + : +
authenticate(e)} > +

Login

+ + Email address + setEmail(e.target.value)} + required + /> + + + + Password + setPassword(e.target.value)} + required + /> + + + + { isActive ? + + : + + + } +
+ ) +} \ No newline at end of file diff --git a/individual/fullstack/s54-s62/src/pages/Logout.js b/individual/fullstack/s54-s62/src/pages/Logout.js new file mode 100644 index 0000000..aab8048 --- /dev/null +++ b/individual/fullstack/s54-s62/src/pages/Logout.js @@ -0,0 +1,22 @@ +import { useContext, useEffect } from 'react'; +import { Navigate } from 'react-router-dom'; +import UserContext from '../UserContext'; + +export default function Logout() { + + const { unsetUser, setUser } = useContext(UserContext); + + unsetUser(); + + useEffect(() => { + setUser({ + id: null, + isAdmin: null + }) + }) + + // Redirect back to login + return ( + + ) +} \ No newline at end of file diff --git a/individual/fullstack/s54-s62/src/pages/NotFound.js b/individual/fullstack/s54-s62/src/pages/NotFound.js new file mode 100644 index 0000000..c2098e9 --- /dev/null +++ b/individual/fullstack/s54-s62/src/pages/NotFound.js @@ -0,0 +1,20 @@ +import Banner from '../components/Banner'; + +export default function NotFound(){ + + const data = { + + title: "404 - Not found", + content: "Sorry, the page you are looking for does not exist.", + destination: "/", + label: "Back home" + + } + + return( + + + + ) + +} diff --git a/individual/fullstack/s54-s62/src/pages/Profile.js b/individual/fullstack/s54-s62/src/pages/Profile.js new file mode 100644 index 0000000..6da062a --- /dev/null +++ b/individual/fullstack/s54-s62/src/pages/Profile.js @@ -0,0 +1,83 @@ +import { useEffect, useContext, useState } from 'react'; +import { Row, Col } from 'react-bootstrap'; +import UserContext from '../UserContext'; +import { Navigate } from 'react-router-dom'; +import ResetPassword from '../components/ResetPassword'; + +export default function Profile() { + const { user } = useContext(UserContext); + const [profileData, setProfileData] = useState(null); + + useEffect(() => { + if (user && user.id) { + // Fetch user profile data when the component mounts + fetchUserProfile(); + } + }, [user]); + + const fetchUserProfile = async () => { + try { + const response = await fetch( + `${process.env.REACT_APP_API_URL}/users/details`, + { + headers: { + Authorization: `Bearer ${localStorage.getItem('token')}`, + }, + } + ); + + if (response.ok) { + const data = await response.json(); + console.log('Profile Data:', data); + + if (data && data.firstName && data.lastName && data.email && data.mobileNo) { + setProfileData(data); + } else { + console.error('Invalid user profile data received from the server.'); + } + } else { + console.error('Failed to fetch user profile'); + } + } catch (error) { + console.error('Error fetching user profile:', error); + } + }; + + if (!user || user.id === null) { + return ; + } + + return ( + <> + + +

Profile

+ {profileData ? ( + <> +

+ {profileData.firstName} {profileData.lastName} +

+
+

Contacts

+
    +
  • + Name: {profileData.firstName} {profileData.lastName} +
  • +
  • Email: {profileData.email}
  • +
  • Mobile No: {profileData.mobileNo}
  • +
+ + ) : ( +

Loading profile...

+ )} + +
+ + + + + + + + ); +} diff --git a/individual/fullstack/s54-s62/src/pages/Register.js b/individual/fullstack/s54-s62/src/pages/Register.js index c5d085c..81bf90b 100644 --- a/individual/fullstack/s54-s62/src/pages/Register.js +++ b/individual/fullstack/s54-s62/src/pages/Register.js @@ -1,8 +1,12 @@ -import { useState, useEffect } from 'react'; +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(""); @@ -25,7 +29,7 @@ export default function Register() { e.preventDefault(); - fetch("http://localhost:4000/users/register", { + fetch(`${process.env.REACT_APP_API_URL}/users/register`, { method: "POST", headers: { "Content-Type": "application/json" @@ -40,6 +44,7 @@ export default function Register() { }) .then(res => res.json()) .then(data => { + console.log(data) if (data) { @@ -72,80 +77,83 @@ export default function Register() { return ( -
registerUser(e)}> -

Register

- - First Name: - {setFirstName(e.target.value)}} - /> - - - Last Name: - {setLastName(e.target.value)}} - /> - - - Email: - {setEmail(e.target.value)}} - /> - - - Mobile No: - {setMobileNo(e.target.value)}} - /> - - - Password: - {setPassword(e.target.value)}} + (user.id !== null) ? + + : + registerUser(e)}> +

Register

+ + First Name: + {setFirstName(e.target.value)}} /> - - - Confirm Password: - {setConfirmPassword(e.target.value)}} + + + Last Name: + {setLastName(e.target.value)}} /> - - - {/*conditionally renders the submit button based on the isActive state*/} - { - isActive - ? - - : - - } - +
+ + Email: + {setEmail(e.target.value)}} + /> + + + Mobile No: + {setMobileNo(e.target.value)}} + /> + + + Password: + {setPassword(e.target.value)}} + /> + + + Confirm Password: + {setConfirmPassword(e.target.value)}} + /> + + + {/*conditionally renders the submit button based on the isActive state*/} + { + isActive + ? + + : + + } -
+ + ) } \ No newline at end of file diff --git a/individual/fullstack/s63 b/individual/fullstack/s63 new file mode 160000 index 0000000..a9253e2 --- /dev/null +++ b/individual/fullstack/s63 @@ -0,0 +1 @@ +Subproject commit a9253e2e4f52925478c882822da78456d1526540