diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 2807869..efc3dc3 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -10,6 +10,8 @@ "dependencies": { "axios": "^1.13.2", "framer-motion": "^12.23.26", + "jwt-decode": "^4.0.0", + "lucide-react": "^0.562.0", "react": "^19.2.0", "react-dom": "^19.2.0", "react-icons": "^5.5.0", @@ -2673,6 +2675,14 @@ "node": ">=6" } }, + "node_modules/jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "engines": { + "node": ">=18" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -2743,6 +2753,14 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-react": { + "version": "0.562.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.562.0.tgz", + "integrity": "sha512-82hOAu7y0dbVuFfmO4bYF1XEwYk/mEbM5E+b1jgci/udUBEE/R7LF5Ip0CCEmXe8AybRM8L+04eP+LGZeDvkiw==", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 1793da2..7ddcad9 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,6 +12,8 @@ "dependencies": { "axios": "^1.13.2", "framer-motion": "^12.23.26", + "jwt-decode": "^4.0.0", + "lucide-react": "^0.562.0", "react": "^19.2.0", "react-dom": "^19.2.0", "react-icons": "^5.5.0", diff --git a/frontend/src/Admin/HomeAdmin.jsx b/frontend/src/Admin/HomeAdmin.jsx new file mode 100644 index 0000000..2730b86 --- /dev/null +++ b/frontend/src/Admin/HomeAdmin.jsx @@ -0,0 +1,210 @@ +// src/Admin/HomeAdmin.jsx - 100% WORKING (react-icons/fa only) +import React, { useEffect, useState } from "react"; +import { motion } from "framer-motion"; +import { useAuth } from "../utils/auth"; +import { useNavigate } from "react-router-dom"; +import { FaShoppingCart, FaUsers, FaBox, FaRupeeSign, FaChartLine, FaChartBar, FaSignOutAlt, FaUser } from "react-icons/fa"; + +const stats = [ + { title: "Revenue", value: "₹2,45,000", change: "+12%", icon: FaRupeeSign, color: "emerald" }, + { title: "Orders", value: "1,240", change: "+8%", icon: FaShoppingCart, color: "blue" }, + { title: "Customers", value: "860", change: "+15%", icon: FaUsers, color: "purple" }, + { title: "Products", value: "320", icon: FaBox, color: "orange" }, +]; + +const recentOrders = [ + { id: "#1021", customer: "Rahul K.", amount: "₹4,500", status: "Delivered", date: "2025-12-26" }, + { id: "#1022", customer: "Sneha M.", amount: "₹2,200", status: "Pending", date: "2025-12-27" }, + { id: "#1023", customer: "Amit S.", amount: "₹6,800", status: "Cancelled", date: "2025-12-27" }, +]; + +const HomeAdmin = () => { + const { user, logout } = useAuth(); + const navigate = useNavigate(); + const [time, setTime] = useState(new Date()); + + useEffect(() => { + const interval = setInterval(() => setTime(new Date()), 60000); + return () => clearInterval(interval); + }, []); + + const handleLogout = () => { + logout(); + navigate("/"); + }; + + return ( +
+ {/* Header */} + +
+

+ Admin Dashboard +

+

+ Welcome back, {user?.username}! +

+
+ +
+
+ + {user?.username} + ADMIN +
+ +
+
+ + {/* Stats Cards */} + + {stats.map(({ title, value, change, icon: Icon, color }, index) => ( + +
+
+

{title}

+

{value}

+ {change && ( +

+ + {change} from last month +

+ )} +
+
+ +
+
+
+ ))} +
+ + {/* Main Content */} +
+ {/* Sales Chart */} + +
+

+ Sales Overview + Live Data +

+ +
+
+
+ +

Interactive Sales Chart

+

Real-time analytics coming soon

+
+
+
+ + {/* Quick Stats */} + +

Top Products

+
+ {[ + { name: "RO Water Purifier", sales: "120", trend: "+24%" }, + { name: "UV Filter Kit", sales: "95", trend: "+18%" }, + { name: "Service AMC", sales: "70", trend: "+12%" }, + { name: "Installation", sales: "58", trend: "+9%" }, + ].map((product, index) => ( + + {product.name} +
+

{product.sales}

+

{product.trend}

+
+
+ ))} +
+
+
+ + {/* Recent Orders Table */} + +
+

Recent Orders

+ +
+ +
+ + + + + + + + + + + + {recentOrders.map((order) => ( + + + + + + + + ))} + +
OrderCustomerAmountStatusDate
{order.id} + {order.customer} + {order.amount} + + {order.status} + + {order.date}
+
+
+
+ ); +}; + +export default HomeAdmin; diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 170f592..953821a 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,4 +1,5 @@ -import { Routes, Route } from "react-router-dom"; +// src/App.jsx - PERFECT: ANY USER=LandingPage | ANY ADMIN=HomeAdmin +import { Routes, Route, Navigate } from "react-router-dom"; import LandingPage from "./Pages/LandingPage"; import Login from "./Authentication/Login"; import Signup from "./Authentication/Signup"; @@ -7,24 +8,97 @@ import ProductDetails from "./Pages/ProductsDetails"; import Cart from "./Pages/Cart"; import AboutUs from "./Pages/About"; import Contact from "./Pages/Contact"; +import HomeAdmin from "./Admin/HomeAdmin"; +import ProtectedRoute from "./ProtectedRoutes/ProtectedRote"; +import Settings from "./Profile/ProfileSettings"; +import Profile from "./Profile/Profile"; +import Navbar from "./reusableComponents/Navbar"; +import { useAuth } from "./utils/auth"; -export default function App() { +function LoadingSpinner() { return ( -
- - } /> - } /> - } /> +
+
+
+ ); +} - } /> - } /> +// ✅ PERFECT LOGIC: role="user" → LandingPage | role="admin" → HomeAdmin +function HomePage() { + const { user, loading } = useAuth(); - } /> + console.log("🎯 HomePage:", { + userId: user?.id, + role: user?.role, + isUser: user?.role === "user", + isAdmin: user?.role === "admin", + loading + }); - {/* NEW ROUTES */} - } /> - } /> - -
+ if (loading) return ; + + // ✅ GUEST → LandingPage + if (!user) { + console.log("✅ GUEST → LANDING PAGE"); + return ( + <> + + + + ); + } + + // ✅ ANY USER (role="user") → LANDING PAGE + if (user.role === "user") { + console.log("✅ USER (", user.id, ") → LANDING PAGE"); + return ( + <> + + + + ); + } + + // ✅ ANY ADMIN (role="admin") → HOMEADMIN + if (user.role === "admin") { + console.log("✅ ADMIN (", user.id, ") → HOMEADMIN"); + return ( + <> + + + + ); + } + + // ✅ FALLBACK → LANDING PAGE + console.log("✅ FALLBACK → LANDING PAGE"); + return ( + <> + + + + ); +} + +export default function App() { + return ( + + } /> + } /> + } /> + } /> + } /> + + } /> + } /> + } /> + + } /> + + } /> + } /> + + } /> + ); } diff --git a/frontend/src/Authentication/Login.jsx b/frontend/src/Authentication/Login.jsx index dc2a19a..ef39d0c 100644 --- a/frontend/src/Authentication/Login.jsx +++ b/frontend/src/Authentication/Login.jsx @@ -1,47 +1,176 @@ -// Login.jsx -import React, { useState } from "react"; +// src/Authentication/Login.jsx - FULL WORKING CODE +import React, { useState, useEffect } from "react"; +import { useNavigate, Link, useLocation } from "react-router-dom"; import Input from "../reusableComponents/Input"; -import { useNavigate, Link } from "react-router-dom"; import { loginUser } from "../Services/authService"; +import { useAuth } from "../utils/auth"; export default function Login() { const [form, setForm] = useState({ identifier: "", password: "" }); const [errors, setErrors] = useState({}); + const [isSubmitting, setIsSubmitting] = useState(false); const navigate = useNavigate(); + const location = useLocation(); + const { login, user } = useAuth(); - const handleLogin = async (e) => { + // ✅ AUTO REDIRECT if already logged in + useEffect(() => { + if (user) { + console.log("🔄 AUTO REDIRECT - Already logged in:", user.id, user.role); + navigate("/", { replace: true }); + } + }, [user, navigate]); + + const handleSubmit = async (e) => { e.preventDefault(); + setErrors({}); - let newErrors = {}; - if (!form.identifier) newErrors.identifier = "Email or Username required"; - if (!form.password) newErrors.password = "Password required"; - setErrors(newErrors); - if (Object.keys(newErrors).length !== 0) return; + if (!form.identifier.trim() || !form.password.trim()) { + setErrors({ general: "Please fill all fields" }); + return; + } + setIsSubmitting(true); + try { - const res = await loginUser(form); - localStorage.setItem("token", res.data.access_token); - navigate("/products"); - } catch (err) { - console.error(err.response); - alert(err.response?.data?.detail || "Invalid credentials"); + console.log("🔍 LOGIN API CALL..."); + const response = await loginUser(form); + console.log("🔍 LOGIN RESPONSE:", response.data); + + const token = response.data.tokens.access; + + // ✅ STORE BACKEND ROLE + USER INFO (CRITICAL!) + localStorage.setItem('userRole', response.data.role); + localStorage.setItem('username', response.data.username); + localStorage.setItem('fullname', response.data.fullname); + + console.log("💾 STORED ROLE:", response.data.role); + + // ✅ Trigger AuthProvider + login(token); + + // ✅ Wait for state update then redirect + setTimeout(() => { + console.log("✅ LOGIN COMPLETE - Redirecting..."); + navigate("/", { replace: true }); + }, 100); + + } catch (error) { + console.error("❌ LOGIN ERROR:", error.response?.data); + setErrors({ + general: error.response?.data?.error || + error.response?.data?.detail || + "Login failed. Please try again." + }); + } finally { + setIsSubmitting(false); } }; + const handleInputChange = (e) => { + const { name, value } = e.target; + setForm(prev => ({ ...prev, [name]: value })); + if (errors[name]) setErrors(prev => ({ ...prev, [name]: '' })); + }; + return ( -
-
- setForm({ ...form, identifier: e.target.value })} placeholder="Enter email or username" error={errors.identifier} /> - setForm({ ...form, password: e.target.value })} placeholder="Enter password" error={errors.password} /> - - - -

- Don’t have an account? Sign Up -

-
+
+
+ {/* Header */} +
+
+ + + +
+

+ Welcome Back +

+

Sign in to your account

+
+ + {/* Success Message */} + {location.state?.message && ( +
+ {location.state.message} +
+ )} + + {/* Form */} +
+ + + + + {/* Error Message */} + {errors.general && ( +
+ {errors.general} +
+ )} + + {/* Submit Button */} + +
+ + {/* Footer */} +
+

+ Don't have an account? + + Sign up here + +

+

+ Forgot password? + + Reset now + +

+
+
); } diff --git a/frontend/src/Authentication/Signup.jsx b/frontend/src/Authentication/Signup.jsx index 47f5515..a58aa00 100644 --- a/frontend/src/Authentication/Signup.jsx +++ b/frontend/src/Authentication/Signup.jsx @@ -1,82 +1,246 @@ -// Signup.jsx +// src/Authentication/Signup.jsx - FULL PRODUCTION READY import React, { useState } from "react"; +import { useNavigate, Link, useLocation } from "react-router-dom"; import Input from "../reusableComponents/Input"; -import { useNavigate, Link } from "react-router-dom"; -import { registerUser } from "../Services/authService"; +import { registerUser, registerAdmin } from "../Services/authService"; export default function Signup() { const [form, setForm] = useState({ - fullname: "", - username: "", - email: "", - mobile: "", - password: "", - confirm_password: "", - role: "user", + fullname: "", username: "", email: "", mobile: "", password: "", confirm_password: "" }); const [errors, setErrors] = useState({}); + const [isSubmitting, setIsSubmitting] = useState(false); + const [registeringAs, setRegisteringAs] = useState("user"); const navigate = useNavigate(); + const location = useLocation(); - const handleChange = (key, value) => setForm({ ...form, [key]: value }); - - const handleSignup = async (e) => { - e.preventDefault(); - - // Frontend validation - let newErrors = {}; - if (!form.fullname) newErrors.fullname = "Full name is required"; - if (!form.username) newErrors.username = "Username is required"; - if (!form.email) newErrors.email = "Email is required"; - if (!form.password) newErrors.password = "Password is required"; - if (!form.confirm_password) newErrors.confirm_password = "Confirm password is required"; - if (form.password !== form.confirm_password) newErrors.confirm_password = "Passwords do not match"; + const validateForm = () => { + const newErrors = {}; + + if (!form.fullname.trim()) newErrors.fullname = "Full name is required"; + if (!form.username.trim()) newErrors.username = "Username is required"; + if (!form.email.trim()) newErrors.email = "Email is required"; + if (!form.mobile.trim()) newErrors.mobile = "Mobile number is required"; + if (form.password.length < 6) newErrors.password = "Password must be at least 6 characters"; + if (form.password !== form.confirm_password) newErrors.confirm_password = "Passwords don't match"; + setErrors(newErrors); + return Object.keys(newErrors).length === 0; + }; - if (Object.keys(newErrors).length !== 0) return; - - const payload = { - fullname: form.fullname, - username: form.username, - email: form.email, - mobile: form.mobile, - password: form.password, - confirm_password: form.confirm_password, - }; + const handleSubmit = async (e) => { + e.preventDefault(); + if (!validateForm()) return; + setIsSubmitting(true); + setErrors({}); + try { - await registerUser(form.role, payload); - alert("Registration successful"); - navigate("/login"); - } catch (err) { - console.error(err.response); - alert(err.response?.data?.detail || err.response?.data || "Registration failed"); + console.log(`🔍 REGISTER ${registeringAs.toUpperCase()}...`); + + if (registeringAs === "admin") { + await registerAdmin(form); + } else { + await registerUser(form); + } + + console.log(`✅ ${registeringAs.toUpperCase()} REGISTERED!`); + navigate("/login", { + state: { + message: `${registeringAs.charAt(0).toUpperCase() + registeringAs.slice(1)} account created successfully! Please login.` + } + }); + + } catch (error) { + console.error("❌ REGISTRATION ERROR:", error.response?.data); + setErrors({ + general: error.response?.data?.detail || + error.response?.data?.error || + "Registration failed. Please try again." + }); + } finally { + setIsSubmitting(false); } }; + const handleInputChange = (e) => { + const { name, value } = e.target; + setForm(prev => ({ ...prev, [name]: value })); + if (errors[name]) setErrors(prev => ({ ...prev, [name]: '' })); + }; + return ( -
-
- handleChange("fullname", e.target.value)} placeholder="Enter full name" error={errors.fullname} /> - handleChange("username", e.target.value)} placeholder="Enter username" error={errors.username} /> - handleChange("email", e.target.value)} placeholder="Enter email" error={errors.email} /> - handleChange("mobile", e.target.value)} placeholder="Enter mobile number" error={errors.mobile} /> - handleChange("password", e.target.value)} placeholder="Enter password" error={errors.password} /> - handleChange("confirm_password", e.target.value)} placeholder="Confirm password" error={errors.confirm_password} /> +
+
+ {/* Header */} +
+
+ + + +
+

+ Create Account +

+

Join ShopEase today

+
+ + {/* Success Message */} + {location.state?.message && ( +
+ {location.state.message} +
+ )} + + {/* Role Toggle */} +
+ + +
+ + {/* Form */} + + + + + + + - + {/* Error Message */} + {errors.general && ( +
+
+ + + + {errors.general} +
+
+ )} - + {/* Submit Button */} +
+ +
+ -

- Already have an account? Login -

- + {/* Footer */} +
+

+ Already have an account? + + Sign in now → + +

+

+ By creating an account, you agree to our Terms of Service and Privacy Policy. +

+
+
); } diff --git a/frontend/src/Pages/LandingPage.jsx b/frontend/src/Pages/LandingPage.jsx index 6896e45..79a3590 100644 --- a/frontend/src/Pages/LandingPage.jsx +++ b/frontend/src/Pages/LandingPage.jsx @@ -1,5 +1,7 @@ +// src/Pages/LandingPage.jsx import React from "react"; import { motion } from "framer-motion"; +import { useNavigate } from "react-router-dom"; import Navbar from "../reusableComponents/Navbar"; import Carousel from "../reusableComponents/CarouselSlider"; import ListCards from "../reusableComponents/ListCards"; @@ -9,61 +11,157 @@ import FeatureList from "../reusableComponents/FeaturesList"; import TechnicianCard from "../reusableComponents/TechnicianCard"; import BentoGrid from "../reusableComponents/BentoGrid"; import BannerCard from "../reusableComponents/BannerCard"; +import { useAuth } from "../utils/auth"; // ✅ NEW AUTH HOOK const LandingPage = () => { + const { user, logout } = useAuth(); // ✅ Perfect auth integration + const navigate = useNavigate(); + + const handleLogout = () => { + logout(); // ✅ No more import errors! + }; + + const handleUserDashboard = () => { + navigate("/products"); // User goes to products + }; + + const handleAdminDashboard = () => { + navigate("/admin/dashboard"); // Admin direct access + }; + return ( -
- +
+ {/* Enhanced Navbar with Auth Status */} + {/* navigate("/login")} + onSignup={() => navigate("/signup")} + onProfile={() => navigate("/profile")} + onUserDashboard={handleUserDashboard} + onAdminDashboard={handleAdminDashboard} + /> */} - {/* Carousel */} + {/* Hero Carousel */} - + {/* Featured Categories */} + +
+ +

+ Everything You Need +

+

+ Discover premium products and expert technicians for all your tech needs +

+
+ +
+
{/* Featured Products */} -
+
- - Featured Products - - {/* Now renders all featured products internally */} +

+ Featured Products +

+

+ Best selling items with guaranteed quality +

+ +
-
+ - {/* Service Features */} -
- -
+ {/* Features Section */} + +
+ +
+
- {/* Technicians Section */} -
+ {/* Technicians */} +
- - Our Top Skilled Technicians - - {/* Now renders all technicians internally */} +

+ Top Skilled Technicians +

+

+ Certified experts ready to serve you +

+ +
-
+ + + {/* Bento Grid */} + + + - - -
+ {/* Banner */} + + + + + {/* Footer */} + +
+
); }; diff --git a/frontend/src/Profile/Profile.jsx b/frontend/src/Profile/Profile.jsx new file mode 100644 index 0000000..1263133 --- /dev/null +++ b/frontend/src/Profile/Profile.jsx @@ -0,0 +1,58 @@ +import { useState } from "react"; +import { getUserFromToken } from "../utils/Jwt"; +import axiosInstance from "../Services/axiosInstance"; + +export default function Profile() { + const user = getUserFromToken(); + + const [form, setForm] = useState({ + username: user?.username || "", + email: user?.email || "", + }); + + const handleSubmit = async (e) => { + e.preventDefault(); + + try { + await axiosInstance.put("/profile/update/", form); + alert("Profile updated successfully"); + } catch { + alert("Update failed"); + } + }; + + return ( +
+
+

My Profile

+

+ Update your account information +

+ +
+ + setForm({ ...form, username: e.target.value }) + } + className="w-full border rounded-lg px-3 py-2" + placeholder="Username" + /> + + + setForm({ ...form, email: e.target.value }) + } + className="w-full border rounded-lg px-3 py-2" + placeholder="Email" + /> + + +
+
+
+ ); +} diff --git a/frontend/src/Profile/ProfileSettings.jsx b/frontend/src/Profile/ProfileSettings.jsx new file mode 100644 index 0000000..697b667 --- /dev/null +++ b/frontend/src/Profile/ProfileSettings.jsx @@ -0,0 +1,29 @@ +export default function Settings() { + return ( +
+

Settings

+ +
+ + + +
+
+ ); +} + +const SettingItem = ({ title, desc }) => ( +
+

{title}

+

{desc}

+
+); diff --git a/frontend/src/ProtectedRoutes/ProtectedRote.jsx b/frontend/src/ProtectedRoutes/ProtectedRote.jsx index 3e6883b..b426eaf 100644 --- a/frontend/src/ProtectedRoutes/ProtectedRote.jsx +++ b/frontend/src/ProtectedRoutes/ProtectedRote.jsx @@ -1,13 +1,16 @@ -import React from "react"; +// src/ProtectedRoutes/ProtectedRoute.jsx import { Navigate } from "react-router-dom"; +import { getUserFromToken } from "../utils/Jwt"; -// Protect routes that require authentication -export default function ProtectedRoute({ children }) { - const isAuthenticated = localStorage.getItem("isAuthenticated") === "true"; +export default function ProtectedRoute({ children, allowedRoles }) { + const user = getUserFromToken(); - if (!isAuthenticated) { - // Redirect to signup if not logged in - return ; + if (!user) { + return ; + } + + if (allowedRoles && !allowedRoles.includes(user.role)) { + return ; } return children; diff --git a/frontend/src/Services/authService.jsx b/frontend/src/Services/authService.jsx index 0aeb2d9..0958446 100644 --- a/frontend/src/Services/authService.jsx +++ b/frontend/src/Services/authService.jsx @@ -1,13 +1,28 @@ -// authService.jsx +// src/Services/authService.js import axiosInstance from "./axiosInstance"; -// Register user based on role -export const registerUser = (role, payload) => { - // Role can be 'user', 'admin', 'technician' - return axiosInstance.post(`/register/${role}/`, payload); +export const registerUser = (payload) => { + return axiosInstance.post("register/user/", payload); }; -// Login user -export const loginUser = (payload) => { - return axiosInstance.post("/login/", payload); +export const registerAdmin = (payload) => { + return axiosInstance.post("register/admin/", payload); +}; + +export const loginUser = async (payload) => { + const response = await axiosInstance.post("login/", payload); + + // Store tokens (adjust based on your API response) + if (response.data.access) { + localStorage.setItem("accessToken", response.data.access); + localStorage.setItem("refreshToken", response.data.refresh); + } else if (response.data.token) { + localStorage.setItem("accessToken", response.data.token); + } + + return response; +}; + +export const logoutUser = () => { + localStorage.clear(); }; diff --git a/frontend/src/Services/axiosInstance.jsx b/frontend/src/Services/axiosInstance.jsx index c619e9a..c9f56d7 100644 --- a/frontend/src/Services/axiosInstance.jsx +++ b/frontend/src/Services/axiosInstance.jsx @@ -1,11 +1,20 @@ -// axiosInstance.jsx +// src/Services/axiosInstance.js import axios from "axios"; const axiosInstance = axios.create({ - baseURL: import.meta.env.VITE_API_BASE_URL, // e.g., http://127.0.0.1:8000/api + baseURL: import.meta.env.VITE_API_BASE_URL, // http://127.0.0.1:8000/api headers: { "Content-Type": "application/json", }, }); +// Add token to every request +axiosInstance.interceptors.request.use((config) => { + const token = localStorage.getItem("accessToken"); + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + return config; +}); + export default axiosInstance; diff --git a/frontend/src/index.css b/frontend/src/index.css index 0326a3d..a4d2375 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -18,3 +18,15 @@ @apply hover:shadow-xl hover:-translate-y-2 transition-all duration-300; } } + +/* src/index.css - Add at bottom */ +img[src*="ibb.co"] { + display: none !important; +} + +img { + background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); + background-size: cover; + border-radius: 12px; +} + diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx index 97aa19e..26bd9c8 100644 --- a/frontend/src/main.jsx +++ b/frontend/src/main.jsx @@ -1,16 +1,20 @@ +// src/main.jsx import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; import { BrowserRouter } from "react-router-dom"; -import App from "./App"; +import App from "./App.jsx"; import "./index.css"; import { CartProvider } from "./context/CartContext"; +import { AuthProvider } from "./utils/auth"; createRoot(document.getElementById("root")).render( - - - + + + + + ); diff --git a/frontend/src/reusableComponents/Input.jsx b/frontend/src/reusableComponents/Input.jsx index d3e3157..a872159 100644 --- a/frontend/src/reusableComponents/Input.jsx +++ b/frontend/src/reusableComponents/Input.jsx @@ -1,18 +1,51 @@ -// Input.jsx -export default function Input({ label, type = "text", value, onChange, error, placeholder }) { +// src/reusableComponents/Input.jsx +import React from "react"; + +export default function Input({ + label, + type = "text", + value, + onChange, + error, + placeholder, + disabled = false, + className = "", + name, + ...props +}) { return ( -
- {label && } +
+ {label && ( + + )} - {error &&

{error}

} + {error && ( +

+ {error} +

+ )}
); } diff --git a/frontend/src/reusableComponents/Navbar.jsx b/frontend/src/reusableComponents/Navbar.jsx index 049f10e..a5b943f 100644 --- a/frontend/src/reusableComponents/Navbar.jsx +++ b/frontend/src/reusableComponents/Navbar.jsx @@ -1,101 +1,194 @@ +// src/reusableComponents/Navbar.jsx - 100% WORKING + RESPONSIVE +import { useState, useRef, useEffect } from "react"; import { useNavigate, useLocation } from "react-router-dom"; -import { FaShoppingCart, FaUserCircle } from "react-icons/fa"; +import { FaShoppingCart, FaUserCircle, FaSignOutAlt, FaBars, FaTimes } from "react-icons/fa"; // ✅ ALL fa icons ONLY import { useCart } from "../context/CartContext"; +import { useAuth } from "../utils/auth"; export default function Navbar() { + const { user, logout } = useAuth(); const navigate = useNavigate(); const location = useLocation(); const { cart } = useCart(); + const [openProfile, setOpenProfile] = useState(false); + const [openMobile, setOpenMobile] = useState(false); + const dropdownRef = useRef(null); - const cartCount = cart.reduce((total, item) => total + item.qty, 0); - const isAuthenticated = localStorage.getItem("isAuthenticated") === "true"; + const cartCount = cart.reduce((total, item) => total + (item.qty || 0), 0); + const isAuthenticated = !!user; + + useEffect(() => { + const handleClickOutside = (e) => { + if (dropdownRef.current && !dropdownRef.current.contains(e.target)) { + setOpenProfile(false); + } + }; + document.addEventListener("click", handleClickOutside); + return () => document.removeEventListener("click", handleClickOutside); + }, []); const handleLogout = () => { - localStorage.removeItem("isAuthenticated"); - navigate("/"); + logout(); + navigate("/", { replace: true }); + setOpenMobile(false); }; - const isActive = (path) => - location.pathname === path - ? "text-blue-600 font-semibold border-b-2 border-blue-600" - : "hover:text-blue-600"; + const navigateAndClose = (path) => { + navigate(path); + setOpenProfile(false); + setOpenMobile(false); + }; return ( -