From 986d1cbda352ae87ee93278dfbd4c782847242fa Mon Sep 17 00:00:00 2001 From: Cornelia Dahlin Date: Mon, 20 May 2024 15:08:44 +0200 Subject: [PATCH 01/28] Setting up backend --- backend/package.json | 2 +- backend/server.js | 12 ++++++------ package.json | 3 +++ pull_request_template.md | 7 ------- 4 files changed, 10 insertions(+), 14 deletions(-) delete mode 100644 pull_request_template.md diff --git a/backend/package.json b/backend/package.json index 8de5c4ce0..e1f3a6562 100644 --- a/backend/package.json +++ b/backend/package.json @@ -14,7 +14,7 @@ "@babel/preset-env": "^7.16.11", "cors": "^2.8.5", "express": "^4.17.3", - "mongoose": "^8.0.0", + "mongoose": "^8.4.0", "nodemon": "^3.0.1" } } diff --git a/backend/server.js b/backend/server.js index dfe86fb8e..6fa7d1072 100644 --- a/backend/server.js +++ b/backend/server.js @@ -2,17 +2,13 @@ import cors from "cors"; import express from "express"; import mongoose from "mongoose"; -const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/project-mongo"; +const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/auth"; mongoose.connect(mongoUrl); mongoose.Promise = Promise; -// 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 || 8082; const app = express(); -// Add middlewares to enable cors and json body parsing app.use(cors()); app.use(express.json()); @@ -21,6 +17,10 @@ app.get("/", (req, res) => { res.send("Hello Technigo!"); }); +app.get("/secrets", (req, res) => { + res.json({ secret: "This is a super secret message" }); +}); + // Start the server app.listen(port, () => { console.log(`Server running on http://localhost:${port}`); diff --git a/package.json b/package.json index d774b8cc3..e7534ce40 100644 --- a/package.json +++ b/package.json @@ -3,5 +3,8 @@ "version": "1.0.0", "scripts": { "postinstall": "npm install --prefix backend" + }, + "dependencies": { + "mongoose": "^8.4.0" } } diff --git a/pull_request_template.md b/pull_request_template.md deleted file mode 100644 index d92c89b51..000000000 --- a/pull_request_template.md +++ /dev/null @@ -1,7 +0,0 @@ -## Netlify link -Add your Netlify link here. -PS. Don't forget to add it in your readme as well. - -## Collaborators -Add your collaborators here. Write their GitHub usernames in square brackets. If there's more than one, separate them with a comma, like this: -[github-username-1, github-username-2] From 025645158c22810912992aff456148175f1ca9cd Mon Sep 17 00:00:00 2001 From: Cornelia Dahlin Date: Mon, 20 May 2024 16:40:02 +0200 Subject: [PATCH 02/28] Model, authentication, registration ect added --- backend/package.json | 2 ++ backend/server.js | 55 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/backend/package.json b/backend/package.json index e1f3a6562..bb1ea689c 100644 --- a/backend/package.json +++ b/backend/package.json @@ -12,8 +12,10 @@ "@babel/core": "^7.17.9", "@babel/node": "^7.16.8", "@babel/preset-env": "^7.16.11", + "bcrypt-nodejs": "^0.0.3", "cors": "^2.8.5", "express": "^4.17.3", + "express-list-endpoints": "^7.1.0", "mongoose": "^8.4.0", "nodemon": "^3.0.1" } diff --git a/backend/server.js b/backend/server.js index 6fa7d1072..3666e6746 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1,22 +1,73 @@ import cors from "cors"; import express from "express"; import mongoose from "mongoose"; +import crypto from "crypto"; +import bcrypt from "bcrypt-nodejs"; +import expressListEndpoints from "express-list-endpoints"; const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/auth"; mongoose.connect(mongoUrl); mongoose.Promise = Promise; +// Model +const User = mongoose.model("User", { + name: { + type: String, + unique: true, + }, + email: { + type: String, + unique: true, + }, + password: { + type: String, + required: true, + }, + accessToken: { + type: String, + default: () => crypto.randomBytes(128).toString("hex"), + }, +}); + +// Middleware to authenticate user with access token +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 }); + } +}; + const port = process.env.PORT || 8082; const app = express(); app.use(cors()); app.use(express.json()); -// Start defining your routes here +// Routes app.get("/", (req, res) => { - res.send("Hello Technigo!"); + const endpoints = expressListEndpoints(app); + res.json(endpoints); +}); + +// Registration endpoint to create a new user +app.post("/users", async (req, res) => { + try { + const { name, email, password } = req.body; + const user = new User({ name, email, password: bcrypt.hashSync(password) }); + 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 }); + } }); +// Route to access secrets after authentication +app.get("/secrets", authenticateUser); app.get("/secrets", (req, res) => { res.json({ secret: "This is a super secret message" }); }); From 8e4d19db44e84075eaa32c4257d765ff83076e79 Mon Sep 17 00:00:00 2001 From: Cornelia Dahlin Date: Mon, 20 May 2024 16:50:44 +0200 Subject: [PATCH 03/28] Login endpoint added --- backend/server.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/backend/server.js b/backend/server.js index 3666e6746..196715342 100644 --- a/backend/server.js +++ b/backend/server.js @@ -66,6 +66,20 @@ app.post("/users", async (req, res) => { } }); +// Login endpoint to authenticate a user +// Find the user by email, if user is found and password is correct, generate and return an access token +app.post("/login", async (req, res) => { + const { email, password } = req.body; + + const user = await User.findOne({ email }); + if (!user || !bcrypt.compareSync(password, user.password)) { + return res.status(401).json({ message: "Invalid email or password" }); + } + + const accessToken = user.accessToken; + res.status(200).json({ accessToken }); +}); + // Route to access secrets after authentication app.get("/secrets", authenticateUser); app.get("/secrets", (req, res) => { From 6138e9dd871dbc0bd80fd237d2d0cc37a439ad0c Mon Sep 17 00:00:00 2001 From: Cornelia Dahlin Date: Tue, 21 May 2024 16:02:38 +0200 Subject: [PATCH 04/28] Frontend styling, responsive and functions missing --- frontend/package.json | 4 +- frontend/src/App.jsx | 17 +++++- frontend/src/assets/react.svg | 1 - frontend/src/components/AuthForm.jsx | 31 +++++++++++ frontend/src/components/LoginPage.jsx | 49 +++++++++++++++++ frontend/src/components/SignUpPage.jsx | 70 ++++++++++++++++++++++++ frontend/src/components/StartPage.jsx | 29 ++++++++++ frontend/src/components/StyledButton.jsx | 20 +++++++ frontend/src/components/StyledText.jsx | 13 +++++ frontend/src/index.css | 25 +++++---- 10 files changed, 243 insertions(+), 16 deletions(-) delete mode 100644 frontend/src/assets/react.svg create mode 100644 frontend/src/components/AuthForm.jsx create mode 100644 frontend/src/components/LoginPage.jsx create mode 100644 frontend/src/components/SignUpPage.jsx create mode 100644 frontend/src/components/StartPage.jsx create mode 100644 frontend/src/components/StyledButton.jsx create mode 100644 frontend/src/components/StyledText.jsx diff --git a/frontend/package.json b/frontend/package.json index e9c95b79f..3e6453269 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,7 +11,9 @@ }, "dependencies": { "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "react-router-dom": "^6.23.1", + "styled-components": "^6.1.11" }, "devDependencies": { "@types/react": "^18.2.15", diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 1091d4310..5296438bd 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,3 +1,16 @@ +import { BrowserRouter as Router, Route, Routes } from "react-router-dom"; +import { StartPage } from "./components/StartPage.jsx"; +import { LoginPage } from "./components/LoginPage.jsx"; +import { SignUpPage } from "./components/SignUpPage.jsx"; + export const App = () => { - return
Find me in src/app.jsx!
; -}; + return ( + + + } /> + } /> + } /> + + + ); +}; \ No newline at end of file diff --git a/frontend/src/assets/react.svg b/frontend/src/assets/react.svg deleted file mode 100644 index 6c87de9bb..000000000 --- a/frontend/src/assets/react.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/src/components/AuthForm.jsx b/frontend/src/components/AuthForm.jsx new file mode 100644 index 000000000..def422bc0 --- /dev/null +++ b/frontend/src/components/AuthForm.jsx @@ -0,0 +1,31 @@ +import styled from "styled-components"; + +// Reusable styled components for forms +export const FormContainer = styled.div` + text-align: center; +`; + +export const Form = styled.form` + display: flex; + flex-direction: column; + align-items: center; + margin-top: 2em; +`; + +export const Input = styled.input` + font-size: 1em; + padding: 0.75em; + margin: 0.25em; + width: 325px; + border: 1.5px solid #ffffff; + border-radius: 0.5em; + background-color: rgba(255, 255, 255, 0.13); +`; + +export const AuthForm = ({ onSubmit, children }) => { + return ( + +
{children}
+
+ ); +}; diff --git a/frontend/src/components/LoginPage.jsx b/frontend/src/components/LoginPage.jsx new file mode 100644 index 000000000..bf964ec34 --- /dev/null +++ b/frontend/src/components/LoginPage.jsx @@ -0,0 +1,49 @@ +import { useState } from "react"; +import { StyledHeading, SmallText } from "./StyledText.jsx"; +import { StyledButton } from "./StyledButton.jsx"; +import { AuthForm, Input } from "./AuthForm.jsx"; + +export const LoginPage = () => { + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + + const handleSubmit = (e) => { + e.preventDefault(); + // Handle login logic here + }; + + return ( +
+ Welcome Back! + Ready to dive in? + + setEmail(e.target.value)} + required + /> + setPassword(e.target.value)} + required + /> + Login + + Don't have an account?{" "} + + Sign Up Now + + + +
+ ); +}; diff --git a/frontend/src/components/SignUpPage.jsx b/frontend/src/components/SignUpPage.jsx new file mode 100644 index 000000000..3d43865d4 --- /dev/null +++ b/frontend/src/components/SignUpPage.jsx @@ -0,0 +1,70 @@ +import { useState } from "react"; +import { StyledHeading, SmallText } from "./StyledText.jsx"; +import { StyledButton } from "./StyledButton.jsx"; +import { AuthForm, Input } from "./AuthForm.jsx"; + +export const SignUpPage = () => { + const [name, setName] = useState(""); + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [showPassword, setShowPassword] = useState(false); + + const handleSubmit = (e) => { + e.preventDefault(); + // Handle sign up logic here + }; + + const toggleShowPassword = () => { + setShowPassword((prevShowPassword) => !prevShowPassword); + }; + + return ( +
+ Create Account + to get started now! + + setName(e.target.value)} + required + /> + setEmail(e.target.value)} + required + /> + setPassword(e.target.value)} + required + /> + + Sign Up + + Already have an account?{" "} + + Login Now + + + +
+ ); +}; diff --git a/frontend/src/components/StartPage.jsx b/frontend/src/components/StartPage.jsx new file mode 100644 index 000000000..2eb741442 --- /dev/null +++ b/frontend/src/components/StartPage.jsx @@ -0,0 +1,29 @@ +import { Link } from "react-router-dom"; +import { StyledHeading } from "./StyledText.jsx"; +import { StyledButton } from "./StyledButton.jsx"; +import styled from "styled-components"; + +// StyledButtonContainer to hold the buttons +const StyledButtonContainer = styled.div` + display: flex; + flex-direction: column; + align-items: center; + margin-top: 2em; +`; + +export const StartPage = () => { + return ( +
+ Welcome, + Glad to see you! + + + Login + + + Sign Up + + +
+ ); +}; diff --git a/frontend/src/components/StyledButton.jsx b/frontend/src/components/StyledButton.jsx new file mode 100644 index 000000000..db6b271e5 --- /dev/null +++ b/frontend/src/components/StyledButton.jsx @@ -0,0 +1,20 @@ +import styled from "styled-components"; + +export const StyledButton = styled.button` + font-size: 1em; + padding: 0.75em; + margin: 0.25em; + width: 350px; + color: #000000; + background-color: #ffffff; + border: none; + border-radius: 0.5em; + cursor: pointer; + box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1); + transition: all 0.3s ease; + + &:hover { + transform: scale(1.03); + box-shadow: 0px 6px 8px rgba(0, 0, 0, 0.2); + } +`; diff --git a/frontend/src/components/StyledText.jsx b/frontend/src/components/StyledText.jsx new file mode 100644 index 000000000..f5eb43a27 --- /dev/null +++ b/frontend/src/components/StyledText.jsx @@ -0,0 +1,13 @@ +import styled from "styled-components"; + +export const StyledHeading = styled.div` + font-size: ${(props) => props.size || "2.2em"}; + color: #ffffff; + font-weight: ${(props) => (props.fontWeight === "bold" ? "bold" : "normal")}; + text-align: center; +`; + +export const SmallText = styled.p` + font-size: 0.85em; + color: #000000; +`; diff --git a/frontend/src/index.css b/frontend/src/index.css index 3e560a674..a713e870e 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -1,13 +1,14 @@ -:root { - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", - "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", - sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} +@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap"); -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", - monospace; -} \ No newline at end of file +body { + background: linear-gradient( + to bottom, + #b562b3, + #ff9a26 + ); /* Gradient from top to bottom */ + min-height: 100vh; + display: flex; + justify-content: center; + align-items: center; + font-family: "Inter", sans-serif; +} From 476f39a7540568a39c0f33ed22ddb4b7217dfadf Mon Sep 17 00:00:00 2001 From: Cornelia Dahlin Date: Tue, 21 May 2024 16:21:14 +0200 Subject: [PATCH 05/28] Show/hide password icon/button added --- frontend/package.json | 2 ++ frontend/src/components/AuthForm.jsx | 49 ++++++++++++++++++++++++-- frontend/src/components/LoginPage.jsx | 3 +- frontend/src/components/SignUpPage.jsx | 15 +------- 4 files changed, 51 insertions(+), 18 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 3e6453269..3e4de261d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,6 +10,8 @@ "preview": "vite preview" }, "dependencies": { + "@fortawesome/free-solid-svg-icons": "^6.5.2", + "@fortawesome/react-fontawesome": "^0.2.1", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.23.1", diff --git a/frontend/src/components/AuthForm.jsx b/frontend/src/components/AuthForm.jsx index def422bc0..9fe1212bb 100644 --- a/frontend/src/components/AuthForm.jsx +++ b/frontend/src/components/AuthForm.jsx @@ -1,6 +1,9 @@ +import { useState } from "react"; // Import useState from react + import styled from "styled-components"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faEye, faEyeSlash } from "@fortawesome/free-solid-svg-icons"; -// Reusable styled components for forms export const FormContainer = styled.div` text-align: center; `; @@ -12,16 +15,56 @@ export const Form = styled.form` margin-top: 2em; `; -export const Input = styled.input` +const InputContainer = styled.div` + position: relative; +`; + +const InputField = styled.input` font-size: 1em; padding: 0.75em; + padding-right: 2.5em; margin: 0.25em; - width: 325px; + width: 295px; border: 1.5px solid #ffffff; border-radius: 0.5em; background-color: rgba(255, 255, 255, 0.13); `; +const EyeIcon = styled(FontAwesomeIcon)` + position: absolute; + top: 50%; + right: 0.75em; + transform: translateY(-50%); + cursor: pointer; + color: #fffffff; +`; + +export const Input = ({ type, placeholder, value, onChange }) => { + const [showPassword, setShowPassword] = useState(false); + + const toggleShowPassword = () => { + setShowPassword((prevShowPassword) => !prevShowPassword); + }; + + return ( + + + {type === "password" && ( + + )} + + ); +}; + export const AuthForm = ({ onSubmit, children }) => { return ( diff --git a/frontend/src/components/LoginPage.jsx b/frontend/src/components/LoginPage.jsx index bf964ec34..c35f53cb4 100644 --- a/frontend/src/components/LoginPage.jsx +++ b/frontend/src/components/LoginPage.jsx @@ -1,7 +1,8 @@ import { useState } from "react"; import { StyledHeading, SmallText } from "./StyledText.jsx"; import { StyledButton } from "./StyledButton.jsx"; -import { AuthForm, Input } from "./AuthForm.jsx"; +import { AuthForm } from "./AuthForm.jsx"; // Import AuthForm without Input +import { Input } from "./AuthForm.jsx"; // Import Input from AuthForm export const LoginPage = () => { const [email, setEmail] = useState(""); diff --git a/frontend/src/components/SignUpPage.jsx b/frontend/src/components/SignUpPage.jsx index 3d43865d4..7acc5a97e 100644 --- a/frontend/src/components/SignUpPage.jsx +++ b/frontend/src/components/SignUpPage.jsx @@ -7,17 +7,12 @@ export const SignUpPage = () => { const [name, setName] = useState(""); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); - const [showPassword, setShowPassword] = useState(false); const handleSubmit = (e) => { e.preventDefault(); // Handle sign up logic here }; - const toggleShowPassword = () => { - setShowPassword((prevShowPassword) => !prevShowPassword); - }; - return (
Create Account @@ -38,20 +33,12 @@ export const SignUpPage = () => { required /> setPassword(e.target.value)} required /> - Sign Up Already have an account?{" "} From 2dcd3c26b0962060081ac39ab3a88c8467a051d6 Mon Sep 17 00:00:00 2001 From: Cornelia Dahlin Date: Tue, 21 May 2024 17:33:58 +0200 Subject: [PATCH 06/28] Login and sign up fixed --- frontend/src/App.jsx | 2 +- frontend/src/components/LoginPage.jsx | 147 +++++++++++++++++++------ frontend/src/components/SignUpPage.jsx | 49 +++++++-- frontend/src/components/StyledText.jsx | 6 + 4 files changed, 157 insertions(+), 47 deletions(-) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 5296438bd..edd13986a 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,4 +1,4 @@ -import { BrowserRouter as Router, Route, Routes } from "react-router-dom"; +import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; import { StartPage } from "./components/StartPage.jsx"; import { LoginPage } from "./components/LoginPage.jsx"; import { SignUpPage } from "./components/SignUpPage.jsx"; diff --git a/frontend/src/components/LoginPage.jsx b/frontend/src/components/LoginPage.jsx index c35f53cb4..921639fdd 100644 --- a/frontend/src/components/LoginPage.jsx +++ b/frontend/src/components/LoginPage.jsx @@ -1,50 +1,125 @@ import { useState } from "react"; -import { StyledHeading, SmallText } from "./StyledText.jsx"; +import { Link } from "react-router-dom"; +import { StyledHeading, SmallText, SecretText } from "./StyledText.jsx"; import { StyledButton } from "./StyledButton.jsx"; -import { AuthForm } from "./AuthForm.jsx"; // Import AuthForm without Input -import { Input } from "./AuthForm.jsx"; // Import Input from AuthForm +import { AuthForm, Input } from "./AuthForm.jsx"; export const LoginPage = () => { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); + const [accessToken, setAccessToken] = useState(null); + const [secretMessage, setSecretMessage] = useState(""); + const [error, setError] = useState(null); - const handleSubmit = (e) => { + const handleSubmit = async (e) => { e.preventDefault(); - // Handle login logic here + + try { + const response = await fetch( + "https://project-auth-0pi0.onrender.com/login", + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ email, password }), + } + ); + + if (response.ok) { + const { accessToken } = await response.json(); + setAccessToken(accessToken); + setError(null); // Clear any previous errors + fetchSecret(accessToken); + } else { + setError("Invalid email or password"); // Set error message + } + } catch (error) { + console.error("Login failed:", error); + setError("Failed to log in. Please try again later."); // Set generic error message + } + }; + + const fetchSecret = async (accessToken) => { + try { + const response = await fetch( + "https://project-auth-0pi0.onrender.com/secrets", + { + headers: { + Authorization: accessToken, + }, + } + ); + + if (response.ok) { + const { secret } = await response.json(); + setSecretMessage(secret); + } else { + console.error("Unauthorized access to secrets"); + } + } catch (error) { + console.error("Failed to fetch secret:", error); + } + }; + + const handleLogout = () => { + setAccessToken(null); + setSecretMessage(""); }; return ( -
- Welcome Back! - Ready to dive in? - - setEmail(e.target.value)} - required - /> - setPassword(e.target.value)} - required - /> - Login - - Don't have an account?{" "} - - Sign Up Now - - - +
+ {accessToken ? ( + <> + You're in! + + You're in! What would you like to do first? + + {secretMessage} + Logout + + ) : ( + <> + Welcome Back! + Ready to dive in? + + setEmail(e.target.value)} + required + /> + setPassword(e.target.value)} + required + /> + Login + {error && {error}} + + Don't have an account?{" "} + + Sign Up Now + + + + + )}
); }; diff --git a/frontend/src/components/SignUpPage.jsx b/frontend/src/components/SignUpPage.jsx index 7acc5a97e..e1d2b23f3 100644 --- a/frontend/src/components/SignUpPage.jsx +++ b/frontend/src/components/SignUpPage.jsx @@ -1,4 +1,5 @@ import { useState } from "react"; +import { Link, useNavigate } from "react-router-dom"; import { StyledHeading, SmallText } from "./StyledText.jsx"; import { StyledButton } from "./StyledButton.jsx"; import { AuthForm, Input } from "./AuthForm.jsx"; @@ -7,14 +8,46 @@ export const SignUpPage = () => { const [name, setName] = useState(""); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); + const [error, setError] = useState(null); + const navigate = useNavigate(); // Access the navigate function - const handleSubmit = (e) => { + const handleSubmit = async (e) => { e.preventDefault(); - // Handle sign up logic here + + try { + const response = await fetch( + "https://project-auth-0pi0.onrender.com/users", + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ name, email, password }), + } + ); + + if (response.ok) { + navigate("/login"); // Use navigate function to redirect to login page + } else { + const errorMessage = await response.text(); + setError(errorMessage); + } + } catch (error) { + console.error("Signup failed:", error); + setError("Failed to create account. Please try again later."); + } }; return ( -
+
Create Account to get started now! @@ -40,16 +73,12 @@ export const SignUpPage = () => { required /> Sign Up + {error && {error}} Already have an account?{" "} - + Login Now - +
diff --git a/frontend/src/components/StyledText.jsx b/frontend/src/components/StyledText.jsx index f5eb43a27..3b7229dec 100644 --- a/frontend/src/components/StyledText.jsx +++ b/frontend/src/components/StyledText.jsx @@ -11,3 +11,9 @@ export const SmallText = styled.p` font-size: 0.85em; color: #000000; `; + +export const SecretText = styled.p` + font-size: 1.5em; + color: #000000; + text-align: center; +`; From 9a147df8dd0b143486c26a326ca44dab9fae292b Mon Sep 17 00:00:00 2001 From: Cornelia Dahlin <154337428+lunek1@users.noreply.github.com> Date: Tue, 21 May 2024 17:38:02 +0200 Subject: [PATCH 07/28] Delete .gitignore --- .gitignore | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 .gitignore diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 3d70248ba..000000000 --- a/.gitignore +++ /dev/null @@ -1,15 +0,0 @@ -node_modules -.DS_Store -.env -.env.local -.env.development.local -.env.test.local -.env.production.local - -build - -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -package-lock.json \ No newline at end of file From 3981a8b3e9e8015759f46192a2a0d212d0b67da9 Mon Sep 17 00:00:00 2001 From: Cornelia Dahlin <154337428+lunek1@users.noreply.github.com> Date: Tue, 21 May 2024 17:38:13 +0200 Subject: [PATCH 08/28] Delete frontend/.gitignore --- frontend/.gitignore | 26 -------------------------- 1 file changed, 26 deletions(-) delete mode 100644 frontend/.gitignore diff --git a/frontend/.gitignore b/frontend/.gitignore deleted file mode 100644 index 265f50c92..000000000 --- a/frontend/.gitignore +++ /dev/null @@ -1,26 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist -dist-ssr -*.local - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? - -package-lock.json \ No newline at end of file From ae3d22765d5b6d15346cdfa147697dd2b95e8277 Mon Sep 17 00:00:00 2001 From: Cornelia Dahlin <154337428+lunek1@users.noreply.github.com> Date: Tue, 21 May 2024 17:38:27 +0200 Subject: [PATCH 09/28] Delete frontend/public directory --- frontend/public/vite.svg | 1 - 1 file changed, 1 deletion(-) delete mode 100644 frontend/public/vite.svg diff --git a/frontend/public/vite.svg b/frontend/public/vite.svg deleted file mode 100644 index e7b8dfb1b..000000000 --- a/frontend/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file From de6637d58f4ff68fcc948fdf8d2196fcb3257d49 Mon Sep 17 00:00:00 2001 From: Cornelia Dahlin <154337428+lunek1@users.noreply.github.com> Date: Tue, 21 May 2024 17:38:43 +0200 Subject: [PATCH 10/28] Delete frontend/.eslintrc.cjs --- frontend/.eslintrc.cjs | 20 -------------------- 1 file changed, 20 deletions(-) delete mode 100644 frontend/.eslintrc.cjs diff --git a/frontend/.eslintrc.cjs b/frontend/.eslintrc.cjs deleted file mode 100644 index 4dcb43901..000000000 --- a/frontend/.eslintrc.cjs +++ /dev/null @@ -1,20 +0,0 @@ -module.exports = { - root: true, - env: { browser: true, es2020: true }, - extends: [ - 'eslint:recommended', - 'plugin:react/recommended', - 'plugin:react/jsx-runtime', - 'plugin:react-hooks/recommended', - ], - ignorePatterns: ['dist', '.eslintrc.cjs'], - parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, - settings: { react: { version: '18.2' } }, - plugins: ['react-refresh'], - rules: { - 'react-refresh/only-export-components': [ - 'warn', - { allowConstantExport: true }, - ], - }, -} From f3985b16a73198b42d1b47a0456f0cc99c539cb9 Mon Sep 17 00:00:00 2001 From: Cornelia Dahlin <154337428+lunek1@users.noreply.github.com> Date: Tue, 21 May 2024 17:39:00 +0200 Subject: [PATCH 11/28] Delete frontend/README.md --- frontend/README.md | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 frontend/README.md diff --git a/frontend/README.md b/frontend/README.md deleted file mode 100644 index f768e33fc..000000000 --- a/frontend/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# 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 e386d78a7969ada1380022a107c24405777a518f Mon Sep 17 00:00:00 2001 From: Cornelia Dahlin <154337428+lunek1@users.noreply.github.com> Date: Tue, 21 May 2024 17:49:19 +0200 Subject: [PATCH 12/28] Delete Procfile --- Procfile | 1 - 1 file changed, 1 deletion(-) delete mode 100644 Procfile diff --git a/Procfile b/Procfile deleted file mode 100644 index c154cc553..000000000 --- a/Procfile +++ /dev/null @@ -1 +0,0 @@ -web: npm start --prefix backend From ce3103e59babd5b9f3fe6eef6e3df111b3bb730a Mon Sep 17 00:00:00 2001 From: Cornelia Dahlin <154337428+lunek1@users.noreply.github.com> Date: Tue, 21 May 2024 17:50:00 +0200 Subject: [PATCH 13/28] Delete package.json --- package.json | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 package.json diff --git a/package.json b/package.json deleted file mode 100644 index e7534ce40..000000000 --- a/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "project-auth-parent", - "version": "1.0.0", - "scripts": { - "postinstall": "npm install --prefix backend" - }, - "dependencies": { - "mongoose": "^8.4.0" - } -} From 54bda1b0020248cb44bc9a312e2e19f2aa08a152 Mon Sep 17 00:00:00 2001 From: Cornelia Dahlin <154337428+lunek1@users.noreply.github.com> Date: Wed, 22 May 2024 10:18:34 +0200 Subject: [PATCH 14/28] Update netlify.toml --- netlify.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netlify.toml b/netlify.toml index 95443a1f3..a0ad77f26 100644 --- a/netlify.toml +++ b/netlify.toml @@ -2,5 +2,5 @@ # how it should build the JavaScript assets to deploy from. [build] base = "frontend/" - publish = "build/" + publish = "dist/" command = "npm run build" From 34827965cce7753f118af8370e7db7629c32657a Mon Sep 17 00:00:00 2001 From: Cornelia Dahlin <154337428+lunek1@users.noreply.github.com> Date: Wed, 22 May 2024 10:36:59 +0200 Subject: [PATCH 15/28] Update LoginPage.jsx --- frontend/src/components/LoginPage.jsx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/LoginPage.jsx b/frontend/src/components/LoginPage.jsx index 921639fdd..beb091efb 100644 --- a/frontend/src/components/LoginPage.jsx +++ b/frontend/src/components/LoginPage.jsx @@ -29,14 +29,14 @@ export const LoginPage = () => { if (response.ok) { const { accessToken } = await response.json(); setAccessToken(accessToken); - setError(null); // Clear any previous errors + setError(null); fetchSecret(accessToken); } else { - setError("Invalid email or password"); // Set error message + setError("Invalid email or password"); } } catch (error) { console.error("Login failed:", error); - setError("Failed to log in. Please try again later."); // Set generic error message + setError("Failed to log in. Please try again later."); } }; @@ -80,9 +80,7 @@ export const LoginPage = () => { {accessToken ? ( <> You're in! - - You're in! What would you like to do first? - + What would you like to do first? {secretMessage} Logout From 6435e864c4bf7a2f3be80f633894b1164dd9f843 Mon Sep 17 00:00:00 2001 From: Cornelia Dahlin <154337428+lunek1@users.noreply.github.com> Date: Wed, 22 May 2024 10:37:15 +0200 Subject: [PATCH 16/28] Update AuthForm.jsx --- frontend/src/components/AuthForm.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/AuthForm.jsx b/frontend/src/components/AuthForm.jsx index 9fe1212bb..21c92461a 100644 --- a/frontend/src/components/AuthForm.jsx +++ b/frontend/src/components/AuthForm.jsx @@ -1,4 +1,4 @@ -import { useState } from "react"; // Import useState from react +import { useState } from "react"; import styled from "styled-components"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; From 4b74bee8adcd70dc7632b3abc18126d451b0e375 Mon Sep 17 00:00:00 2001 From: Cornelia Dahlin <154337428+lunek1@users.noreply.github.com> Date: Wed, 22 May 2024 10:38:08 +0200 Subject: [PATCH 17/28] Update SignUpPage.jsx --- frontend/src/components/SignUpPage.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/SignUpPage.jsx b/frontend/src/components/SignUpPage.jsx index e1d2b23f3..7566caa5f 100644 --- a/frontend/src/components/SignUpPage.jsx +++ b/frontend/src/components/SignUpPage.jsx @@ -9,7 +9,7 @@ export const SignUpPage = () => { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [error, setError] = useState(null); - const navigate = useNavigate(); // Access the navigate function + const navigate = useNavigate(); const handleSubmit = async (e) => { e.preventDefault(); @@ -27,7 +27,7 @@ export const SignUpPage = () => { ); if (response.ok) { - navigate("/login"); // Use navigate function to redirect to login page + navigate("/login"); } else { const errorMessage = await response.text(); setError(errorMessage); From c22d173fce9dd1a4888123ad1a438898287f3ed5 Mon Sep 17 00:00:00 2001 From: Cornelia Dahlin <154337428+lunek1@users.noreply.github.com> Date: Wed, 22 May 2024 10:38:23 +0200 Subject: [PATCH 18/28] Update StartPage.jsx --- frontend/src/components/StartPage.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/components/StartPage.jsx b/frontend/src/components/StartPage.jsx index 2eb741442..d85692821 100644 --- a/frontend/src/components/StartPage.jsx +++ b/frontend/src/components/StartPage.jsx @@ -3,7 +3,6 @@ import { StyledHeading } from "./StyledText.jsx"; import { StyledButton } from "./StyledButton.jsx"; import styled from "styled-components"; -// StyledButtonContainer to hold the buttons const StyledButtonContainer = styled.div` display: flex; flex-direction: column; From 9fc07a3235e8ee347b164b54f0a459baea08a21f Mon Sep 17 00:00:00 2001 From: Cornelia Dahlin <154337428+lunek1@users.noreply.github.com> Date: Wed, 22 May 2024 10:49:44 +0200 Subject: [PATCH 19/28] Update LoginPage.jsx --- frontend/src/components/LoginPage.jsx | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/LoginPage.jsx b/frontend/src/components/LoginPage.jsx index beb091efb..3adb81dbf 100644 --- a/frontend/src/components/LoginPage.jsx +++ b/frontend/src/components/LoginPage.jsx @@ -1,15 +1,23 @@ -import { useState } from "react"; -import { Link } from "react-router-dom"; +import { useState, useEffect } from "react"; +import { Link, useLocation } from "react-router-dom"; import { StyledHeading, SmallText, SecretText } from "./StyledText.jsx"; import { StyledButton } from "./StyledButton.jsx"; import { AuthForm, Input } from "./AuthForm.jsx"; export const LoginPage = () => { + const location = useLocation(); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [accessToken, setAccessToken] = useState(null); const [secretMessage, setSecretMessage] = useState(""); const [error, setError] = useState(null); + const [success, setSuccess] = useState(""); + + useEffect(() => { + if (location.state?.message) { + setSuccess(location.state.message); + } + }, [location.state]); const handleSubmit = async (e) => { e.preventDefault(); @@ -29,14 +37,14 @@ export const LoginPage = () => { if (response.ok) { const { accessToken } = await response.json(); setAccessToken(accessToken); - setError(null); + setError(null); fetchSecret(accessToken); } else { setError("Invalid email or password"); } } catch (error) { console.error("Login failed:", error); - setError("Failed to log in. Please try again later."); + setError("Failed to log in. Please try again later."); } }; @@ -88,6 +96,9 @@ export const LoginPage = () => { <> Welcome Back! Ready to dive in? + {success && ( + {success} + )} Date: Wed, 22 May 2024 10:50:20 +0200 Subject: [PATCH 20/28] Update SignUpPage.jsx --- frontend/src/components/SignUpPage.jsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/SignUpPage.jsx b/frontend/src/components/SignUpPage.jsx index 7566caa5f..88b2520bf 100644 --- a/frontend/src/components/SignUpPage.jsx +++ b/frontend/src/components/SignUpPage.jsx @@ -11,7 +11,7 @@ export const SignUpPage = () => { const [error, setError] = useState(null); const navigate = useNavigate(); - const handleSubmit = async (e) => { + const handleSubmit = async (e) => { e.preventDefault(); try { @@ -27,10 +27,15 @@ export const SignUpPage = () => { ); if (response.ok) { - navigate("/login"); + navigate("/login", { + state: { + message: "Account created successfully. You can now log in.", + }, + }); } else { - const errorMessage = await response.text(); - setError(errorMessage); + const errorMessage = await response.json(); + console.error("Error response:", errorMessage); + setError(errorMessage.message || "Signup failed"); } } catch (error) { console.error("Signup failed:", error); From 1e8acb26a6e97b9ab221e947b9c56ff817281f11 Mon Sep 17 00:00:00 2001 From: Cornelia Dahlin <154337428+lunek1@users.noreply.github.com> Date: Wed, 22 May 2024 11:08:55 +0200 Subject: [PATCH 21/28] Update server.js --- backend/server.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/backend/server.js b/backend/server.js index 196715342..06b77f6cc 100644 --- a/backend/server.js +++ b/backend/server.js @@ -46,6 +46,11 @@ const app = express(); app.use(cors()); app.use(express.json()); +app.use(cors({ + origin: 'https://cheerful-froyo-159f59.netlify.app' +})); +app.use(express.json()); + // Routes app.get("/", (req, res) => { const endpoints = expressListEndpoints(app); From db8a46427324d98d941d42e72a8a54eba998d56d Mon Sep 17 00:00:00 2001 From: Cornelia Dahlin <154337428+lunek1@users.noreply.github.com> Date: Wed, 22 May 2024 11:19:00 +0200 Subject: [PATCH 22/28] Update server.js --- backend/server.js | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/backend/server.js b/backend/server.js index 06b77f6cc..b9db93e92 100644 --- a/backend/server.js +++ b/backend/server.js @@ -31,21 +31,23 @@ const User = mongoose.model("User", { // Middleware to authenticate user with access token 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 }); + const accessToken = req.header("Authorization"); + if (!accessToken) { + return res.status(401).json({ error: "Unauthorized: Access token is missing" }); } + + const user = await User.findOne({ accessToken }); + if (!user) { + return res.status(401).json({ error: "Unauthorized: Access token is invalid" }); + } + + req.user = user; + next(); }; const port = process.env.PORT || 8082; const app = express(); -app.use(cors()); -app.use(express.json()); - app.use(cors({ origin: 'https://cheerful-froyo-159f59.netlify.app' })); @@ -86,8 +88,7 @@ app.post("/login", async (req, res) => { }); // Route to access secrets after authentication -app.get("/secrets", authenticateUser); -app.get("/secrets", (req, res) => { +app.get("/secrets", authenticateUser, (req, res) => { res.json({ secret: "This is a super secret message" }); }); From a4238ac8bd33b361883e4253dd39478fb8e74f30 Mon Sep 17 00:00:00 2001 From: Cornelia Dahlin <154337428+lunek1@users.noreply.github.com> Date: Wed, 22 May 2024 11:21:14 +0200 Subject: [PATCH 23/28] Update server.js --- backend/server.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/backend/server.js b/backend/server.js index b9db93e92..9b1571085 100644 --- a/backend/server.js +++ b/backend/server.js @@ -63,13 +63,22 @@ app.get("/", (req, res) => { app.post("/users", async (req, res) => { try { const { name, email, password } = req.body; + if (!name) { + return res.status(400).json({ error: "Name is required" }); + } + if (!email) { + return res.status(400).json({ error: "Email is required" }); + } + if (!password) { + return res.status(400).json({ error: "Password is required" }); + } + const user = new User({ name, email, password: bcrypt.hashSync(password) }); - user.save(); + 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 }); + console.error("User creation failed:", err); + res.status(400).json({ error: "Could not create user. Something went wrong." }); } }); From a5cb66f9c319443abdfc631e7b54f20a27217ea2 Mon Sep 17 00:00:00 2001 From: Cornelia Dahlin <154337428+lunek1@users.noreply.github.com> Date: Wed, 22 May 2024 11:44:29 +0200 Subject: [PATCH 24/28] Update README.md --- README.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index dfa05e177..df7fd1ce3 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,16 @@ # Project Auth API -Replace this readme with your own information about your project. - -Start by briefly describing the assignment in a sentence or two. Keep it short and to the point. +This weeks assignment was to build a backend API and a React frontend. The backend handles user authentication, while the frontend includes a registration form, token storage, and access to restricted (secret) content after login. ## 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? +Backend: +I followed the codealong and added more specific error messages. + +Frontend: +I started by creating separate components for login and signup functionalities. These components communicate with the backend API for user authentication and account creation. Using styled components for styling and React Router for navigation. Error handling was implemented to provide clear feedback to users. You should get errors if you try and create an account that already exists, if you try to log in without creating a user, if you write the wrong username/password, you should get a success message when you've succesfully created a user which will be displayed on the login page ect. If I had more time, I would refine error messages and enhance user experience with features like form validation. I also intended to create a separate component named AccountPage.jsx to handle account-related functions and content. However, due to issues with the login, I the the code intended for the "Account Page" within LoginPage.jsx. This decision was made to resolve the login issues. ## 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. +Backend: +Frontend: From 85205ed5d34afd321f50d1b8c6d6274973fc4401 Mon Sep 17 00:00:00 2001 From: Cornelia Dahlin <154337428+lunek1@users.noreply.github.com> Date: Wed, 22 May 2024 11:44:43 +0200 Subject: [PATCH 25/28] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index df7fd1ce3..7e6759e40 100644 --- a/README.md +++ b/README.md @@ -13,4 +13,5 @@ I started by creating separate components for login and signup functionalities. ## View it live Backend: + Frontend: From 9ec31ffad63de581c3dc3a280a94cc413e42c07b Mon Sep 17 00:00:00 2001 From: Cornelia Dahlin <154337428+lunek1@users.noreply.github.com> Date: Wed, 22 May 2024 11:45:03 +0200 Subject: [PATCH 26/28] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7e6759e40..ec26dc636 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,6 @@ I started by creating separate components for login and signup functionalities. ## View it live -Backend: +Backend: https://project-auth-0pi0.onrender.com/ Frontend: From d06823175cd9bbf3815da14f0e782bc2c5493fd7 Mon Sep 17 00:00:00 2001 From: Cornelia Dahlin <154337428+lunek1@users.noreply.github.com> Date: Wed, 22 May 2024 11:45:20 +0200 Subject: [PATCH 27/28] Delete instructions.md --- instructions.md | 37 ------------------------------------- 1 file changed, 37 deletions(-) delete mode 100644 instructions.md diff --git a/instructions.md b/instructions.md deleted file mode 100644 index eccc02575..000000000 --- a/instructions.md +++ /dev/null @@ -1,37 +0,0 @@ -# Instructions -Your project needs two parts; a backend API, and a React frontend. You'll need to create a `User` model using mongoose, with properties for your registered user, and to store a user's access token. - -Then, on the frontend side of things, you'll need to build up a registration form that POSTs to your API. You'll need to store the access token you get back in the browser using local storage, and then use that token when making other requests to your API. - -Once a user is logged in, you will need to have one last endpoint which returns some content which only logged-in users should be able to access. You can choose what you want this endpoint to return, and if you want to make multiple endpoints, that's fine too. It could return hard-coded data or something from the database - either is fine. Whatever you choose, it should be displayed in the frontend after you've logged in. - -To summarise, your API needs: -- Registration endpoint, to create a new user. -- Sign-in endpoint, to authenticate a returning user. -- An authenticated endpoint which only returns content if the `Authorization` header with the user's token was correct. - -Your frontend needs: -- A registration form. -- A sign-in form. -- A page to show the authenticated content from the API. -- A 'sign out' button that removes the saved access token and redirects the user to the login form. - -## Requirements -- Your API should have routes to register and login, and finally an authenticated endpoint. -- The authenticated endpoint should return a 401 or 403 (see [401 vs. 403 on SO](https://stackoverflow.com/questions/3297048/403-forbidden-vs-401-unauthorized-http-responses)) with an error message if you try to access it without an `Authentication` access token or with an invalid token. -- Your frontend should have a registration form which POSTs to the API to create a new user. -- Your passwords in the database should be encrypted with bcrypt. -- Your API should validate the user input when creating a new user, and return error messages which could be shown by the frontend (displaying the errors in a nice way in the frontend is a stretch goal - it’s fine to just show 'Something went wrong' on the frontend if you run out of time). - -## Stretch goals -So you’ve completed the requirements? Great job! Make sure you've committed and pushed a version of your project before starting on the stretch goals. Remember that the stretch goals are optional. - -### Intermediate Stretch Goals -- Store data in the database for your authenticated data routes. -- When registering, display error messages from the API next to the field which has the error. For example, if the email address is invalid, show an error message next to the email input. -- To challenge yourself, try to implement Google authentication with Firebase. [Here](https://www.freecodecamp.org/news/react-firebase-authentication-and-crud-operations/) you will find detailed tutorial which will guide you through implementation (some of the steps connected to [Material UI](https://mui.com/) components can be replaced with your custom components). - - -### Advanced Stretch Goals -- Add more routes, perhaps even a `POST` route to create new objects in your database as a logged-in user. -- Improve validations in the backend to ensure unique email addresses, or validate the email address format using a regular expression. From 134ce3d9c51fc889379d4c4ad5b451835351728a Mon Sep 17 00:00:00 2001 From: Cornelia Dahlin <154337428+lunek1@users.noreply.github.com> Date: Fri, 24 May 2024 08:05:29 +0200 Subject: [PATCH 28/28] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ec26dc636..78a608b7f 100644 --- a/README.md +++ b/README.md @@ -14,4 +14,4 @@ I started by creating separate components for login and signup functionalities. Backend: https://project-auth-0pi0.onrender.com/ -Frontend: +Frontend: https://cheerful-froyo-159f59.netlify.app/