diff --git a/README.md b/README.md index dfa05e177..cf9bd6ce7 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,12 @@ # 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. +An authentication website for signing up and 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? +We got some issues when deployed both frontend and backend. ## 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](https://project-auth-3-ueps.onrender.com/) +[Frontend](https://res-auth.netlify.app/) diff --git a/backend/package.json b/backend/package.json index 8de5c4ce0..2b478afb1 100644 --- a/backend/package.json +++ b/backend/package.json @@ -12,9 +12,12 @@ "@babel/core": "^7.17.9", "@babel/node": "^7.16.8", "@babel/preset-env": "^7.16.11", + "bcrypt": "^5.1.1", "cors": "^2.8.5", "express": "^4.17.3", - "mongoose": "^8.0.0", + "express-list-endpoints": "^7.1.0", + "mongodb": "^6.6.2", + "mongoose": "^8.4.0", "nodemon": "^3.0.1" } } diff --git a/backend/server.js b/backend/server.js index dfe86fb8e..9d0ccdcbc 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1,14 +1,44 @@ import cors from "cors"; import express from "express"; -import mongoose from "mongoose"; +import mongoose, { Schema, model } from "mongoose"; +import bcrypt from "bcrypt"; +import expressListEndpoints from "express-list-endpoints"; -const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/project-mongo"; +const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/project-auth"; mongoose.connect(mongoUrl); mongoose.Promise = Promise; +const userSchema = new Schema({ + name: { + type: String, + required: true, + }, + password: { + type: String, + required: true, + }, + email: { + type: String, + required: true, + unique: true, + }, + token: { + type: String, + default: () => bcrypt.genSaltSync(), + }, + phone: { + type: String, + required: true, + unique: true, + }, +}); + +const User = model("User", userSchema); + // 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 app = express(); @@ -18,7 +48,69 @@ app.use(express.json()); // Start defining your routes here app.get("/", (req, res) => { - res.send("Hello Technigo!"); + const endpoints = expressListEndpoints(app); + res.json(endpoints); +}); + +// Register new user +app.post("/register", async (req, res) => { + try { + const { name, phone, email, password } = req.body; + const salt = bcrypt.genSaltSync(); + const user = new User({ + name, + phone, + email, + password: bcrypt.hashSync(password, salt), + }); + user.save(); + res + .status(201) + .json({ message: `UserID ${user._id} successfully created!` }); + } catch (err) { + res.status(400).json({ + message: "Could not create user.", + errors: err.errors, + }); + } +}); + +// Login +app.post("/login", async (req, res) => { + const matchUser = await User.findOne({ + email: req.body.email, + }); + if (matchUser && bcrypt.compareSync(req.body.password, matchUser.password)) { + res.json({ + message: "User logged in.", + matchUserId: matchUser._id, + token: matchUser.token, + }); + } else { + res.json({ message: "User not found." }); + } +}); + +// Authorize +const authenticateUser = async (req, res, next) => { + const user = await User.findOne({ + token: req.header("Authorization"), + }); + if (user) { + req.user = user; + next(); + } else { + res.status(401).json({ + message: "failed.", + }); + } +}; + +app.get("/secrets", authenticateUser); +app.get("/secrets", (req, res) => { + res.json({ + secret: "This is secrets.", + }); }); // Start the server diff --git a/frontend/.eslintrc.cjs b/frontend/.eslintrc.cjs index 4dcb43901..bb4f312b1 100644 --- a/frontend/.eslintrc.cjs +++ b/frontend/.eslintrc.cjs @@ -2,19 +2,20 @@ module.exports = { root: true, env: { browser: true, es2020: true }, extends: [ - 'eslint:recommended', - 'plugin:react/recommended', - 'plugin:react/jsx-runtime', - 'plugin:react-hooks/recommended', + "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'], + ignorePatterns: ["dist", ".eslintrc.cjs"], + parserOptions: { ecmaVersion: "latest", sourceType: "module" }, + settings: { react: { version: "18.2" } }, + plugins: ["react-refresh"], rules: { - 'react-refresh/only-export-components': [ - 'warn', + "react-refresh/only-export-components": [ + "warn", { allowConstantExport: true }, ], + "react/prop-types": "off", }, -} +}; diff --git a/frontend/package.json b/frontend/package.json index e9c95b79f..07f6c7c53 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,13 +10,15 @@ "preview": "vite preview" }, "dependencies": { + "antd": "^5.17.3", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "react-router-dom": "^6.23.1" }, "devDependencies": { "@types/react": "^18.2.15", "@types/react-dom": "^18.2.7", - "@vitejs/plugin-react": "^4.0.3", + "@vitejs/plugin-react": "^4.2.1", "eslint": "^8.45.0", "eslint-plugin-react": "^7.32.2", "eslint-plugin-react-hooks": "^4.6.0", diff --git a/frontend/public/login.png b/frontend/public/login.png new file mode 100644 index 000000000..9c4f84df3 Binary files /dev/null and b/frontend/public/login.png differ diff --git a/frontend/public/photo1.jpg b/frontend/public/photo1.jpg new file mode 100644 index 000000000..5041f27e5 Binary files /dev/null and b/frontend/public/photo1.jpg differ diff --git a/frontend/public/photo2.jpg b/frontend/public/photo2.jpg new file mode 100644 index 000000000..b5c09bfd5 Binary files /dev/null and b/frontend/public/photo2.jpg differ diff --git a/frontend/public/photo3.jpg b/frontend/public/photo3.jpg new file mode 100644 index 000000000..873822b60 Binary files /dev/null and b/frontend/public/photo3.jpg differ diff --git a/frontend/public/photo4.jpg b/frontend/public/photo4.jpg new file mode 100644 index 000000000..b83171034 Binary files /dev/null and b/frontend/public/photo4.jpg differ diff --git a/frontend/public/signup.png b/frontend/public/signup.png new file mode 100644 index 000000000..b839b5df9 Binary files /dev/null and b/frontend/public/signup.png differ diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 1091d4310..1d2ae7089 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,3 +1,53 @@ +import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom"; +import { Login } from "./Pages/Login"; +import { Signup } from "./Pages/Signup"; +import { NavBar } from "./Pages/NavBar"; +import { HomePage } from "./Pages/HomePage"; + +// Check if Net-Token in local storage is not empty +const checkAuth = () => { + const isLoggedin = localStorage.getItem("Net-Token") !== null; + return isLoggedin; +}; + export const App = () => { - return
Find me in src/app.jsx!
; + return ( + + <> + + + } + /> + + ) : ( + + ) + } + /> + + ) : ( + + ) + } + /> + + + + ); }; diff --git a/frontend/src/Pages/HomePage.jsx b/frontend/src/Pages/HomePage.jsx new file mode 100644 index 000000000..434174892 --- /dev/null +++ b/frontend/src/Pages/HomePage.jsx @@ -0,0 +1,45 @@ +import { Carousel } from "antd"; + +const imageStyle = { + width: "100%", + height: "500px", + objectFit: "cover", +}; + +export const HomePage = ({ checkAuth }) => ( +
+

+ {checkAuth() ? `Nice to see you again!` : `Welcome to Authentication`} +

+ +
+ Slide 1 +
+
+ Slide 2 +
+
+ Slide 3 +
+
+ Slide 4 +
+
+
+); diff --git a/frontend/src/Pages/Login.css b/frontend/src/Pages/Login.css new file mode 100644 index 000000000..43506e3e4 --- /dev/null +++ b/frontend/src/Pages/Login.css @@ -0,0 +1,80 @@ +/* General styles for the form to center everything */ +body { + background-color: #e5e5e5; + font-family: "Poppins", sans-serif; + font-weight: 300; +} + +form { + align-items: center; + padding: 20px; + margin: auto; + max-width: 300px; +} + +/* Styles for the icon image */ +.login-icon { + display: flex; + justify-content: center; + width: 100px; + margin: 0 auto 30px; +} + +/* Styles for headers inside the form */ +h2 { + text-align: center; + margin-bottom: 20px; +} + +/* Styles for form input containers */ +div { + width: 100%; + margin-bottom: 10px; +} + +/* Styles for labels */ +label { + display: block; + text-align: left; + margin-bottom: 5px; +} + +/* Styles for input fields to expand to full width of their container */ +input[type="email"], +input[type="password"] { + width: 100%; + padding: 8px; + box-sizing: border-box; +} + +/* Styles for the submit button */ +button { + width: 100%; + padding: 10px; + background-color: #ffd935; + color: #333; + border: none; + border-radius: 5px; + cursor: pointer; + border-radius: 30px; + font-size: 15px; +} + +/* Additional styling for hover effect on button */ +button:hover { + background-color: #fe9c80; +} + +/* Centering paragraph below the form */ +.login-form p { + text-align: center; + margin-top: 20px; +} + +p { + text-align: center; +} + +.login-form { + margin-top: 30px; +} diff --git a/frontend/src/Pages/Login.jsx b/frontend/src/Pages/Login.jsx new file mode 100644 index 000000000..83ac3f17f --- /dev/null +++ b/frontend/src/Pages/Login.jsx @@ -0,0 +1,91 @@ +import "./Login.css"; +import { useState, useEffect } from "react"; +import { useNavigate } from "react-router-dom"; + +export const Login = () => { + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const navigate = useNavigate(); + + const handleSubmit = async event => { + event.preventDefault(); + const userInfo = { + email, + password, + }; + + try { + const response = await fetch( + "https://project-auth-3-ueps.onrender.com/login", + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(userInfo), + } + ); + + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.message || "An error occurred while logging in."); + } + + data.token && localStorage.setItem("Net-Token", data.token); + navigate("/"); + } catch (error) { + console.error("Login error:", error); + alert(error.message); // Displays the error message from the backend + } + }; + + useEffect(() => { + if (localStorage.getItem("Net-Token")) { + navigate("/"); + } + }, [navigate]); + + return ( + <> +
+ Login Icon +

Login

+
+ + setEmail(e.target.value)} + required + placeholder="name@domain.com" + autoComplete="email" + /> +
+
+ + setPassword(e.target.value)} + placeholder="Type your password" + autoComplete="current-password" + required + /> +
+ +
+

+ Have not signed up yet?Click here to sign up +

+ + ); +}; diff --git a/frontend/src/Pages/NavBar.css b/frontend/src/Pages/NavBar.css new file mode 100644 index 000000000..f1c7a1cc5 --- /dev/null +++ b/frontend/src/Pages/NavBar.css @@ -0,0 +1,57 @@ +.navbar-wrapper { + background-color: #ffffff; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + font-family: "Poppins", sans-serif; + display: flex; + justify-content: space-between; + align-items: center; + padding: 0 20px; + height: 60px; +} + +.navbar-logo { + font-size: 24px; + font-weight: bold; + color: #fa7268; + cursor: pointer; +} + +ul { + list-style-type: none; + display: flex; + margin: 0; + padding: 0; + white-space: nowrap; +} + +.nav-links li { + padding: 10px 15px; + margin-right: 10px; +} + +.nav-links li a { + color: #666; + text-decoration: none; + transition: color 0.3s; +} + +.nav-links li a:hover, +.nav-links li a:focus { + color: #fa7268; +} + +.nav-links li:last-child { + margin-right: 0; +} + +.logout-btn { + width: 90px; + padding: 10px; + background-color: #ffd935; + color: #fff; + border: none; + border-radius: 30px; + cursor: pointer; + margin:0; +} + diff --git a/frontend/src/Pages/NavBar.jsx b/frontend/src/Pages/NavBar.jsx new file mode 100644 index 000000000..7dce79907 --- /dev/null +++ b/frontend/src/Pages/NavBar.jsx @@ -0,0 +1,40 @@ +import "./NavBar.css"; +import { useNavigate } from "react-router-dom"; + +export const NavBar = ({ checkAuth }) => { + const navigate = useNavigate(); + + const handleLogout = () => { + localStorage.removeItem("Net-Token"); + navigate("/login"); + }; + + return ( + + ); +}; diff --git a/frontend/src/Pages/Signup.css b/frontend/src/Pages/Signup.css new file mode 100644 index 000000000..3ad4c3610 --- /dev/null +++ b/frontend/src/Pages/Signup.css @@ -0,0 +1,84 @@ +/* General styles for the form to center everything */ +body { + background-color: #e5e5e5; + font-family: "Poppins", sans-serif; + font-weight: 300; +} + +.signup-wrapper { + display: flex; + flex-direction: column; + align-items: center; + padding: 20px; + margin: auto; + max-width: 300px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + background-color: #fff; + border-radius: 8px; + margin-top: 35px; +} + +/* Styles for the icon image */ +.signup-icon { + width: 100px; + margin-bottom: 20px; +} + +/* Styles for headers inside the form */ +h2 { + text-align: center; + margin-bottom: 20px; + color: #333; +} + +/* Styles for form input containers */ +div { + width: 100%; + margin-bottom: 10px; +} + +/* Styles for labels */ +label { + display: block; + text-align: left; + margin-bottom: 5px; +} + +/* Styles for input fields to expand to full width of their container */ +input[type="text"], +input[type="email"], +input[type="tel"], +input[type="password"] { + width: 100%; + padding: 8px; + box-sizing: border-box; + border: 1px solid #ccc; + border-radius: 4px; +} + +/* Styles for the submit button */ +button { + width: 100%; + padding: 10px; + background-color: #ffd935; + color: #fff; + border: none; + border-radius: 30px; + cursor: pointer; + margin-top: 20px; +} + +/* Additional styling for hover effect on button */ +button:hover { + background-color: #e0665e; +} + +/* Centering paragraph below the form */ +.signup-wrapper p { + text-align: center; + margin-top: 20px; +} + +p { + margin-top: 20px; +} diff --git a/frontend/src/Pages/Signup.jsx b/frontend/src/Pages/Signup.jsx new file mode 100644 index 000000000..eb48d5bfe --- /dev/null +++ b/frontend/src/Pages/Signup.jsx @@ -0,0 +1,116 @@ +import "./Signup.css"; +import { useState, useEffect } from "react"; +import { useNavigate } from "react-router-dom"; + +export const Signup = () => { + const [name, setName] = useState(""); + const [email, setEmail] = useState(""); + const [phone, setPhone] = useState(""); + const [password, setPassword] = useState(""); + const [confirmPassword, setConfirmPassword] = useState(""); + const navigate = useNavigate(); + + const handleSubmit = async (event) => { + event.preventDefault(); + const newUser = { + name, + email, + phone, + password, + }; + + try { + const response = await fetch( + "https://project-auth-3-ueps.onrender.com/register", + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(newUser), + } + ); + + if (!response.ok) { + const errorData = await response.json(); // Assuming the server sends back a JSON error message + throw new Error(errorData.message || "Failed to register"); + } + + const data = await response.json(); + console.log("Registration successful:", data); + navigate("/"); + } catch (error) { + console.error("Registration error:", error); + alert("Registration failed: " + error.message); + } + }; + + useEffect(() => { + if (localStorage.getItem("Net-Token")) { + navigate("/"); + } + }, [navigate]); + + return ( + <> +
+ Sign Up +

Sign Up

+
+ + setName(e.target.value)} + required + /> +
+
+ + setEmail(e.target.value)} + required + /> +
+
+ + setPhone(e.target.value)} + required + /> +
+
+ + setPassword(e.target.value)} + required + /> +
+
+ + setConfirmPassword(e.target.value)} + required + /> +
+ +
+

+ Already have an account? Click here to log in. +

+ + ); +}; diff --git a/frontend/src/index.css b/frontend/src/index.css index 3e560a674..6141a02a2 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -10,4 +10,13 @@ code { font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; +} + +* { + margin:0; + border: 0; +} + +h1 { + padding: 20px; } \ No newline at end of file diff --git a/netlify.toml b/netlify.toml index 95443a1f3..c05012bee 100644 --- a/netlify.toml +++ b/netlify.toml @@ -1,6 +1,11 @@ # 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/" + base = "frontend" + publish = "dist" command = "npm run build" + +[[redirects]] + from = "/*" + to = "/index.html" + status = 200 \ No newline at end of file diff --git a/package.json b/package.json index d774b8cc3..5597ab044 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,10 @@ "name": "project-auth-parent", "version": "1.0.0", "scripts": { - "postinstall": "npm install --prefix backend" + "postinstall": "npm install --prefix backend", + "dev": "vite" + }, + "devDependencies": { + "vite": "^5.2.11" } }