diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 66e596d..42a2ead 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -43,6 +43,7 @@ "cmdk": "^1.1.1", "date-fns": "^3.6.0", "embla-carousel-react": "^8.6.0", + "framer-motion": "^12.23.24", "input-otp": "^1.4.2", "lucide-react": "^0.462.0", "next-themes": "^0.3.0", @@ -4517,6 +4518,33 @@ "dev": true, "license": "ISC" }, + "node_modules/framer-motion": { + "version": "12.23.24", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.24.tgz", + "integrity": "sha512-HMi5HRoRCTou+3fb3h9oTLyJGBxHfW+HnNE25tAXOvVx/IvwMHK0cx7IR4a2ZU6sh3IX1Z+4ts32PcYBOqka8w==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.23.23", + "motion-utils": "^12.23.6", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -5170,6 +5198,21 @@ "node": ">= 18" } }, + "node_modules/motion-dom": { + "version": "12.23.23", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.23.tgz", + "integrity": "sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.23.6" + } + }, + "node_modules/motion-utils": { + "version": "12.23.6", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.23.6.tgz", + "integrity": "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", diff --git a/frontend/package.json b/frontend/package.json index 9f8ace1..22787d0 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -45,6 +45,7 @@ "cmdk": "^1.1.1", "date-fns": "^3.6.0", "embla-carousel-react": "^8.6.0", + "framer-motion": "^12.23.24", "input-otp": "^1.4.2", "lucide-react": "^0.462.0", "next-themes": "^0.3.0", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 416435e..374c8fe 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -9,6 +9,10 @@ import Browse from "./pages/Browse"; import Sell from "./pages/Sell"; import ProductDetailsPage from "./pages/ProductDetail"; import Navbar from "./components/Navbar"; +import { CartProvider } from "./components/CartContext.jsx"; +import Home from "./pages/Home.jsx"; +import Cart from "./pages/Cart.jsx"; +import Payment from "./pages/Payment"; import Dashboard from "./pages/Dashboard"; const queryClient = new QueryClient(); @@ -20,16 +24,22 @@ const App = () => (
- + + } /> + } /> } /> } /> } /> + } /> + } /> + } /> } /> } /> +
diff --git a/frontend/src/components/CartContext.jsx b/frontend/src/components/CartContext.jsx new file mode 100644 index 0000000..a1f5945 --- /dev/null +++ b/frontend/src/components/CartContext.jsx @@ -0,0 +1,38 @@ +import React, { createContext, useReducer, useEffect, useState } from "react"; + +export const CartContext = createContext(); + +const initialState = { + cart: JSON.parse(localStorage.getItem("cart")) || [], +}; + +const reducer = (state, action) => { + switch (action.type) { + case "ADD_ITEM": + return { ...state, cart: [...state.cart, action.payload] }; + case "REMOVE_ITEM": + return { + ...state, + cart: state.cart.filter((_, i) => i !== action.payload), + }; + case "CLEAR_CART": + return { ...state, cart: [] }; + default: + return state; + } +}; + +export const CartProvider = ({ children }) => { + const [state, dispatch] = useReducer(reducer, initialState); + const [notification, setNotification] = useState(null); + + useEffect(() => { + localStorage.setItem("cart", JSON.stringify(state.cart)); + }, [state.cart]); + + return ( + + {children} + + ); +}; \ No newline at end of file diff --git a/frontend/src/pages/Cart.jsx b/frontend/src/pages/Cart.jsx new file mode 100644 index 0000000..a48c0b2 --- /dev/null +++ b/frontend/src/pages/Cart.jsx @@ -0,0 +1,271 @@ +import React, { useContext } from "react"; +import { CartContext } from "../components/CartContext"; +import { Link } from "react-router-dom"; +import { motion, AnimatePresence } from "framer-motion"; + +const Cart = () => { + const { state, dispatch } = useContext(CartContext); + + // Group items by ID and calculate quantities + const cartItems = state.cart.reduce((acc, item) => { + const existingItem = acc.find(i => i.id === item.id); + if (existingItem) { + existingItem.quantity += 1; + existingItem.totalPrice = existingItem.quantity * existingItem.price; + } else { + acc.push({ ...item, quantity: 1, totalPrice: item.price }); + } + return acc; + }, []); + + const subtotal = cartItems.reduce((sum, item) => sum + item.totalPrice, 0); + const shipping = subtotal > 0 ? 99 : 0; + const total = subtotal + shipping; + + const updateQuantity = (id, newQuantity) => { + if (newQuantity < 1) return; + + // Find all items with this ID + const items = state.cart.filter(item => item.id === id); + + if (newQuantity > items.length) { + // Add more items + const itemToAdd = items[0]; + dispatch({ type: "ADD_ITEM", payload: itemToAdd }); + } else if (newQuantity < items.length) { + // Remove items + const itemsToRemove = items.length - newQuantity; + for (let i = 0; i < itemsToRemove; i++) { + const lastIndex = state.cart.map((item, index) => ({ item, index })) + .filter(({ item }) => item.id === id) + .pop().index; + dispatch({ type: "REMOVE_ITEM", payload: lastIndex }); + } + } + }; + + const removeItemCompletely = (id) => { + const itemsToRemove = state.cart + .map((item, index) => ({ item, index })) + .filter(({ item }) => item.id === id) + .map(({ index }) => index) + .reverse(); // Remove from end to avoid index issues + + itemsToRemove.forEach(index => { + dispatch({ type: "REMOVE_ITEM", payload: index }); + }); + }; + + const clearCart = () => { + dispatch({ type: "CLEAR_CART" }); + }; + + return ( +
+ {/* Header */} + +

+ Your Shopping Cart +

+

+ {cartItems.length === 0 ? "Your cart is waiting to be filled! 🛍️" : `You have ${cartItems.length} unique item${cartItems.length > 1 ? 's' : ''} in your cart`} +

+
+ +
+ {/* Cart Items */} +
+ {cartItems.length === 0 ? ( + +
🛒
+

+ Your cart is empty +

+

+ Discover amazing products and add them to your cart! +

+ + Start Shopping + +
+ ) : ( + <> + {/* Cart Header */} +
+

+ Cart Items ({state.cart.length}) +

+ +
+ + {/* Cart Items List */} +
+ + {cartItems.map((item, index) => ( + +
+ {/* Product Image */} +
+ {item.name} +
+ + {/* Product Info */} +
+

+ {item.name} +

+

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

+
+ {/* Quantity Controls */} +
+ + + {item.quantity} + + +
+ + {/* Total Price for this item */} +
+ ₹{(item.totalPrice).toLocaleString()} +
+
+
+ + {/* Remove Button */} + +
+
+ ))} +
+
+ + )} +
+ + {/* Order Summary - Only show when cart has items */} + {cartItems.length > 0 && ( + +
+

+ Order Summary +

+ + {/* Price Breakdown */} +
+
+ Subtotal ({state.cart.length} items) + ₹{subtotal.toLocaleString()} +
+
+ Shipping + {shipping > 0 ? `₹${shipping}` : "Free"} +
+ {shipping > 0 && subtotal < 999 && ( +
+ 🚚 Add ₹{(999 - subtotal).toLocaleString()} more for free shipping! +
+ )} +
+
+ Total + + ₹{total.toLocaleString()} + +
+
+
+ + {/* Action Buttons */} +
+ + Proceed to Payment + + + Continue Shopping + +
+ + {/* Security Badge */} +
+
+ 🔒 + Secure Checkout +
+

+ Your payment information is encrypted and secure +

+
+
+
+ )} +
+ + {/* Background Decoration */} +
+
+
+
+
+ ); +}; + +export default Cart; \ No newline at end of file diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx new file mode 100644 index 0000000..b62ca17 --- /dev/null +++ b/frontend/src/pages/Home.jsx @@ -0,0 +1,187 @@ +import React, { useContext } from "react"; +import { CartContext } from "../components/CartContext"; +import { Link } from "react-router-dom"; +import { motion, AnimatePresence } from "framer-motion"; + +const products = [ + { + id: 1, + name: "Wireless Headphones", + price: 2999, + image: "https://images.unsplash.com/photo-1505740420928-5e560c06d30e?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1000&q=80", + description: "Premium sound quality with noise cancellation" + }, + { + id: 2, + name: "Smartwatch", + price: 1999, + image: "https://images.unsplash.com/photo-1523275335684-37898b6baf30?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1000&q=80", + description: "Track your fitness and stay connected" + }, + { + id: 3, + name: "Bluetooth Speaker", + price: 1499, + image: "https://images.unsplash.com/photo-1608043152269-423dbba4e7e1?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1000&q=80", + description: "Portable speaker with crystal clear audio" + }, + { + id: 4, + name: "Gaming Mouse", + price: 1299, + image: "https://images.unsplash.com/photo-1527864550417-7fd91fc51a46?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1000&q=80", + description: "High precision gaming mouse with RGB lighting" + }, + { + id: 5, + name: "Laptop Stand", + price: 899, + image: "https://images.unsplash.com/photo-1586953208448-b95a79798f07?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1000&q=80", + description: "Ergonomic aluminum laptop stand" + }, + { + id: 6, + name: "Wireless Keyboard", + price: 1799, + image: "https://images.unsplash.com/photo-1587829741301-dc798b83add3?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1000&q=80", + description: "Mechanical wireless keyboard" + } +]; + +const Home = () => { + const { dispatch, setNotification, notification } = useContext(CartContext); + + const addToCart = (product) => { + dispatch({ type: "ADD_ITEM", payload: product }); + setNotification(product); + setTimeout(() => setNotification(null), 2000); + }; + + return ( +
+ {/* Header */} + +

+ Discover Amazing Products +

+

+ Explore our curated collection of premium electronics and accessories +

+
+ + {/* Products Grid */} +
+
+ {products.map((product, index) => ( + + {/* Product Image - Fixed with proper height */} +
+ {product.name} +
+
+ + {/* Product Info */} +
+
+

+ {product.name} +

+ + ₹{product.price.toLocaleString()} + +
+ +

+ {product.description} +

+ + addToCart(product)} + className="w-full bg-gradient-to-r from-blue-600 to-purple-600 text-white py-3 px-6 rounded-xl font-semibold hover:from-blue-700 hover:to-purple-700 transition-all duration-300 transform hover:scale-105 active:scale-95 shadow-lg hover:shadow-xl mt-auto" + whileHover={{ scale: 1.02 }} + whileTap={{ scale: 0.98 }} + > + Add to Cart 🛒 + +
+ + ))} +
+
+ + {/* Floating Cart Button */} + + + View Cart + 🛒 + + + + {/* Notification */} + + {notification && ( + +
+ {notification.name} +
+
+

+ {notification.name} +

+

+ Added to cart successfully! 🎉 +

+
+
+ )} +
+ + {/* Background Decoration */} +
+
+
+
+
+
+ ); +}; + +export default Home; \ No newline at end of file diff --git a/frontend/src/pages/Payment.jsx b/frontend/src/pages/Payment.jsx new file mode 100644 index 0000000..9e02637 --- /dev/null +++ b/frontend/src/pages/Payment.jsx @@ -0,0 +1,496 @@ +import React, { useState, useContext } from "react"; +import { CartContext } from "../components/CartContext"; +import { Link, useNavigate } from "react-router-dom"; +import { motion, AnimatePresence } from "framer-motion"; + +const Payment = () => { + const { state, dispatch } = useContext(CartContext); + const navigate = useNavigate(); + const [paymentMethod, setPaymentMethod] = useState("card"); + const [formData, setFormData] = useState({ + cardNumber: "", + cardName: "", + expiry: "", + cvv: "", + upiId: "", + wallet: "", + bank: "" + }); + const [isProcessing, setIsProcessing] = useState(false); + + const total = state.cart.reduce((sum, item) => sum + item.price, 0); + const tax = total * 0.18; + const shipping = 99; + const finalTotal = total + tax + shipping; + + const handleInputChange = (e) => { + const { name, value } = e.target; + + // Format card number with spaces + if (name === "cardNumber") { + const formattedValue = value.replace(/\s/g, '').replace(/(\d{4})/g, '$1 ').trim(); + setFormData(prev => ({ ...prev, [name]: formattedValue })); + return; + } + + // Format expiry date + if (name === "expiry") { + const formattedValue = value.replace(/\D/g, '').replace(/(\d{2})(\d)/, '$1/$2'); + setFormData(prev => ({ ...prev, [name]: formattedValue })); + return; + } + + setFormData(prev => ({ ...prev, [name]: value })); + }; + + const handlePayment = async (e) => { + e.preventDefault(); + setIsProcessing(true); + + // Simulate payment processing + setTimeout(() => { + setIsProcessing(false); + dispatch({ type: "CLEAR_CART" }); + navigate("/success"); + }, 3000); + }; + + const paymentMethods = [ + { id: "card", name: "Credit/Debit Card", icon: "💳", color: "from-blue-500 to-blue-700" }, + { id: "upi", name: "UPI Payment", icon: "📱", color: "from-green-500 to-green-700" }, + { id: "wallet", name: "Wallet", icon: "👛", color: "from-purple-500 to-purple-700" }, + { id: "netbanking", name: "Net Banking", icon: "🏦", color: "from-orange-500 to-orange-700" }, + ]; + + const wallets = [ + { id: "paytm", name: "Paytm", icon: "📲" }, + { id: "phonepe", name: "PhonePe", icon: "📳" }, + { id: "amazonpay", name: "Amazon Pay", icon: "🛒" }, + { id: "other", name: "Other Wallets", icon: "💰" }, + ]; + + const banks = [ + { id: "sbi", name: "State Bank of India" }, + { id: "hdfc", name: "HDFC Bank" }, + { id: "icici", name: "ICICI Bank" }, + { id: "axis", name: "Axis Bank" }, + { id: "kotak", name: "Kotak Mahindra Bank" }, + { id: "other", name: "Other Banks" }, + ]; + + return ( +
+
+ {/* Header */} + +

+ Secure Checkout +

+

+ Complete your purchase with secure payment +

+
+ +
+ {/* Payment Methods & Form */} +
+ {/* Payment Method Selection */} + +
+

+ Select Payment Method +

+
+ {paymentMethods.map((method) => ( + setPaymentMethod(method.id)} + className={`p-4 rounded-xl border-2 transition-all duration-200 ${ + paymentMethod === method.id + ? `border-blue-500 bg-gradient-to-r ${method.color} text-white shadow-lg` + : "border-gray-200 hover:border-gray-300 bg-white text-gray-700" + }`} + whileHover={{ scale: 1.02 }} + whileTap={{ scale: 0.98 }} + > +
+ {method.icon} + {method.name} +
+
+ ))} +
+
+
+ + {/* Payment Form */} + +
+

+ {paymentMethod === "card" && "Card Details"} + {paymentMethod === "upi" && "UPI Payment"} + {paymentMethod === "wallet" && "Digital Wallet"} + {paymentMethod === "netbanking" && "Net Banking"} +

+ +
+ + {/* Card Payment */} + {paymentMethod === "card" && ( + +
+ + +
+ +
+ + +
+ +
+
+ + +
+
+ + +
+
+ +
+ 🔒 +
+

Secure Payment

+

Your card details are encrypted and secure

+
+
+
+ )} + + {/* UPI Payment */} + {paymentMethod === "upi" && ( + +
+ + +
+ +
+ + +
+ +
+
+ 💡 +
+

Quick Payment

+

You'll be redirected to your UPI app to complete the payment

+
+
+
+
+ )} + + {/* Wallet Payment */} + {paymentMethod === "wallet" && ( + +
+ +
+ {wallets.map(wallet => ( + + ))} +
+
+ + {formData.wallet && ( + +

+ You'll be redirected to {wallets.find(w => w.id === formData.wallet)?.name} to complete your payment +

+
+ )} +
+ )} + + {/* Net Banking */} + {paymentMethod === "netbanking" && ( + +
+ + +
+ +
+
🏦
+

+ Secure Net Banking +

+

+ You'll be redirected to your bank's secure portal to complete the payment +

+
+
+ )} +
+ + {/* Pay Button */} + + {isProcessing ? ( +
+
+ Processing Payment... +
+ ) : ( + `Pay ₹${finalTotal.toLocaleString()}` + )} + + +
+ +
+ + {/* Order Summary */} +
+ +

+ Order Summary +

+ + {/* Items List */} +
+ {state.cart.map((item, index) => ( +
+ {item.name} +
+

+ {item.name} +

+

₹{item.price.toLocaleString()}

+
+
+ ))} +
+ + {/* Price Breakdown */} +
+
+ Subtotal ({state.cart.length} items) + ₹{total.toLocaleString()} +
+
+ Shipping + ₹{shipping} +
+
+ Tax (18%) + ₹{tax.toLocaleString()} +
+
+
+ Total Amount + + ₹{finalTotal.toLocaleString()} + +
+
+
+ + {/* Back to Cart */} + + ← Back to Cart + +
+
+
+ + {/* Security Badges */} + + {[ + { icon: "🔒", text: "256-bit SSL Secure" }, + { icon: "🛡️", text: "PCI DSS Compliant" }, + { icon: "💰", text: "100% Money Back" }, + { icon: "⚡", text: "Instant Refund" } + ].map((badge, index) => ( +
+ {badge.icon} + {badge.text} +
+ ))} +
+
+ + {/* Background Decoration */} +
+
+
+
+
+ ); +}; + +export default Payment; \ No newline at end of file