From e392fb835753bf40b35344c780cee85c67d83ab7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olga=20Lepist=C3=B6?= Date: Fri, 19 Jan 2024 22:08:28 +0200 Subject: [PATCH 01/15] new routes --- backend/config/db.js | 16 ++++++ backend/models/taskModel.js | 21 +++++++ backend/models/userModel.js | 33 +++++++++++ backend/package.json | 7 ++- backend/routes/taskRoutes.js | 64 +++++++++++++++++++++ backend/routes/userRoutes.js | 97 ++++++++++++++++++++++++++++++++ backend/server.js | 63 ++++++++++++++++++--- backend/tasks.json | 10 ++++ frontend/package.json | 5 +- frontend/src/App.jsx | 22 +++++++- frontend/src/pages/Home.jsx | 15 +++++ frontend/src/pages/LogIn.jsx | 21 +++++++ frontend/src/pages/LoggedIn.jsx | 0 frontend/src/pages/SignUp.jsx | 73 ++++++++++++++++++++++++ frontend/src/stores/userStore.js | 8 +++ netlify.toml | 12 +++- package.json | 8 +++ 17 files changed, 461 insertions(+), 14 deletions(-) create mode 100644 backend/config/db.js create mode 100644 backend/models/taskModel.js create mode 100644 backend/models/userModel.js create mode 100644 backend/routes/taskRoutes.js create mode 100644 backend/routes/userRoutes.js create mode 100644 backend/tasks.json create mode 100644 frontend/src/pages/Home.jsx create mode 100644 frontend/src/pages/LogIn.jsx create mode 100644 frontend/src/pages/LoggedIn.jsx create mode 100644 frontend/src/pages/SignUp.jsx create mode 100644 frontend/src/stores/userStore.js diff --git a/backend/config/db.js b/backend/config/db.js new file mode 100644 index 000000000..8eb5c28a8 --- /dev/null +++ b/backend/config/db.js @@ -0,0 +1,16 @@ +import mongoose from "mongoose"; +import dotenv from "dotenv"; +dotenv.config(); +import asyncHandler from "express-async-handler"; + +export const connectDB = asyncHandler(async () => { + try { + const conn = await mongoose.connect( + process.env.MONGO_URL + ) + console.log(`Mongo DB connected: ${conn.connection.host}`) + } catch (error) { + console.log(error); + process.exit(1); + } +}) \ No newline at end of file diff --git a/backend/models/taskModel.js b/backend/models/taskModel.js new file mode 100644 index 000000000..7dbccca99 --- /dev/null +++ b/backend/models/taskModel.js @@ -0,0 +1,21 @@ +import mongoose from "mongoose"; + +const { Schema } = mongoose; + +export const taskSchema = new Schema ({ + task: { + type: String, + required: true, + minLength: 5 + }, + done: { + type: Boolean, + default: false + }, + createdAt: { + type: Date, + default: Date.now + } +}); + +export const TaskModel = mongoose.model('tasks', taskSchema) \ No newline at end of file diff --git a/backend/models/userModel.js b/backend/models/userModel.js new file mode 100644 index 000000000..00e5fa099 --- /dev/null +++ b/backend/models/userModel.js @@ -0,0 +1,33 @@ +import mongoose from "mongoose"; +import crypto from "crypto"; + +const { Schema } = mongoose; + +export const userSchema = new Schema({ + username: { + type: String, + required: true, + minlength: 5, + unique: true + }, + email: { + type: String, + required: true, + unique: true + }, + password: { + type: String, + required: true, + minlength: 6 + }, + accessToken: { + type: String, + default: () => crypto.randomBytes(128).toString('hex') + } +}, + { + timestamps: true + } +) + +export const UserModel = mongoose.model('user', userSchema) \ No newline at end of file diff --git a/backend/package.json b/backend/package.json index 8de5c4ce0..65e671c19 100644 --- a/backend/package.json +++ b/backend/package.json @@ -12,9 +12,14 @@ "@babel/core": "^7.17.9", "@babel/node": "^7.16.8", "@babel/preset-env": "^7.16.11", + "bcrypt": "^5.1.1", + "bcrypt-nodejs": "^0.0.3", "cors": "^2.8.5", + "crypto": "^1.0.1", "express": "^4.17.3", - "mongoose": "^8.0.0", + "express-async-handler": "^1.2.0", + "jsonwebtoken": "^9.0.2", + "mongoose": "^8.0.4", "nodemon": "^3.0.1" } } diff --git a/backend/routes/taskRoutes.js b/backend/routes/taskRoutes.js new file mode 100644 index 000000000..dcd26d6aa --- /dev/null +++ b/backend/routes/taskRoutes.js @@ -0,0 +1,64 @@ +import express from "express" +import { TaskModel } from "../models/taskModel.js" +/* import tasks from "../tasks.json" */ + +const router = express.Router(); + +/* const seedDatabase = async () => { + await TaskModel.deleteMany({}) + + tasks.tasks.forEach((task) => { + new TaskModel(task).save() + }) +} + +seedDatabase() */ + +router.get("/get", async (req, res) => { + await TaskModel.find() + .then((result) => res.json(result)) + .catch((error) => res.json(error)) +}) + +router.post("/add", async (req, res) => { + const task = req.body.task; + await TaskModel.create({ task: task }) + .then((result) => res.json(result)) + .catch((error) => res.json(error)) +}) + +router.put("/update/:id", async (req, res) => { + const { id } = req.params; + await TaskModel.findByIdAndUpdate({ _id: id }, { done: true }) + .then((result) => res.json(result)) + .catch((error) => res.json(error)) +}) + +router.delete("/delete/:id", async (req, res) => { + const { id } = req.params; + await TaskModel.findByIdAndDelete(id) + .then((result) => { + if (result) { + res.json({ + message: "Task deleted successfully", + deleteTask: result + }) + } else { + res.status(404).json({ message: "Task not found" }) + } + }) + .catch((err) => res.status(500).json(err)) +}); + +router.delete("/deleteAll", async (req, res) => { + await TaskModel.deleteMany({}) + .then((result) => { + res.json({ + message: "All tasks deleted", + deletedCount: result.deletedCount + }) + }) + .catch((err) => res.status(500).json(err)) +}) + +export default router \ No newline at end of file diff --git a/backend/routes/userRoutes.js b/backend/routes/userRoutes.js new file mode 100644 index 000000000..4030f5c8d --- /dev/null +++ b/backend/routes/userRoutes.js @@ -0,0 +1,97 @@ +import express from "express"; +import { UserModel } from "../models/userModel.js" +import bcrypt, { genSaltSync } from "bcrypt"; +import jwt from "jsonwebtoken"; +import asyncHandler from "express-async-handler"; +import dotenv from "dotenv" +dotenv.config() + +const router = express.Router() + +const generateToken = (user) => { + return jwt.sign({ id: user._id }, process.env.JWT_SECRET, { + expiresIn: "24h" + }) +} + +router.post("/register", + asyncHandler(async (req, res) => { + const { username, password, email } = req.body; + + try { + if (!username || !password || !email) { + res.status(400) + throw new Error("Please add all fields") + } + + const existingUser = await UserModel.findOne({ + $or: [{ username }, { email }] + }) + if (existingUser) { + res.status(400) + throw new Error( + `User with ${existingUser.username === username ? "username" : "email"} + already exists.` + ) + } + + const salt = genSaltSync(10); + const hashedPassword = bcrypt.hashSync(password, salt) + + const newUser = new UserModel({ + username, + email, + password: hashedPassword + }) + + await newUser.save(); + + res.status(201).json({ + success: true, + response: { + username: newUser.username, + email: newUser.email, + id: newUser._id, + accessToken: generateToken(newUser._id) + } + }) + } catch (e) { + res.status(500).json({ success: false }, { response: e.message }) + } + }) +) + +router.post("/login", + asyncHandler(async (req, res) => { + const { username, password } = req.body; + + try { + const user = await UserModel.findOne({ username }) + if (!user) { + return res + .status(401) + .json({ success: false, response: "User not found" }) + } + + const isMatch = bcrypt.compare(password, user.password); + if (!isMatch) { + return res + .status(401) + .json({ success: false, response: "Incorrect password" }) + } + + res.status(200).json({ + success: true, + response: { + user: user.username, + id: user._id, + accessToken: generateToken(user._id) + } + }) + } catch (e) { + res.status(500).json({ success: false, response: e.message }) + } + }) +) + +export default router \ No newline at end of file diff --git a/backend/server.js b/backend/server.js index 2d7ae8aa1..ce30e0d04 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1,27 +1,74 @@ import express from "express"; import cors from "cors"; import mongoose from "mongoose"; - -const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/project-mongo"; -mongoose.connect(mongoUrl, { useNewUrlParser: true, useUnifiedTopology: true }); -mongoose.Promise = Promise; +import dotenv from "dotenv"; +dotenv.config(); +import listEndpoints from "express-list-endpoints"; +import taskRoutes from "./routes/taskRoutes.js" +import userRoutes from "./routes/userRoutes.js" +import { connectDB } from "./config/db.js" // Defines the port the app will run on. Defaults to 8080, but can be overridden // when starting the server. Example command to overwrite PORT env variable value: // PORT=9000 npm start -const port = process.env.PORT || 8080; +const port = process.env.PORT || 3000; const app = express(); // Add middlewares to enable cors and json body parsing app.use(cors()); app.use(express.json()); +app.use(express.urlencoded({ extended: false })); + +app.use(taskRoutes); +app.use(userRoutes); + +connectDB(); + +// This is now in the db.js file! +/* const mongoUrl = process.env.MONGO_URL || "mongodb://127.0.0.1:27017/tasks"; +mongoose.connect(mongoUrl, { useNewUrlParser: true, useUnifiedTopology: true }); +mongoose.Promise = Promise; */ + +mongoose.connection.on('connected', () => { + console.log('Connected to MongoDB'); +}); + +mongoose.connection.on('error', (err) => { + console.error('MongoDB connection error:', err); +}); + +/* const authenticateUser = async ( req, res, next ) => { + const user = await User.findOne({ accessToken: req.header('Authorization') }) + if (user) { + req.user = user; + next(); + } else { + res.status(401).json({ loggedOut: true }) + } +} */ -// Start defining your routes here +// List all endpoints app.get("/", (req, res) => { - res.send("Hello Technigo!"); + res.send(listEndpoints(app)); }); +/*app.post("/users", async (req, res) => { + try { + const{ name, email, password } = req.body; + const user = new User({ name, email, password: bcrypt.hashSync(password)}) + await user.save() + res.status(201).json({ id: user._id, accessToken: user.accessToken }) + } catch (err) { + res.status(400).json({ message: "Could not create user", errors: err.errors }) + } +}) + +app.get("/dogs", authenticateUser) +app.get("/dogs", async (req, res) => { + res.json({ secret: "Dogs are the best"}) +}) */ + // Start the server app.listen(port, () => { console.log(`Server running on http://localhost:${port}`); -}); +}); \ No newline at end of file diff --git a/backend/tasks.json b/backend/tasks.json new file mode 100644 index 000000000..abd04cef7 --- /dev/null +++ b/backend/tasks.json @@ -0,0 +1,10 @@ +{ + "tasks": [ + { + "task": "Drink water" + }, + { + "task": "Take a walk" + } + ] +} \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index e9c95b79f..b36447f2f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,8 +10,11 @@ "preview": "vite preview" }, "dependencies": { + "crypto": "^1.0.1", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "react-router": "^6.21.2", + "react-router-dom": "^6.21.2" }, "devDependencies": { "@types/react": "^18.2.15", diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 1091d4310..c44c1ca03 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,3 +1,23 @@ +/* import { BrowserRouter, Routes, Route } from "react-router-dom"; +import "./index.css"; +import { Home } from "./pages/Home.jsx" +import { SignUp } from "./pages/SignUp.jsx" +import { LogIn } from "./pages/LogIn.jsx" + export const App = () => { - return
Find me in src/app.jsx!
; + return ( + <> + +
+ + } /> + } /> + } /> + +
+
+ + ); }; + +export default App */ \ No newline at end of file diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx new file mode 100644 index 000000000..e395368f8 --- /dev/null +++ b/frontend/src/pages/Home.jsx @@ -0,0 +1,15 @@ +/* import "../index.css" +import { Link } from "react-router-dom" + +export const Home = () => { + return ( + <> +
+ + +
+ + ) +} + +export default Home */ \ No newline at end of file diff --git a/frontend/src/pages/LogIn.jsx b/frontend/src/pages/LogIn.jsx new file mode 100644 index 000000000..d55f19920 --- /dev/null +++ b/frontend/src/pages/LogIn.jsx @@ -0,0 +1,21 @@ +import "../index.css" +import { Link } from "react-router-dom" + +export const LogIn = () => { + return ( + <> +
+

Log In

+
+ + + + +
+ +
+ + ) +} + +export default LogIn \ No newline at end of file diff --git a/frontend/src/pages/LoggedIn.jsx b/frontend/src/pages/LoggedIn.jsx new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/src/pages/SignUp.jsx b/frontend/src/pages/SignUp.jsx new file mode 100644 index 000000000..af070b9f8 --- /dev/null +++ b/frontend/src/pages/SignUp.jsx @@ -0,0 +1,73 @@ +/* import "../index.css" +import { Link } from "react-router-dom" +import { useState, useEffect } from "react" + +export const SignUp = () => { + const [newUser, setNewUser] = useState('') + const [email, setEmail] = useState('') + const [username, setUsername] = useState('') + const [password, setPassword] = useState('') + + useEffect(() => { + const signUpData = { + email: email, + username: username, + password: password + }; + + if (email && username && password) { + fetch('http://localhost:8080/users', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(signUpData) + }) + .then((response) => { + if (!response.ok) { + throw new Error('Sign-up request failed'); + } + return response.json(); + }) + .then((data) => setNewUser(data.id)) + .catch((error) => console.error('Registration failed', error)); + } + }, [email, username, password]); // Run this effect whenever email, username, or password changes + + const handleSubmit = async (e) => { + e.preventDefault(); + } + + return ( + <> +
+

Sign Up

+
+ + setEmail(e.target.value)} /> + + setUsername(e.target.value)} /> + + setPassword(e.target.value)} /> + +
+ +
+ + ) + } + +export default SignUp */ \ No newline at end of file diff --git a/frontend/src/stores/userStore.js b/frontend/src/stores/userStore.js new file mode 100644 index 000000000..ea5e325c4 --- /dev/null +++ b/frontend/src/stores/userStore.js @@ -0,0 +1,8 @@ +/* import create from 'zustand'; + +const userStore = create((set) => ({ + newUser: null, + setNewUser: (data) => set({ newUser: data }), +})); + +export default userStore; */ \ No newline at end of file diff --git a/netlify.toml b/netlify.toml index 95443a1f3..360bf460e 100644 --- a/netlify.toml +++ b/netlify.toml @@ -1,6 +1,12 @@ # This file tells netlify where the code for this project is and # how it should build the JavaScript assets to deploy from. -[build] - base = "frontend/" - publish = "build/" + +[ build ] + base = "frontend" + publish = "dist" command = "npm run build" + +[[redirects]] + from = "/*" + to = "/index.html" + status = 200 diff --git a/package.json b/package.json index d774b8cc3..ffd42c7ee 100644 --- a/package.json +++ b/package.json @@ -3,5 +3,13 @@ "version": "1.0.0", "scripts": { "postinstall": "npm install --prefix backend" + }, + "dependencies": { + "dotenv": "^16.3.1", + "express-list-endpoints": "^6.0.0", + "mongo": "^0.1.0", + "mongodb": "^6.3.0", + "mongoose": "^8.1.0", + "zustand": "^4.4.7" } } From 7a9182edea085d741439a7a62209c2479ba33516 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olga=20Lepist=C3=B6?= Date: Fri, 19 Jan 2024 22:28:01 +0200 Subject: [PATCH 02/15] dotenv installed --- backend/package.json | 1 + package.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/package.json b/backend/package.json index 65e671c19..2c25d4cfd 100644 --- a/backend/package.json +++ b/backend/package.json @@ -16,6 +16,7 @@ "bcrypt-nodejs": "^0.0.3", "cors": "^2.8.5", "crypto": "^1.0.1", + "dotenv": "^16.3.2", "express": "^4.17.3", "express-async-handler": "^1.2.0", "jsonwebtoken": "^9.0.2", diff --git a/package.json b/package.json index ffd42c7ee..9027f8fc1 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "postinstall": "npm install --prefix backend" }, "dependencies": { - "dotenv": "^16.3.1", + "dotenv": "^16.3.2", "express-list-endpoints": "^6.0.0", "mongo": "^0.1.0", "mongodb": "^6.3.0", From ec2443ae0dc2cc9926e2b3f5feb14644b2d033f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olga=20Lepist=C3=B6?= Date: Fri, 19 Jan 2024 22:34:08 +0200 Subject: [PATCH 03/15] installed express-list-endpoints --- backend/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/package.json b/backend/package.json index 2c25d4cfd..2af8da0e1 100644 --- a/backend/package.json +++ b/backend/package.json @@ -19,6 +19,7 @@ "dotenv": "^16.3.2", "express": "^4.17.3", "express-async-handler": "^1.2.0", + "express-list-endpoints": "^6.0.0", "jsonwebtoken": "^9.0.2", "mongoose": "^8.0.4", "nodemon": "^3.0.1" From 3c1af9343bb26ec84e26beea4b55492a7bf0df70 Mon Sep 17 00:00:00 2001 From: Elin Olausson <140396742+Soygirt@users.noreply.github.com> Date: Fri, 19 Jan 2024 23:45:11 +0100 Subject: [PATCH 04/15] styling added --- backend/config/db.js | 2 +- backend/server.js | 2 +- frontend/src/App.jsx | 4 +-- frontend/src/index.css | 67 ++++++++++++++++++++++++++++++++++- frontend/src/pages/Home.jsx | 5 +-- frontend/src/pages/SignUp.jsx | 4 +-- 6 files changed, 75 insertions(+), 9 deletions(-) diff --git a/backend/config/db.js b/backend/config/db.js index 8eb5c28a8..3e9afb127 100644 --- a/backend/config/db.js +++ b/backend/config/db.js @@ -12,5 +12,5 @@ export const connectDB = asyncHandler(async () => { } catch (error) { console.log(error); process.exit(1); - } + } }) \ No newline at end of file diff --git a/backend/server.js b/backend/server.js index ce30e0d04..a748eb787 100644 --- a/backend/server.js +++ b/backend/server.js @@ -71,4 +71,4 @@ app.get("/dogs", async (req, res) => { // Start the server app.listen(port, () => { console.log(`Server running on http://localhost:${port}`); -}); \ No newline at end of file +}); \ No newline at end of file diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index c44c1ca03..27a791b68 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,4 +1,4 @@ -/* import { BrowserRouter, Routes, Route } from "react-router-dom"; +import { BrowserRouter, Routes, Route } from "react-router-dom"; import "./index.css"; import { Home } from "./pages/Home.jsx" import { SignUp } from "./pages/SignUp.jsx" @@ -20,4 +20,4 @@ export const App = () => { ); }; -export default App */ \ No newline at end of file +export default App \ No newline at end of file diff --git a/frontend/src/index.css b/frontend/src/index.css index 3e560a674..abf57c4b0 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -10,4 +10,69 @@ code { font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; -} \ No newline at end of file +} + +html { + background-color:rgb(178, 192, 183); +} + +.homePage { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100vh; /* Adjust this value based on your layout */ +} + +.naviButton { + margin: 20px; /* Adjust margin as needed */ +} + +h1 { + color:rgb(70, 116, 117); + text-align: center; + font-size: 50px; + text-shadow: 2px 2px 4px #35353580; +} + +button { + background-color: cadetblue; + border: solid; + border-color: azure; + border-radius: 23px; + margin: 40px; + padding: 10px; + font-size: 15px; + width: 140px; + +} + +.logIn { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; +} + +h3 { +color: rgb(70, 116, 117); +text-align: center; +font-size: 30px; +} + +label { + color:rgb(39, 64, 65); + font-size: 20px; + padding: 10px; +} + +input { + padding: 2px; + +} + +form { + display: flex; + flex-direction: column; + align-items: center; +} diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx index e395368f8..ce4e97ebc 100644 --- a/frontend/src/pages/Home.jsx +++ b/frontend/src/pages/Home.jsx @@ -1,10 +1,11 @@ -/* import "../index.css" +import "../index.css" import { Link } from "react-router-dom" export const Home = () => { return ( <>
+

Task planner

@@ -12,4 +13,4 @@ export const Home = () => { ) } -export default Home */ \ No newline at end of file +export default Home \ No newline at end of file diff --git a/frontend/src/pages/SignUp.jsx b/frontend/src/pages/SignUp.jsx index af070b9f8..03a022d3c 100644 --- a/frontend/src/pages/SignUp.jsx +++ b/frontend/src/pages/SignUp.jsx @@ -1,4 +1,4 @@ -/* import "../index.css" +import "../index.css" import { Link } from "react-router-dom" import { useState, useEffect } from "react" @@ -70,4 +70,4 @@ export const SignUp = () => { ) } -export default SignUp */ \ No newline at end of file +export default SignUp \ No newline at end of file From 8a579d298986b4559d37ea3206c715cee51b0f25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olga=20Lepist=C3=B6?= Date: Thu, 25 Jan 2024 02:11:27 +0200 Subject: [PATCH 05/15] added routes --- backend/config/db.js | 2 +- backend/middleware/authenticateUser.js | 22 +++++++ backend/models/quoteModel.js | 11 ++++ backend/models/taskModel.js | 21 ------ backend/routes/quoteRoutes.js | 54 +++++++++++++++ backend/routes/taskRoutes.js | 64 ------------------ backend/routes/userRoutes.js | 13 +--- backend/server.js | 45 +------------ backend/tasks.json | 10 --- frontend/src/App.jsx | 10 +-- frontend/src/index.css | 16 ++++- frontend/src/pages/Home.jsx | 2 +- frontend/src/pages/LogIn.jsx | 40 ++++++++--- frontend/src/pages/LoggedIn.jsx | 16 +++++ frontend/src/pages/SignUp.jsx | 2 + frontend/src/routes/routes.jsx | 15 +++++ frontend/src/stores/quoteStore.jsx | 31 +++++++++ frontend/src/stores/userStore.js | 8 --- frontend/src/stores/userStore.jsx | 91 ++++++++++++++++++++++++++ 19 files changed, 297 insertions(+), 176 deletions(-) create mode 100644 backend/middleware/authenticateUser.js create mode 100644 backend/models/quoteModel.js delete mode 100644 backend/models/taskModel.js create mode 100644 backend/routes/quoteRoutes.js delete mode 100644 backend/routes/taskRoutes.js delete mode 100644 backend/tasks.json create mode 100644 frontend/src/routes/routes.jsx create mode 100644 frontend/src/stores/quoteStore.jsx delete mode 100644 frontend/src/stores/userStore.js create mode 100644 frontend/src/stores/userStore.jsx diff --git a/backend/config/db.js b/backend/config/db.js index 3e9afb127..8eb5c28a8 100644 --- a/backend/config/db.js +++ b/backend/config/db.js @@ -12,5 +12,5 @@ export const connectDB = asyncHandler(async () => { } catch (error) { console.log(error); process.exit(1); - } + } }) \ No newline at end of file diff --git a/backend/middleware/authenticateUser.js b/backend/middleware/authenticateUser.js new file mode 100644 index 000000000..c3d6c4686 --- /dev/null +++ b/backend/middleware/authenticateUser.js @@ -0,0 +1,22 @@ +import { UserModel } from "../models/userModel"; + +export const authenticateUser = async (req, res, next) => { + + // Retrieve the access token from the request header + const accessToken = req.header("Authorization"); + + try { + // Find a user in the database using the retrieved access token + const user = await UserModel.findOne({ accessToken: accessToken }); + if (user) { + // If a user is found, add the user object to the request object + req.user = user; // Add user to the request object + next(); + } else { + res.status(401).json({ success: false, response: "Please log in" }); + } + } catch (e) { + // Handle any errors that occur during the database query or user authentication + res.status(500).json({ success: false, response: e.message }); + } +}; \ No newline at end of file diff --git a/backend/models/quoteModel.js b/backend/models/quoteModel.js new file mode 100644 index 000000000..724a9d221 --- /dev/null +++ b/backend/models/quoteModel.js @@ -0,0 +1,11 @@ +import mongoose from "mongoose"; + +const { Schema } = mongoose; + +export const quoteSchema = new Schema({ + quote: { + type: String + } +}); + +export const QuoteModel = mongoose.model('quote', quoteSchema) \ No newline at end of file diff --git a/backend/models/taskModel.js b/backend/models/taskModel.js deleted file mode 100644 index 7dbccca99..000000000 --- a/backend/models/taskModel.js +++ /dev/null @@ -1,21 +0,0 @@ -import mongoose from "mongoose"; - -const { Schema } = mongoose; - -export const taskSchema = new Schema ({ - task: { - type: String, - required: true, - minLength: 5 - }, - done: { - type: Boolean, - default: false - }, - createdAt: { - type: Date, - default: Date.now - } -}); - -export const TaskModel = mongoose.model('tasks', taskSchema) \ No newline at end of file diff --git a/backend/routes/quoteRoutes.js b/backend/routes/quoteRoutes.js new file mode 100644 index 000000000..ce6acb9ef --- /dev/null +++ b/backend/routes/quoteRoutes.js @@ -0,0 +1,54 @@ +import express from "express" +import { QuoteModel } from "../models/quoteModel.js"; +import { authenticateUser } from "../middleware/authenticateUser.js"; +import asyncHandler from "express-async-handler" + +const router = express.Router(); + +router.get( + "/getQuote", + authenticateUser, + asyncHandler(async (req, res) => { + try { + const quotes = await QuoteModel.find(); + if (quotes.length > 0) { + const randomIndex = Math.floor(Math.random() * quotes.length); + const randomQuote = quotes[randomIndex]; + res.json(randomQuote); + } else { + res.status(404).json({ error: "No quotes found" }); + } + } catch (error) { + res.status(500).json({ success: false, response: error.message }); + } + }) +); + + +// Get all the quotes in the database +router.get( + "/allQuotes", async (req, res) => { + try { + const quotes = await QuoteModel.find(); + res.json(quotes); + } catch (err) { + res.status(400).json({ error: err.message }); + } + } +); + +router.post( + "/addQuote", async (req, res) => { + try { + const { quote } = req.body.quote; + const newQuote = new QuoteModel({ quote }) + + await newQuote.save() + res.json(newQuote) + } catch (err) { + res.status(404).json({ success: false, message: "Could not add quote" }) + } + } +) + +export default router \ No newline at end of file diff --git a/backend/routes/taskRoutes.js b/backend/routes/taskRoutes.js deleted file mode 100644 index dcd26d6aa..000000000 --- a/backend/routes/taskRoutes.js +++ /dev/null @@ -1,64 +0,0 @@ -import express from "express" -import { TaskModel } from "../models/taskModel.js" -/* import tasks from "../tasks.json" */ - -const router = express.Router(); - -/* const seedDatabase = async () => { - await TaskModel.deleteMany({}) - - tasks.tasks.forEach((task) => { - new TaskModel(task).save() - }) -} - -seedDatabase() */ - -router.get("/get", async (req, res) => { - await TaskModel.find() - .then((result) => res.json(result)) - .catch((error) => res.json(error)) -}) - -router.post("/add", async (req, res) => { - const task = req.body.task; - await TaskModel.create({ task: task }) - .then((result) => res.json(result)) - .catch((error) => res.json(error)) -}) - -router.put("/update/:id", async (req, res) => { - const { id } = req.params; - await TaskModel.findByIdAndUpdate({ _id: id }, { done: true }) - .then((result) => res.json(result)) - .catch((error) => res.json(error)) -}) - -router.delete("/delete/:id", async (req, res) => { - const { id } = req.params; - await TaskModel.findByIdAndDelete(id) - .then((result) => { - if (result) { - res.json({ - message: "Task deleted successfully", - deleteTask: result - }) - } else { - res.status(404).json({ message: "Task not found" }) - } - }) - .catch((err) => res.status(500).json(err)) -}); - -router.delete("/deleteAll", async (req, res) => { - await TaskModel.deleteMany({}) - .then((result) => { - res.json({ - message: "All tasks deleted", - deletedCount: result.deletedCount - }) - }) - .catch((err) => res.status(500).json(err)) -}) - -export default router \ No newline at end of file diff --git a/backend/routes/userRoutes.js b/backend/routes/userRoutes.js index 4030f5c8d..d95358bcc 100644 --- a/backend/routes/userRoutes.js +++ b/backend/routes/userRoutes.js @@ -1,19 +1,12 @@ import express from "express"; import { UserModel } from "../models/userModel.js" import bcrypt, { genSaltSync } from "bcrypt"; -import jwt from "jsonwebtoken"; import asyncHandler from "express-async-handler"; import dotenv from "dotenv" dotenv.config() const router = express.Router() -const generateToken = (user) => { - return jwt.sign({ id: user._id }, process.env.JWT_SECRET, { - expiresIn: "24h" - }) -} - router.post("/register", asyncHandler(async (req, res) => { const { username, password, email } = req.body; @@ -52,11 +45,11 @@ router.post("/register", username: newUser.username, email: newUser.email, id: newUser._id, - accessToken: generateToken(newUser._id) + accessToken: newUser.accessToken } }) } catch (e) { - res.status(500).json({ success: false }, { response: e.message }) + res.status(500).json({ success: false, response: e.message }) } }) ) @@ -85,7 +78,7 @@ router.post("/login", response: { user: user.username, id: user._id, - accessToken: generateToken(user._id) + accessToken: user.accessToken } }) } catch (e) { diff --git a/backend/server.js b/backend/server.js index a748eb787..bb5baec5e 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1,11 +1,10 @@ import express from "express"; import cors from "cors"; -import mongoose from "mongoose"; import dotenv from "dotenv"; dotenv.config(); import listEndpoints from "express-list-endpoints"; -import taskRoutes from "./routes/taskRoutes.js" import userRoutes from "./routes/userRoutes.js" +import quoteRoutes from "./routes/quoteroutes.js" import { connectDB } from "./config/db.js" // Defines the port the app will run on. Defaults to 8080, but can be overridden @@ -19,55 +18,17 @@ app.use(cors()); app.use(express.json()); app.use(express.urlencoded({ extended: false })); -app.use(taskRoutes); app.use(userRoutes); +app.use(quoteRoutes); +// Function to connect to MongoDB connectDB(); -// This is now in the db.js file! -/* const mongoUrl = process.env.MONGO_URL || "mongodb://127.0.0.1:27017/tasks"; -mongoose.connect(mongoUrl, { useNewUrlParser: true, useUnifiedTopology: true }); -mongoose.Promise = Promise; */ - -mongoose.connection.on('connected', () => { - console.log('Connected to MongoDB'); -}); - -mongoose.connection.on('error', (err) => { - console.error('MongoDB connection error:', err); -}); - -/* const authenticateUser = async ( req, res, next ) => { - const user = await User.findOne({ accessToken: req.header('Authorization') }) - if (user) { - req.user = user; - next(); - } else { - res.status(401).json({ loggedOut: true }) - } -} */ - // List all endpoints app.get("/", (req, res) => { res.send(listEndpoints(app)); }); -/*app.post("/users", async (req, res) => { - try { - const{ name, email, password } = req.body; - const user = new User({ name, email, password: bcrypt.hashSync(password)}) - await user.save() - res.status(201).json({ id: user._id, accessToken: user.accessToken }) - } catch (err) { - res.status(400).json({ message: "Could not create user", errors: err.errors }) - } -}) - -app.get("/dogs", authenticateUser) -app.get("/dogs", async (req, res) => { - res.json({ secret: "Dogs are the best"}) -}) */ - // Start the server app.listen(port, () => { console.log(`Server running on http://localhost:${port}`); diff --git a/backend/tasks.json b/backend/tasks.json deleted file mode 100644 index abd04cef7..000000000 --- a/backend/tasks.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "tasks": [ - { - "task": "Drink water" - }, - { - "task": "Take a walk" - } - ] -} \ No newline at end of file diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 27a791b68..bc0424bb8 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,8 +1,6 @@ -import { BrowserRouter, Routes, Route } from "react-router-dom"; +import { BrowserRouter, Routes } from "react-router-dom"; import "./index.css"; -import { Home } from "./pages/Home.jsx" -import { SignUp } from "./pages/SignUp.jsx" -import { LogIn } from "./pages/LogIn.jsx" +import { routes } from "./routes/routes.jsx" export const App = () => { return ( @@ -10,9 +8,7 @@ export const App = () => {
- } /> - } /> - } /> + {routes}
diff --git a/frontend/src/index.css b/frontend/src/index.css index abf57c4b0..08b4db8b0 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -16,7 +16,7 @@ html { background-color:rgb(178, 192, 183); } -.homePage { +.homePage, .logIn, .signUp { display: flex; flex-direction: column; align-items: center; @@ -44,7 +44,17 @@ button { padding: 10px; font-size: 15px; width: 140px; - +} + +button:hover { + color: cadetblue; + background-color: azure; + cursor: pointer; +} + +.buttonContainer { + display: flex; + justify-content: center; } .logIn { @@ -68,6 +78,8 @@ label { input { padding: 2px; + height: 2em; + border-radius: 10px; } diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx index ce4e97ebc..c14fa9f5c 100644 --- a/frontend/src/pages/Home.jsx +++ b/frontend/src/pages/Home.jsx @@ -5,7 +5,7 @@ export const Home = () => { return ( <>
-

Task planner

+

Something about coding...

diff --git a/frontend/src/pages/LogIn.jsx b/frontend/src/pages/LogIn.jsx index d55f19920..547ee72df 100644 --- a/frontend/src/pages/LogIn.jsx +++ b/frontend/src/pages/LogIn.jsx @@ -1,19 +1,39 @@ import "../index.css" import { Link } from "react-router-dom" +import { userStore } from '../stores/userStore.jsx' +import { useNavigate } from 'react-router-dom'; export const LogIn = () => { + const { handleLogin } = userStore(); + const navigate = useNavigate(); + + const handleLoginFormSubmit = async (e) => { + e.preventDefault(); + const username = e.target.username.value; + const password = e.target.password.value; + + try { + await handleLogin(username, password); + navigate('/loggedIn'); + } catch (error) { + console.error('Login error:', error); + } + + }; + return ( <> -
-

Log In

-
- - - - -
- -
+
+

Log In

+
+ + + + + +
+ +
) } diff --git a/frontend/src/pages/LoggedIn.jsx b/frontend/src/pages/LoggedIn.jsx index e69de29bb..34bd711a5 100644 --- a/frontend/src/pages/LoggedIn.jsx +++ b/frontend/src/pages/LoggedIn.jsx @@ -0,0 +1,16 @@ +import "./index.css" + +export const LoggedIn = () => { + return ( + <> +
+

Welcome!

+

Here's a little thought for today:

+

"Coding is the art of turning imagination into reality, + where every line of code is a stroke of creativity that brings + a digital masterpiece to life."

+

-ChatGPT

+
+ + ) +} \ No newline at end of file diff --git a/frontend/src/pages/SignUp.jsx b/frontend/src/pages/SignUp.jsx index 03a022d3c..895493745 100644 --- a/frontend/src/pages/SignUp.jsx +++ b/frontend/src/pages/SignUp.jsx @@ -64,7 +64,9 @@ export const SignUp = () => { onChange={(e) => setPassword(e.target.value)} /> +
+
) diff --git a/frontend/src/routes/routes.jsx b/frontend/src/routes/routes.jsx new file mode 100644 index 000000000..cf39a64a7 --- /dev/null +++ b/frontend/src/routes/routes.jsx @@ -0,0 +1,15 @@ +import { Route } from "react-router-dom" +import { Home } from "../pages/Home.jsx" +import { SignUp } from "../pages/SignUp.jsx" +import { LogIn } from "../pages/LogIn.jsx" + +const routes = ( + <> + } /> + } /> + } /> + } /> + +) + +export default routes \ No newline at end of file diff --git a/frontend/src/stores/quoteStore.jsx b/frontend/src/stores/quoteStore.jsx new file mode 100644 index 000000000..166420097 --- /dev/null +++ b/frontend/src/stores/quoteStore.jsx @@ -0,0 +1,31 @@ +import { create } from "zustand"; +import { userStore } from "./userStore"; + +/* const apiEnv = import.meta.env.VITE_BACKEND_API; +console.log(apiEnv); */ + +export const quoteStore = create((set) => ({ + quotes: [], + addQuote: (newQuote) => set((state) => ({ quotes: [...state.quotes, newQuote] })), + setQuotes: (qus) => set({ dogs }), + + // Fetch the dogs a specific user has added in their profile + fetchDogs: async () => { + try { + const response = await fetch('https://rescue-helper.onrender.com/yourDogs', { + method: "GET", + headers: { + Authorization: localStorage.getItem("accessToken"), + }, + }); + if (response.ok) { + const data = await response.json(); + set({ dogs: data }); + } else { + console.error("Failed to fetch your dogs"); + } + } catch (error) { + console.error(error); + } + } +})) \ No newline at end of file diff --git a/frontend/src/stores/userStore.js b/frontend/src/stores/userStore.js deleted file mode 100644 index ea5e325c4..000000000 --- a/frontend/src/stores/userStore.js +++ /dev/null @@ -1,8 +0,0 @@ -/* import create from 'zustand'; - -const userStore = create((set) => ({ - newUser: null, - setNewUser: (data) => set({ newUser: data }), -})); - -export default userStore; */ \ No newline at end of file diff --git a/frontend/src/stores/userStore.jsx b/frontend/src/stores/userStore.jsx new file mode 100644 index 000000000..ba908a908 --- /dev/null +++ b/frontend/src/stores/userStore.jsx @@ -0,0 +1,91 @@ +import { create } from "zustand"; + +const apiEnv = import.meta.env.VITE_BACKEND_API; + +export const userStore = create((set, get) => ({ + username: "", + setUsername: (username) => set({ username }), + email: "", + setEmail: (email) => set({ email }), + password: "", + setPassword: (password) => set({ password }), + accessToken: null, // Add this if you plan to store the access token + setAccessToken: (token) => set({ accessToken: token }), + isLoggedIn: false, // Added to track if the user is logged in + setIsLoggedIn: (isLoggedIn) => set({ isLoggedIn }), + + // FUNCTION TO REGISTER USERS + handleSignup: async (username, password, email) => { + if (!username || !password || !email) { + alert("Please enter username, email and password"); + return; + } + + try { + const response = await fetch(`${apiEnv}/register`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ email, username, password }), + }); + + const data = await response.json(); + if (data.success) { + set({ username }); + // Redirect or update UI + alert("Signup successful!"); + console.log("Signing up with:", username); + } else { + // Display error message from server + alert(data.response || "Signup failed"); + } + } catch (error) { + console.error("Signup error:", error); + alert("An error occurred during signup"); + } + }, + + // LOGIN + handleLogin: async (username, password) => { + if (!username || !password) { + alert("Please enter both username and password"); + return; + } + + try { + const response = await fetch(`${apiEnv}/login`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ username, password }), + }); + + const data = await response.json(); + if (data.success) { + set({ + username, + accessToken: data.response.accessToken, + isLoggedIn: true, + }); // Update the state with username and accessToken + // Redirect or update UI + localStorage.setItem("accessToken", data.response.accessToken); + alert("Login successful!"); + console.log("Loging up with:", username, password); + } else { + // Display error message from server + alert(data.response || "Login failed"); + } + } catch (error) { + console.error("Login error:", error); + alert("An error occurred during login"); + } + }, + handleLogout: () => { + // Clear user information and set isLoggedIn to false + set({ username: "", accessToken: null, isLoggedIn: false }); + localStorage.removeItem("accessToken"); + // Additional logout logic if needed + }, +})); \ No newline at end of file From 1b8820c143a96489d40166b41c8b11e9d83a4470 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olga=20Lepist=C3=B6?= Date: Thu, 25 Jan 2024 12:48:08 +0200 Subject: [PATCH 06/15] backend and frontend connected --- backend/middleware/authenticateUser.js | 2 +- backend/routes/quoteRoutes.js | 4 +- backend/routes/userRoutes.js | 14 +-- frontend/index.html | 2 +- frontend/src/index.css | 32 +++++- frontend/src/pages/Home.jsx | 12 ++- frontend/src/pages/LogIn.jsx | 22 ++-- frontend/src/pages/LoggedIn.jsx | 42 ++++++-- frontend/src/pages/SignUp.jsx | 139 ++++++++++++++----------- frontend/src/routes/routes.jsx | 3 +- frontend/src/stores/quoteStore.jsx | 14 +-- frontend/src/stores/userStore.jsx | 37 ++++--- 12 files changed, 206 insertions(+), 117 deletions(-) diff --git a/backend/middleware/authenticateUser.js b/backend/middleware/authenticateUser.js index c3d6c4686..d07e118fe 100644 --- a/backend/middleware/authenticateUser.js +++ b/backend/middleware/authenticateUser.js @@ -1,4 +1,4 @@ -import { UserModel } from "../models/userModel"; +import { UserModel } from "../models/userModel.js"; export const authenticateUser = async (req, res, next) => { diff --git a/backend/routes/quoteRoutes.js b/backend/routes/quoteRoutes.js index ce6acb9ef..2fba20dc1 100644 --- a/backend/routes/quoteRoutes.js +++ b/backend/routes/quoteRoutes.js @@ -29,7 +29,7 @@ router.get( router.get( "/allQuotes", async (req, res) => { try { - const quotes = await QuoteModel.find(); + const quotes = await QuoteModel.find().select('quote'); res.json(quotes); } catch (err) { res.status(400).json({ error: err.message }); @@ -40,7 +40,7 @@ router.get( router.post( "/addQuote", async (req, res) => { try { - const { quote } = req.body.quote; + const { quote } = req.body; const newQuote = new QuoteModel({ quote }) await newQuote.save() diff --git a/backend/routes/userRoutes.js b/backend/routes/userRoutes.js index d95358bcc..48606b44b 100644 --- a/backend/routes/userRoutes.js +++ b/backend/routes/userRoutes.js @@ -9,21 +9,21 @@ const router = express.Router() router.post("/register", asyncHandler(async (req, res) => { - const { username, password, email } = req.body; + const { email, username, password } = req.body; try { - if (!username || !password || !email) { + if (!email || !username || !password) { res.status(400) throw new Error("Please add all fields") } const existingUser = await UserModel.findOne({ - $or: [{ username }, { email }] + $or: [{ email }, { username }] }) if (existingUser) { res.status(400) throw new Error( - `User with ${existingUser.username === username ? "username" : "email"} + `User with ${existingUser.username === username ? "email" : "username"} already exists.` ) } @@ -32,8 +32,8 @@ router.post("/register", const hashedPassword = bcrypt.hashSync(password, salt) const newUser = new UserModel({ - username, email, + username, password: hashedPassword }) @@ -42,8 +42,8 @@ router.post("/register", res.status(201).json({ success: true, response: { - username: newUser.username, email: newUser.email, + username: newUser.username, id: newUser._id, accessToken: newUser.accessToken } @@ -66,7 +66,7 @@ router.post("/login", .json({ success: false, response: "User not found" }) } - const isMatch = bcrypt.compare(password, user.password); + const isMatch = await bcrypt.compare(password, user.password); if (!isMatch) { return res .status(401) diff --git a/frontend/index.html b/frontend/index.html index 0c589eccd..21b0e0fdc 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -4,7 +4,7 @@ - Vite + React + Something about coding...
diff --git a/frontend/src/index.css b/frontend/src/index.css index 08b4db8b0..6d2eb8b93 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -16,16 +16,22 @@ html { background-color:rgb(178, 192, 183); } -.homePage, .logIn, .signUp { +.mainContainer { + display: grid; + grid-template-columns: repeat(4, 1fr); +} + +.homePage, .logIn, .signUp, .loggedIn { + grid-column: 2 / 4; display: flex; flex-direction: column; align-items: center; justify-content: center; - height: 100vh; /* Adjust this value based on your layout */ + /* height: 100vh; */ } .naviButton { - margin: 20px; /* Adjust margin as needed */ + margin: 20px; } h1 { @@ -88,3 +94,23 @@ form { flex-direction: column; align-items: center; } + +@media screen and (max-width: 667px) { + .mainContainer { + grid-template-columns: 1fr; + } + + .homePage, .logIn, .signUp, .loggedIn { + grid-column: 1 / 2; + } +} + +@media screen and (min-width: 668px) and (max-width: 1024px) { + .mainContainer { + grid-template-columns: repeat(2, 1fr); + } + + .homePage, .logIn, .signUp, .loggedIn { + grid-column: 1 / 3; + } +} \ No newline at end of file diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx index c14fa9f5c..cb9f5bead 100644 --- a/frontend/src/pages/Home.jsx +++ b/frontend/src/pages/Home.jsx @@ -4,11 +4,13 @@ import { Link } from "react-router-dom" export const Home = () => { return ( <> -
-

Something about coding...

- - -
+
+
+

Something about coding...

+ + +
+
) } diff --git a/frontend/src/pages/LogIn.jsx b/frontend/src/pages/LogIn.jsx index 547ee72df..d3858c098 100644 --- a/frontend/src/pages/LogIn.jsx +++ b/frontend/src/pages/LogIn.jsx @@ -23,16 +23,18 @@ export const LogIn = () => { return ( <> -
-

Log In

-
- - - - - -
- +
+
+

Log In

+
+ + + + + +
+ +
) diff --git a/frontend/src/pages/LoggedIn.jsx b/frontend/src/pages/LoggedIn.jsx index 34bd711a5..e389c78d0 100644 --- a/frontend/src/pages/LoggedIn.jsx +++ b/frontend/src/pages/LoggedIn.jsx @@ -1,16 +1,44 @@ -import "./index.css" +import "../index.css" +import { userStore } from "../stores/userStore.jsx"; +import { quoteStore } from "../stores/quoteStore.jsx"; +import { useStore } from "zustand" +import { useNavigate } from "react-router-dom" +import { useEffect } from "react"; export const LoggedIn = () => { + const storeHandleLogout = userStore((state) => state.handleLogout); + const navigate = useNavigate(); + + const { quotes, fetchQuotes } = useStore(quoteStore); + + useEffect(() => { + console.log("Quotes array:", quotes); + // Fetch dogs when the component mounts + fetchQuotes(); + }, []); + + // Handle the click event of the logout button + const onLogoutClick = () => { + storeHandleLogout(); + alert("Logout successful"); + // Navigate to the homepage if logout was successful + navigate("/"); + }; + return ( <> -
+
+

Welcome!

Here's a little thought for today:

-

"Coding is the art of turning imagination into reality, - where every line of code is a stroke of creativity that brings - a digital masterpiece to life."

-

-ChatGPT

+

{quotes.length > 0 ? `${quotes[0].quote}` : "Couldn't find a quote."}

+
+ +
+
) -} \ No newline at end of file +} + +export default LoggedIn \ No newline at end of file diff --git a/frontend/src/pages/SignUp.jsx b/frontend/src/pages/SignUp.jsx index 895493745..1c44fe882 100644 --- a/frontend/src/pages/SignUp.jsx +++ b/frontend/src/pages/SignUp.jsx @@ -1,75 +1,96 @@ import "../index.css" import { Link } from "react-router-dom" -import { useState, useEffect } from "react" +import { useNavigate } from "react-router-dom" +import { userStore } from "../stores/userStore" export const SignUp = () => { - const [newUser, setNewUser] = useState('') + const { handleSignup } = userStore(); + const navigate = useNavigate(); + + const handleSignUpSubmit = async (e) => { + e.preventDefault(); + const email = e.target.email.value; + const username = e.target.username.value; + const password = e.target.password.value; + + try { + await handleSignup(email, username, password); + navigate('/'); + } catch (error) { + console.error('Signup error:', error); + } + } + + +/* const [newUser, setNewUser] = useState('') const [email, setEmail] = useState('') const [username, setUsername] = useState('') const [password, setPassword] = useState('') - useEffect(() => { - const signUpData = { - email: email, - username: username, - password: password - }; + useEffect(() => { + const signUpData = { + email: email, + username: username, + password: password + }; - if (email && username && password) { - fetch('http://localhost:8080/users', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(signUpData) + if (email && username && password) { + fetch('http://localhost:3000/register', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(signUpData) + }) + .then((response) => { + if (!response.ok) { + throw new Error('Registration failed'); + } + return response.json(); }) - .then((response) => { - if (!response.ok) { - throw new Error('Sign-up request failed'); - } - return response.json(); - }) - .then((data) => setNewUser(data.id)) - .catch((error) => console.error('Registration failed', error)); - } - }, [email, username, password]); // Run this effect whenever email, username, or password changes - - const handleSubmit = async (e) => { - e.preventDefault(); + .then((data) => setNewUser(data.id)) + .catch((error) => console.error('Registration failed', error)); } + }, [email, username, password]); - return ( - <> -
-

Sign Up

-
- - setEmail(e.target.value)} /> - - setUsername(e.target.value)} /> - - setPassword(e.target.value)} /> - -
-
+ const handleSubmit = async (e) => { + e.preventDefault(); + } */ + + return ( + <> +
+
+

Sign Up

+
+ + setEmail(e.target.value)} */ /> + + setUsername(e.target.value)} */ /> + + setPassword(e.target.value)} */ /> + +
+
-
- - ) - } +
+
+ + ) +} export default SignUp \ No newline at end of file diff --git a/frontend/src/routes/routes.jsx b/frontend/src/routes/routes.jsx index cf39a64a7..5616ad880 100644 --- a/frontend/src/routes/routes.jsx +++ b/frontend/src/routes/routes.jsx @@ -2,8 +2,9 @@ import { Route } from "react-router-dom" import { Home } from "../pages/Home.jsx" import { SignUp } from "../pages/SignUp.jsx" import { LogIn } from "../pages/LogIn.jsx" +import { LoggedIn } from "../pages/LoggedIn.jsx" -const routes = ( +export const routes = ( <> } /> } /> diff --git a/frontend/src/stores/quoteStore.jsx b/frontend/src/stores/quoteStore.jsx index 166420097..4cf6f7890 100644 --- a/frontend/src/stores/quoteStore.jsx +++ b/frontend/src/stores/quoteStore.jsx @@ -1,5 +1,4 @@ import { create } from "zustand"; -import { userStore } from "./userStore"; /* const apiEnv = import.meta.env.VITE_BACKEND_API; console.log(apiEnv); */ @@ -7,12 +6,12 @@ console.log(apiEnv); */ export const quoteStore = create((set) => ({ quotes: [], addQuote: (newQuote) => set((state) => ({ quotes: [...state.quotes, newQuote] })), - setQuotes: (qus) => set({ dogs }), + setQuotes: (quotes) => set({ quotes }), - // Fetch the dogs a specific user has added in their profile - fetchDogs: async () => { + // Fetch a quote for the user to see + fetchQuotes: async () => { try { - const response = await fetch('https://rescue-helper.onrender.com/yourDogs', { + const response = await fetch('http://localhost:3000/getQuote', { method: "GET", headers: { Authorization: localStorage.getItem("accessToken"), @@ -20,9 +19,10 @@ export const quoteStore = create((set) => ({ }); if (response.ok) { const data = await response.json(); - set({ dogs: data }); + set({ quotes: [data] }); + return data; } else { - console.error("Failed to fetch your dogs"); + console.error("Failed to find a quote"); } } catch (error) { console.error(error); diff --git a/frontend/src/stores/userStore.jsx b/frontend/src/stores/userStore.jsx index ba908a908..e324e48a7 100644 --- a/frontend/src/stores/userStore.jsx +++ b/frontend/src/stores/userStore.jsx @@ -1,12 +1,12 @@ import { create } from "zustand"; -const apiEnv = import.meta.env.VITE_BACKEND_API; +//const apiEnv = import.meta.env.VITE_BACKEND_API; export const userStore = create((set, get) => ({ - username: "", - setUsername: (username) => set({ username }), email: "", setEmail: (email) => set({ email }), + username: "", + setUsername: (username) => set({ username }), password: "", setPassword: (password) => set({ password }), accessToken: null, // Add this if you plan to store the access token @@ -15,14 +15,14 @@ export const userStore = create((set, get) => ({ setIsLoggedIn: (isLoggedIn) => set({ isLoggedIn }), // FUNCTION TO REGISTER USERS - handleSignup: async (username, password, email) => { - if (!username || !password || !email) { + handleSignup: async (email, username, password) => { + if (!email || !username || !password) { alert("Please enter username, email and password"); return; } try { - const response = await fetch(`${apiEnv}/register`, { + const response = await fetch('http://localhost:3000/register', { method: "POST", headers: { "Content-Type": "application/json", @@ -48,13 +48,15 @@ export const userStore = create((set, get) => ({ // LOGIN handleLogin: async (username, password) => { + // Check if both username and password are provided and display an alert if not. if (!username || !password) { alert("Please enter both username and password"); return; } try { - const response = await fetch(`${apiEnv}/login`, { + // Send a POST request to the login endpoint with user data. + const response = await fetch('http://localhost:3000/login', { method: "POST", headers: { "Content-Type": "application/json", @@ -62,26 +64,33 @@ export const userStore = create((set, get) => ({ body: JSON.stringify({ username, password }), }); + // Parse the response data as JSON. const data = await response.json(); if (data.success) { + // Update the state with username, accessToken, and set isLoggedIn to true. set({ username, accessToken: data.response.accessToken, isLoggedIn: true, - }); // Update the state with username and accessToken - // Redirect or update UI + }); + // Store the accessToken in the browser's localStorage. localStorage.setItem("accessToken", data.response.accessToken); + // Display a success alert. alert("Login successful!"); - console.log("Loging up with:", username, password); + console.log("Logging in with:", username, password); } else { - // Display error message from server - alert(data.response || "Login failed"); + // Display an error message from the server or a generic message. + alert("Incorrect username or password") + throw new Error(data.response || "Login failed"); } } catch (error) { - console.error("Login error:", error); - alert("An error occurred during login"); + // Handle and log any login errors. + throw error; + /* console.error("Login error:", error); + alert("An error occurred during login"); */ } }, + handleLogout: () => { // Clear user information and set isLoggedIn to false set({ username: "", accessToken: null, isLoggedIn: false }); From 6bcf7fe6e8bee830be1651c11e5c38029f235ac5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olga=20Lepist=C3=B6?= Date: Thu, 25 Jan 2024 16:13:42 +0200 Subject: [PATCH 07/15] typo issue fix phase 1 --- backend/routes/{quoteRoutes.js => quoteRautes.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename backend/routes/{quoteRoutes.js => quoteRautes.js} (100%) diff --git a/backend/routes/quoteRoutes.js b/backend/routes/quoteRautes.js similarity index 100% rename from backend/routes/quoteRoutes.js rename to backend/routes/quoteRautes.js From f616b9a77b706a7511b0acd4d5cd13d749f886eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olga=20Lepist=C3=B6?= Date: Thu, 25 Jan 2024 16:14:58 +0200 Subject: [PATCH 08/15] typo issue fix phase 2 --- backend/routes/{quoteRautes.js => quoteRoutes.js} | 0 backend/server.js | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename backend/routes/{quoteRautes.js => quoteRoutes.js} (100%) diff --git a/backend/routes/quoteRautes.js b/backend/routes/quoteRoutes.js similarity index 100% rename from backend/routes/quoteRautes.js rename to backend/routes/quoteRoutes.js diff --git a/backend/server.js b/backend/server.js index bb5baec5e..c07327513 100644 --- a/backend/server.js +++ b/backend/server.js @@ -4,7 +4,7 @@ import dotenv from "dotenv"; dotenv.config(); import listEndpoints from "express-list-endpoints"; import userRoutes from "./routes/userRoutes.js" -import quoteRoutes from "./routes/quoteroutes.js" +import quoteRoutes from "./routes/quoteRoutes.js" import { connectDB } from "./config/db.js" // Defines the port the app will run on. Defaults to 8080, but can be overridden From b2e2ac02b63b66235d2aff6ec110f4eedc99b233 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olga=20Lepist=C3=B6?= Date: Thu, 25 Jan 2024 16:54:34 +0200 Subject: [PATCH 09/15] troubleshooting --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9027f8fc1..cfea95321 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,6 @@ "mongo": "^0.1.0", "mongodb": "^6.3.0", "mongoose": "^8.1.0", - "zustand": "^4.4.7" + "zustand": "^4.5.0" } } From 521d730313e314c4850777329843bffdd983f687 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olga=20Lepist=C3=B6?= Date: Thu, 25 Jan 2024 17:07:29 +0200 Subject: [PATCH 10/15] installed zustand in frontend --- frontend/package.json | 3 ++- frontend/src/pages/LoggedIn.jsx | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index b36447f2f..c7c3cc96a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -14,7 +14,8 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-router": "^6.21.2", - "react-router-dom": "^6.21.2" + "react-router-dom": "^6.21.2", + "zustand": "^4.5.0" }, "devDependencies": { "@types/react": "^18.2.15", diff --git a/frontend/src/pages/LoggedIn.jsx b/frontend/src/pages/LoggedIn.jsx index e389c78d0..00558560f 100644 --- a/frontend/src/pages/LoggedIn.jsx +++ b/frontend/src/pages/LoggedIn.jsx @@ -1,7 +1,7 @@ import "../index.css" import { userStore } from "../stores/userStore.jsx"; import { quoteStore } from "../stores/quoteStore.jsx"; -import { useStore } from "zustand" +/* import { useStore } from "zustand" */ import { useNavigate } from "react-router-dom" import { useEffect } from "react"; From 77a81f1261869daed577350f4bfc5568d2677b04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olga=20Lepist=C3=B6?= Date: Thu, 25 Jan 2024 17:19:30 +0200 Subject: [PATCH 11/15] text type changed to password --- frontend/src/pages/LoggedIn.jsx | 2 +- frontend/src/pages/SignUp.jsx | 48 +++------------------------------ 2 files changed, 5 insertions(+), 45 deletions(-) diff --git a/frontend/src/pages/LoggedIn.jsx b/frontend/src/pages/LoggedIn.jsx index 00558560f..e389c78d0 100644 --- a/frontend/src/pages/LoggedIn.jsx +++ b/frontend/src/pages/LoggedIn.jsx @@ -1,7 +1,7 @@ import "../index.css" import { userStore } from "../stores/userStore.jsx"; import { quoteStore } from "../stores/quoteStore.jsx"; -/* import { useStore } from "zustand" */ +import { useStore } from "zustand" import { useNavigate } from "react-router-dom" import { useEffect } from "react"; diff --git a/frontend/src/pages/SignUp.jsx b/frontend/src/pages/SignUp.jsx index 1c44fe882..8a4b7fe9d 100644 --- a/frontend/src/pages/SignUp.jsx +++ b/frontend/src/pages/SignUp.jsx @@ -21,40 +21,6 @@ export const SignUp = () => { } } - -/* const [newUser, setNewUser] = useState('') - const [email, setEmail] = useState('') - const [username, setUsername] = useState('') - const [password, setPassword] = useState('') - - useEffect(() => { - const signUpData = { - email: email, - username: username, - password: password - }; - - if (email && username && password) { - fetch('http://localhost:3000/register', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(signUpData) - }) - .then((response) => { - if (!response.ok) { - throw new Error('Registration failed'); - } - return response.json(); - }) - .then((data) => setNewUser(data.id)) - .catch((error) => console.error('Registration failed', error)); - } - }, [email, username, password]); - - const handleSubmit = async (e) => { - e.preventDefault(); - } */ - return ( <>
@@ -65,23 +31,17 @@ export const SignUp = () => { setEmail(e.target.value)} */ /> + id="email" /> setUsername(e.target.value)} */ /> + id="username" /> setPassword(e.target.value)} */ /> + id="password" />
From dd30246b7e01d775c7850f185ebfd114b14229f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olga=20Lepist=C3=B6?= Date: Thu, 25 Jan 2024 17:23:22 +0200 Subject: [PATCH 12/15] vite backend api added --- frontend/src/stores/quoteStore.jsx | 6 +++--- frontend/src/stores/userStore.jsx | 8 +++----- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/frontend/src/stores/quoteStore.jsx b/frontend/src/stores/quoteStore.jsx index 4cf6f7890..d0fd0b188 100644 --- a/frontend/src/stores/quoteStore.jsx +++ b/frontend/src/stores/quoteStore.jsx @@ -1,7 +1,7 @@ import { create } from "zustand"; -/* const apiEnv = import.meta.env.VITE_BACKEND_API; -console.log(apiEnv); */ +const apiEnv = import.meta.env.VITE_BACKEND_API; +console.log(apiEnv); export const quoteStore = create((set) => ({ quotes: [], @@ -11,7 +11,7 @@ export const quoteStore = create((set) => ({ // Fetch a quote for the user to see fetchQuotes: async () => { try { - const response = await fetch('http://localhost:3000/getQuote', { + const response = await fetch(`${apiEnv}/getQuote`, { method: "GET", headers: { Authorization: localStorage.getItem("accessToken"), diff --git a/frontend/src/stores/userStore.jsx b/frontend/src/stores/userStore.jsx index e324e48a7..8ffcf6d5a 100644 --- a/frontend/src/stores/userStore.jsx +++ b/frontend/src/stores/userStore.jsx @@ -1,6 +1,6 @@ import { create } from "zustand"; -//const apiEnv = import.meta.env.VITE_BACKEND_API; +const apiEnv = import.meta.env.VITE_BACKEND_API; export const userStore = create((set, get) => ({ email: "", @@ -22,7 +22,7 @@ export const userStore = create((set, get) => ({ } try { - const response = await fetch('http://localhost:3000/register', { + const response = await fetch(`${apiEnv}/register`, { method: "POST", headers: { "Content-Type": "application/json", @@ -56,7 +56,7 @@ export const userStore = create((set, get) => ({ try { // Send a POST request to the login endpoint with user data. - const response = await fetch('http://localhost:3000/login', { + const response = await fetch(`${apiEnv}/login`, { method: "POST", headers: { "Content-Type": "application/json", @@ -86,8 +86,6 @@ export const userStore = create((set, get) => ({ } catch (error) { // Handle and log any login errors. throw error; - /* console.error("Login error:", error); - alert("An error occurred during login"); */ } }, From e2cf737d6a422f90afcfd89128af95c54f4e56fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olga=20Lepist=C3=B6?= Date: Thu, 25 Jan 2024 18:31:34 +0200 Subject: [PATCH 13/15] edited apiEnv --- frontend/src/stores/quoteStore.jsx | 2 +- frontend/src/stores/userStore.jsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/stores/quoteStore.jsx b/frontend/src/stores/quoteStore.jsx index d0fd0b188..6a47d7293 100644 --- a/frontend/src/stores/quoteStore.jsx +++ b/frontend/src/stores/quoteStore.jsx @@ -1,6 +1,6 @@ import { create } from "zustand"; -const apiEnv = import.meta.env.VITE_BACKEND_API; +const apiEnv = import.meta.env.VITE_BACKEND_API.replace(/\/$/, '');; console.log(apiEnv); export const quoteStore = create((set) => ({ diff --git a/frontend/src/stores/userStore.jsx b/frontend/src/stores/userStore.jsx index 8ffcf6d5a..8a1e0d430 100644 --- a/frontend/src/stores/userStore.jsx +++ b/frontend/src/stores/userStore.jsx @@ -1,6 +1,6 @@ import { create } from "zustand"; -const apiEnv = import.meta.env.VITE_BACKEND_API; +const apiEnv = import.meta.env.VITE_BACKEND_API.replace(/\/$/, '');; export const userStore = create((set, get) => ({ email: "", From db5673d6273ea24921395650094b92c74a520c39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olga=20Lepist=C3=B6?= Date: Thu, 25 Jan 2024 18:58:50 +0200 Subject: [PATCH 14/15] updated README --- frontend/README.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/frontend/README.md b/frontend/README.md index f768e33fc..8b1378917 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -1,8 +1 @@ -# React + Vite -This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. - -Currently, two official plugins are available: - -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh From c241d3085acd5468aaea7b3efbbae2781df23f1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olga=20Lepist=C3=B6?= Date: Thu, 25 Jan 2024 20:07:47 +0200 Subject: [PATCH 15/15] added note to README --- README.md | 13 ++++++++----- backend/routes/quoteRoutes.js | 2 +- frontend/src/pages/LoggedIn.jsx | 2 +- netlify.toml | 2 +- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index dfa05e177..1c2a66b4e 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,16 @@ -# Project Auth API +# Project Auth - Olga & Elin -Replace this readme with your own information about your project. +This is a project to pratice authentication and authorization. The task was to create endpoints and UI for signing up, logging in/out and displaying some authenticated content for the user. The project utilises Express.js, MongoDB and React. -Start by briefly describing the assignment in a sentence or two. Keep it short and to the point. +Our version of the project returns the user a random inspirational coding-related quote from our database. ## The problem -Describe how you approached to problem, and what tools and techniques you used to solve it. How did you plan? What technologies did you use? If you had more time, what would be next? +As we're handing this project in really late, the final project is already finished and approved. As most of the needed codes were "already there", we thought this one would've been really straightforward - however, we ran into at least as many errors connecting the backend and frontend of this one as with the final project. Problems especially arose after deploying everything, but many of them were mostly careless mistakes and lack of knowledge. ## View it live -Every project should be deployed somewhere. Be sure to include the link to the deployed project so that the viewer can click around and see what it's all about. +Please note that the deployed backend is quite slow and the initial attempt to sign up or login might take a while! + +Deployed site: https://projectauth-olgaelin.netlify.app +Backend: https://project-auth-pjm5.onrender.com/ diff --git a/backend/routes/quoteRoutes.js b/backend/routes/quoteRoutes.js index 2fba20dc1..4e0f86e10 100644 --- a/backend/routes/quoteRoutes.js +++ b/backend/routes/quoteRoutes.js @@ -29,7 +29,7 @@ router.get( router.get( "/allQuotes", async (req, res) => { try { - const quotes = await QuoteModel.find().select('quote'); + const quotes = await QuoteModel.find(); res.json(quotes); } catch (err) { res.status(400).json({ error: err.message }); diff --git a/frontend/src/pages/LoggedIn.jsx b/frontend/src/pages/LoggedIn.jsx index e389c78d0..2048031e4 100644 --- a/frontend/src/pages/LoggedIn.jsx +++ b/frontend/src/pages/LoggedIn.jsx @@ -13,7 +13,7 @@ export const LoggedIn = () => { useEffect(() => { console.log("Quotes array:", quotes); - // Fetch dogs when the component mounts + // Fetch a quote when the component mounts fetchQuotes(); }, []); diff --git a/netlify.toml b/netlify.toml index 360bf460e..ad1dc5d88 100644 --- a/netlify.toml +++ b/netlify.toml @@ -9,4 +9,4 @@ [[redirects]] from = "/*" to = "/index.html" - status = 200 + status = 200 \ No newline at end of file