diff --git a/Frontend/.env b/Frontend/.env new file mode 100644 index 0000000..eea892b --- /dev/null +++ b/Frontend/.env @@ -0,0 +1 @@ +VITE_API_URL=http://192.168.29.61:8080 diff --git a/Frontend/package-lock.json b/Frontend/package-lock.json index fa6a50a..f247e76 100644 --- a/Frontend/package-lock.json +++ b/Frontend/package-lock.json @@ -1,11 +1,11 @@ { - "name": "drive-thru", + "name": "Skycrate", "version": "0.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "drive-thru", + "name": "Skycrate", "version": "0.0.0", "dependencies": { "@reduxjs/toolkit": "^2.6.0", @@ -13,6 +13,7 @@ "lucide-react": "^0.476.0", "react": "^19.0.0", "react-dom": "^19.0.0", + "react-hot-toast": "^2.5.2", "react-icons": "^5.5.0", "react-redux": "^9.2.0", "react-router-dom": "^7.2.0" @@ -1929,8 +1930,7 @@ "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "devOptional": true + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "node_modules/data-view-buffer": { "version": "1.0.2", @@ -2767,6 +2767,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/goober": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.16.tgz", + "integrity": "sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g==", + "license": "MIT", + "peerDependencies": { + "csstype": "^3.0.10" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -4036,6 +4045,23 @@ "react": "^19.0.0" } }, + "node_modules/react-hot-toast": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.5.2.tgz", + "integrity": "sha512-Tun3BbCxzmXXM7C+NI4qiv6lT0uwGh4oAfeJyNOjYUejTsm35mK9iCaYLGv8cBz9L5YxZLx/2ii7zsIwPtPUdw==", + "license": "MIT", + "dependencies": { + "csstype": "^3.1.3", + "goober": "^2.1.16" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, "node_modules/react-icons": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", diff --git a/Frontend/package.json b/Frontend/package.json index 7f2bdff..1cb21f3 100644 --- a/Frontend/package.json +++ b/Frontend/package.json @@ -15,6 +15,7 @@ "lucide-react": "^0.476.0", "react": "^19.0.0", "react-dom": "^19.0.0", + "react-hot-toast": "^2.5.2", "react-icons": "^5.5.0", "react-redux": "^9.2.0", "react-router-dom": "^7.2.0" diff --git a/Frontend/src/components/Sidebar.jsx b/Frontend/src/components/Sidebar.jsx index a667fa9..1b252af 100644 --- a/Frontend/src/components/Sidebar.jsx +++ b/Frontend/src/components/Sidebar.jsx @@ -1,144 +1,171 @@ import { Link } from "react-router-dom"; - +import { toast } from 'react-hot-toast'; +import { useNavigate } from 'react-router-dom'; const Sidebar = () => { + + const navigate = useNavigate(); // Hook for programmatic navigation + + const handleLogout = () => { + // Show loading toast + const loadingToast = toast.loading("Logging out..."); + + // Simulate a delay (for example, network request) + setTimeout(() => { + // Remove the token from localStorage + localStorage.removeItem('token'); // Adjust the key if necessary + + // Redirect user to the homepage + navigate('/'); + + // Show success toast after logout + toast.update(loadingToast, { + render: "Logged out successfully!", + type: "success", + isLoading: false, + autoClose: 2000, + }); + }, 1500); // Simulate a 1.5 second delay before showing success + }; + return ( <> - - + + ); }; -export default Sidebar; +export default Sidebar; \ No newline at end of file diff --git a/Frontend/src/pages/Authentication/Login.jsx b/Frontend/src/pages/Authentication/Login.jsx index 919bf72..8f755b6 100644 --- a/Frontend/src/pages/Authentication/Login.jsx +++ b/Frontend/src/pages/Authentication/Login.jsx @@ -1,14 +1,76 @@ -import React from "react"; +import React, { useState, useEffect } from "react"; import { FiEye, FiEyeOff } from "react-icons/fi"; -import { Link } from "react-router-dom"; +import { Link, useNavigate } from "react-router-dom"; +import toast from "react-hot-toast"; // Import React Hot Toast + + +const API_URL = import.meta.env.VITE_API_URL; // Using .env variable const Login = () => { - const [showPassword, setShowPassword] = React.useState(false); + const [showPassword, setShowPassword] = useState(false); + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [loading, setLoading] = useState(false); + const navigate = useNavigate(); // For navigation + + useEffect(() => { + // Check if token is present in localStorage and redirect to Dashboard + if (localStorage.getItem("token")) { + navigate("/dashboard"); // Redirect to Dashboard + } + }, [navigate]); const togglePassword = () => { setShowPassword(!showPassword); }; + const handleSubmit = async (e) => { + e.preventDefault(); + setLoading(true); + + // Show loading toast + const toastId = toast.loading("Logging in..."); + + try { + const response = await fetch(`${API_URL}/api/login`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + email, + password, + }), + }); + + const data = await response.json(); + + // Dismiss the loading toast after the response + toast.dismiss(toastId); + + if (response.ok) { + // On success, store the token in localStorage + localStorage.setItem("token", data.token); + localStorage.setItem("expiresIn", data.expiresIn); + + // Show success toast + toast.success("Login successful!"); + + // Redirect to Dashboard + navigate("/dashboard"); + } else { + // Show error toast if login fails + toast.error(data.message || "Login failed."); + } + } catch (error) { + // Dismiss the loading toast and show error + toast.dismiss(toastId); + toast.error("An error occurred. Please try again."); + } finally { + setLoading(false); + } + }; + return (
@@ -16,44 +78,56 @@ const Login = () => { Log in -
-
- +
+
+
+ setEmail(e.target.value)} + required + /> +
-
-
-
- - +
+
+
+ - {showPassword ? : } - + Forgot password? +
-
-
- - Forgot password? - -
- + {loading ? "Logging In..." : "Login"} + +

Don’t have an account?{" "} @@ -70,4 +144,4 @@ const Login = () => { ); }; -export default Login; +export default Login; \ No newline at end of file diff --git a/Frontend/src/pages/Authentication/SignUp.jsx b/Frontend/src/pages/Authentication/SignUp.jsx index 2ebe0e7..ab265ce 100644 --- a/Frontend/src/pages/Authentication/SignUp.jsx +++ b/Frontend/src/pages/Authentication/SignUp.jsx @@ -1,41 +1,115 @@ -// eslint-disable-next-line no-unused-vars import React, { useState } from "react"; import { FiEye, FiEyeOff } from "react-icons/fi"; import { Link } from "react-router-dom"; +import toast, { Toaster } from "react-hot-toast"; + +// const API_BASE_URL = process.env.REACT_APP_API_URL; +const API_BASE_URL = import.meta.env.VITE_API_URL; + const SignUp = () => { + const [formData, setFormData] = useState({ + firstname: "", + lastname: "", + email: "", + password: "", + confirmPassword: "", + }); + const [showPassword, setShowPassword] = useState(false); const [showConfirmPassword, setShowConfirmPassword] = useState(false); + const [loading, setLoading] = useState(false); + + const handleChange = (e) => { + setFormData((prev) => ({ + ...prev, + [e.target.name]: e.target.value, + })); + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + + if (formData.password !== formData.confirmPassword) { + toast.error("Passwords do not match."); + return; + } + + try { + setLoading(true); + const toastId = toast.loading("Registering..."); + + const response = await fetch(`${API_BASE_URL}/api/signup`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + firstname: formData.firstname, + lastname: formData.lastname, + email: formData.email, + password: formData.password, + }), + }); + + const data = await response.json(); + + if (response.ok) { + toast.success("Successfully registered!", { id: toastId }); + // Optionally redirect to login + } else { + toast.error(data.message || "Signup failed.", { id: toastId }); + } + } catch (error) { + toast.error("An error occurred. Please try again."); + } finally { + setLoading(false); + } + }; return (

+
-

Sign Up

+

Sign Up

- {/* Form Fields */} -
+
{/* Password Field */}
-
- {/* Sign Up Button */} - + {/* Sign Up Button */} + + {/* Redirect to Login */}

@@ -84,3 +170,92 @@ const SignUp = () => { }; export default SignUp; + + + +// // eslint-disable-next-line no-unused-vars +// import React, { useState } from "react"; +// import { FiEye, FiEyeOff } from "react-icons/fi"; +// import { Link } from "react-router-dom"; + +// const SignUp = () => { +// const [showPassword, setShowPassword] = useState(false); +// const [showConfirmPassword, setShowConfirmPassword] = useState(false); + +// return ( +//

+//
+//

Sign Up

+ +// {/* Form Fields */} +//
+// +// +// + +// {/* Password Field */} +//
+// +// +//
+ +// {/* Confirm Password Field */} +//
+// +// +//
+//
+ +// {/* Sign Up Button */} +// + +// {/* Redirect to Login */} +//

+// Already have an account?{" "} +// +// Login +// +//

+//
+//
+// ); +// }; + +// export default SignUp;