From e58f3fbe05e4585c5cec1dbf44750057ff4c41e1 Mon Sep 17 00:00:00 2001 From: GaneshArumugam05 Date: Wed, 31 Dec 2025 16:25:00 +0530 Subject: [PATCH 1/2] Login and Signup role base todays update: login, Signup for Admin and User Role based access API Interation --- frontend/src/Admin/HomeAdmin.jsx | 305 +++++++++--------- frontend/src/App.jsx | 158 +++++---- frontend/src/Authentication/Login.jsx | 234 ++++++-------- frontend/src/Authentication/Signup.jsx | 207 +++--------- frontend/src/Authentication/SignupAdmin.jsx | 143 ++++++++ frontend/src/Authentication/SignupUser.jsx | 143 ++++++++ frontend/src/Pages/Cart.jsx | 102 +++--- frontend/src/Pages/LandingPage.jsx | 124 ++----- frontend/src/Profile/Profile.jsx | 92 ++++-- frontend/src/Services/authService.jsx | 65 +++- frontend/src/context/CartContext.jsx | 51 ++- frontend/src/main.jsx | 2 +- .../src/reusableComponents/AdminNavbar.jsx | 204 ++++++++++++ .../src/reusableComponents/BannerCard.jsx | 112 ++++--- frontend/src/reusableComponents/BentoGrid.jsx | 174 ++++------ frontend/src/reusableComponents/Button.jsx | 31 +- .../src/reusableComponents/CarouselSlider.jsx | 142 ++++++-- .../src/reusableComponents/FeaturesList.jsx | 19 +- .../src/reusableComponents/FilterOption.jsx | 50 +-- frontend/src/reusableComponents/Footer.jsx | 153 ++++++--- frontend/src/reusableComponents/Input.jsx | 42 +-- frontend/src/reusableComponents/ListCards.jsx | 49 +-- .../src/reusableComponents/ModalPopup.jsx | 67 +++- frontend/src/reusableComponents/Navbar.jsx | 178 ++++------ .../src/reusableComponents/ProductCard.jsx | 82 ++--- frontend/src/reusableComponents/SearchBar.jsx | 25 +- .../src/reusableComponents/TechnicianCard.jsx | 102 ++++-- frontend/src/utils/auth.jsx | 62 ++-- 28 files changed, 1845 insertions(+), 1273 deletions(-) create mode 100644 frontend/src/Authentication/SignupAdmin.jsx create mode 100644 frontend/src/Authentication/SignupUser.jsx create mode 100644 frontend/src/reusableComponents/AdminNavbar.jsx diff --git a/frontend/src/Admin/HomeAdmin.jsx b/frontend/src/Admin/HomeAdmin.jsx index 2730b86..b66167c 100644 --- a/frontend/src/Admin/HomeAdmin.jsx +++ b/frontend/src/Admin/HomeAdmin.jsx @@ -1,202 +1,213 @@ -// src/Admin/HomeAdmin.jsx - 100% WORKING (react-icons/fa only) -import React, { useEffect, useState } from "react"; +import React, { useEffect, useRef } 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"; +import { useAuth } from "../utils/auth"; +import { + FaShoppingCart, + FaUsers, + FaBox, + FaRupeeSign, + FaChartBar, + FaArrowUp, + FaArrowDown, +} from "react-icons/fa"; + +/* -------------------- DATA -------------------- */ + +const ICON_COLORS = { + indigo: "bg-indigo-600", + emerald: "bg-emerald-600", + purple: "bg-purple-600", + orange: "bg-orange-600", +}; 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" }, + { title: "Total Revenue", value: "₹2.45L", change: "+12.4%", trend: "up", icon: FaRupeeSign, color: "indigo" }, + { title: "Orders", value: "1,240", change: "+8.2%", trend: "up", icon: FaShoppingCart, color: "emerald" }, + { title: "Customers", value: "860", change: "+15.1%", trend: "up", icon: FaUsers, color: "purple" }, + { title: "Products", value: "320", change: "-2.3%", trend: "down", 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" }, + { id: "#ORD-1021", customer: "Rahul Kumar", amount: "₹4,500", status: "completed" }, + { id: "#ORD-1022", customer: "Sneha M.", amount: "₹2,200", status: "pending" }, + { id: "#ORD-1023", customer: "Amit Singh", amount: "₹6,800", status: "cancelled" }, ]; +/* -------------------- COMPONENT -------------------- */ + const HomeAdmin = () => { - const { user, logout } = useAuth(); + const { user } = useAuth(); const navigate = useNavigate(); - const [time, setTime] = useState(new Date()); + const redirecting = useRef(false); + /* 🔐 NO FLICKER LOGOUT FIX */ useEffect(() => { - const interval = setInterval(() => setTime(new Date()), 60000); - return () => clearInterval(interval); - }, []); + if (!user && !redirecting.current) { + redirecting.current = true; + navigate("/login", { replace: true }); + } + }, [user, navigate]); - const handleLogout = () => { - logout(); - navigate("/"); - }; + if (!user) return null; return ( -
- {/* Header */} - -
-

- Admin Dashboard -

-

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

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

+ Admin Dashboard +

+

+ Monitor business performance and activities +

+
+ + {/* STATS */} +
+ {stats.map(({ title, value, change, trend, icon: Icon, color }, i) => ( -
+
-

{title}

-

{value}

- {change && ( -

- - {change} from last month -

- )} +

+ {title} +

+

+ {value} +

+ + {trend === "up" ? : } + {change} +
-
- + +
+
))} - +
- {/* Main Content */} -
- {/* Sales Chart */} - + + {/* REVENUE */} + -
-

- Sales Overview - Live Data -

- +
+
+

+ Revenue Analytics +

+

Monthly overview

+
+ + Live +
-
-
- -

Interactive Sales Chart

-

Real-time analytics coming soon

+ +
+
+ +

Charts Coming Soon

+

Real-time analytics

- {/* 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}

-
-
- ))} -
+

+ Quick Stats +

+ + {[ + { label: "Avg Order Value", value: "₹3,250", change: "+9.2%" }, + { label: "Conversion Rate", value: "4.2%", change: "+1.2%" }, + { label: "New Customers", value: "124", change: "+28%" }, + ].map((item) => ( +
+ + {item.label} + +
+

{item.value}

+

+ {item.change} +

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

Recent Orders

- +
+

+ Recent Orders +

+

Latest transactions

- -
- - + +
+
+ - - - - - + + + + - - {recentOrders.map((order) => ( - - - - - + {recentOrders.map((o) => ( + + + + + - ))} diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 953821a..9b54840 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,56 +1,51 @@ -// src/App.jsx - PERFECT: ANY USER=LandingPage | ANY ADMIN=HomeAdmin +// src/App.jsx import { Routes, Route, Navigate } from "react-router-dom"; +import { useAuth } from "./utils/auth"; + +/* Pages */ import LandingPage from "./Pages/LandingPage"; -import Login from "./Authentication/Login"; -import Signup from "./Authentication/Signup"; import ProductsPage from "./Pages/ProductsPage"; 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"; + +/* Auth Pages */ +import Login from "./Authentication/Login"; +import SignupUser from "./Authentication/SignupUser"; +import SignupAdmin from "./Authentication/SignupAdmin"; + +/* Profile */ import Profile from "./Profile/Profile"; +import Settings from "./Profile/ProfileSettings"; + +/* Admin */ +import HomeAdmin from "./Admin/HomeAdmin"; + +/* Components */ import Navbar from "./reusableComponents/Navbar"; -import { useAuth } from "./utils/auth"; +import AdminNavbar from "./reusableComponents/AdminNavbar"; +import ProtectedRoute from "./ProtectedRoutes/ProtectedRote"; +/* Loading Spinner */ function LoadingSpinner() { return (
-
+
); } -// ✅ PERFECT LOGIC: role="user" → LandingPage | role="admin" → HomeAdmin +/* ======================= + HOME PAGE LOGIC +======================= */ function HomePage() { const { user, loading } = useAuth(); - console.log("🎯 HomePage:", { - userId: user?.id, - role: user?.role, - isUser: user?.role === "user", - isAdmin: user?.role === "admin", - loading - }); - 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"); + // 👤 Guest or User → Public Navbar + Landing + if (!user || user.role === "user") { return ( <> @@ -59,45 +54,100 @@ function HomePage() { ); } - // ✅ ANY ADMIN (role="admin") → HOMEADMIN + // 👑 Admin → Admin Navbar + Admin Home if (user.role === "admin") { - console.log("✅ ADMIN (", user.id, ") → HOMEADMIN"); return ( <> - + ); } - // ✅ FALLBACK → LANDING PAGE - console.log("✅ FALLBACK → LANDING PAGE"); - return ( - <> - - - - ); + return ; } +/* ======================= + APP ROUTES +======================= */ export default function App() { return ( + {/* 🌍 PUBLIC ROUTES */} } /> - } /> - } /> + } /> + } /> } /> } /> - - } /> - } /> - } /> - - } /> - - } /> - } /> - + + {/* 🔐 AUTH ROUTES */} + } /> + } /> + } /> + + {/* 👤 USER PROTECTED ROUTES */} + + <> + + + + + } + /> + + + <> + + + + + } + /> + + + <> + + + + + } + /> + + {/* 👑 ADMIN PROTECTED ROUTES */} + + <> + + + + + } + /> + + + <> + + + + + } + /> + + {/* ❌ FALLBACK */} } /> ); diff --git a/frontend/src/Authentication/Login.jsx b/frontend/src/Authentication/Login.jsx index ef39d0c..25e208d 100644 --- a/frontend/src/Authentication/Login.jsx +++ b/frontend/src/Authentication/Login.jsx @@ -1,174 +1,148 @@ -// 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 { Link, useNavigate, useLocation } 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 [error, setError] = useState(""); + const [loading, setLoading] = useState(false); + const navigate = useNavigate(); const location = useLocation(); const { login, user } = useAuth(); - // ✅ AUTO REDIRECT if already logged in + // Auto redirect if already logged in useEffect(() => { if (user) { - console.log("🔄 AUTO REDIRECT - Already logged in:", user.id, user.role); - navigate("/", { replace: true }); + navigate(user.role === "admin" ? "/admin/home" : "/", { replace: true }); } }, [user, navigate]); + const handleChange = (e) => { + setForm({ ...form, [e.target.name]: e.target.value }); + setError(""); + }; + const handleSubmit = async (e) => { e.preventDefault(); - setErrors({}); - - if (!form.identifier.trim() || !form.password.trim()) { - setErrors({ general: "Please fill all fields" }); - return; - } + setLoading(true); - setIsSubmitting(true); - try { - 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." - }); + const res = await loginUser(form); + login(res.data.tokens.access); + + navigate( + res.data.role === "admin" ? "/admin/home" : "/", + { replace: true } + ); + } catch (err) { + setError( + err.response?.data?.error || + err.response?.data?.detail || + "Invalid credentials" + ); } finally { - setIsSubmitting(false); + setLoading(false); } }; - const handleInputChange = (e) => { - const { name, value } = e.target; - setForm(prev => ({ ...prev, [name]: value })); - if (errors[name]) setErrors(prev => ({ ...prev, [name]: '' })); - }; - return ( -
-
- {/* Header */} +
+ + {/* CARD */} +
+ + {/* LOGO / TITLE */}
-
- - - +
+ S
-

- Welcome Back +

+ Sign in to ShopEase

-

Sign in to your account

+

+ Welcome back, please login +

- {/* Success Message */} + {/* SUCCESS MESSAGE */} {location.state?.message && ( -
+
{location.state.message}
)} - {/* Form */} -
- - - - - {/* Error Message */} - {errors.general && ( -
- {errors.general} -
- )} - - {/* Submit Button */} + {/* ERROR */} + {error && ( +
+ {error} +
+ )} + + {/* FORM */} + +
+ + +
+ +
+ + +
+ +
+ + Forgot password? + +
+ - {/* Footer */} -
-

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

-

- Forgot password? - - Reset now - -

+ {/* FOOTER */} +
+ Don’t have an account? + + Create account +
diff --git a/frontend/src/Authentication/Signup.jsx b/frontend/src/Authentication/Signup.jsx index a58aa00..3507ebb 100644 --- a/frontend/src/Authentication/Signup.jsx +++ b/frontend/src/Authentication/Signup.jsx @@ -1,8 +1,7 @@ -// 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 { registerUser, registerAdmin } from "../Services/authService"; +import { registerUser } from "../Services/authService"; export default function Signup() { const [form, setForm] = useState({ @@ -10,20 +9,17 @@ export default function Signup() { }); const [errors, setErrors] = useState({}); const [isSubmitting, setIsSubmitting] = useState(false); - const [registeringAs, setRegisteringAs] = useState("user"); const navigate = useNavigate(); const location = useLocation(); 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; }; @@ -31,32 +27,14 @@ export default function Signup() { const handleSubmit = async (e) => { e.preventDefault(); if (!validateForm()) return; - setIsSubmitting(true); setErrors({}); - try { - 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.` - } - }); - + await registerUser(form); + navigate("/login", { state: { message: "User 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." + general: error.response?.data?.detail || error.response?.data?.error || "Registration failed. Please try again." }); } finally { setIsSubmitting(false); @@ -71,173 +49,62 @@ export default function Signup() { return (
-
+
+ {/* Header */} -
-
- +
+
+
-

+

Create Account

-

Join ShopEase today

+

Join ShopEase as a user

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

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

-

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

+

+ Already have an account?{' '} + Sign in →

diff --git a/frontend/src/Authentication/SignupAdmin.jsx b/frontend/src/Authentication/SignupAdmin.jsx new file mode 100644 index 0000000..9c37e17 --- /dev/null +++ b/frontend/src/Authentication/SignupAdmin.jsx @@ -0,0 +1,143 @@ +// src/Authentication/SignupAdmin.jsx +import React, { useState } from "react"; +import { useNavigate, Link, useLocation } from "react-router-dom"; +import Input from "../reusableComponents/Input"; +import { registerAdmin } from "../Services/authService"; + +export default function SignupAdmin() { + const [form, setForm] = useState({ + fullname: "", username: "", email: "", mobile: "", password: "", confirm_password: "" + }); + const [errors, setErrors] = useState({}); + const [isSubmitting, setIsSubmitting] = useState(false); + const navigate = useNavigate(); + const location = useLocation(); + + 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 < 8) newErrors.password = "Admin password must be at least 8 characters"; + if (form.password !== form.confirm_password) newErrors.confirm_password = "Passwords don't match"; + setErrors(newErrors); + return Object.keys(newErrors).length === 0; + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + if (!validateForm()) return; + setIsSubmitting(true); + setErrors({}); + + try { + // ✅ Admin registration with role: "admin" + await registerAdmin({ + ...form, + role: "admin" + }); + navigate("/login", { + state: { + message: "Admin account created successfully! Please login to access admin dashboard." + } + }); + } catch (error) { + console.error("❌ ADMIN REGISTRATION ERROR:", error.response?.data); + setErrors({ + general: error.response?.data?.detail || + error.response?.data?.error || + "Admin 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 ( +
+
+ + {/* Header */} +
+
+ + + +
+

+ Admin Registration +

+

Create your admin account

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

+ Already have an account?{" "} + + Sign in → + +

+
+ +
+
+ ); +} diff --git a/frontend/src/Authentication/SignupUser.jsx b/frontend/src/Authentication/SignupUser.jsx new file mode 100644 index 0000000..9cbffc6 --- /dev/null +++ b/frontend/src/Authentication/SignupUser.jsx @@ -0,0 +1,143 @@ +// src/Authentication/SignupUser.jsx +import React, { useState } from "react"; +import { useNavigate, Link, useLocation } from "react-router-dom"; +import Input from "../reusableComponents/Input"; +import { registerUser } from "../Services/authService"; + +export default function SignupUser() { + const [form, setForm] = useState({ + fullname: "", username: "", email: "", mobile: "", password: "", confirm_password: "" + }); + const [errors, setErrors] = useState({}); + const [isSubmitting, setIsSubmitting] = useState(false); + const navigate = useNavigate(); + const location = useLocation(); + + 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; + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + if (!validateForm()) return; + setIsSubmitting(true); + setErrors({}); + + try { + // ✅ User registration with role: "user" + await registerUser({ + ...form, + role: "user" + }); + navigate("/login", { + state: { + message: "User account created successfully! Please login to continue shopping." + } + }); + } 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 ( +
+
+ + {/* Header */} +
+
+ + + +
+

+ Join ShopEase +

+

Create your shopper account

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

+ Already have an account?{" "} + + Sign in → + +

+
+ +
+
+ ); +} diff --git a/frontend/src/Pages/Cart.jsx b/frontend/src/Pages/Cart.jsx index 0679919..7683205 100644 --- a/frontend/src/Pages/Cart.jsx +++ b/frontend/src/Pages/Cart.jsx @@ -1,72 +1,81 @@ +// src/pages/Cart.jsx - WORKING WITH YOUR CartContext import React from "react"; import Navbar from "../reusableComponents/Navbar"; import { useCart } from "../context/CartContext"; import { useNavigate } from "react-router-dom"; +import { FaTrash, FaRupeeSign } from "react-icons/fa"; export default function Cart() { - const { cart, removeFromCart, increaseQty, decreaseQty } = useCart(); + const { cart, removeFromCart, increaseQty, decreaseQty, clearCart } = useCart(); // ✅ clearCart works now const navigate = useNavigate(); const subtotal = cart.reduce( - (total, item) => total + item.price * item.qty, + (total, item) => total + (item.price || 0) * (item.qty || 0), 0 ); return (
- +
-

Your Cart

+

Your Cart

{cart.length === 0 ? ( -
+
+
+ + + +

Your cart is empty

-
) : (
- {/* LEFT SIDE – CART LIST */}
{cart.map((item) => (
{/* Product Image */} {item.name} {/* Product Details */} -
-

{item.name}

-

- ₹{item.price.toLocaleString()} +

+

{item.name}

+

+ + {item.price?.toLocaleString() || "0"}

{/* Quantity Buttons */} -
+
- {item.qty} + + {item.qty || 1} +
{/* Subtotal of this product */} -

- ₹{(item.qty * item.price).toLocaleString()} +

+ ₹{((item.qty || 0) * (item.price || 0)).toLocaleString()}

- {/* Remove Button */} + {/* Remove SINGLE Item */}
@@ -91,32 +101,44 @@ export default function Cart() {
{/* RIGHT SIDE – ORDER SUMMARY */} -
-

Order Summary

+
+

Order Summary

-
- -
- Subtotal - ₹{subtotal.toLocaleString()} +
+
+ Subtotal ({cart.length} items) + ₹{subtotal.toLocaleString()}
- -
+
Shipping - FREE + FREE
- -
- Total - ₹{subtotal.toLocaleString()} +
+ Total + + ₹{Math.round(subtotal * 1.18).toLocaleString()} +
- -
+ {/* ✅ FIXED: WORKING Clear All Button */} + +
)}
diff --git a/frontend/src/Pages/LandingPage.jsx b/frontend/src/Pages/LandingPage.jsx index 79a3590..ae5c0ef 100644 --- a/frontend/src/Pages/LandingPage.jsx +++ b/frontend/src/Pages/LandingPage.jsx @@ -1,9 +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 CarouselSlider from "../reusableComponents/CarouselSlider"; import ListCards from "../reusableComponents/ListCards"; import Footer from "../reusableComponents/Footer"; import ProductCard from "../reusableComponents/ProductCard"; @@ -11,65 +9,40 @@ 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} - /> */} +
+ {/* Navbar */} + {/* */} {/* Hero Carousel */} - + {/* Featured Categories */} -
+
-

+

Everything You Need

-

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

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

@@ -77,22 +50,13 @@ const LandingPage = () => { {/* Featured Products */} - -
- -

+ +
+ +

Featured Products

-

+

Best selling items with guaranteed quality

@@ -100,35 +64,21 @@ const LandingPage = () => {
- {/* Features Section */} - -
+ {/* Features */} + +
{/* Technicians */} - -
- -

+ +
+ +

Top Skilled Technicians

-

+

Certified experts ready to serve you

@@ -137,31 +87,17 @@ const LandingPage = () => { {/* Bento Grid */} - + {/* Banner */} - + {/* Footer */} - -
- +
); }; diff --git a/frontend/src/Profile/Profile.jsx b/frontend/src/Profile/Profile.jsx index 1263133..7a1fbe6 100644 --- a/frontend/src/Profile/Profile.jsx +++ b/frontend/src/Profile/Profile.jsx @@ -1,6 +1,7 @@ import { useState } from "react"; import { getUserFromToken } from "../utils/Jwt"; import axiosInstance from "../Services/axiosInstance"; +import { FaUserAlt, FaEnvelope, FaSave } from "react-icons/fa"; export default function Profile() { const user = getUserFromToken(); @@ -10,46 +11,77 @@ export default function Profile() { email: user?.email || "", }); + const [isSubmitting, setIsSubmitting] = useState(false); + const handleSubmit = async (e) => { e.preventDefault(); + setIsSubmitting(true); try { await axiosInstance.put("/profile/update/", form); - alert("Profile updated successfully"); + alert("✅ Profile updated successfully"); } catch { - alert("Update failed"); + alert("❌ Update failed. Try again."); + } finally { + setIsSubmitting(false); } }; 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/Services/authService.jsx b/frontend/src/Services/authService.jsx index 0958446..cd88f96 100644 --- a/frontend/src/Services/authService.jsx +++ b/frontend/src/Services/authService.jsx @@ -1,4 +1,4 @@ -// src/Services/authService.js +// src/Services/authService.js - ✅ FIXED RESPONSE HANDLING import axiosInstance from "./axiosInstance"; export const registerUser = (payload) => { @@ -12,17 +12,68 @@ export const registerAdmin = (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) { + console.log("🔑 Login Response:", response.data); // ✅ DEBUG + + // ✅ FIXED: Match your actual backend response structure + // Assuming backend returns: { role, username, fullname, tokens: {access, refresh} } + + // Store tokens + if (response.data.tokens?.access) { + localStorage.setItem("accessToken", response.data.tokens.access); + if (response.data.tokens.refresh) { + localStorage.setItem("refreshToken", response.data.tokens.refresh); + } + } else 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); } + + // ✅ FIXED: Store user data IMMEDIATELY from response + localStorage.setItem('userRole', response.data.role || response.data.user?.role); + localStorage.setItem('username', response.data.username || response.data.user?.username); + localStorage.setItem('fullname', response.data.fullname || response.data.user?.fullname || response.data.user?.full_name); + localStorage.setItem('email', response.data.email || response.data.user?.email); + localStorage.setItem('userId', response.data.user_id || response.data.user?.id || response.data.id); + + console.log("💾 Stored User Data:", { + role: localStorage.getItem('userRole'), + username: localStorage.getItem('username'), + token: localStorage.getItem('accessToken') ? '✅' : '❌' + }); return response; }; export const logoutUser = () => { - localStorage.clear(); + const keysToRemove = [ + 'accessToken', 'refreshToken', 'userRole', 'username', + 'fullname', 'userId', 'email' + ]; + + keysToRemove.forEach(key => localStorage.removeItem(key)); + delete axiosInstance.defaults.headers.common['Authorization']; + + console.log("🔓 Logged out - Storage cleared"); }; + +export const refreshToken = async () => { + const refreshToken = localStorage.getItem('refreshToken'); + if (!refreshToken) throw new Error('No refresh token'); + + const response = await axiosInstance.post("token/refresh/", { refresh: refreshToken }); + if (response.data.access) { + localStorage.setItem("accessToken", response.data.access); + return response.data.access; + } + throw new Error('Token refresh failed'); +}; + +// ✅ UTILITY FUNCTIONS +export const getCurrentUserRole = () => localStorage.getItem('userRole'); +export const isAuthenticated = () => !!localStorage.getItem('accessToken'); +export const getUserInfo = () => ({ + userId: localStorage.getItem('userId'), + role: localStorage.getItem('userRole'), + username: localStorage.getItem('username'), + fullname: localStorage.getItem('fullname'), + email: localStorage.getItem('email') +}); diff --git a/frontend/src/context/CartContext.jsx b/frontend/src/context/CartContext.jsx index e7deb87..5b0d808 100644 --- a/frontend/src/context/CartContext.jsx +++ b/frontend/src/context/CartContext.jsx @@ -1,27 +1,34 @@ +// src/context/CartContext.jsx - YOUR CODE + clearCart FIXED import { createContext, useContext, useEffect, useState } from "react"; const CartContext = createContext(); export const CartProvider = ({ children }) => { const [cart, setCart] = useState(() => { - const saved = localStorage.getItem("cart"); - return saved ? JSON.parse(saved) : []; + try { + const saved = localStorage.getItem("cart"); + return saved ? JSON.parse(saved) : []; + } catch { + return []; + } }); - // Save cart to localStorage useEffect(() => { - localStorage.setItem("cart", JSON.stringify(cart)); + try { + localStorage.setItem("cart", JSON.stringify(cart)); + } catch (error) { + console.warn("localStorage save failed:", error); + } }, [cart]); - // Add product to cart const addToCart = (product) => { + console.log("🛒 ADDING TO CART:", product); const exists = cart.find((item) => item.id === product.id); - if (exists) { setCart( cart.map((item) => item.id === product.id - ? { ...item, qty: item.qty + 1 } + ? { ...item, qty: (item.qty || 0) + 1 } : item ) ); @@ -30,31 +37,37 @@ export const CartProvider = ({ children }) => { } }; - // Remove product from cart const removeFromCart = (id) => { setCart(cart.filter((item) => item.id !== id)); }; - // Increase product quantity const increaseQty = (id) => { setCart( cart.map((item) => - item.id === id ? { ...item, qty: item.qty + 1 } : item + item.id === id ? { ...item, qty: (item.qty || 0) + 1 } : item ) ); }; - // Decrease product quantity const decreaseQty = (id) => { setCart( cart.map((item) => - item.id === id && item.qty > 1 + item.id === id && (item.qty || 0) > 1 ? { ...item, qty: item.qty - 1 } : item - ) + ).filter(item => (item.qty || 0) > 0) ); }; + // ✅ NEW: Clear ALL cart function + const clearCart = () => { + console.log("🗑️ CLEARING ALL CART"); + setCart([]); + }; + + const totalItems = cart.reduce((sum, item) => sum + (item.qty || 0), 0); + const totalPrice = cart.reduce((sum, item) => sum + ((item.price || 0) * (item.qty || 0)), 0); + return ( { removeFromCart, increaseQty, decreaseQty, + clearCart, // ✅ ADDED THIS + totalItems, + totalPrice }} > {children} @@ -70,5 +86,10 @@ export const CartProvider = ({ children }) => { ); }; -// Custom Hook -export const useCart = () => useContext(CartContext); +export const useCart = () => { + const context = useContext(CartContext); + if (!context) { + throw new Error('useCart must be used within CartProvider'); + } + return context; +}; diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx index 26bd9c8..1ce186d 100644 --- a/frontend/src/main.jsx +++ b/frontend/src/main.jsx @@ -1,4 +1,4 @@ -// src/main.jsx +// src/main.jsx import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; import { BrowserRouter } from "react-router-dom"; diff --git a/frontend/src/reusableComponents/AdminNavbar.jsx b/frontend/src/reusableComponents/AdminNavbar.jsx new file mode 100644 index 0000000..bf63693 --- /dev/null +++ b/frontend/src/reusableComponents/AdminNavbar.jsx @@ -0,0 +1,204 @@ +import React, { useState, useEffect, useRef } from "react"; +import { Link, useNavigate, useLocation } from "react-router-dom"; +import { useAuth } from "../utils/auth"; +import { logoutUser } from "../Services/authService"; +import { + FaBars, + FaTimes, + FaHome, + FaBox, + FaUsers, + FaShoppingCart, + FaChartBar, + FaCog, + FaUser, + FaSignOutAlt, + FaBell, + FaSearch, +} from "react-icons/fa"; + +const adminNavLinks = [ + { path: "/admin/home", label: "Dashboard", icon: FaHome }, + { path: "/admin/products", label: "Products", icon: FaBox }, + { path: "/admin/orders", label: "Orders", icon: FaShoppingCart }, + { path: "/admin/customers", label: "Customers", icon: FaUsers }, + { path: "/admin/analytics", label: "Analytics", icon: FaChartBar }, + { path: "/admin/settings", label: "Settings", icon: FaCog }, +]; + +export default function AdminNavbar() { + const { user } = useAuth(); + const navigate = useNavigate(); + const location = useLocation(); + + const [searchQuery, setSearchQuery] = useState(""); + const [isMobileOpen, setIsMobileOpen] = useState(false); + const loggingOut = useRef(false); + + /* 🔐 LOGOUT WITHOUT FLICKER */ + const handleLogout = async () => { + if (loggingOut.current) return; + loggingOut.current = true; + await logoutUser(); + navigate("/login", { replace: true }); + }; + + /* 🔒 LOCK BODY SCROLL WHEN MOBILE MENU OPEN */ + useEffect(() => { + document.body.style.overflow = isMobileOpen ? "hidden" : "auto"; + return () => (document.body.style.overflow = "auto"); + }, [isMobileOpen]); + + const isActive = (path) => location.pathname.startsWith(path); + + return ( + + ); +} diff --git a/frontend/src/reusableComponents/BannerCard.jsx b/frontend/src/reusableComponents/BannerCard.jsx index 1683241..06e8f35 100644 --- a/frontend/src/reusableComponents/BannerCard.jsx +++ b/frontend/src/reusableComponents/BannerCard.jsx @@ -2,87 +2,85 @@ import { motion } from "framer-motion"; export default function BannerCard() { const bannerData = { - image: "https://images.unsplash.com/photo-1615484473812-66b5c0b8c63f?auto=format&fit=crop&w=1200&q=80", title: "Exchange Your Old RO", - subtitle: "Get instant value for your old purifier. Upgrade to a new model with advanced filtration and smart design.", - ctaText: "Explore Exchange Offers" + subtitle: + "Get instant value for your old purifier. Upgrade to a new model with advanced filtration and smart design.", + ctaText: "Explore Exchange Offers", + image: + "https://images.unsplash.com/photo-1615484473812-66b5c0b8c63f?auto=format&fit=crop&w=1200&q=80", }; return ( - - {/* Background RO Purifier Image - Right Corner */} -
- RO Purifier -
-
- - {/* Content Section - Full Width */} -
-
- + + {/* CONTENT */} +
+ {bannerData.title} - - + + {bannerData.subtitle} - - {bannerData.ctaText} - - - - -
+ {bannerData.ctaText} +
-
- {/* RO Purifier Accent - Bottom Right Corner */} - - RO Purifier Accent - + {/* IMAGE */} +
+ RO Purifier +
+
+
- {/* Decorative particles */} -
-
- + {/* DECOR ELEMENTS */} +
+
+ ); } diff --git a/frontend/src/reusableComponents/BentoGrid.jsx b/frontend/src/reusableComponents/BentoGrid.jsx index b550d6d..ee251c1 100644 --- a/frontend/src/reusableComponents/BentoGrid.jsx +++ b/frontend/src/reusableComponents/BentoGrid.jsx @@ -2,123 +2,77 @@ import { motion } from "framer-motion"; export default function BentoGrid() { return ( -
+
- {/* 1️⃣ LEFT TALL CARD - Exchange Offer */} - -
+ {/* Left Tall */} + -
-

Exchange Your Old Purifier

-

- Save up to 30% when you trade in your old RO system. Keep your - home healthy with the latest models. -

+ {/* Top Middle */} + - -
-
+ {/* Right Tall */} + - {/* 2️⃣ TOP MIDDLE - Freshness */} - -
- -
-

Experience the Freshness

-

This Season, Stay Hydrated & Save Big!

- - -
-
- - {/* 3️⃣ RIGHT TALL - Latest RO Purifiers */} - -
- -
-

- Discover Our Latest RO Purifiers -

-

- Energy-efficient, compact, and stylish. Designed for modern homes. -

- - -
-
- - {/* 4️⃣ BOTTOM MIDDLE - Service Booking */} - -
- -
-

Book a Technician

-

- Certified technicians at your doorstep — fast & reliable service. -

+ {/* Bottom Middle */} + +
+
+ ); +} - -
- +function Card({ title, desc, img, btn, className = "" }) { + return ( + +
+
+

{title}

+

{desc}

+
-
+
); } diff --git a/frontend/src/reusableComponents/Button.jsx b/frontend/src/reusableComponents/Button.jsx index 7d94861..b44534e 100644 --- a/frontend/src/reusableComponents/Button.jsx +++ b/frontend/src/reusableComponents/Button.jsx @@ -1,17 +1,28 @@ -// Button.jsx -export default function Button({ children, variant="primary", size="md", onClick, disabled, className="" }) { - const base = "px-4 py-2 rounded-lg font-medium transition-all duration-200 flex items-center gap-2"; +export default function Button({ + children, + variant = "primary", + size = "md", + className = "", + ...props +}) { const variants = { primary: "bg-blue-600 text-white hover:bg-blue-700", - secondary: "bg-gray-200 text-gray-800 hover:bg-gray-300", - outline: "border-2 border-blue-600 text-blue-600 hover:bg-blue-50" + secondary: "bg-gray-200 text-gray-800", + outline: "border border-blue-600 text-blue-600", }; - const sizes = { sm: "px-3 py-1.5 text-sm", md: "px-4 py-2", lg: "px-6 py-3 text-lg" }; - + return ( - diff --git a/frontend/src/reusableComponents/CarouselSlider.jsx b/frontend/src/reusableComponents/CarouselSlider.jsx index 18e8aeb..cbbeadf 100644 --- a/frontend/src/reusableComponents/CarouselSlider.jsx +++ b/frontend/src/reusableComponents/CarouselSlider.jsx @@ -2,56 +2,136 @@ import { useState, useEffect } from "react"; import { motion, AnimatePresence } from "framer-motion"; export default function Carousel({ autoPlay = true }) { - const [current, setCurrent] = useState(0); - - const images = [ - "https://images.unsplash.com/photo-1523275335684-37898b6baf30?auto=format&fit=crop&w=1200&q=80", - "https://images.unsplash.com/photo-1441986300917-64674bd600d8?auto=format&fit=crop&w=1200&q=80", - "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=crop&w=1200&q=80", + // ✅ All three banners in one component + const banners = [ + { + title: "Find Verified RO Technicians Near You", + subtitle: "Fast, Reliable & Affordable", + description: + "Book trusted experts to repair, install, or service your water purifier. Compare reviews, ratings, and service prices — all in one place.", + buttons: [ + { text: "Book a Technician", action: "#book" }, + { text: "Explore Service Plans", action: "#plans" }, + ], + image: + "https://images.unsplash.com/photo-1581091870620-0b146eb55f90?auto=format&fit=crop&w=1000&q=80", + }, + { + title: "Reliable RO Services at Your Doorstep", + subtitle: "Expert Technicians, Anytime", + description: + "Schedule your water purifier service with verified professionals near you.", + buttons: [ + { text: "Book a Technician", action: "#book" }, + { text: "Explore Service Plans", action: "#plans" }, + ], + image: + "https://images.unsplash.com/photo-1581093448791-7a6e6a158330?auto=format&fit=crop&w=1000&q=80", + }, + { + title: "Affordable & Trusted RO Service", + subtitle: "Compare, Book, Relax", + description: + "Check reviews, ratings, and prices before hiring the best RO technician.", + buttons: [ + { text: "Book a Technician", action: "#book" }, + { text: "Explore Service Plans", action: "#plans" }, + ], + image: + "https://images.unsplash.com/photo-1568605114967-8130f3a36994?auto=format&fit=crop&w=1000&q=80", + }, ]; + const [current, setCurrent] = useState(0); + + /* ✅ Auto Play */ useEffect(() => { - if (autoPlay) { - const interval = setInterval(() => { - setCurrent((current + 1) % images.length); - }, 4000); - return () => clearInterval(interval); - } - }, [current, autoPlay]); + if (!autoPlay) return; + + const interval = setInterval(() => { + setCurrent((prev) => (prev + 1) % banners.length); + }, 5000); + + return () => clearInterval(interval); + }, [autoPlay, banners.length]); return ( -
+
+ {/* SLIDES */} - + className="absolute inset-0 w-full h-full" + initial={{ opacity: 0, x: 80 }} + animate={{ opacity: 1, x: 0 }} + exit={{ opacity: 0, x: -80 }} + transition={{ duration: 0.6, ease: "easeInOut" }} + > + {banners[current].title} + {/* Overlay */} +
+

+ {banners[current].title} +

+

+ {banners[current].subtitle} +

+

+ {banners[current].description} +

+
+ {banners[current].buttons.map((btn, idx) => ( + + {btn.text} + + ))} +
+
+
- + + {/* LEFT ARROW */} + + {/* RIGHT ARROW */} - -
- {images.map((_, idx) => ( + + {/* DOT INDICATORS */} +
+ {banners.map((_, idx) => ( + )} +
+ + {/* Mobile: scroll | Desktop: grid */} +
{categories.map((category) => ( -
- {selected.length > 0 && ( - - )}
); } diff --git a/frontend/src/reusableComponents/Footer.jsx b/frontend/src/reusableComponents/Footer.jsx index ba92c69..72d7d22 100644 --- a/frontend/src/reusableComponents/Footer.jsx +++ b/frontend/src/reusableComponents/Footer.jsx @@ -11,100 +11,157 @@ export default function Footer() { const navigate = useNavigate(); return ( -
- - {/* Main Footer Grid */} -
- - {/* Company Info */} -
+
+ {/* MAIN GRID */} +
+ {/* COMPANY INFO */} +

PureFlow RO

-

- Pure Water. Pure Trust.
+

+ Pure Water. Pure Trust. +
Delivering clean and safe drinking water for every home.

- {/* Contact */} + {/* CONTACT */}
-
+
contact@moralizer.com
-
+
+1 234 678 9108 99
- {/* Quick Links */} -
+ {/* QUICK LINKS */} +

Quick Links

    -
  • navigate("/")} className="cursor-pointer hover:text-white">Home
  • -
  • navigate("/products")} className="cursor-pointer hover:text-white">Shop
  • -
  • navigate("/about")} className="cursor-pointer hover:text-white">About Us
  • -
  • navigate("/contact")} className="cursor-pointer hover:text-white">Contact
  • +
  • navigate("/")} + className="cursor-pointer hover:text-white transition" + > + Home +
  • +
  • navigate("/products")} + className="cursor-pointer hover:text-white transition" + > + Shop +
  • +
  • navigate("/about")} + className="cursor-pointer hover:text-white transition" + > + About Us +
  • +
  • navigate("/contact")} + className="cursor-pointer hover:text-white transition" + > + Contact +
- {/* Information */} -
+ {/* INFORMATION */} +

Information

    -
  • Shipping & Delivery
  • -
  • Returns & Refunds
  • -
  • Terms & Conditions
  • -
  • Privacy Policy
  • -
  • FAQs
  • +
  • + Shipping & Delivery +
  • +
  • + Returns & Refunds +
  • +
  • + Terms & Conditions +
  • +
  • + Privacy Policy +
  • +
  • + FAQs +
- {/* Newsletter */} -
+ {/* NEWSLETTER */} +

Stay Connected

-

+

Get updates, maintenance tips, and exclusive offers.

-
+ {/* FORM */} + - {/* Social Icons */} -
-
- -
-
- -
-
- -
+ {/* SOCIAL ICONS */} +
+ {[FaFacebookF, FaInstagram, FaTwitter].map((Icon, idx) => ( +
+ +
+ ))}
- {/* Divider */} -
+ {/* DIVIDER */} +

- {/* Copyright */} + {/* COPYRIGHT */}
© {new Date().getFullYear()} PureFlow RO — All Rights Reserved.
diff --git a/frontend/src/reusableComponents/Input.jsx b/frontend/src/reusableComponents/Input.jsx index a872159..6604f17 100644 --- a/frontend/src/reusableComponents/Input.jsx +++ b/frontend/src/reusableComponents/Input.jsx @@ -1,51 +1,21 @@ -// src/reusableComponents/Input.jsx -import React from "react"; - -export default function Input({ - label, - type = "text", - value, - onChange, - error, - placeholder, - disabled = false, - className = "", - name, - ...props -}) { +// src/Authentication/Input.jsx +export default function Input({ label, name, type = "text", value, onChange, className = "", placeholder = "", ...props }) { return (
{label && ( -
); } diff --git a/frontend/src/reusableComponents/ListCards.jsx b/frontend/src/reusableComponents/ListCards.jsx index 972e42d..c3172b6 100644 --- a/frontend/src/reusableComponents/ListCards.jsx +++ b/frontend/src/reusableComponents/ListCards.jsx @@ -1,44 +1,23 @@ -import React from "react"; -import { FaMapMarkerAlt, FaExchangeAlt } from "react-icons/fa"; -import { MdCompareArrows } from "react-icons/md"; - export default function ListCards() { - const cards = [ - { - icon: FaMapMarkerAlt, - title: "Track Location", - content: "Track your product delivery in real-time.", - }, - { - icon: MdCompareArrows, - title: "Compare Product", - content: "Compare features between multiple products.", - }, - { - icon: FaExchangeAlt, - title: "Xchange Product", - content: "Exchange your products quickly and easily.", - }, + const data = [ + { title: "Track Location", desc: "Real-time order tracking" }, + { title: "Compare Products", desc: "Compare before buying" }, + { title: "Easy Exchange", desc: "Fast & hassle-free" }, ]; return ( -
- {cards.map((card, index) => ( +
+ {data.map((item, i) => (
-
- -
-
-

- {card.title} -

-

- {card.content} -

-
+

{item.title}

+

{item.desc}

))}
diff --git a/frontend/src/reusableComponents/ModalPopup.jsx b/frontend/src/reusableComponents/ModalPopup.jsx index c414365..a84452b 100644 --- a/frontend/src/reusableComponents/ModalPopup.jsx +++ b/frontend/src/reusableComponents/ModalPopup.jsx @@ -1,11 +1,26 @@ -// Modal.jsx import { motion, AnimatePresence } from "framer-motion"; +import { useEffect } from "react"; + +export default function Modal({ isOpen, onClose, title, children }) { + + // 🔒 Lock background scroll when modal is open + useEffect(() => { + if (isOpen) { + document.body.style.overflow = "hidden"; + } else { + document.body.style.overflow = ""; + } + + return () => { + document.body.style.overflow = ""; + }; + }, [isOpen]); -export default function Modal({ isOpen, onClose, children, title }) { return ( {isOpen && ( <> + {/* BACKDROP */} + + {/* MODAL WRAPPER */} + {/* MODAL CONTENT */} e.stopPropagation()} > -
-

{title}

-
-
{children}
+ + {/* BODY */} +
+ {children} +
diff --git a/frontend/src/reusableComponents/Navbar.jsx b/frontend/src/reusableComponents/Navbar.jsx index a5b943f..dbedea2 100644 --- a/frontend/src/reusableComponents/Navbar.jsx +++ b/frontend/src/reusableComponents/Navbar.jsx @@ -1,21 +1,20 @@ -// src/reusableComponents/Navbar.jsx - 100% WORKING + RESPONSIVE +// src/reusableComponents/Navbar.jsx import { useState, useRef, useEffect } from "react"; import { useNavigate, useLocation } from "react-router-dom"; -import { FaShoppingCart, FaUserCircle, FaSignOutAlt, FaBars, FaTimes } from "react-icons/fa"; // ✅ ALL fa icons ONLY +import { FaShoppingCart, FaSignOutAlt, FaBars, FaTimes } from "react-icons/fa"; import { useCart } from "../context/CartContext"; import { useAuth } from "../utils/auth"; export default function Navbar() { - const { user, logout } = useAuth(); + const { user, logout, isLoading } = useAuth(); const navigate = useNavigate(); const location = useLocation(); - const { cart } = useCart(); + const { totalItems } = 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), 0); - const isAuthenticated = !!user; + const isAuthenticated = !!user && !isLoading; useEffect(() => { const handleClickOutside = (e) => { @@ -30,6 +29,12 @@ export default function Navbar() { const handleLogout = () => { logout(); navigate("/", { replace: true }); + }; + + const handleCartClick = () => { + console.log("🛒 NAVIGATING TO CART"); + navigate("/cart"); + setOpenProfile(false); setOpenMobile(false); }; @@ -39,106 +44,82 @@ export default function Navbar() { setOpenMobile(false); }; + if (isLoading) { + return ( + + ); + } + return (

OrderCustomerAmountStatusDateORDERCUSTOMERAMOUNTSTATUS
{order.id} - {order.customer} - {order.amount} - - {order.status} +
{o.id}{o.customer}{o.amount} + + {o.status} {order.date}