diff --git a/backend/authenticateUser.js b/backend/authenticateUser.js
new file mode 100644
index 000000000..ce69d6435
--- /dev/null
+++ b/backend/authenticateUser.js
@@ -0,0 +1,26 @@
+import { UserModel } from "./models/UserModel"
+
+
+
+export const authenticateUser = async(req , res , next) =>{
+ const accessToken = req.header("Authorization")
+
+ if (
+ req.headers.authorization &&
+ req.headers.authorization.startsWith("Bearer")
+ )
+ try{
+ const user = await UserModel.findOne({accessToken: accessToken})
+ if(user){
+ req.user = user
+ next()
+ }else{
+ res.status(401).json({success: false , response: "please log in" })
+ }
+ }
+ catch(e){
+ res.status(500).json({success: false , response: e.message })
+ }
+}
+
+
diff --git a/backend/models/UserModel.js b/backend/models/UserModel.js
new file mode 100644
index 000000000..16fc24840
--- /dev/null
+++ b/backend/models/UserModel.js
@@ -0,0 +1,37 @@
+import mongoose from "mongoose";
+import crypto from "crypto";
+
+const { Schema } = mongoose;
+
+
+const userSchema = new Schema({
+ username: {
+ type: String,
+ required: true,
+ unique: true,
+ minlenght: 2
+ },
+ password: {
+ type: String,
+ required: true,
+ minlenght: 6
+
+ },
+ email: {
+ type: String,
+ required: true,
+ unique: true,
+
+ },
+ accessToken: {
+ type: String,
+ default: () => crypto.randomBytes(128).toString("hex"),
+ required: false
+ }
+},
+{
+ 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..62595567e 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -12,8 +12,13 @@
"@babel/core": "^7.17.9",
"@babel/node": "^7.16.8",
"@babel/preset-env": "^7.16.11",
+ "bcrypt": "^5.1.1",
"cors": "^2.8.5",
+ "crypto": "^1.0.1",
+ "dotenv": "^16.3.1",
"express": "^4.17.3",
+ "express-async-handler": "^1.2.0",
+ "jsonwebtoken": "^9.0.2",
"mongoose": "^8.0.0",
"nodemon": "^3.0.1"
}
diff --git a/backend/routes/userRoutes.js b/backend/routes/userRoutes.js
new file mode 100644
index 000000000..0d0dad52a
--- /dev/null
+++ b/backend/routes/userRoutes.js
@@ -0,0 +1,99 @@
+
+import express from "express";
+import bcrypt from "bcrypt";
+import jwt from "jsonwebtoken";
+
+import { UserModel } from "../models/UserModel";
+import asyncHandler from "express-async-handler";
+import dotenv from "dotenv";
+import { authenticateUser } from "../authenticateUser";
+dotenv.config()
+
+
+
+
+
+const router = express.Router()
+
+const geneerateToken = (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 = bcrypt.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: geneerateToken(newUser)
+
+ }
+ });
+ }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 = await bcrypt.compare(password , user.password);
+ if(!isMatch){
+ return res.status(401).json({success: false , response: "incorrect password"});
+ }
+ res.status(200).json({success: true , response: {
+
+ username: user.username,
+ email: user.email,
+ id: user._id,
+ accessToken: geneerateToken(user)
+
+
+ }})
+ } catch (error) {
+ res.status(500).json({success: false , response: {
+
+ message: error.message
+
+
+ }})
+ }
+
+}))
+
+router.get("/logged-in", authenticateUser, asyncHandler(async (req, res) => {
+ res.status(200).json({success: true , response: {
+ message: "user is logged in" }})
+ }))
+
+export default router
\ No newline at end of file
diff --git a/backend/server.js b/backend/server.js
index 2d7ae8aa1..9ef0b69da 100644
--- a/backend/server.js
+++ b/backend/server.js
@@ -1,6 +1,9 @@
-import express from "express";
+import express, { urlencoded } from "express";
import cors from "cors";
import mongoose from "mongoose";
+import dotenv from "dotenv";
+dotenv.config()
+import userRoutes from "./routes/userRoutes"
const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/project-mongo";
mongoose.connect(mongoUrl, { useNewUrlParser: true, useUnifiedTopology: true });
@@ -15,6 +18,10 @@ 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(userRoutes);
+
+
// Start defining your routes here
app.get("/", (req, res) => {
diff --git a/frontend/package.json b/frontend/package.json
index e9c95b79f..5dba88de0 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -10,8 +10,12 @@
"preview": "vite preview"
},
"dependencies": {
+ "lottie-react": "^2.4.0",
"react": "^18.2.0",
- "react-dom": "^18.2.0"
+ "react-dom": "^18.2.0",
+ "react-router-dom": "^6.20.1",
+ "styled-components": "^6.1.1",
+ "zustand": "^4.4.7"
},
"devDependencies": {
"@types/react": "^18.2.15",
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index 1091d4310..768d5a05a 100644
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -1,3 +1,14 @@
+import { BrowserRouter , Routes } from "react-router-dom";
+import {routes} from "./routes/RoutesComp"
+
+
+
export const App = () => {
- return
Find me in src/app.jsx!
;
+ return ;
};
diff --git a/frontend/src/components/LogIn.jsx b/frontend/src/components/LogIn.jsx
new file mode 100644
index 000000000..ec90cd0cf
--- /dev/null
+++ b/frontend/src/components/LogIn.jsx
@@ -0,0 +1,87 @@
+import { useEffect } from "react";
+import { useNavigate } from "react-router-dom";
+import { userStore } from "./UserStore";
+
+
+const apiEnv = import.meta.env.VITE_BACKEND_API;
+
+export const Login = () => {
+
+ const {
+ username,
+ setUsername,
+ password,
+ setPassword,
+ user,
+ setUser,
+ setAccessToken,
+ } = userStore();
+ const navigate = useNavigate();
+
+ const handleLogin = async () => {
+
+ 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 }),
+ });
+ console.log(response);
+
+
+ if (response.ok) {
+
+ const data = await response.json();
+
+ setAccessToken(data.accessToken);
+
+ setUser(data);
+
+ localStorage.setItem("accessToken", data.accessToken);
+ setUsername("");
+ setPassword("");
+
+ alert("Login successful!");
+ navigate("/logged-in");
+ } else {
+
+ const errorData = await response.json();
+ alert(`Login failed: ${errorData.error}`);
+ }
+ } catch (error) {
+
+ alert("Error during login:", error.message);
+ }
+ };
+
+
+ useEffect(() => {
+ console.log("User:", user);
+ }, [user]);
+
+ return (
+
+ setUsername(e.target.value)}
+ />
+ setPassword(e.target.value)}
+ />
+
+
+ );
+};
\ No newline at end of file
diff --git a/frontend/src/components/LogOut.jsx b/frontend/src/components/LogOut.jsx
new file mode 100644
index 000000000..ecef17a47
--- /dev/null
+++ b/frontend/src/components/LogOut.jsx
@@ -0,0 +1,28 @@
+import { useNavigate } from "react-router-dom";
+import { userStore } from "./UserStore";
+
+
+export const Logout = () => {
+
+ const navigate = useNavigate();
+
+ const { setAccessToken, setIsLoggedIn, setUser } = userStore();
+
+
+ const handleLogOut = () => {
+
+ setUser(null);
+ setAccessToken(null);
+ setIsLoggedIn(false);
+ localStorage.removeItem("accessToken");
+
+ alert("Log out successful!");
+ navigate("/");
+ };
+
+ return (
+ <>
+
+ >
+ );
+};
\ No newline at end of file
diff --git a/frontend/src/components/LogedInComponet.jsx b/frontend/src/components/LogedInComponet.jsx
new file mode 100644
index 000000000..85f947e88
--- /dev/null
+++ b/frontend/src/components/LogedInComponet.jsx
@@ -0,0 +1,59 @@
+import { useNavigate } from "react-router-dom";
+import { useEffect } from "react";
+import { userStore } from "./UserStore";
+
+const apiEnv = import.meta.env.VITE_BACKEND_API;
+
+export const LogedInComp = () => {
+
+ const { accessToken, isLoggedIn, setIsLoggedIn } = userStore();
+
+ const navigate = useNavigate();
+
+
+ useEffect(() => {
+ const fetchLoggedInData = async () => {
+ try {
+
+ const response = await fetch(`${apiEnv}/logged-in`, {
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `${accessToken}`,
+ },
+ });
+
+
+ if (response.ok) {
+
+ const data = await response.json();
+ console.log(data);
+
+ setIsLoggedIn(true);
+ } else {
+
+ console.error("Failed to fetch authenticated content");
+ navigate("/");
+ }
+ } catch (error) {
+
+ console.error("Error fetching authenticated content:", error);
+ }
+ };
+
+
+ fetchLoggedInData();
+ });
+
+ return (
+ isLoggedIn && (
+
+

+
Congratulations! You are logged in. Here is a cute puppy for you!
+
+ )
+ );
+};
\ No newline at end of file
diff --git a/frontend/src/components/SignUp.jsx b/frontend/src/components/SignUp.jsx
new file mode 100644
index 000000000..26b409fd9
--- /dev/null
+++ b/frontend/src/components/SignUp.jsx
@@ -0,0 +1,73 @@
+import { useState } from "react";
+
+
+const apiEnv = import.meta.env.VITE_BACKEND_API;
+
+
+export const SignUp = () => {
+
+ const [email, setEmail] = useState("");
+ const [username, setUsername] = useState("");
+ const [password, setPassword] = useState("");
+
+
+ const handleSignup = async () => {
+ try {
+ console.log("URL:", `${apiEnv}/register`);
+ console.log("Request Body:", { email, username, password });
+
+ const response = await fetch(`${apiEnv}/register`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+
+ body: JSON.stringify({ email, username, password }),
+
+ });
+
+
+ if (response.ok) {
+ alert("Signup was successful, please log in!");
+ setEmail("")
+ setUsername("")
+ setPassword("")
+ } else {
+ const data = await response.json();
+ console.log(data)
+ alert("Signup failed:" + data.message);
+ }
+ } catch (error) {
+
+ alert("Error during registration:", error);
+ }
+ };
+
+
+
+ return (
+ <>
+
+ setEmail(e.target.value)}
+ />
+ setUsername(e.target.value)}
+ />
+ setPassword(e.target.value)}
+ />
+
+
+ >
+ );
+};
\ No newline at end of file
diff --git a/frontend/src/components/UserStore.jsx b/frontend/src/components/UserStore.jsx
new file mode 100644
index 000000000..92c601000
--- /dev/null
+++ b/frontend/src/components/UserStore.jsx
@@ -0,0 +1,18 @@
+import { create } from "zustand";
+
+
+export const userStore = create((set) => ({
+
+ username: "",
+ password: "",
+ accessToken: null,
+ user: null,
+ isLoggedIn: false,
+
+
+ setUsername: (username) => set({ username }),
+ setPassword: (password) => set({ password }),
+ setAccessToken: (accessToken) => set({ accessToken }),
+ setUser: (user) => set({ user }),
+ setIsLoggedIn: (isLoggedIn) => set({ isLoggedIn }),
+}));
\ No newline at end of file
diff --git a/frontend/src/index.css b/frontend/src/index.css
index 3e560a674..92be80071 100644
--- a/frontend/src/index.css
+++ b/frontend/src/index.css
@@ -10,4 +10,100 @@
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
monospace;
+}
+
+
+body {
+ display: flex;
+ justify-content: center;
+ place-items: center;
+ min-width: 320px;
+ min-height: 100vh;
+ background-color: #a9ddc1;
+}
+
+p {
+ margin: 5px;
+}
+
+.wrapper {
+ display: flex;
+ flex-direction: column;
+ gap: 3rem;
+ padding: 2rem;
+}
+
+.logged-in-content img{
+ max-width: 100%;
+}
+
+.logged-in-wrapper {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 1rem;
+ max-width: 250px;
+ background-color: #d4aeb1;
+ border-radius: 5px;
+ padding: 1rem;
+ text-align: center;
+}
+
+.login-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 1rem;
+ background-color: #e4c1c3;
+ border-radius: 5px;
+}
+
+.login-form {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+.signup-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 1rem;
+ background-color: #e5c1c4;
+ border-radius: 5px;
+}
+
+.signup-form {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+ input {
+ padding: 10px;
+ margin-bottom: 10px;
+ border-radius: 5px;
+ border: none;
+ width: 70%;
+}
+
+button {
+ padding: 10px;
+ margin-bottom: 10px;
+ border-radius: 20px;
+ border: none;
+ width: 70%;
+ background-color: rgb(64, 0, 255);
+ color: white;
+ font-weight: 700;
+}
+
+button:hover {
+ background-color: #3a5640;
+ cursor: pointer;
+}
+
+p {
+ font-weight: 700;
+ color: white;
}
\ No newline at end of file
diff --git a/frontend/src/pages/LandingPage.jsx b/frontend/src/pages/LandingPage.jsx
new file mode 100644
index 000000000..183efd131
--- /dev/null
+++ b/frontend/src/pages/LandingPage.jsx
@@ -0,0 +1,19 @@
+import { Login } from "../components/LogIn";
+import { SignUp } from "../components/SignUp";
+
+export const LandingPage = () => {
+ return (
+ <>
+
+
+
+
No account?
+
Please sign up:
+
+
+ >
+ );
+};
\ No newline at end of file
diff --git a/frontend/src/pages/LogedInPage.jsx b/frontend/src/pages/LogedInPage.jsx
new file mode 100644
index 000000000..07a998e28
--- /dev/null
+++ b/frontend/src/pages/LogedInPage.jsx
@@ -0,0 +1,12 @@
+
+import { LogedInComp } from "../components/LogedInComponet";
+import { Logout } from "../components/LogOut";
+
+export const LogedInPage = () => {
+ return (
+
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/frontend/src/routes/RoutesComp.jsx b/frontend/src/routes/RoutesComp.jsx
new file mode 100644
index 000000000..bc97c0a90
--- /dev/null
+++ b/frontend/src/routes/RoutesComp.jsx
@@ -0,0 +1,10 @@
+import { Route } from "react-router-dom";
+import { LandingPage } from "../pages/LandingPage";
+import { LogedInPage } from "../pages/LogedInPage";
+
+export const routes = (
+ <>
+ }>
+ }>
+ >
+);
\ No newline at end of file
diff --git a/netlify.toml b/netlify.toml
deleted file mode 100644
index 95443a1f3..000000000
--- a/netlify.toml
+++ /dev/null
@@ -1,6 +0,0 @@
-# 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/"
- command = "npm run build"
diff --git a/package.json b/package.json
deleted file mode 100644
index d774b8cc3..000000000
--- a/package.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "name": "project-auth-parent",
- "version": "1.0.0",
- "scripts": {
- "postinstall": "npm install --prefix backend"
- }
-}