PROMPT START
Browse filesBuild a complete Online Shopping Website using Next.js (App Router), TypeScript, Tailwind CSS, and shadcn/ui.
π Features I want
1. Authentication (Frontend Only)
Signup page
Login page
Logout button
Store logged-in user in localStorage
Protect cart page β if user is not logged in, redirect to login
Authentication logic should be client-side only (I will add backend later)
2. Pages Needed
/ β Home Page
/products β Product listing
/products/[id] β Product details page
/cart β Cart page (PRIVATE)
/login β Login
/signup β Signup
3. Add to Cart System
Only logged-in users can add items to cart
Cart data stored in localStorage
Add, remove, increase, decrease quantity
Show cart count in navbar
Sync cart on page reload
4. Components Needed
Client Components
Navbar with login/signup/logout + cart count
ProductCard
AddToCartButton
LoginForm
SignupForm
CartItems
ProtectedRoute wrapper
Server Components
HomePage
ProductList (server-fetched dummy data)
ProductDetails (server component with client-side AddToCart button)
5. UI / Styling
Tailwind CSS
Use shadcn/ui for reusable UI components
Responsive + mobile-first
Beautiful minimal design
6. Dummy Product Data
Use products.json or a simple array inside server components
Structure: id, title, description, image, price, category
π Folder Structure to Generate
app/
ββ layout.tsx
ββ page.tsx (Home)
ββ login/page.tsx
ββ signup/page.tsx
ββ products/page.tsx
ββ products/[id]/page.tsx
ββ cart/page.tsx
components/
ββ Navbar.tsx
ββ ProductCard.tsx
ββ AddToCartButton.tsx
ββ LoginForm.tsx
ββ SignupForm.tsx
ββ CartItems.tsx
ββ ProtectedRoute.tsx
lib/
ββ cart.ts (cart utils localStorage)
ββ auth.ts (login/signup localStorage)
data/
ββ products.ts
7. Functionality Details
π Authentication
signup() saves user to localStorage
login() checks user
logout() clears currentUser
Save user as:
currentUser: { email: "", name: "" }
π Cart System Logic (localStorage)
cart = [
{ id, name, price, quantity, image }
]
Functions:
addToCart()
removeFromCart()
increaseQuantity()
decreaseQuantity()
getCart()
clearCart()
8. Add Realistic UI
Modern buttons
Product grid
Mobile drawer menu for navbar
Toast notifications for add to cart, login, signup
9. Code Requirements
β Use client/server components correctly
β Use "use client" only where needed
β Full working components
β Every page design clean
β Functions well-commented
β Redirect unauthenticated users
β No backend β everything on localStorage
PROMPT END
- README.md +8 -5
- components/footer.js +64 -0
- components/navbar.js +125 -0
- index.html +50 -19
- script.js +170 -0
- style.css +29 -18
|
@@ -1,10 +1,13 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
|
|
|
|
|
|
| 8 |
---
|
| 9 |
|
| 10 |
-
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: ShopSphere - E-Commerce Delight ποΈ
|
| 3 |
+
colorFrom: red
|
| 4 |
+
colorTo: red
|
| 5 |
+
emoji: π³
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
| 8 |
+
tags:
|
| 9 |
+
- deepsite-v3
|
| 10 |
---
|
| 11 |
|
| 12 |
+
# Welcome to your new DeepSite project!
|
| 13 |
+
This project was created with [DeepSite](https://huggingface.co/deepsite).
|
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
class CustomFooter extends HTMLElement {
|
| 2 |
+
connectedCallback() {
|
| 3 |
+
this.attachShadow({ mode: 'open' });
|
| 4 |
+
this.shadowRoot.innerHTML = `
|
| 5 |
+
<style>
|
| 6 |
+
.footer-link {
|
| 7 |
+
transition: color 0.2s ease;
|
| 8 |
+
}
|
| 9 |
+
|
| 10 |
+
.footer-link:hover {
|
| 11 |
+
color: #6366f1;
|
| 12 |
+
}
|
| 13 |
+
</style>
|
| 14 |
+
<footer class="bg-gray-800 text-white py-8">
|
| 15 |
+
<div class="container mx-auto px-4">
|
| 16 |
+
<div class="grid grid-cols-1 md:grid-cols-4 gap-8">
|
| 17 |
+
<div>
|
| 18 |
+
<h3 class="text-xl font-bold mb-4">ShopSphere</h3>
|
| 19 |
+
<p class="text-gray-400">Your one-stop shop for all your needs.</p>
|
| 20 |
+
</div>
|
| 21 |
+
|
| 22 |
+
<div>
|
| 23 |
+
<h4 class="font-semibold mb-4">Quick Links</h4>
|
| 24 |
+
<ul class="space-y-2">
|
| 25 |
+
<li><a href="/" class="footer-link text-gray-400">Home</a></li>
|
| 26 |
+
<li><a href="/products" class="footer-link text-gray-400">Products</a></li>
|
| 27 |
+
<li><a href="/cart" class="footer-link text-gray-400">Cart</a></li>
|
| 28 |
+
</ul>
|
| 29 |
+
</div>
|
| 30 |
+
|
| 31 |
+
<div>
|
| 32 |
+
<h4 class="font-semibold mb-4">Help</h4>
|
| 33 |
+
<ul class="space-y-2">
|
| 34 |
+
<li><a href="#" class="footer-link text-gray-400">FAQs</a></li>
|
| 35 |
+
<li><a href="#" class="footer-link text-gray-400">Shipping</a></li>
|
| 36 |
+
<li><a href="#" class="footer-link text-gray-400">Returns</a></li>
|
| 37 |
+
</ul>
|
| 38 |
+
</div>
|
| 39 |
+
|
| 40 |
+
<div>
|
| 41 |
+
<h4 class="font-semibold mb-4">Contact</h4>
|
| 42 |
+
<ul class="space-y-2">
|
| 43 |
+
<li class="flex items-center">
|
| 44 |
+
<i data-feather="mail" class="mr-2"></i>
|
| 45 |
+
<span class="text-gray-400">support@shopsphere.com</span>
|
| 46 |
+
</li>
|
| 47 |
+
<li class="flex items-center">
|
| 48 |
+
<i data-feather="phone" class="mr-2"></i>
|
| 49 |
+
<span class="text-gray-400">(123) 456-7890</span>
|
| 50 |
+
</li>
|
| 51 |
+
</ul>
|
| 52 |
+
</div>
|
| 53 |
+
</div>
|
| 54 |
+
|
| 55 |
+
<div class="border-t border-gray-700 mt-8 pt-8 text-center text-gray-400">
|
| 56 |
+
<p>© ${new Date().getFullYear()} ShopSphere. All rights reserved.</p>
|
| 57 |
+
</div>
|
| 58 |
+
</div>
|
| 59 |
+
</footer>
|
| 60 |
+
`;
|
| 61 |
+
}
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
customElements.define('custom-footer', CustomFooter);
|
|
@@ -0,0 +1,125 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
class CustomNavbar extends HTMLElement {
|
| 2 |
+
connectedCallback() {
|
| 3 |
+
this.attachShadow({ mode: 'open' });
|
| 4 |
+
this.shadowRoot.innerHTML = `
|
| 5 |
+
<style>
|
| 6 |
+
.nav-link {
|
| 7 |
+
position: relative;
|
| 8 |
+
padding-bottom: 4px;
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
.nav-link::after {
|
| 12 |
+
content: '';
|
| 13 |
+
position: absolute;
|
| 14 |
+
bottom: 0;
|
| 15 |
+
left: 0;
|
| 16 |
+
width: 0;
|
| 17 |
+
height: 2px;
|
| 18 |
+
background-color: #6366f1;
|
| 19 |
+
transition: width 0.3s ease;
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
.nav-link:hover::after {
|
| 23 |
+
width: 100%;
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
.mobile-menu {
|
| 27 |
+
transition: all 0.3s ease;
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
.cart-count {
|
| 31 |
+
display: none;
|
| 32 |
+
position: absolute;
|
| 33 |
+
top: -8px;
|
| 34 |
+
right: -8px;
|
| 35 |
+
background-color: #f43f5e;
|
| 36 |
+
color: white;
|
| 37 |
+
border-radius: 50%;
|
| 38 |
+
width: 20px;
|
| 39 |
+
height: 20px;
|
| 40 |
+
font-size: 12px;
|
| 41 |
+
align-items: center;
|
| 42 |
+
justify-content: center;
|
| 43 |
+
}
|
| 44 |
+
</style>
|
| 45 |
+
<nav class="bg-white shadow-sm">
|
| 46 |
+
<div class="container mx-auto px-4 py-4">
|
| 47 |
+
<div class="flex justify-between items-center">
|
| 48 |
+
<a href="/" class="text-2xl font-bold text-primary">ShopSphere</a>
|
| 49 |
+
|
| 50 |
+
<div class="hidden md:flex items-center space-x-8">
|
| 51 |
+
<a href="/" class="nav-link text-gray-700 hover:text-primary">Home</a>
|
| 52 |
+
<a href="/products" class="nav-link text-gray-700 hover:text-primary">Products</a>
|
| 53 |
+
<a href="/cart" class="nav-link text-gray-700 hover:text-primary relative">
|
| 54 |
+
<i data-feather="shopping-cart"></i>
|
| 55 |
+
<span id="cart-count" class="cart-count">0</span>
|
| 56 |
+
</a>
|
| 57 |
+
|
| 58 |
+
${auth.isAuthenticated() ? `
|
| 59 |
+
<div class="flex items-center space-x-4">
|
| 60 |
+
<span class="text-gray-700">Hi, ${auth.currentUser.name}</span>
|
| 61 |
+
<button onclick="auth.logout()" class="bg-secondary text-white px-4 py-2 rounded hover:bg-secondary-600 transition">
|
| 62 |
+
Logout
|
| 63 |
+
</button>
|
| 64 |
+
</div>
|
| 65 |
+
` : `
|
| 66 |
+
<div class="flex items-center space-x-4">
|
| 67 |
+
<a href="/login" class="text-gray-700 hover:text-primary">Login</a>
|
| 68 |
+
<a href="/signup" class="bg-primary text-white px-4 py-2 rounded hover:bg-primary-600 transition">
|
| 69 |
+
Sign Up
|
| 70 |
+
</a>
|
| 71 |
+
</div>
|
| 72 |
+
`}
|
| 73 |
+
</div>
|
| 74 |
+
|
| 75 |
+
<button id="mobile-menu-button" class="md:hidden text-gray-700">
|
| 76 |
+
<i data-feather="menu"></i>
|
| 77 |
+
</button>
|
| 78 |
+
</div>
|
| 79 |
+
|
| 80 |
+
<div id="mobile-menu" class="mobile-menu hidden md:hidden mt-4 pb-4">
|
| 81 |
+
<a href="/" class="block py-2 text-gray-700 hover:text-primary">Home</a>
|
| 82 |
+
<a href="/products" class="block py-2 text-gray-700 hover:text-primary">Products</a>
|
| 83 |
+
<a href="/cart" class="block py-2 text-gray-700 hover:text-primary relative">
|
| 84 |
+
<span class="flex items-center">
|
| 85 |
+
<i data-feather="shopping-cart" class="mr-2"></i> Cart
|
| 86 |
+
<span id="mobile-cart-count" class="cart-count ml-2">0</span>
|
| 87 |
+
</span>
|
| 88 |
+
</a>
|
| 89 |
+
|
| 90 |
+
${auth.isAuthenticated() ? `
|
| 91 |
+
<div class="mt-4 pt-4 border-t border-gray-200">
|
| 92 |
+
<p class="text-gray-700 mb-2">Hi, ${auth.currentUser.name}</p>
|
| 93 |
+
<button onclick="auth.logout()" class="w-full bg-secondary text-white px-4 py-2 rounded hover:bg-secondary-600 transition">
|
| 94 |
+
Logout
|
| 95 |
+
</button>
|
| 96 |
+
</div>
|
| 97 |
+
` : `
|
| 98 |
+
<div class="mt-4 pt-4 border-t border-gray-200 space-y-2">
|
| 99 |
+
<a href="/login" class="block py-2 text-gray-700 hover:text-primary">Login</a>
|
| 100 |
+
<a href="/signup" class="block bg-primary text-white px-4 py-2 rounded text-center hover:bg-primary-600 transition">
|
| 101 |
+
Sign Up
|
| 102 |
+
</a>
|
| 103 |
+
</div>
|
| 104 |
+
`}
|
| 105 |
+
</div>
|
| 106 |
+
</div>
|
| 107 |
+
</nav>
|
| 108 |
+
|
| 109 |
+
<script>
|
| 110 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 111 |
+
const menuButton = this.shadowRoot.getElementById('mobile-menu-button');
|
| 112 |
+
const mobileMenu = this.shadowRoot.getElementById('mobile-menu');
|
| 113 |
+
|
| 114 |
+
menuButton.addEventListener('click', () => {
|
| 115 |
+
mobileMenu.classList.toggle('hidden');
|
| 116 |
+
});
|
| 117 |
+
|
| 118 |
+
feather.replace();
|
| 119 |
+
});
|
| 120 |
+
</script>
|
| 121 |
+
`;
|
| 122 |
+
}
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
customElements.define('custom-navbar', CustomNavbar);
|
|
@@ -1,19 +1,50 @@
|
|
| 1 |
-
<!
|
| 2 |
-
<html>
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>ShopSphere - E-Commerce Delight</title>
|
| 7 |
+
<link rel="stylesheet" href="style.css">
|
| 8 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 9 |
+
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
|
| 10 |
+
<script src="https://unpkg.com/feather-icons"></script>
|
| 11 |
+
<script>
|
| 12 |
+
tailwind.config = {
|
| 13 |
+
theme: {
|
| 14 |
+
extend: {
|
| 15 |
+
colors: {
|
| 16 |
+
primary: '#6366f1',
|
| 17 |
+
secondary: '#f43f5e',
|
| 18 |
+
}
|
| 19 |
+
}
|
| 20 |
+
}
|
| 21 |
+
}
|
| 22 |
+
</script>
|
| 23 |
+
</head>
|
| 24 |
+
<body class="bg-gray-50">
|
| 25 |
+
<custom-navbar></custom-navbar>
|
| 26 |
+
<main class="container mx-auto px-4 py-8">
|
| 27 |
+
<section class="hero-section mb-12">
|
| 28 |
+
<div class="bg-gradient-to-r from-primary to-secondary rounded-2xl p-8 text-white">
|
| 29 |
+
<h1 class="text-4xl md:text-5xl font-bold mb-4">Welcome to ShopSphere</h1>
|
| 30 |
+
<p class="text-xl mb-6">Discover amazing products at unbeatable prices</p>
|
| 31 |
+
<a href="/products" class="bg-white text-primary px-6 py-3 rounded-lg font-medium hover:bg-opacity-90 transition">Shop Now</a>
|
| 32 |
+
</div>
|
| 33 |
+
</section>
|
| 34 |
+
|
| 35 |
+
<section class="featured-products mb-12">
|
| 36 |
+
<h2 class="text-2xl font-bold mb-6">Featured Products</h2>
|
| 37 |
+
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6" id="featured-products">
|
| 38 |
+
<!-- Products will be loaded via JavaScript -->
|
| 39 |
+
</div>
|
| 40 |
+
</section>
|
| 41 |
+
</main>
|
| 42 |
+
<custom-footer></custom-footer>
|
| 43 |
+
|
| 44 |
+
<script src="components/navbar.js"></script>
|
| 45 |
+
<script src="components/footer.js"></script>
|
| 46 |
+
<script src="script.js"></script>
|
| 47 |
+
<script>feather.replace();</script>
|
| 48 |
+
<script src="https://huggingface.co/deepsite/deepsite-badge.js"></script>
|
| 49 |
+
</body>
|
| 50 |
+
</html>
|
|
@@ -0,0 +1,170 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Auth functions
|
| 2 |
+
const auth = {
|
| 3 |
+
currentUser: JSON.parse(localStorage.getItem('currentUser')) || null,
|
| 4 |
+
|
| 5 |
+
signup: (name, email, password) => {
|
| 6 |
+
const user = { name, email };
|
| 7 |
+
localStorage.setItem('currentUser', JSON.stringify(user));
|
| 8 |
+
auth.currentUser = user;
|
| 9 |
+
showToast('Account created successfully!');
|
| 10 |
+
window.location.href = '/';
|
| 11 |
+
},
|
| 12 |
+
|
| 13 |
+
login: (email, password) => {
|
| 14 |
+
// In a real app, this would verify against a backend
|
| 15 |
+
const user = { name: 'Demo User', email };
|
| 16 |
+
localStorage.setItem('currentUser', JSON.stringify(user));
|
| 17 |
+
auth.currentUser = user;
|
| 18 |
+
showToast('Logged in successfully!');
|
| 19 |
+
window.location.href = '/';
|
| 20 |
+
},
|
| 21 |
+
|
| 22 |
+
logout: () => {
|
| 23 |
+
localStorage.removeItem('currentUser');
|
| 24 |
+
auth.currentUser = null;
|
| 25 |
+
showToast('Logged out successfully!');
|
| 26 |
+
window.location.href = '/';
|
| 27 |
+
},
|
| 28 |
+
|
| 29 |
+
isAuthenticated: () => {
|
| 30 |
+
return auth.currentUser !== null;
|
| 31 |
+
}
|
| 32 |
+
};
|
| 33 |
+
|
| 34 |
+
// Cart functions
|
| 35 |
+
const cart = {
|
| 36 |
+
getCart: () => {
|
| 37 |
+
return JSON.parse(localStorage.getItem('cart')) || [];
|
| 38 |
+
},
|
| 39 |
+
|
| 40 |
+
addToCart: (product) => {
|
| 41 |
+
if (!auth.isAuthenticated()) {
|
| 42 |
+
window.location.href = '/login';
|
| 43 |
+
return;
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
const cartItems = cart.getCart();
|
| 47 |
+
const existingItem = cartItems.find(item => item.id === product.id);
|
| 48 |
+
|
| 49 |
+
if (existingItem) {
|
| 50 |
+
existingItem.quantity += 1;
|
| 51 |
+
} else {
|
| 52 |
+
cartItems.push({
|
| 53 |
+
...product,
|
| 54 |
+
quantity: 1
|
| 55 |
+
});
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
localStorage.setItem('cart', JSON.stringify(cartItems));
|
| 59 |
+
updateCartCount();
|
| 60 |
+
showToast(`${product.name} added to cart!`);
|
| 61 |
+
},
|
| 62 |
+
|
| 63 |
+
removeFromCart: (productId) => {
|
| 64 |
+
const cartItems = cart.getCart().filter(item => item.id !== productId);
|
| 65 |
+
localStorage.setItem('cart', JSON.stringify(cartItems));
|
| 66 |
+
updateCartCount();
|
| 67 |
+
},
|
| 68 |
+
|
| 69 |
+
updateQuantity: (productId, newQuantity) => {
|
| 70 |
+
const cartItems = cart.getCart();
|
| 71 |
+
const item = cartItems.find(item => item.id === productId);
|
| 72 |
+
|
| 73 |
+
if (item) {
|
| 74 |
+
if (newQuantity <= 0) {
|
| 75 |
+
cart.removeFromCart(productId);
|
| 76 |
+
} else {
|
| 77 |
+
item.quantity = newQuantity;
|
| 78 |
+
localStorage.setItem('cart', JSON.stringify(cartItems));
|
| 79 |
+
}
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
updateCartCount();
|
| 83 |
+
},
|
| 84 |
+
|
| 85 |
+
clearCart: () => {
|
| 86 |
+
localStorage.removeItem('cart');
|
| 87 |
+
updateCartCount();
|
| 88 |
+
}
|
| 89 |
+
};
|
| 90 |
+
|
| 91 |
+
// Helper functions
|
| 92 |
+
function updateCartCount() {
|
| 93 |
+
const cartCount = document.getElementById('cart-count');
|
| 94 |
+
if (cartCount) {
|
| 95 |
+
const count = cart.getCart().reduce((sum, item) => sum + item.quantity, 0);
|
| 96 |
+
cartCount.textContent = count;
|
| 97 |
+
cartCount.style.display = count > 0 ? 'flex' : 'none';
|
| 98 |
+
}
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
function showToast(message) {
|
| 102 |
+
const toast = document.createElement('div');
|
| 103 |
+
toast.className = 'toast';
|
| 104 |
+
toast.textContent = message;
|
| 105 |
+
document.body.appendChild(toast);
|
| 106 |
+
|
| 107 |
+
setTimeout(() => {
|
| 108 |
+
toast.remove();
|
| 109 |
+
}, 3000);
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
// Load featured products
|
| 113 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 114 |
+
const featuredContainer = document.getElementById('featured-products');
|
| 115 |
+
|
| 116 |
+
if (featuredContainer) {
|
| 117 |
+
const products = [
|
| 118 |
+
{
|
| 119 |
+
id: 1,
|
| 120 |
+
name: 'Wireless Headphones',
|
| 121 |
+
price: 99.99,
|
| 122 |
+
image: 'http://static.photos/technology/320x240/1',
|
| 123 |
+
description: 'Premium wireless headphones with noise cancellation'
|
| 124 |
+
},
|
| 125 |
+
{
|
| 126 |
+
id: 2,
|
| 127 |
+
name: 'Smart Watch',
|
| 128 |
+
price: 199.99,
|
| 129 |
+
image: 'http://static.photos/technology/320x240/2',
|
| 130 |
+
description: 'Track your fitness and stay connected'
|
| 131 |
+
},
|
| 132 |
+
{
|
| 133 |
+
id: 3,
|
| 134 |
+
name: 'Bluetooth Speaker',
|
| 135 |
+
price: 79.99,
|
| 136 |
+
image: 'http://static.photos/technology/320x240/3',
|
| 137 |
+
description: 'Portable speaker with crystal clear sound'
|
| 138 |
+
},
|
| 139 |
+
{
|
| 140 |
+
id: 4,
|
| 141 |
+
name: 'Wireless Charger',
|
| 142 |
+
price: 29.99,
|
| 143 |
+
image: 'http://static.photos/technology/320x240/4',
|
| 144 |
+
description: 'Fast charging for all Qi-enabled devices'
|
| 145 |
+
}
|
| 146 |
+
];
|
| 147 |
+
|
| 148 |
+
products.forEach(product => {
|
| 149 |
+
const productCard = document.createElement('div');
|
| 150 |
+
productCard.className = 'product-card bg-white rounded-lg overflow-hidden shadow-md';
|
| 151 |
+
productCard.innerHTML = `
|
| 152 |
+
<img src="${product.image}" alt="${product.name}" class="w-full h-48 object-cover">
|
| 153 |
+
<div class="p-4">
|
| 154 |
+
<h3 class="font-semibold text-lg mb-1">${product.name}</h3>
|
| 155 |
+
<p class="text-gray-600 text-sm mb-2">${product.description}</p>
|
| 156 |
+
<div class="flex justify-between items-center mt-4">
|
| 157 |
+
<span class="font-bold text-primary">$${product.price.toFixed(2)}</span>
|
| 158 |
+
<button onclick="cart.addToCart(${JSON.stringify(product).replace(/"/g, '"')})"
|
| 159 |
+
class="bg-primary text-white px-4 py-2 rounded hover:bg-primary-600 transition">
|
| 160 |
+
Add to Cart
|
| 161 |
+
</button>
|
| 162 |
+
</div>
|
| 163 |
+
</div>
|
| 164 |
+
`;
|
| 165 |
+
featuredContainer.appendChild(productCard);
|
| 166 |
+
});
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
updateCartCount();
|
| 170 |
+
});
|
|
@@ -1,28 +1,39 @@
|
|
|
|
|
|
|
|
| 1 |
body {
|
| 2 |
-
|
| 3 |
-
|
|
|
|
|
|
|
| 4 |
}
|
| 5 |
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
margin-top: 0;
|
| 9 |
}
|
| 10 |
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
font-size: 15px;
|
| 14 |
-
margin-bottom: 10px;
|
| 15 |
-
margin-top: 5px;
|
| 16 |
}
|
| 17 |
|
| 18 |
-
.card {
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
padding: 16px;
|
| 22 |
-
border: 1px solid lightgray;
|
| 23 |
-
border-radius: 16px;
|
| 24 |
}
|
| 25 |
|
| 26 |
-
|
| 27 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
|
| 2 |
+
|
| 3 |
body {
|
| 4 |
+
font-family: 'Inter', sans-serif;
|
| 5 |
+
min-height: 100vh;
|
| 6 |
+
display: flex;
|
| 7 |
+
flex-direction: column;
|
| 8 |
}
|
| 9 |
|
| 10 |
+
main {
|
| 11 |
+
flex: 1;
|
|
|
|
| 12 |
}
|
| 13 |
|
| 14 |
+
.product-card {
|
| 15 |
+
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
|
|
|
|
|
|
|
|
|
| 16 |
}
|
| 17 |
|
| 18 |
+
.product-card:hover {
|
| 19 |
+
transform: translateY(-5px);
|
| 20 |
+
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1);
|
|
|
|
|
|
|
|
|
|
| 21 |
}
|
| 22 |
|
| 23 |
+
/* Toast notification styles */
|
| 24 |
+
.toast {
|
| 25 |
+
position: fixed;
|
| 26 |
+
bottom: 20px;
|
| 27 |
+
right: 20px;
|
| 28 |
+
background: #4CAF50;
|
| 29 |
+
color: white;
|
| 30 |
+
padding: 16px;
|
| 31 |
+
border-radius: 8px;
|
| 32 |
+
z-index: 1000;
|
| 33 |
+
animation: slideIn 0.5s forwards;
|
| 34 |
}
|
| 35 |
+
|
| 36 |
+
@keyframes slideIn {
|
| 37 |
+
from { transform: translateX(100%); }
|
| 38 |
+
to { transform: translateX(0); }
|
| 39 |
+
}
|