├── .gitignore ├── README.md ├── config ├── checkToken.js ├── database.js └── ensureLoggedIn.js ├── controllers └── api │ └── users.js ├── models └── user.js ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── routes └── api │ └── users.js ├── server.js └── src ├── App.css ├── App.js ├── components ├── EditUserForm.js ├── LogInForm.js ├── NavBar.js ├── NavBar.module.css ├── Profile.js ├── Profile.module.css ├── SignUpForm.js ├── WeatherDisplay.js └── WeatherDisplay.module.css ├── index.css ├── index.js ├── logo.svg ├── pages ├── AuthPage.js ├── DeleteConfirmationPage.js ├── DeleteConfirmationPage.module.css ├── EditPage.js ├── EditPage.module.css ├── ProfilePage.js └── WeatherPage.js ├── reportWebVitals.js ├── setupTests.js └── utilities ├── users-api.js ├── users-service.js └── weather-api.js /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | #DrawIo 26 | .$flow.bkp 27 | *.drawio 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Minecraft-Server-Deployer(Work-in-Progress) 2 | 3 | ## Description 4 | 5 | This application will eventually be a website where users can launch their own minecraft dedicated servers. It is designed to connect non-tech individuals with the power of dedicated servers, without having to learn all of the technical bits. Ideally, I would like to create containerized Java minecraft servers hosted on Amazon Web Services'(AWS) Elastic Kubernetes Service (EKS). I want to develop this app so I can connect non-tech individuals to normally tech-savvy functions and so I can earn passive income while I continue to search for permanent employment. 6 | 7 | ## Technology Used 8 | 9 | bcrypt 10 | dotenv 11 | express 12 | jsonwebtoken 13 | mongoose 14 | morgan 15 | react 16 | react-dom 17 | react-router-dom 18 | react-scripts 19 | serve 20 | serve-favicon 21 | web-vitals 22 | 23 | ## Installation 24 | 25 | npm i 26 | create a .env file 27 | add environment variables in .env: 28 | DATABASE_URL= 29 | SECRET= 30 | REACT_APP_API_KEY= 31 | 32 | ## Getting started 33 | 34 | Deployed App: https://minecraft-server-deployer.onrender.com/ 35 | https://trello.com/b/EZTgyPfB/final-project 36 | -------------------------------------------------------------------------------- /config/checkToken.js: -------------------------------------------------------------------------------- 1 | const jwt = require("jsonwebtoken") 2 | 3 | module.exports = function (req, res, next) { 4 | // Check for the token being sent in a header or as a query parameter 5 | let token = req.get("Authorization") || req.query.token 6 | if (token) { 7 | // Remove the 'Bearer ' if it was included in the token header 8 | token = token.replace("Bearer ", "") 9 | // Check if token is valid and not expired 10 | jwt.verify(token, process.env.SECRET, function (err, decoded) { 11 | // If valid token, decoded will be the token's entire payload 12 | // If invalid token, err will be set 13 | req.user = err ? null : decoded.user 14 | // If your app cares... (optional) 15 | req.exp = err ? null : new Date(decoded.exp * 1000) 16 | return next() 17 | }) 18 | } else { 19 | // No token was sent 20 | req.user = null 21 | return next() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /config/database.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose") 2 | 3 | mongoose.set("strictQuery", true) 4 | mongoose.connect(process.env.DATABASE_URL) 5 | 6 | const db = mongoose.connection 7 | 8 | db.on("connected", function () { 9 | console.log(`Connected to ${db.name} at ${db.host}:${db.port}`) 10 | }) 11 | -------------------------------------------------------------------------------- /config/ensureLoggedIn.js: -------------------------------------------------------------------------------- 1 | module.exports = function (req, res, next) { 2 | // Status code of 401 is Unauthorized 3 | if (!req.user) return res.status(401).json("Unauthorized") 4 | // A okay 5 | next() 6 | } 7 | -------------------------------------------------------------------------------- /controllers/api/users.js: -------------------------------------------------------------------------------- 1 | //* Request handler Logic 2 | const User = require("../../models/user") 3 | const jwt = require("jsonwebtoken") 4 | const bcrypt = require("bcrypt") 5 | 6 | //* /*-- Helper Functions --*/ 7 | function createJWT(user) { 8 | return jwt.sign({ user }, process.env.SECRET, { expiresIn: "24h" }) 9 | } 10 | 11 | async function create(req, res) { 12 | // console.log('[From POST handler]', req.body) 13 | try { 14 | //* creating a new user 15 | const user = await User.create(req.body) 16 | console.log(user) 17 | 18 | //* creating a new jwt 19 | const token = createJWT(user) 20 | 21 | res.json(token) 22 | } catch (error) { 23 | console.log(error) 24 | res.status(400).json(error) 25 | } 26 | } 27 | 28 | async function editUser(req, res) { 29 | try { 30 | if (!req.user) { 31 | throw new Error() 32 | } 33 | User.findById(req.user._id, (err, user) => { 34 | if (err) throw new Error() 35 | 36 | console.log(req.body) 37 | 38 | // Update all user attributes which are different or missing from user with values from req.body 39 | Object.assign(user, req.body) 40 | 41 | user.save().then((savedUser) => { 42 | console.log(savedUser) 43 | res.json(savedUser) 44 | }) 45 | }) 46 | } catch (error) { 47 | console.log(error) 48 | res.status(400).json(error) 49 | } 50 | } 51 | 52 | async function getUser(req, res) { 53 | try { 54 | if (!req.user) { 55 | throw new Error() 56 | } 57 | const user = await User.findById(req.user._id) 58 | res.json(user) 59 | } catch (error) { 60 | console.log(error) 61 | res.status(400).json(error) 62 | } 63 | } 64 | 65 | async function getAllUsers(req, res) { 66 | try { 67 | const users = await User.find({}) 68 | res.json(users) 69 | } catch (error) { 70 | console.log(error) 71 | res.status(400).json(error) 72 | } 73 | } 74 | 75 | async function deleteUser(req, res) { 76 | try { 77 | if (!req.user) { 78 | throw new Error() 79 | } 80 | await User.findByIdAndDelete(req.user._id) 81 | res.status(200).json({ message: "user deleted" }) 82 | } catch (error) { 83 | console.log(error) 84 | res.status(400).json(error) 85 | } 86 | } 87 | 88 | async function login(req, res) { 89 | try { 90 | // find user in db 91 | const user = await User.findOne({ email: req.body.email }) 92 | // check if we found an user 93 | if (!user) throw new Error() 94 | // compare the password to hashed password 95 | const match = await bcrypt.compare(req.body.password, user.password) 96 | // check is password matched 97 | if (!match) throw new Error() 98 | // send back a new token with the user data in the payload 99 | res.json(createJWT(user)) 100 | } catch { 101 | res.status(400).json("Bad Credentials") 102 | } 103 | } 104 | 105 | async function checkToken(req, res) { 106 | console.log(req.user) 107 | res.json(req.exp) 108 | } 109 | 110 | module.exports = { 111 | create, 112 | login, 113 | checkToken, 114 | editUser, 115 | getUser, 116 | deleteUser, 117 | getAllUsers, 118 | } 119 | -------------------------------------------------------------------------------- /models/user.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose") 2 | 3 | const Schema = mongoose.Schema 4 | const bcrypt = require("bcrypt") 5 | 6 | //* determines how much processing time it will take to perform the hash 7 | const SALT_ROUNDS = 6 // 6 is a reasonable value 8 | 9 | const userSchema = new Schema( 10 | { 11 | name: { type: String, required: true }, 12 | email: { 13 | type: String, 14 | unique: true, 15 | trim: true, 16 | lowercase: true, 17 | required: true, 18 | }, 19 | password: { 20 | type: String, 21 | trim: true, 22 | minLength: 3, 23 | required: true, 24 | }, 25 | }, 26 | { 27 | timestamps: true, 28 | toJSON: function (doc, ret) { 29 | delete ret.password 30 | return ret 31 | }, 32 | } 33 | ) 34 | 35 | //* Pre Hook 36 | userSchema.pre("save", async function (next) { 37 | // if password was NOT modified continue to the next middleware 38 | if (!this.isModified("password")) return next() 39 | 40 | // update the password with the computed hash 41 | this.password = await bcrypt.hash(this.password, SALT_ROUNDS) 42 | return next() 43 | }) 44 | 45 | userSchema.pre("findOneAndUpdate", async function (next) { 46 | const docToUpdate = await this.model.findOne(this.getQuery()) 47 | this.password = await bcrypt.hash(docToUpdate.password, SALT_ROUNDS) 48 | return next() 49 | }) 50 | 51 | module.exports = mongoose.model("User", userSchema) 52 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Minecraft-Server-Deployer", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "bcrypt": "^5.1.0", 7 | "dotenv": "^16.0.3", 8 | "express": "^4.18.2", 9 | "jsonwebtoken": "^9.0.0", 10 | "mongoose": "^6.10.5", 11 | "morgan": "^1.10.0", 12 | "react": "^18.2.0", 13 | "react-dom": "^18.2.0", 14 | "react-router-dom": "^6.10.0", 15 | "react-scripts": "5.0.1", 16 | "serve": "^14.2.1", 17 | "serve-favicon": "^2.5.0", 18 | "web-vitals": "^2.1.4" 19 | }, 20 | "scripts": { 21 | "start": "BROWSER=none react-scripts start", 22 | "build": "react-scripts build", 23 | "test": "react-scripts test", 24 | "eject": "react-scripts eject" 25 | }, 26 | "eslintConfig": { 27 | "extends": [ 28 | "react-app", 29 | "react-app/jest" 30 | ] 31 | }, 32 | "browserslist": { 33 | "production": [ 34 | ">0.2%", 35 | "not dead", 36 | "not op_mini all" 37 | ], 38 | "development": [ 39 | "last 1 chrome version", 40 | "last 1 firefox version", 41 | "last 1 safari version" 42 | ] 43 | }, 44 | "proxy": "http://localhost:3001" 45 | } 46 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cburgee/Minecraft-Server-Deployer/2a0b689fba40a32150a83f8db5204aab3211868d/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cburgee/Minecraft-Server-Deployer/2a0b689fba40a32150a83f8db5204aab3211868d/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cburgee/Minecraft-Server-Deployer/2a0b689fba40a32150a83f8db5204aab3211868d/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /routes/api/users.js: -------------------------------------------------------------------------------- 1 | //* Routing Logic 2 | 3 | const express = require("express") 4 | const router = express.Router() 5 | const usersCtrl = require("../../controllers/api/users") 6 | const ensureLoggedIn = require("../../config/ensureLoggedIn") 7 | 8 | //* POST 9 | router.post("/", usersCtrl.create) 10 | 11 | router.get("/", usersCtrl.getAllUsers) 12 | 13 | router.put("/edit", usersCtrl.editUser) 14 | 15 | router.get("/me", usersCtrl.getUser) 16 | 17 | router.delete("/", usersCtrl.deleteUser) 18 | 19 | router.post("/login", usersCtrl.login) 20 | 21 | router.get("/check-token", ensureLoggedIn, usersCtrl.checkToken) 22 | 23 | module.exports = router 24 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | require("dotenv").config() 2 | require("./config/database") // connects to db 3 | const express = require("express") 4 | const path = require("path") // node module 5 | const favicon = require("serve-favicon") 6 | const logger = require("morgan") 7 | 8 | const app = express() 9 | // development port: 3001 10 | // in production we'll a PORT number set in the environment variables 11 | // const PORT = process.env.PORT || 3001 12 | const PORT = 3001 13 | 14 | //* Config 15 | // Logger middleware 16 | app.use(logger("dev")) 17 | // JSON payload middleware (for data coming from frontend functions) 18 | app.use(express.json()) 19 | // Configure both serve-favicon & static middleware 20 | // to serve from the production 'build' folder 21 | app.use(favicon(path.join(__dirname, "build", "favicon.ico"))) 22 | app.use(express.static(path.join(__dirname, "build"))) 23 | // checks if token was sent and sets a user data on the req (req.user) 24 | app.use(require("./config/checkToken")) 25 | 26 | // * All other routes 27 | app.use("/api/users", require("./routes/api/users")) 28 | 29 | app.get("/api/*", (req, res) => { 30 | res.status(500).json("internal error") 31 | }) 32 | 33 | // Put API routes here, before the "catch all" route 34 | // The following "catch all" route (note the *) is necessary 35 | // to return the index.html on all non-AJAX requests 36 | app.get("/*", (req, res) => { 37 | res.sendFile(path.join(__dirname, "build", "index.html")) 38 | }) 39 | 40 | app.listen(PORT, () => { 41 | console.log(`Server is running on port: ${PORT}`) 42 | }) 43 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .body { 6 | background-color: white; 7 | } 8 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react" 2 | 3 | import { Routes, Route } from "react-router-dom" 4 | 5 | import AuthPage from "./pages/AuthPage" 6 | import EditPage from "./pages/EditPage" 7 | import NavBar from "./components/NavBar" 8 | import ProfilePage from "./pages/ProfilePage" 9 | import DeleteConfirmationPage from "./pages/DeleteConfirmationPage" 10 | 11 | import { getUser } from "./utilities/users-service" 12 | 13 | import "./App.css" 14 | import WeatherPage from "./pages/WeatherPage" 15 | 16 | function App() { 17 | const [user, setUser] = useState(getUser()) 18 | 19 | return ( 20 |
21 | {user ? ( 22 | <> 23 | 24 | 25 | } /> 26 | } /> 27 | } /> 28 | } 31 | /> 32 | 33 | 34 | ) : ( 35 | 36 | )} 37 |
38 | ) 39 | } 40 | 41 | export default App 42 | -------------------------------------------------------------------------------- /src/components/EditUserForm.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react" 2 | import { EditUser } from "../utilities/users-service" 3 | 4 | function EditUserForm({ user }) { 5 | console.log(user) 6 | const [formData, setFormData] = useState({ 7 | name: user.name, 8 | email: user.email, 9 | password: "", 10 | }) 11 | 12 | const handleSubmit = async (e) => { 13 | e.preventDefault() 14 | 15 | try { 16 | const userData = { 17 | name: formData.name, 18 | email: formData.email, 19 | password: formData.password, 20 | } 21 | // returns a token with the user info 22 | console.log(userData) 23 | await EditUser(userData) // user service 24 | } catch (error) { 25 | setFormData({ ...formData, error: "Sign Up Failed - Try Again" }) 26 | } 27 | } 28 | 29 | const handleChange = (evt) => { 30 | setFormData({ ...formData, [evt.target.name]: evt.target.value, error: "" }) 31 | } 32 | 33 | return ( 34 |
35 |
36 |
37 | 38 | 45 | 46 | 47 | 54 | 55 | 56 | 63 | 64 | 65 |
66 |
67 | 68 |

{formData.error}

69 |
70 | ) 71 | } 72 | 73 | export default EditUserForm 74 | -------------------------------------------------------------------------------- /src/components/LogInForm.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react" 2 | import { login } from "../utilities/users-service" 3 | 4 | function LoginForm({ setUser }) { 5 | const [credentials, setCredentials] = useState({ 6 | email: "", 7 | password: "", 8 | }) 9 | 10 | const [error, setError] = useState("") 11 | 12 | function handleChange(evt) { 13 | setCredentials({ ...credentials, [evt.target.name]: evt.target.value }) 14 | setError("") 15 | } 16 | 17 | async function handleSubmit(evt) { 18 | // Prevent form from being submitted to the server 19 | evt.preventDefault() 20 | try { 21 | // The promise returned by the signUp service method 22 | // will resolve to the user object included in the 23 | // payload of the JSON Web Token (JWT) 24 | const user = await login(credentials) 25 | console.log(user) 26 | setUser(user) 27 | } catch { 28 | setError("Log In Failed - Try Again") 29 | } 30 | } 31 | 32 | return ( 33 |
34 |
35 |
36 | 37 | 44 | 45 | 46 | 53 | 54 |
55 |
56 |

 {error}

57 |
58 | ) 59 | } 60 | 61 | export default LoginForm 62 | -------------------------------------------------------------------------------- /src/components/NavBar.js: -------------------------------------------------------------------------------- 1 | import { Link } from "react-router-dom" 2 | import { logOut } from "../utilities/users-service" 3 | import styles from "./NavBar.module.css" 4 | 5 | function NavBar({ user, setUser }) { 6 | const handleLogOut = () => { 7 | logOut() 8 | setUser(null) 9 | } 10 | return ( 11 |
12 |

Welcome, {user.name}

13 | 32 |
33 | ) 34 | } 35 | 36 | export default NavBar 37 | -------------------------------------------------------------------------------- /src/components/NavBar.module.css: -------------------------------------------------------------------------------- 1 | .ul { 2 | display: flex; 3 | justify-content: space-between; 4 | } 5 | 6 | .link { 7 | text-decoration: none; 8 | border-radius: 10px; 9 | font-size: 20px; 10 | background-color: #8fca5c; 11 | padding: 10px; 12 | } 13 | 14 | .welcomeMsg { 15 | position: relative; 16 | left: calc(50vw - 215px); 17 | background-color: #61371f; 18 | color: #8fca5c; 19 | border-radius: 20px; 20 | padding: 15px; 21 | width: 350px; 22 | } 23 | 24 | .link:hover { 25 | background-color: #61371f; 26 | color: #8fca5c; 27 | } 28 | -------------------------------------------------------------------------------- /src/components/Profile.js: -------------------------------------------------------------------------------- 1 | import styles from "./Profile.module.css" 2 | function Profile({ user }) { 3 | return ( 4 | 10 | ) 11 | } 12 | 13 | export default Profile 14 | -------------------------------------------------------------------------------- /src/components/Profile.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | position: relative; 3 | display: flex; 4 | flex-direction: column; 5 | width: 45vw; 6 | left: 25vw; 7 | } 8 | 9 | li { 10 | list-style: none; 11 | color: #8fca5c; 12 | background-color: #61371f; 13 | margin: 10px; 14 | border-radius: 15px; 15 | font-size: 20px; 16 | padding: 5px 0px 5px 0px; 17 | } 18 | 19 | .title { 20 | font-size: 24px; 21 | background-color: #61371f; 22 | color: #8fca5c; 23 | border-radius: 20px; 24 | padding: 15px; 25 | } 26 | -------------------------------------------------------------------------------- /src/components/SignUpForm.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react" 2 | import { signUp } from "../utilities/users-service" 3 | 4 | function SignUpForm({ setUser }) { 5 | const [formData, setFormData] = useState({ 6 | name: "", 7 | email: "", 8 | password: "", 9 | confirm: "", 10 | error: "", 11 | }) 12 | 13 | const disable = formData.password !== formData.confirm 14 | 15 | const handleSubmit = async (e) => { 16 | e.preventDefault() 17 | 18 | try { 19 | console.log(formData) 20 | // data to be send to the backend to create a new user 21 | const userData = { 22 | name: formData.name, 23 | email: formData.email, 24 | password: formData.password, 25 | } 26 | // returns a token with the user info 27 | const user = await signUp(userData) // user service 28 | setUser(user) 29 | } catch (error) { 30 | setFormData({ ...formData, error: "Sign Up Failed - Try Again" }) 31 | } 32 | } 33 | 34 | const handleChange = (evt) => { 35 | setFormData({ ...formData, [evt.target.name]: evt.target.value, error: "" }) 36 | } 37 | 38 | return ( 39 |
40 |
41 |
42 | 43 | 50 | 51 | 52 | 59 | 60 | 61 | 68 | 69 | 70 | 77 | 78 | 81 |
82 |
83 | 84 |

{formData.error}

85 |
86 | ) 87 | } 88 | 89 | export default SignUpForm 90 | -------------------------------------------------------------------------------- /src/components/WeatherDisplay.js: -------------------------------------------------------------------------------- 1 | import styles from "./WeatherDisplay.module.css" 2 | export default function WeatherDisplay({ weatherData }) { 3 | return weatherData ? ( 4 |
5 |

Whats the weather in:

6 |

Dallas, TX

7 |
Temp: {weatherData.current.temp}F
8 |
Feels Like: {weatherData.current.feels_like}F
9 |
10 | ) : ( 11 |
12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /src/components/WeatherDisplay.module.css: -------------------------------------------------------------------------------- 1 | .content { 2 | width: 175px; 3 | padding: 5px; 4 | border-radius: 10px; 5 | background-color: #854f2b; 6 | color: #70b237; 7 | border: solid 2px #61371f; 8 | position: relative; 9 | left: 43vw; 10 | margin-bottom: 50px; 11 | } 12 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | /* CSS Custom Properties */ 2 | :root { 3 | --white: #8fca5c; 4 | --tan-1: #8fca5c; 5 | --tan-2: #e7e2dd; 6 | --tan-3: #e2d9d1; 7 | --tan-4: #d3c1ae; 8 | --orange: #61371f; 9 | --text-light: #61371f; 10 | --text-dark: #854f2b; 11 | } 12 | 13 | *, 14 | *:before, 15 | *:after { 16 | box-sizing: border-box; 17 | } 18 | 19 | body { 20 | margin: 0; 21 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 22 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 23 | sans-serif; 24 | -webkit-font-smoothing: antialiased; 25 | -moz-osx-font-smoothing: grayscale; 26 | background-color: white; 27 | padding: 2vmin; 28 | height: 100vh; 29 | } 30 | 31 | code { 32 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 33 | monospace; 34 | } 35 | 36 | #root { 37 | height: 100%; 38 | } 39 | 40 | .align-ctr { 41 | text-align: center; 42 | } 43 | 44 | .align-rt { 45 | text-align: right; 46 | } 47 | 48 | .smaller { 49 | font-size: smaller; 50 | } 51 | 52 | .flex-ctr-ctr { 53 | display: flex; 54 | justify-content: center; 55 | align-items: center; 56 | } 57 | 58 | .flex-col { 59 | flex-direction: column; 60 | } 61 | 62 | .flex-j-end { 63 | justify-content: flex-end; 64 | } 65 | 66 | .scroll-y { 67 | overflow-y: scroll; 68 | } 69 | 70 | .section-heading { 71 | display: flex; 72 | justify-content: space-around; 73 | align-items: center; 74 | background-color: var(--tan-1); 75 | color: var(--text-dark); 76 | border: 0.1vmin solid var(--tan-3); 77 | border-radius: 1vmin; 78 | padding: 0.6vmin; 79 | text-align: center; 80 | font-size: 2vmin; 81 | } 82 | 83 | .form-container { 84 | padding: 3vmin; 85 | background-color: var(--tan-1); 86 | border: 0.1vmin solid var(--tan-3); 87 | border-radius: 1vmin; 88 | } 89 | 90 | p.error-message { 91 | color: var(--orange); 92 | text-align: center; 93 | } 94 | 95 | form { 96 | display: grid; 97 | grid-template-columns: 1fr 3fr; 98 | gap: 1.25vmin; 99 | color: var(--text-light); 100 | } 101 | 102 | label { 103 | font-size: 2vmin; 104 | display: flex; 105 | align-items: center; 106 | } 107 | 108 | input { 109 | padding: 1vmin; 110 | font-size: 2vmin; 111 | border: 0.1vmin solid var(--tan-3); 112 | border-radius: 0.5vmin; 113 | color: var(--text-dark); 114 | background-image: none !important; /* prevent lastpass */ 115 | outline: none; 116 | } 117 | 118 | input:focus { 119 | border-color: var(--orange); 120 | } 121 | 122 | button, 123 | a.button { 124 | margin: 1vmin; 125 | padding: 1vmin; 126 | color: var(--white); 127 | background-color: var(--orange); 128 | font-size: 2vmin; 129 | font-weight: bold; 130 | text-decoration: none; 131 | text-align: center; 132 | border: 0.1vmin solid var(--tan-2); 133 | border-radius: 0.5vmin; 134 | outline: none; 135 | cursor: pointer; 136 | } 137 | 138 | button.btn-sm { 139 | font-size: 1.5vmin; 140 | padding: 0.6vmin 0.8vmin; 141 | } 142 | 143 | button.btn-xs { 144 | font-size: 1vmin; 145 | padding: 0.4vmin 0.5vmin; 146 | } 147 | 148 | button:disabled, 149 | form:invalid button[type="submit"] { 150 | cursor: not-allowed; 151 | background-color: var(--tan-4); 152 | } 153 | 154 | button[type="submit"] { 155 | grid-column: span 2; 156 | margin: 1vmin 0 0; 157 | } 158 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import ReactDOM from "react-dom/client" 3 | import "./index.css" 4 | import App from "./App" 5 | import reportWebVitals from "./reportWebVitals" 6 | import { BrowserRouter as Router } from "react-router-dom" 7 | 8 | const root = ReactDOM.createRoot(document.getElementById("root")) 9 | root.render( 10 | 11 | 12 | 13 | 14 | 15 | ) 16 | 17 | // If you want to start measuring performance in your app, pass a function 18 | // to log results (for example: reportWebVitals(console.log)) 19 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 20 | reportWebVitals() 21 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/pages/AuthPage.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react" 2 | 3 | import SignUpForm from "../components/SignUpForm" 4 | import LoginForm from "../components/LogInForm" 5 | 6 | function AuthPage({ setUser }) { 7 | const [showLogin, setShowLogin] = useState(true) 8 | 9 | return ( 10 |
11 |

Authorization

12 | 13 | 16 | 17 | {showLogin ? ( 18 | 19 | ) : ( 20 | 21 | )} 22 |
23 | ) 24 | } 25 | 26 | export default AuthPage 27 | -------------------------------------------------------------------------------- /src/pages/DeleteConfirmationPage.js: -------------------------------------------------------------------------------- 1 | import { deleteUser } from "../utilities/users-service" 2 | import { logOut } from "../utilities/users-service" 3 | import { Link } from "react-router-dom" 4 | import styles from "./DeleteConfirmationPage.module.css" 5 | 6 | function DeleteConfirmationPage({ user, setUser }) { 7 | async function handleDelete(evt) { 8 | evt.preventDefault() 9 | await deleteUser() 10 | logOut() 11 | setUser(null) 12 | } 13 | return ( 14 |
15 |

16 | Are you sure you want to delete your account? 17 |

18 | 19 | Delete 20 | 21 |
22 | ) 23 | } 24 | 25 | export default DeleteConfirmationPage 26 | -------------------------------------------------------------------------------- /src/pages/DeleteConfirmationPage.module.css: -------------------------------------------------------------------------------- 1 | .title { 2 | color: #61371f; 3 | } 4 | 5 | .deletebutton { 6 | padding: 5px; 7 | border-radius: 10px; 8 | text-decoration: none; 9 | background-color: red; 10 | color: black; 11 | } 12 | 13 | .deletebutton:hover { 14 | text-decoration: underline; 15 | background-color: red; 16 | color: white; 17 | } 18 | -------------------------------------------------------------------------------- /src/pages/EditPage.js: -------------------------------------------------------------------------------- 1 | import EditUserForm from "../components/EditUserForm" 2 | import styles from "./EditPage.module.css" 3 | 4 | function EditPage({ user }) { 5 | return ( 6 |
7 |

Edit your user:

8 | 9 |
10 | ) 11 | } 12 | 13 | export default EditPage 14 | -------------------------------------------------------------------------------- /src/pages/EditPage.module.css: -------------------------------------------------------------------------------- 1 | .title { 2 | color: #61371f; 3 | } 4 | -------------------------------------------------------------------------------- /src/pages/ProfilePage.js: -------------------------------------------------------------------------------- 1 | import Profile from "../components/Profile" 2 | function ProfilePage({ user }) { 3 | return ( 4 |
5 | 6 |
7 | ) 8 | } 9 | 10 | export default ProfilePage 11 | -------------------------------------------------------------------------------- /src/pages/WeatherPage.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react" 2 | import WeatherDisplay from "../components/WeatherDisplay" 3 | import getWeatherData from "../utilities/weather-api" 4 | 5 | function WeatherPage() { 6 | const [weatherData, setWeatherData] = useState(null) 7 | 8 | useEffect(() => { 9 | const fetchData = async () => { 10 | const json = await getWeatherData() 11 | console.log("got data") 12 | console.log(json) 13 | setWeatherData(json) 14 | } 15 | 16 | fetchData() 17 | }, []) 18 | 19 | return ( 20 |
21 | 22 |
23 | ) 24 | } 25 | 26 | export default WeatherPage 27 | -------------------------------------------------------------------------------- /src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /src/utilities/users-api.js: -------------------------------------------------------------------------------- 1 | // * The users-service.js module will definitely need to make AJAX requests to the Express server. 2 | 3 | import { getToken } from "./users-service" 4 | 5 | //* SignUpForm.jsx <--> users-service.js <--> users-api.js <-Internet-> server.js (Express) 6 | 7 | //* handleSubmit <--> [signUp]-users-service <--> [signUp]-users-api <-Internet-> server.js (Express) 8 | 9 | const BASE_URL = "/api/users" 10 | 11 | //* SignUp 12 | export function signUp(userData) { 13 | return sendRequest(BASE_URL, "POST", userData) 14 | } 15 | 16 | export function deleteUser() { 17 | return sendRequest(BASE_URL, "DELETE") 18 | } 19 | 20 | export function EditUser(userData) { 21 | return sendRequest(`${BASE_URL}/edit`, "PUT", userData) 22 | } 23 | 24 | //* Login 25 | export function login(credentials) { 26 | return sendRequest(`${BASE_URL}/login`, "POST", credentials) 27 | } 28 | 29 | export function getAllUsers(userData) { 30 | return sendRequest(`${BASE_URL}/allUsers`, "GET", userData) 31 | } 32 | 33 | //* Check Token 34 | export function checkToken() { 35 | return sendRequest(`${BASE_URL}/check-token`) 36 | } 37 | 38 | /*--- Helper Functions ---*/ 39 | 40 | async function sendRequest(url, method = "GET", payload = null) { 41 | // Fetch accepts an options object as the 2nd argument 42 | // used to include a data payload, set headers, etc. 43 | const options = { method } 44 | if (payload) { 45 | options.headers = { "Content-Type": "application/json" } 46 | options.body = JSON.stringify(payload) 47 | } 48 | 49 | // sends token to backend 50 | const token = getToken() 51 | 52 | if (token) { 53 | options.headers = options.headers || {} 54 | options.headers.Authorization = `Bearer ${token}` 55 | } 56 | 57 | const res = await fetch(url, options) 58 | // res.ok will be false if the status code set to 4xx in the controller action 59 | if (res.ok) return res.json() 60 | throw new Error("Bad Request") 61 | } 62 | -------------------------------------------------------------------------------- /src/utilities/users-service.js: -------------------------------------------------------------------------------- 1 | import * as usersApi from "./users-api" 2 | 3 | //* Get Token 4 | export function getToken() { 5 | const token = localStorage.getItem("token") 6 | // if there is no token 7 | if (!token) return null 8 | 9 | const payload = JSON.parse(atob(token.split(".")[1])) 10 | console.log(payload) 11 | 12 | // if token is expired 13 | if (payload.exp < Date.now() / 1000) { 14 | localStorage.removeItem("token") 15 | return null 16 | } 17 | 18 | // token is valid 19 | return token 20 | } 21 | 22 | //* Get User 23 | export function getUser() { 24 | const token = getToken() 25 | return token ? JSON.parse(atob(token.split(".")[1])).user : null 26 | } 27 | 28 | //* SignUp 29 | export async function signUp(userData) { 30 | const token = await usersApi.signUp(userData) 31 | // saves token to localStorage 32 | localStorage.setItem("token", token) 33 | 34 | return getUser() 35 | } 36 | 37 | export async function EditUser(userData) { 38 | await usersApi.EditUser(userData) 39 | } 40 | 41 | export async function deleteUser() { 42 | await usersApi.deleteUser() 43 | } 44 | 45 | //* LogOut 46 | export function logOut() { 47 | localStorage.removeItem("token") 48 | } 49 | 50 | export async function login(credentials) { 51 | const token = await usersApi.login(credentials) 52 | localStorage.setItem("token", token) 53 | return getUser() 54 | } 55 | 56 | export async function checkToken() { 57 | return usersApi.checkToken().then((dateStr) => new Date(dateStr)) 58 | } 59 | -------------------------------------------------------------------------------- /src/utilities/weather-api.js: -------------------------------------------------------------------------------- 1 | async function getWeatherData() { 2 | const dallasLatitude = 32.78 3 | const dallasLongitude = -96.81 4 | const apiEndpoint = `https://api.openweathermap.org/data/3.0/onecall?lat=${dallasLatitude}&lon=${dallasLongitude}&units=imperial&exclude=hourly,alerts,minutely&appid=${process.env.REACT_APP_API_KEY}` 5 | const res = await fetch(apiEndpoint) 6 | if (res.ok) return res.json() 7 | throw new Error("Bad Request") 8 | } 9 | 10 | export default getWeatherData 11 | --------------------------------------------------------------------------------