├── .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 |
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 |
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 |
5 | - Hello, {user.name}
6 | - Username: {user.name}
7 | - Email: {user.email}
8 | - Hashed Password: {user.password}
9 |
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 |
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 |
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 |
--------------------------------------------------------------------------------