// De-structure React and ReactDOM from the global window object (loaded via CDN) const { useState, createContext, useContext, StrictMode, useRef, useEffect } = React; const e = React.createElement; // --- From constants.ts --- const initialProducts = [ { id: 1, name: 'High-Performance Wireless Earbuds', image: 'https://picsum.photos/seed/product1/400/300', price: 129.99, rating: 4.5, reviewCount: 18450, category: 'Electronics', description: 'Experience immersive sound with these noise-cancelling wireless earbuds. Features include a 24-hour battery life, customizable fit, and seamless Bluetooth 5.2 connectivity. Perfect for music, calls, and workouts.', colors: ['Black', 'White', 'Midnight Blue'] }, { id: 2, name: 'Smart Home Hub - Voice Controlled Assistant', image: 'https://picsum.photos/seed/product2/400/300', price: 89.99, rating: 4.7, reviewCount: 25320, category: 'Smart Home', description: 'Control your smart home with your voice. Play music, set alarms, and get information from this powerful smart hub. Integrates with thousands of devices.' }, { id: 3, name: 'Ergonomic Mechanical Keyboard with RGB Backlighting', image: 'https://picsum.photos/seed/product3/400/300', price: 75.50, rating: 4.8, reviewCount: 9876, category: 'Computers', description: 'A tactile and responsive mechanical keyboard designed for gamers and professionals. Features customizable RGB lighting, programmable macro keys, and a durable aluminum frame.' }, { id: 4, name: 'Professional 4K UHD Drone with GPS', image: 'https://picsum.photos/seed/product4/400/300', price: 499.00, rating: 4.6, reviewCount: 3450, category: 'Camera & Photo', description: 'Capture stunning aerial footage with this 4K UHD drone. Equipped with GPS, auto-return home, and a 3-axis gimbal for stable video. 30-minute flight time.' }, { id: 5, name: 'Organic Cotton Bath Towel Set (6-Piece)', image: 'https://picsum.photos/seed/product5/400/300', price: 45.99, rating: 4.9, reviewCount: 12010, category: 'Home & Kitchen', description: 'Indulge in luxury with this 6-piece towel set made from 100% organic cotton. Ultra-soft, absorbent, and durable. Set includes 2 bath towels, 2 hand towels, and 2 washcloths.', colors: ['Gray', 'White', 'Spa Blue'] }, { id: 6, name: 'Insulated Stainless Steel Water Bottle, 32oz', image: 'https://picsum.photos/seed/product6/400/300', price: 24.95, rating: 4.8, reviewCount: 32189, category: 'Sports & Outdoors', description: 'Keep your drinks cold for 24 hours or hot for 12 hours. This 32oz insulated water bottle is made from food-grade stainless steel and features a leak-proof lid.', colors: ['Matte Black', 'Stainless', 'Pacific Blue'] }, { id: 7, name: 'Bestselling Mystery Novel - "The Silent Witness"', image: 'https://picsum.photos/seed/product7/400/300', price: 14.99, rating: 4.4, reviewCount: 8765, category: 'Books', description: 'A gripping tale of suspense and intrigue that will keep you on the edge of your seat. From a New York Times bestselling author.' }, { id: 8, name: 'Gourmet Coffee Bean Sampler Pack (4 Flavors)', image: 'https://picsum.photos/seed/product8/400/300', price: 29.99, rating: 4.7, reviewCount: 4531, category: 'Grocery', description: 'Explore a world of flavor with this sampler pack of whole bean coffee. Includes four unique roasts from around the globe: Ethiopian, Colombian, French Roast, and Breakfast Blend.' }, { id: 9, name: 'Men\'s Classic Fit T-Shirt', image: 'https://picsum.photos/seed/product9/400/300', price: 19.99, rating: 4.6, reviewCount: 5400, category: 'Fashion', description: 'A wardrobe essential, this classic t-shirt is made from ultra-soft pima cotton. It offers a comfortable fit that is perfect for layering or wearing on its own.', sizes: ['S', 'M', 'L', 'XL'], colors: ['Black', 'White', 'Navy', 'Heather Gray'] }, { id: 10, name: 'Entry-Level DSLR Camera', image: 'https://picsum.photos/seed/product10/400/300', price: 550.00, rating: 4.2, reviewCount: 2200, category: 'Camera & Photo', description: 'Perfect for beginners, this DSLR camera bundle includes an 18-55mm lens, a camera bag, and a memory card. Features a 24.1 Megapixel sensor and easy-to-use automatic modes.' }, { id: 11, name: 'Smart LED Light Strip', image: 'https://picsum.photos/seed/product11/400/300', price: 22.50, rating: 2.9, reviewCount: 500, category: 'Smart Home', description: 'Transform any room with vibrant, customizable lighting. This 16.4ft LED light strip is controllable via app or voice assistant and features millions of colors.' }, ]; // --- contexts/ProductContext.tsx --- const ProductContext = createContext(undefined); const ProductProvider = ({ children }) => { const [products, setProducts] = useState(() => { try { const storedProducts = localStorage.getItem('zwingiProducts'); return storedProducts ? JSON.parse(storedProducts) : initialProducts; } catch (error) { console.error("Error reading products from localStorage", error); return initialProducts; } }); useEffect(() => { try { localStorage.setItem('zwingiProducts', JSON.stringify(products)); } catch (error) { console.error("Error saving products to localStorage", error); } }, [products]); const addProducts = (newProducts) => { const validProducts = newProducts.filter(p => p.name && !isNaN(p.price) && p.category); const productsWithIds = validProducts.map((p, i) => ({ ...p, id: Date.now() + i, image: p.image || `https://picsum.photos/seed/${Date.now() + i}/400/300`, rating: p.rating || 0, reviewCount: p.reviewCount || 0, description: p.description || '', })); setProducts(prevProducts => [...prevProducts, ...productsWithIds]); }; const resetProducts = () => { if(window.confirm('Are you sure you want to delete all products and reset to the demo set?')) { setProducts(initialProducts); alert('Products have been reset to the defaults.'); } }; return e(ProductContext.Provider, { value: { products, addProducts, resetProducts } }, children); }; const useProducts = () => { const context = useContext(ProductContext); if (context === undefined) { throw new Error('useProducts must be used within a ProductProvider'); } return context; }; // --- contexts/AuthContext.tsx --- const AuthContext = createContext(undefined); const AuthProvider = ({ children }) => { const USERS_DB_KEY = 'zwingiUsersDb'; const CURRENT_USER_KEY = 'zwingiUser'; const [user, setUser] = useState(() => { try { const storedUser = localStorage.getItem(CURRENT_USER_KEY); return storedUser ? JSON.parse(storedUser) : null; } catch (error) { console.error("Error reading user session from localStorage", error); return null; } }); useEffect(() => { try { if (user) { localStorage.setItem(CURRENT_USER_KEY, JSON.stringify(user)); } else { localStorage.removeItem(CURRENT_USER_KEY); } } catch (error) { console.error("Error saving user session to localStorage", error); } }, [user]); const getUsersDb = () => { try { const db = localStorage.getItem(USERS_DB_KEY); return db ? JSON.parse(db) : []; } catch { return []; } }; const saveUsersDb = (db) => { localStorage.setItem(USERS_DB_KEY, JSON.stringify(db)); }; const register = (email, password) => { if (!email || !password) { alert('Email and password are required.'); return false; } const db = getUsersDb(); if (db.find(u => u.email === email)) { alert('An account with this email already exists.'); return false; } // NOTE: In a real-world application, NEVER store passwords in plain text. // This should be securely hashed on a server. db.push({ email, password }); saveUsersDb(db); setUser({ type: 'registered', email }); window.location.hash = '#/'; return true; }; const login = (email, password) => { const db = getUsersDb(); const foundUser = db.find(u => u.email === email && u.password === password); if (foundUser) { setUser({ type: 'registered', email: foundUser.email }); window.location.hash = '#/'; } else { alert('Invalid email or password.'); } }; const loginAsGuest = () => { setUser({ type: 'guest', identifier: `guest_${Date.now()}` }); window.location.hash = '#/'; }; const logout = () => { setUser(null); window.location.hash = '#/login'; }; const value = { user, isLoggedIn: !!user, login, register, loginAsGuest, logout }; return e(AuthContext.Provider, { value }, children); }; const useAuth = () => { const context = useContext(AuthContext); if (context === undefined) { throw new Error('useAuth must be used within an AuthProvider'); } return context; }; // --- contexts/ToastContext.tsx --- const ToastContext = createContext(undefined); const ToastProvider = ({ children }) => { const [toast, setToast] = useState({ message: '', visible: false, key: 0 }); const showToast = (message) => { setToast({ message, visible: true, key: Date.now() }); const timer = setTimeout(() => { setToast(t => ({ ...t, visible: false })); }, 3000); return () => clearTimeout(timer); }; const ToastComponent = () => { const [isShowing, setIsShowing] = useState(false); useEffect(() => { let timeout; if (toast.visible) { setIsShowing(true); } else if (isShowing) { timeout = setTimeout(() => setIsShowing(false), 500); } return () => clearTimeout(timeout); }, [toast]); if (!isShowing) return null; const baseClasses = 'fixed top-5 right-5 z-50 px-4 py-2 rounded-md shadow-lg text-white font-semibold flex items-center gap-2'; const transitionClasses = `transition-all duration-300 ease-in-out transform ${toast.visible ? 'translate-x-0 opacity-100' : 'translate-x-full opacity-0'}`; return e('div', { key: toast.key, className: `${baseClasses} bg-green-500 ${transitionClasses}`, role: 'alert', 'aria-live': 'assertive' }, e('svg', { xmlns: "http://www.w3.org/2000/svg", className: "h-6 w-6", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor"}, e('path', { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M5 13l4 4L19 7"})), toast.message ); }; return e(ToastContext.Provider, { value: { showToast } }, e(React.Fragment, null, children, e(ToastComponent, null) ) ); }; const useToast = () => { const context = useContext(ToastContext); if (context === undefined) throw new Error('useToast must be used within a ToastProvider'); return context; }; // --- contexts/CartContext.tsx --- const CartContext = createContext(undefined); const CartProvider = ({ children }) => { const [cartItems, setCartItems] = useState([]); const { showToast } = useToast(); const addToCart = (product, options) => { const { quantity, size, color } = options; setCartItems(prevItems => { const cartItemId = `${product.id}-${size || 'default'}-${color || 'default'}`; const existingItem = prevItems.find(item => item.cartItemId === cartItemId); if (existingItem) { return prevItems.map(item => item.cartItemId === cartItemId ? { ...item, quantity: item.quantity + quantity } : item ); } return [...prevItems, { ...product, ...options, cartItemId }]; }); showToast(`Added "${product.name}"`); }; const clearCart = () => setCartItems([]); const cartCount = cartItems.reduce((count, item) => count + item.quantity, 0); const cartTotal = cartItems.reduce((total, item) => total + (item.price * item.quantity), 0); return e(CartContext.Provider, { value: { cartItems, addToCart, cartCount, cartTotal, clearCart } }, children); }; const useCart = () => { const context = useContext(CartContext); if (context === undefined) { throw new Error('useCart must be used within a CartProvider'); } return context; }; // --- UPDATED contexts/LocationContext.tsx --- const LocationContext = createContext(undefined); const LocationProvider = ({ children }) => { const LOCATION_KEY = 'zwingiLocation'; const [location, setLocation] = useState(() => { try { const storedLocation = localStorage.getItem(LOCATION_KEY); return storedLocation ? JSON.parse(storedLocation) : null; } catch { return null; } }); const [isLoading, setIsLoading] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false); useEffect(() => { if (location) { localStorage.setItem(LOCATION_KEY, JSON.stringify(location)); } else { localStorage.removeItem(LOCATION_KEY); } }, [location]); const detectPreciseLocation = () => { setIsLoading(true); if (isModalOpen) setIsModalOpen(false); if (!navigator.geolocation) { setIsLoading(false); return alert('Geolocation is not supported by your browser.'); } navigator.geolocation.getCurrentPosition( (position) => { // MOCK REVERSE GEOCODING API CALL setTimeout(() => { setLocation({ city: 'Cupertino', country: 'United States', address: '1 Infinite Loop', }); setIsLoading(false); }, 1000); }, (error) => { console.warn(`Geolocation error: ${error.message}`); alert("Couldn't get your precise location. Please ensure location services are enabled and permission is granted."); setIsLoading(false); }, { timeout: 10000, enableHighAccuracy: true } ); }; // On initial load, try a multi-layered approach to get location. useEffect(() => { const initialDetect = () => { if (localStorage.getItem(LOCATION_KEY)) { return; // We already have a location, do nothing. } setIsLoading(true); // 1. SIMULATE an IP-based lookup as a fallback. // This happens first and provides a non-permission-based guess. const ipLookupTimeout = setTimeout(() => { setLocation({ city: 'London', country: 'United Kingdom', address: null }); setIsLoading(false); // Stop loading indicator after IP lookup }, 1500); // 2. ATTEMPT to get a precise location, which will prompt for permission. if (navigator.geolocation) { navigator.geolocation.getCurrentPosition( (position) => { clearTimeout(ipLookupTimeout); // We got a better location, cancel the IP fallback. // MOCK REVERSE GEOCODING API CALL setTimeout(() => { setLocation({ city: 'Westminster', country: 'United Kingdom', address: '10 Downing Street', }); setIsLoading(false); }, 500); }, (error) => { // User denied or an error occurred. The IP lookup will complete as the fallback. console.warn(`Initial geolocation failed: ${error.message}`); }, { timeout: 10000 } ); } }; initialDetect(); }, []); // Empty dependency array ensures this runs only once on mount. const updateLocationManually = (newLocation) => { setLocation({ ...newLocation, address: null }); setIsModalOpen(false); }; const value = { location, isLoading, isModalOpen, setIsModalOpen, updateLocationManually, detectPreciseLocation }; return e(LocationContext.Provider, { value }, children); }; const useLocation = () => { const context = useContext(LocationContext); if (!context) throw new Error('useLocation must be used within a LocationProvider'); return context; }; // --- components/Icons.tsx --- const SearchIcon = ({ className }) => e('svg', { className, xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "currentColor" }, e('path', { d: "M10 18a8 8 0 100-16 8 8 0 000 16zm-5.3-1.7l-4 4L2 21.5l4-4a8 8 0 009.3-11.3A7.9 7.9 0 004.7 16.3z" })); const CartIcon = ({ className }) => e('svg', { className, xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor" }, e('path', { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z" })); const LocationIcon = ({ className }) => e('svg', { className, xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor" }, e('path', { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" }), e('path', { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M15 11a3 3 0 11-6 0 3 3 0 016 0z" })); const MenuIcon = ({ className }) => e('svg', { xmlns: "http://www.w3.org/2000/svg", className, fill: "none", viewBox: "0 0 24 24", stroke: "currentColor" }, e('path', { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 6h16M4 12h16M4 18h16" })); const StarIcon = ({ className, fill }) => e('svg', { className, xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 20 20", fill }, e('path', { d: "M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" })); const CloseIcon = ({ className }) => e('svg', { xmlns: "http://www.w3.org/2000/svg", className, fill: "none", viewBox: "0 0 24 24", stroke: "currentColor" }, e('path', { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" })); // --- UPDATED components/LocationModal.tsx --- const LocationModal = ({ isOpen, onClose }) => { const { location, updateLocationManually, detectPreciseLocation } = useLocation(); const [country, setCountry] = useState(location?.country || ''); const [city, setCity] = useState(location?.city || ''); useEffect(() => { setCountry(location?.country || ''); setCity(location?.city || ''); }, [location, isOpen]); if (!isOpen) return null; const handleSubmit = (ev) => { ev.preventDefault(); if (country.trim() && city.trim()) { updateLocationManually({ country: country.trim(), city: city.trim() }); } }; return e(React.Fragment, null, e('div', { className: 'fixed inset-0 bg-black bg-opacity-50 z-40', onClick: onClose }), e('div', { className: 'fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-white rounded-lg shadow-xl z-50 w-full max-w-md' }, e('div', { className: 'p-6' }, e('div', { className: 'flex justify-between items-center border-b pb-3 mb-4' }, e('h2', { className: 'text-xl font-bold' }, 'Choose your location'), e('button', { onClick: onClose, 'aria-label': 'Close' }, e(CloseIcon, { className: 'h-6 w-6 text-gray-500 hover:text-gray-800' })) ), e('p', { className: 'text-sm text-gray-600 mb-4' }, 'Delivery options and fees may vary based on your location.'), e('form', { onSubmit: handleSubmit, className: 'space-y-4' }, e('div', null, e('label', { htmlFor: 'country', className: 'block text-sm font-medium text-gray-700' }, 'Country/Region'), e('input', { type: 'text', id: 'country', value: country, onChange: (ev) => setCountry(ev.target.value), className: 'mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-yellow-500 focus:border-yellow-500', required: true }) ), e('div', null, e('label', { htmlFor: 'city', className: 'block text-sm font-medium text-gray-700' }, 'City / ZIP Code'), e('input', { type: 'text', id: 'city', value: city, onChange: (ev) => setCity(ev.target.value), className: 'mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-yellow-500 focus:border-yellow-500', required: true }) ), e('button', { type: 'submit', className: 'w-full bg-yellow-400 hover:bg-yellow-500 text-black py-2 rounded-lg font-semibold' }, 'Done') ), e('div', { className: 'relative my-4' }, e('div', { className: 'absolute inset-0 flex items-center', 'aria-hidden': 'true' }, e('div', { className: 'w-full border-t border-gray-300' })), e('div', { className: 'relative flex justify-center text-sm' }, e('span', { className: 'px-2 bg-white text-gray-500' }, 'or')) ), e('button', { onClick: detectPreciseLocation, className: 'w-full bg-gray-200 hover:bg-gray-300 text-black py-2 rounded-lg border border-gray-400 text-sm' }, 'Use my current location') ) ) ); }; // --- UPDATED components/Header.tsx --- const Header = ({ cartCount }) => { const { isLoggedIn, user, logout } = useAuth(); const { location, isLoading, setIsModalOpen } = useLocation(); const [showDropdown, setShowDropdown] = useState(false); const AccountLink = () => { if (isLoggedIn) { const displayName = user.type === 'registered' ? user.email.split('@')[0] : 'Guest'; return e('div', { className: 'relative' }, e('button', { onClick: () => setShowDropdown(!showDropdown), onBlur: () => setTimeout(() => setShowDropdown(false), 200), className: "border border-transparent hover:border-white p-2 rounded-sm text-left" }, e('div', { className: "text-xs capitalize" }, `Hello, ${displayName}`), e('div', { className: "text-sm font-bold" }, "Account & Lists") ), showDropdown && e('div', { className: 'absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg z-50 text-black' }, e('button', { onClick: logout, className: 'block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100' }, 'Sign Out') ) ); } else { return e('a', { href: "#/login", className: "border border-transparent hover:border-white p-2 rounded-sm" }, e('div', { className: "text-xs" }, "Hello, sign in"), e('div', { className: "text-sm font-bold" }, "Account & Lists") ); } }; const LocationDisplay = () => { let textLine1 = 'Deliver to'; let textLine2 = 'Select your address'; if (isLoading) { textLine1 = 'Detecting...'; textLine2 = ' '; } else if (location) { textLine1 = `Deliver to ${location.address ? location.city : ''}`.trim(); textLine2 = location.address || `${location.city}, ${location.country}`; } else { textLine1 = 'Hello'; textLine2 = 'Select your address'; } return e('button', { onClick: () => setIsModalOpen(true), className: "hidden md:flex items-center border border-transparent hover:border-white p-2 rounded-sm cursor-pointer text-left" }, e(LocationIcon, { className: "h-5 w-5 mr-1 flex-shrink-0" }), e('div', null, e('div', { className: "text-xs text-gray-300" }, textLine1), e('div', { className: "text-sm font-bold truncate" }, textLine2) ) ); }; return e('header', { className: "bg-[#131921] text-white sticky top-0 z-30" }, e('div', { className: "flex items-center justify-between px-4 py-2 max-w-7xl mx-auto" }, e('a', { href: "#/", className: "border border-transparent hover:border-white p-2 rounded-sm" }, e('div', null, e('h1', { className: "text-2xl font-bold leading-tight" }, "Zwingi", e('span', { className: "text-[#FF9900]" }, ".com")), e('span', { className: "text-xs text-gray-300 italic -mt-1 block" }, "Spend less. Smile more."))), e(LocationDisplay), e('div', { className: "flex-grow mx-4 hidden sm:flex" }, e('div', { className: "flex w-full" }, e('select', { className: "bg-gray-200 text-black text-xs p-2 rounded-l-md border-r border-gray-300 hover:bg-gray-300 focus:outline-none" }, e('option', null, "All"), e('option', null, "Electronics"), e('option', null, "Books"), e('option', null, "Home"), e('option', null, "Fashion")), e('input', { type: "text", className: "w-full p-2 text-black focus:outline-none", placeholder: "Search Zwingi.com" }), e('button', { className: "bg-[#FF9900] hover:bg-yellow-500 p-2 rounded-r-md" }, e(SearchIcon, { className: "h-6 w-6 text-black" })))), e('div', { className: "flex items-center space-x-4" }, e('a', { href: "#", className: "hidden lg:flex items-center border border-transparent hover:border-white p-2 rounded-sm" }, e('span', { className: "text-2xl mr-2" }, "\ud83c\uddfa\ud83c\uddf8"), e('span', { className: "font-bold text-sm" }, "EN")), e(AccountLink, null), e('a', { href: "#/checkout", className: "flex items-center border border-transparent hover:border-white p-2 rounded-sm relative" }, e(CartIcon, { className: "h-8 w-8" }), e('span', { className: "absolute top-0 left-5 bg-[#FF9900] text-black text-xs font-bold rounded-full h-5 w-5 flex items-center justify-center" }, cartCount), e('span', { className: "hidden xl:inline text-sm font-bold mt-3 ml-1" }, "Cart"))))); }; // --- components/CategoryNav.tsx --- const CategoryNav = ({ onOpenSideMenu }) => { const navItems = ["Today's Deals", "Customer Service", "Registry", "Gift Cards", "Sell"]; return e('div', { className: "bg-[#232F3E] text-white text-sm" }, e('div', { className: "max-w-7xl mx-auto flex items-center px-4 py-2" }, e('button', { onClick: onOpenSideMenu, className: "flex items-center font-bold border border-transparent hover:border-white p-1 rounded-sm", 'aria-label': "Open side menu for all categories" }, e(MenuIcon, { className: "h-6 w-6 mr-1" }), "All"), e('nav', { className: "flex space-x-4 ml-4" }, navItems.map((item) => e('a', { key: item, href: "#", className: "border border-transparent hover:border-white p-1 rounded-sm" }, item))), e('a', { href: "#", className: "ml-auto font-bold border border-transparent hover:border-white p-1 rounded-sm" }, "Shop great deals now"))); }; // --- components/HeroSection.tsx --- const HeroSection = () => e('div', { className: "mb-6" }, e('div', { className: "relative" }, e('img', { src: "https://picsum.photos/seed/herobanner/1500/600", alt: "Promotional banner", className: "w-full h-auto object-cover max-h-96 rounded-lg" }), e('div', { className: "absolute inset-0 bg-gradient-to-t from-gray-100 via-transparent to-transparent" }))); // --- components/ProductCard.tsx --- const RatingStars = ({ rating }) => { const fullStars = Math.floor(rating); const emptyStars = 5 - fullStars; return e('div', { className: "flex items-center" }, [...Array(fullStars)].map((_, i) => e(StarIcon, { key: `full-${i}`, className: "h-5 w-5 text-yellow-500", fill: "currentColor" })), [...Array(emptyStars)].map((_, i) => e(StarIcon, { key: `empty-${i}`, className: "h-5 w-5 text-gray-300", fill: "currentColor" }))); }; const ProductCard = ({ product }) => e('div', { className: "bg-white border border-gray-200 rounded-lg overflow-hidden flex flex-col h-full" }, e('a', { href: `#/product/${product.id}`, className: "flex flex-col flex-grow" }, e('div', { className: "h-48 flex items-center justify-center overflow-hidden" }, e('img', { src: product.image, alt: product.name, className: "w-full h-full object-cover" })), e('div', { className: "p-4 flex flex-col flex-grow" }, e('h3', { className: "text-md font-semibold text-gray-800 hover:text-red-600 cursor-pointer mb-2 leading-tight flex-grow" }, product.name), e('div', { className: "flex items-center my-2" }, e(RatingStars, { rating: product.rating }), e('span', { className: "text-blue-600 hover:text-red-600 ml-2 text-sm" }, product.reviewCount.toLocaleString())), e('div', { className: "flex items-start" }, e('span', { className: "text-sm mt-1" }, "$"), e('span', { className: "text-2xl font-bold" }, Math.floor(product.price)), e('span', { className: "text-sm mt-1" }, (product.price % 1).toFixed(2).substring(2))) ) ) ); // --- components/ProductGrid.tsx --- const ProductGrid = ({ products, title }) => e('div', { className: "bg-white p-6 rounded-md shadow-md mb-6" }, e('h2', { className: "text-2xl font-bold mb-4" }, title), e('div', { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6" }, products.map((product) => e(ProductCard, { key: product.id, product: product })))); // --- components/Footer.tsx --- const Footer = () => { const footerLinks = { "Get to Know Us": ["Careers", "Blog", "About Zwingi", "Investor Relations", "Zwingi Devices"], "Make Money with Us": ["Sell products on Zwingi", "Sell on Zwingi Business", "Sell apps on Zwingi", "Become an Affiliate", "Advertise Your Products", "Self-Publish with Us", "Host an Zwingi Hub"], "Zwingi Payment Products": ["Zwingi Business Card", "Shop with Points", "Reload Your Balance", "Zwingi Currency Converter"], "Let Us Help You": ["Zwingi and COVID-19", "Your Account", "Your Orders", "Shipping Rates & Policies", "Returns & Replacements", "Manage Your Content and Devices", "Zwingi Assistant", "Help"] }; return e('footer', { className: "bg-[#232F3E] text-white" }, e('div', { className: "py-10" }, e('div', { className: "max-w-7xl mx-auto grid grid-cols-2 md:grid-cols-4 gap-8 px-6" }, Object.entries(footerLinks).map(([title, links]) => e('div', { key: title }, e('h3', { className: "font-bold text-lg mb-3" }, title), e('ul', { className: "space-y-2" }, links.map(link => e('li', { key: link }, e('a', { href: "#", className: "text-sm text-gray-300 hover:underline" }, link)))))))), e('div', { className: "bg-[#131921] py-6" }, e('div', { className: "max-w-7xl mx-auto flex flex-col items-center" }, e('div', { className: 'text-center' }, e('h2', { className: "text-2xl font-bold leading-tight" }, "Zwingi", e('span', { className: "text-[#FF9900]" }, ".com")), e('span', { className: "text-xs text-gray-300 italic" }, "Spend less. Smile more.")), e('div', { className: "flex space-x-4 mt-3 text-xs text-gray-400" }, e('a', { href: "#", className: "hover:underline" }, "Conditions of Use"), e('a', { href: "#", className: "hover:underline" }, "Privacy Notice"), e('a', { href: "#/admin", className: "hover:underline" }, "Admin Panel")), e('p', { className: "text-xs text-gray-400 mt-2" }, "\u00A9 1996-2024, Zwingi.com, Inc. or its affiliates")))); }; // --- components/SideMenu.tsx --- const SideMenu = ({ isOpen, onClose }) => { const categories = ["Mobiles", "Accessories", "Cloths", "Men", "Women", "Kids", "Books", "Electronics", "Home & Kitchen", "Sports & Outdoors"]; return e(React.Fragment, null, e('div', { className: `fixed inset-0 bg-black bg-opacity-50 z-40 transition-opacity duration-300 ease-in-out ${isOpen ? 'opacity-100' : 'opacity-0 pointer-events-none'}`, onClick: onClose, 'aria-hidden': "true" }), e('div', { className: `fixed top-0 left-0 h-full bg-white w-80 max-w-[85vw] z-50 shadow-lg transform transition-transform duration-300 ease-in-out ${isOpen ? 'translate-x-0' : '-translate-x-full'}`, role: "dialog", 'aria-modal': "true", 'aria-labelledby': "sidemenu-title" }, e('div', { className: "flex flex-col h-full" }, e('div', { className: "bg-[#232F3E] text-white p-4 flex justify-between items-center" }, e('h2', { id: "sidemenu-title", className: "text-xl font-bold" }, "Hello, sign in"), e('button', { onClick: onClose, 'aria-label': "Close menu" }, e(CloseIcon, { className: "h-6 w-6" }))), e('div', { className: "flex-grow overflow-y-auto" }, e('h3', { className: "font-bold text-lg p-4 border-b" }, "Shop by Department"), e('ul', { className: "py-2" }, categories.map((category) => e('li', { key: category }, e('a', { href: "#", className: "block px-4 py-3 text-gray-700 hover:bg-gray-100" }, category)))))))); }; // --- AdminPage.tsx --- const AdminPage = () => { const [password, setPassword] = useState(''); const [isAuthenticated, setIsAuthenticated] = useState(false); const { addProducts, resetProducts } = useProducts(); const fileInputRef = useRef(null); const ADMIN_PASSWORD = 'zwingiadmin'; const handleLogin = (e) => { e.preventDefault(); if (password === ADMIN_PASSWORD) setIsAuthenticated(true); else alert('Incorrect password.'); }; const handleImport = () => { const file = fileInputRef.current.files[0]; if (!file) return alert("Please select a CSV file first."); const reader = new FileReader(); reader.onload = (event) => { try { const csvText = event.target.result; if (typeof csvText !== 'string') return alert('Error reading file content.'); const lines = csvText.split(/\r?\n/).filter(line => line.trim() !== ''); if (lines.length < 2) throw new Error("CSV must have a header and at least one data row."); const header = lines[0].split(',').map(h => h.trim().toLowerCase().replace(/\s+/g, '')); const requiredHeaders = ['name', 'price', 'category']; if (!requiredHeaders.every(h => header.includes(h))) throw new Error(`CSV header must contain: ${requiredHeaders.join(', ')}`); const newProducts = lines.slice(1).map(line => { const values = line.split(','); // FIX: Add a type annotation with an index signature to 'productData' to inform TypeScript that properties are being added dynamically based on the CSV header. This resolves the errors about properties not existing on type '{}'. const productData: { [key: string]: string | undefined } = {}; header.forEach((key, index) => { productData[key] = values[index] ? values[index].trim() : undefined; }); return { name: productData.name, price: parseFloat(productData.price), category: productData.category, image: productData.image, rating: productData.rating ? parseFloat(productData.rating) : 0, reviewCount: productData.reviewcount ? parseInt(productData.reviewcount, 10) : 0, description: productData.description || '' }; }); addProducts(newProducts); alert(`${newProducts.length} products imported successfully!`); fileInputRef.current.value = ""; } catch (error) { alert(`Error parsing CSV: ${error.message}`); } }; reader.onerror = () => alert("Error reading file."); reader.readAsText(file); }; if (!isAuthenticated) return e('div', { className: 'flex items-center justify-center min-h-screen bg-gray-100' }, e('div', { className: 'p-8 bg-white rounded-lg shadow-md w-full max-w-sm' }, e('h1', { className: 'text-2xl font-bold text-center mb-6' }, 'Admin Panel Login'), e('form', { onSubmit: handleLogin, className: 'space-y-4' }, e('div', null, e('label', { htmlFor: 'password', className: 'block text-sm font-medium text-gray-700' }, 'Password'), e('input', { id: 'password', type: 'password', value: password, onChange: ev => setPassword(ev.target.value), className: 'mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-yellow-500 focus:border-yellow-500', required: true })), e('button', { type: 'submit', className: 'w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-black bg-yellow-400 hover:bg-yellow-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-yellow-500' }, 'Login')))); return e('div', { className: 'p-8 bg-gray-100 min-h-screen' }, e('div', { className: 'max-w-2xl mx-auto bg-white rounded-lg shadow-md p-6' }, e('div', { className: 'flex justify-between items-center mb-6 border-b pb-4' }, e('h1', { className: 'text-3xl font-bold' }, 'Product Management'), e('a', { href: '#/', className: 'text-sm text-blue-600 hover:underline' }, '← Back to Store')), e('div', { className: 'space-y-6' }, e('div', null, e('h2', { className: 'text-xl font-semibold mb-2' }, 'Import New Products'), e('p', { className: 'text-sm text-gray-600 mb-4' }, "Upload a CSV file to add new products. Header must include 'name', 'price', and 'category'."), e('div', { className: 'space-y-2' }, e('label', { htmlFor: 'csv-upload', className: 'block text-sm font-medium text-gray-700' }, "CSV File"), e('input', { ref: fileInputRef, id: 'csv-upload', type: 'file', accept: '.csv', className: 'block w-full text-sm text-gray-900 border border-gray-300 rounded-lg cursor-pointer bg-gray-50 focus:outline-none' }), e('p', { className: 'text-xs text-gray-500' }, "Optional headers: image, rating, reviewcount, description"))), e('button', { onClick: handleImport, className: 'mt-4 px-4 py-2 bg-yellow-400 text-black rounded-md hover:bg-yellow-500 transition' }, 'Import Products')), e('div', { className: 'border-t pt-6' }, e('h2', { className: 'text-xl font-semibold mb-2' }, 'Data Management'), e('p', { className: 'text-sm text-gray-600 mb-4' }, 'This will remove all current products and restore the original demo products.'), e('button', { onClick: resetProducts, className: 'px-4 py-2 bg-red-600 text-white rounded-md hover:bg-red-700 transition' }, 'Reset to Demo Products'))))); }; // --- components/FilterSidebar.tsx --- const FilterSidebar = ({ allCategories, filters, onFilterChange, onClearFilters }) => { const priceRanges = [{ key: 'all', label: 'Any Price' }, { key: '0-25', label: '$0 to $25' }, { key: '25-100', label: '$25 to $100' }, { key: '100-500', label: '$100 to $500' }, { key: '500+', label: '$500+' }]; const ratings = [{ key: 0, label: 'Any Rating' }, { key: 4, label: '4 Stars & Up' }, { key: 3, label: '3 Stars & Up' }, { key: 2, label: '2 Stars & Up' }, { key: 1, label: '1 Star & Up' }]; const handleCategoryChange = (category) => { const newCategories = filters.categories.includes(category) ? filters.categories.filter(c => c !== category) : [...filters.categories, category]; onFilterChange('categories', newCategories); }; return e('aside', { className: 'w-full md:w-64 flex-shrink-0 bg-white rounded-lg shadow-md p-4 mb-6 md:mb-0' }, e('div', { className: 'flex justify-between items-center mb-4 border-b pb-2' }, e('h3', { className: 'text-lg font-bold' }, 'Filter By'), e('button', { onClick: onClearFilters, className: 'text-sm text-blue-600 hover:underline' }, 'Clear All')), e('div', { className: 'mb-6' }, e('h4', { className: 'font-semibold mb-2' }, 'Category'), e('ul', { className: 'space-y-1' }, allCategories.map(cat => e('li', { key: cat }, e('label', { className: 'flex items-center text-sm cursor-pointer' }, e('input', { type: 'checkbox', checked: filters.categories.includes(cat), onChange: () => handleCategoryChange(cat), className: 'h-4 w-4 rounded border-gray-300 text-yellow-500 focus:ring-yellow-500' }), e('span', { className: 'ml-2 text-gray-700' }, cat)))))), e('div', { className: 'mb-6' }, e('h4', { className: 'font-semibold mb-2' }, 'Price'), e('ul', { className: 'space-y-1' }, priceRanges.map(range => e('li', { key: range.key }, e('label', { className: 'flex items-center text-sm cursor-pointer' }, e('input', { type: 'radio', name: 'price', value: range.key, checked: filters.price === range.key, onChange: () => onFilterChange('price', range.key), className: 'h-4 w-4 border-gray-300 text-yellow-500 focus:ring-yellow-500' }), e('span', { className: 'ml-2 text-gray-700' }, range.label)))))), e('div', null, e('h4', { className: 'font-semibold mb-2' }, 'Average Rating'), e('ul', { className: 'space-y-1' }, ratings.map(rating => e('li', { key: rating.key }, e('label', { className: 'flex items-center text-sm cursor-pointer' }, e('input', { type: 'radio', name: 'rating', value: rating.key, checked: filters.rating === rating.key, onChange: () => onFilterChange('rating', rating.key), className: 'h-4 w-4 border-gray-300 text-yellow-500 focus:ring-yellow-500' }), e('span', { className: 'ml-2 flex items-center text-gray-700' }, rating.key > 0 ? e(RatingStars, { rating: rating.key }) : null, e('span', { className: 'ml-1' }, rating.label)))))))); }; // --- components/RelatedProducts.tsx --- const RelatedProducts = ({ currentProductId, category }) => { const { products } = useProducts(); const related = products.filter(p => p.category === category && p.id !== currentProductId).slice(0, 4); if (related.length === 0) return null; return e('div', { className: 'mt-10' }, e(ProductGrid, { title: 'You might also like', products: related })); }; // --- HomePage.tsx (was AppContent) --- const HomePage = () => { const { products } = useProducts(); const [isSideMenuOpen, setIsSideMenuOpen] = useState(false); const [filters, setFilters] = useState({ categories: [], price: 'all', rating: 0 }); const handleFilterChange = (filterName, value) => { setFilters(prevFilters => ({ ...prevFilters, [filterName]: value })); }; const clearFilters = () => setFilters({ categories: [], price: 'all', rating: 0 }); const allCategories = [...new Set(products.map(p => p.category))].sort(); const filteredProducts = products.filter(product => { if (filters.categories.length > 0 && !filters.categories.includes(product.category)) return false; if (filters.rating > 0 && product.rating < filters.rating) return false; if (filters.price !== 'all') { if (filters.price === '500+') { if (product.price < 500) return false; } else { const [min, max] = filters.price.split('-').map(parseFloat); if (product.price < min || product.price > max) return false; } } return true; }); const displayedCategories = [...new Set(filteredProducts.map(p => p.category))]; return e(React.Fragment, null, e(CategoryNav, { onOpenSideMenu: () => setIsSideMenuOpen(true) }), e(SideMenu, { isOpen: isSideMenuOpen, onClose: () => setIsSideMenuOpen(false) }), e('main', { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6" }, e(HeroSection, null), e('div', { className: 'flex flex-col md:flex-row md:space-x-6' }, e(FilterSidebar, { allCategories, filters, onFilterChange: handleFilterChange, onClearFilters: clearFilters }), e('div', { className: 'flex-grow' }, filteredProducts.length > 0 ? displayedCategories.map(category => e(ProductGrid, { key: String(category), products: filteredProducts.filter(p => p.category === category), title: category })) : e('div', { className: 'bg-white p-6 rounded-md shadow-md text-center' }, e('h2', { className: 'text-2xl font-bold mb-2' }, 'No Products Found'), e('p', { className: 'text-gray-600' }, 'Try adjusting your filters or click "Clear All" to see all products.')) ) ), e(RelatedProducts, { currentProductId: null, category: 'Electronics' }) ) ); }; // --- ProductDetailPage.tsx --- const ProductDetailPage = ({ productId }) => { const { products } = useProducts(); const { addToCart } = useCart(); const product = products.find(p => p.id === parseInt(productId)); const [quantity, setQuantity] = useState(1); const [selectedColor, setSelectedColor] = useState(product?.colors?.[0] || null); const [selectedSize, setSelectedSize] = useState(product?.sizes?.[0] || null); if (!product) { return e('div', { className: 'text-center p-10' }, e('h1', { className: 'text-2xl font-bold' }, 'Product not found.')); } const handlePurchaseAction = (andCheckout = false) => { addToCart(product, { quantity, size: selectedSize, color: selectedColor }); if (andCheckout) { window.location.hash = '#/checkout'; } }; return e('main', { className: 'max-w-7xl mx-auto px-4 py-8' }, e('div', { className: 'bg-white p-6 rounded-lg shadow-lg' }, e('div', { className: 'flex flex-col md:flex-row gap-8' }, e('div', { className: 'md:w-1/2' }, e('img', { src: product.image, alt: product.name, className: 'w-full rounded-lg object-cover' })), e('div', { className: 'md:w-1/2 flex flex-col' }, e('h1', { className: 'text-3xl font-bold mb-2' }, product.name), e('div', { className: "flex items-center my-2" }, e(RatingStars, { rating: product.rating }), e('span', { className: "text-blue-600 hover:text-red-600 ml-2 text-sm" }, `${product.reviewCount.toLocaleString()} reviews`)), e('p', { className: 'text-gray-600 mt-4 text-base' }, product.description), e('div', { className: 'my-6' }, e('span', { className: 'text-3xl font-bold' }, `$${product.price.toFixed(2)}`)), product.colors && e('div', { className: 'mb-4' }, e('h4', { className: 'font-semibold mb-2' }, 'Color: ', e('span', {className: 'font-normal'}, selectedColor)), e('div', { className: 'flex gap-2' }, product.colors.map(color => e('button', { key: color, onClick: () => setSelectedColor(color), className: `h-8 w-8 rounded-full border-2 ${selectedColor === color ? 'border-blue-500 ring-2 ring-blue-500' : 'border-gray-300'}`, style: { backgroundColor: color.toLowerCase().replace(/\s+/g, '') } })))), product.sizes && e('div', { className: 'mb-4' }, e('h4', { className: 'font-semibold mb-2' }, 'Size: ', e('span', {className: 'font-normal'}, selectedSize)), e('div', { className: 'flex gap-2' }, product.sizes.map(size => e('button', { key: size, onClick: () => setSelectedSize(size), className: `px-4 py-2 border rounded-md ${selectedSize === size ? 'bg-black text-white' : 'bg-white text-black'}` }, size)))), e('div', { className: 'flex items-center gap-4 mb-6' }, e('h4', { className: 'font-semibold' }, 'Quantity:'), e('div', { className: 'flex items-center border rounded' }, e('button', { onClick: () => setQuantity(q => Math.max(1, q - 1)), className: 'px-3 py-1 text-lg' }, '-'), e('span', { className: 'px-4 py-1' }, quantity), e('button', { onClick: () => setQuantity(q => q + 1), className: 'px-3 py-1 text-lg' }, '+'))), e('div', { className: 'flex flex-col sm:flex-row gap-4 mt-auto' }, e('button', { onClick: () => handlePurchaseAction(false), className: 'w-full bg-yellow-400 hover:bg-yellow-500 text-black py-3 rounded-lg shadow-md font-semibold' }, 'Add to Cart'), e('button', { onClick: () => handlePurchaseAction(true), className: 'w-full bg-orange-500 hover:bg-orange-600 text-white py-3 rounded-lg shadow-md font-semibold' }, 'Buy Now') ) ) ), ), e(RelatedProducts, { currentProductId: product.id, category: product.category }) ); }; // --- CheckoutPage.tsx --- const CheckoutPage = () => { const { cartItems, cartTotal, clearCart } = useCart(); const handlePlaceOrder = (e) => { e.preventDefault(); alert(`Thank you for your order of $${cartTotal.toFixed(2)}! Your items are on their way.`); clearCart(); window.location.hash = '#/'; }; if (cartItems.length === 0) { return e('div', { className: 'text-center p-10 max-w-4xl mx-auto' }, e('h1', { className: 'text-3xl font-bold mb-4' }, 'Your Cart is Empty'), e('p', { className: 'text-gray-600 mb-6' }, 'Looks like you haven\'t added anything to your cart yet.'), e('a', { href: '#/', className: 'bg-yellow-400 hover:bg-yellow-500 text-black py-2 px-6 rounded-lg shadow-md font-semibold' }, 'Continue Shopping') ); } return e('main', { className: 'max-w-4xl mx-auto px-4 py-8' }, e('h1', { className: 'text-3xl font-bold mb-6 border-b pb-4' }, 'Checkout'), e('div', { className: 'grid grid-cols-1 md:grid-cols-2 gap-12' }, e('div', null, e('h2', { className: 'text-xl font-semibold mb-4' }, 'Order Summary'), e('div', { className: 'space-y-4' }, cartItems.map(item => e('div', { key: item.cartItemId, className: 'flex items-center justify-between border-b pb-2' }, e('div', { className: 'flex items-center' }, e('img', { src: item.image, alt: item.name, className: 'h-16 w-16 object-cover rounded-md mr-4' }), e('div', null, e('p', { className: 'font-semibold' }, item.name), e('p', { className: 'text-sm text-gray-500' }, `Qty: ${item.quantity}`), (item.size || item.color) && e('p', { className: 'text-sm text-gray-500' }, `${item.color || ''} ${item.size || ''}`.trim()), ) ), e('p', { className: 'font-semibold' }, `$${(item.price * item.quantity).toFixed(2)}`) )), e('div', { className: 'flex justify-between font-bold text-lg pt-4' }, e('span', null, 'Total:'), e('span', null, `$${cartTotal.toFixed(2)}`)) ) ), e('div', null, e('h2', { className: 'text-xl font-semibold mb-4' }, 'Shipping & Payment'), e('form', { onSubmit: handlePlaceOrder, className: 'space-y-4' }, e('input', { type: 'text', placeholder: 'Full Name', className: 'w-full p-2 border rounded', required: true }), e('input', { type: 'email', placeholder: 'Email Address', className: 'w-full p-2 border rounded', required: true }), e('input', { type: 'text', placeholder: 'Shipping Address', className: 'w-full p-2 border rounded', required: true }), e('input', { type: 'text', placeholder: 'Credit Card Number', className: 'w-full p-2 border rounded', required: true }), e('button', { type: 'submit', className: 'w-full bg-orange-500 hover:bg-orange-600 text-white py-3 rounded-lg shadow-md font-semibold' }, 'Place Order') ) ) ) ); }; // --- LoginPage.tsx --- const LoginPage = () => { const { login, register, loginAsGuest } = useAuth(); const [isRegisterView, setIsRegisterView] = useState(false); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState(''); useEffect(() => { setEmail(''); setPassword(''); setConfirmPassword(''); }, [isRegisterView]); const handleLoginSubmit = (ev) => { ev.preventDefault(); login(email, password); }; const handleRegisterSubmit = (ev) => { ev.preventDefault(); if (password.length < 6) { alert("Password must be at least 6 characters long."); return; } if (password !== confirmPassword) { alert("Passwords do not match."); return; } register(email, password); }; const SignInForm = () => e('form', { onSubmit: handleLoginSubmit, className: 'space-y-4' }, e('div', null, e('label', { htmlFor: 'email', className: 'block text-sm font-medium' }, 'Email address'), e('input', { id: 'email', type: 'email', value: email, onChange: ev => setEmail(ev.target.value), className: 'mt-1 w-full p-2 border rounded', required: true }) ), e('div', null, e('label', { htmlFor: 'password', className: 'block text-sm font-medium' }, 'Password'), e('input', { id: 'password', type: 'password', value: password, onChange: ev => setPassword(ev.target.value), className: 'mt-1 w-full p-2 border rounded', required: true }) ), e('button', { type: 'submit', className: 'w-full bg-yellow-400 hover:bg-yellow-500 text-black py-2 rounded-lg font-semibold' }, 'Sign-In') ); const RegisterForm = () => e('form', { onSubmit: handleRegisterSubmit, className: 'space-y-4' }, e('div', null, e('label', { htmlFor: 'reg-email', className: 'block text-sm font-medium' }, 'Email address'), e('input', { id: 'reg-email', type: 'email', value: email, onChange: ev => setEmail(ev.target.value), className: 'mt-1 w-full p-2 border rounded', required: true }) ), e('div', null, e('label', { htmlFor: 'reg-password', className: 'block text-sm font-medium' }, 'Password'), e('input', { id: 'reg-password', type: 'password', value: password, onChange: ev => setPassword(ev.target.value), className: 'mt-1 w-full p-2 border rounded', required: true, placeholder: 'At least 6 characters' }) ), e('div', null, e('label', { htmlFor: 'reg-confirm-password', className: 'block text-sm font-medium' }, 'Re-enter password'), e('input', { id: 'reg-confirm-password', type: 'password', value: confirmPassword, onChange: ev => setConfirmPassword(ev.target.value), className: 'mt-1 w-full p-2 border rounded', required: true }) ), e('button', { type: 'submit', className: 'w-full bg-yellow-400 hover:bg-yellow-500 text-black py-2 rounded-lg font-semibold' }, 'Create your Zwingi account') ); return e('main', { className: 'max-w-md mx-auto px-4 py-12' }, e('div', { className: 'bg-white p-8 rounded-lg shadow-md border' }, e('h1', { className: 'text-2xl font-bold mb-6' }, isRegisterView ? 'Create Account' : 'Sign-In'), isRegisterView ? e(RegisterForm) : e(SignInForm), e('p', { className: 'text-xs text-gray-600 my-4' }, 'By continuing, you agree to Zwingi\'s Conditions of Use and Privacy Notice.'), isRegisterView ? e('div', { className: 'text-center text-sm mt-4' }, 'Already have an account? ', e('button', { onClick: () => setIsRegisterView(false), className: 'text-blue-600 hover:underline font-semibold' }, 'Sign-In') ) : e(React.Fragment, null, e('div', { className: 'relative my-6' }, e('div', { className: 'absolute inset-0 flex items-center', 'aria-hidden': 'true' }, e('div', { className: 'w-full border-t border-gray-300' })), e('div', { className: 'relative flex justify-center text-sm' }, e('span', { className: 'px-2 bg-white text-gray-500' }, 'New to Zwingi?')) ), e('button', { onClick: () => setIsRegisterView(true), className: 'w-full bg-gray-200 hover:bg-gray-300 text-black py-2 rounded-lg border border-gray-400 text-sm' }, 'Create your Zwingi account') ), e('div', { className: 'relative my-6' }, e('div', { className: 'absolute inset-0 flex items-center', 'aria-hidden': 'true' }, e('div', { className: 'w-full border-t border-gray-300' })), e('div', { className: 'relative flex justify-center text-sm' }, e('span', { className: 'px-2 bg-white text-gray-500' }, 'or')) ), e('button', { onClick: loginAsGuest, className: 'w-full bg-white hover:bg-gray-100 text-black py-2 rounded-lg border border-gray-400 text-sm' }, 'Continue as Guest') ) ); }; // --- UPDATED Router --- const Router = () => { const [route, setRoute] = useState(window.location.hash); const { cartCount } = useCart(); const { isLoggedIn } = useAuth(); const { isModalOpen, setIsModalOpen } = useLocation(); useEffect(() => { const handleHashChange = () => { // Redirect to login if not authenticated and trying to access protected routes const isProtectedRoute = ['#/checkout', '#/admin'].includes(window.location.hash); if (!isLoggedIn && isProtectedRoute) { window.location.hash = '#/login'; return; } setRoute(window.location.hash); }; window.addEventListener('hashchange', handleHashChange); handleHashChange(); // Initial check return () => window.removeEventListener('hashchange', handleHashChange); }, [isLoggedIn]); let page; if (route.startsWith('#/product/')) { const productId = route.split('/')[2]; page = e(ProductDetailPage, { productId }); } else if (route === '#/admin') { page = e(AdminPage, null); } else if (route === '#/checkout') { page = e(CheckoutPage, null); } else if (route === '#/login' && !isLoggedIn) { page = e(LoginPage, null); } else { // If logged in and trying to go to login, redirect to home if (route === '#/login' && isLoggedIn) window.location.hash = '#/'; page = e(HomePage, null); } return e('div', { className: 'bg-gray-100 min-h-screen font-sans' }, e(Header, { cartCount }), page, e(Footer, null), e(LocationModal, { isOpen: isModalOpen, onClose: () => setIsModalOpen(false) }) ); }; const App = () => e(ToastProvider, null, e(ProductProvider, null, e(AuthProvider, null, e(LocationProvider, null, e(CartProvider, null, e(Router, null)))))); // --- From index.tsx (original) --- const rootElement = document.getElementById('root'); if (!rootElement) throw new Error("Could not find root element to mount to"); // FIX: Switched from the legacy ReactDOM.render to ReactDOM.createRoot to support React 18+. // This resolves the error "Property 'render' does not exist on type 'ReactDOM'", // which indicates the project is using React 18 or newer type definitions. const root = ReactDOM.createRoot(rootElement); root.render(e(StrictMode, null, e(App, null)));