2026-01-01 22:24:30 -06:00
|
|
|
/**
|
|
|
|
|
* Shopping Cart Component
|
|
|
|
|
* Handles cart dropdown, updates, and interactions
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
(function () {
|
|
|
|
|
"use strict";
|
|
|
|
|
|
|
|
|
|
class ShoppingCart {
|
|
|
|
|
constructor() {
|
|
|
|
|
this.cartToggle = document.getElementById("cartToggle");
|
|
|
|
|
this.cartPanel = document.getElementById("cartPanel");
|
|
|
|
|
this.cartContent = document.getElementById("cartContent");
|
|
|
|
|
this.cartClose = document.getElementById("cartClose");
|
|
|
|
|
this.isOpen = false;
|
|
|
|
|
|
|
|
|
|
this.init();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
init() {
|
|
|
|
|
this.setupEventListeners();
|
|
|
|
|
this.render();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setupEventListeners() {
|
|
|
|
|
if (this.cartToggle) {
|
|
|
|
|
this.cartToggle.addEventListener("click", () => this.toggle());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.cartClose) {
|
|
|
|
|
this.cartClose.addEventListener("click", () => this.close());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Close when clicking outside
|
|
|
|
|
document.addEventListener("click", (e) => {
|
|
|
|
|
if (this.isOpen && !e.target.closest(".cart-dropdown-wrapper")) {
|
|
|
|
|
this.close();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Listen for cart updates
|
|
|
|
|
window.addEventListener("cart-updated", () => this.render());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
toggle() {
|
|
|
|
|
this.isOpen ? this.close() : this.open();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
open() {
|
|
|
|
|
if (this.cartPanel) {
|
|
|
|
|
this.cartPanel.classList.add("active");
|
|
|
|
|
this.cartPanel.setAttribute("aria-hidden", "false");
|
|
|
|
|
this.isOpen = true;
|
|
|
|
|
this.render();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
close() {
|
|
|
|
|
if (this.cartPanel) {
|
|
|
|
|
this.cartPanel.classList.remove("active");
|
|
|
|
|
this.cartPanel.setAttribute("aria-hidden", "true");
|
|
|
|
|
this.isOpen = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
render() {
|
|
|
|
|
if (!this.cartContent) return;
|
|
|
|
|
|
|
|
|
|
const cart = window.AppState.cart;
|
|
|
|
|
|
|
|
|
|
if (cart.length === 0) {
|
|
|
|
|
this.cartContent.innerHTML =
|
|
|
|
|
'<p class="empty-state">Your cart is empty</p>';
|
|
|
|
|
this.updateFooter(null);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const html = cart.map((item) => this.renderCartItem(item)).join("");
|
|
|
|
|
this.cartContent.innerHTML = html;
|
|
|
|
|
|
|
|
|
|
// Add event listeners to cart items
|
|
|
|
|
this.setupCartItemListeners();
|
|
|
|
|
|
|
|
|
|
// Update footer with total
|
|
|
|
|
this.updateFooter(window.AppState.getCartTotal());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
renderCartItem(item) {
|
|
|
|
|
const imageUrl =
|
|
|
|
|
item.imageUrl || item.image_url || "/assets/images/placeholder.jpg";
|
|
|
|
|
const title = window.Utils.escapeHtml(
|
|
|
|
|
item.title || item.name || "Product"
|
|
|
|
|
);
|
|
|
|
|
const price = window.Utils.formatCurrency(item.price || 0);
|
|
|
|
|
const subtotal = window.Utils.formatCurrency(
|
|
|
|
|
(item.price || 0) * item.quantity
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return `
|
|
|
|
|
<div class="cart-item" data-id="${item.id}">
|
|
|
|
|
<img src="${imageUrl}" alt="${title}" class="cart-item-image" loading="lazy">
|
|
|
|
|
<div class="cart-item-details">
|
|
|
|
|
<h4 class="cart-item-title">${title}</h4>
|
|
|
|
|
<p class="cart-item-price">${price}</p>
|
|
|
|
|
<div class="cart-item-quantity">
|
|
|
|
|
<button class="quantity-btn quantity-minus" data-id="${item.id}" aria-label="Decrease quantity">
|
|
|
|
|
<i class="bi bi-dash"></i>
|
|
|
|
|
</button>
|
|
|
|
|
<span class="quantity-value">${item.quantity}</span>
|
|
|
|
|
<button class="quantity-btn quantity-plus" data-id="${item.id}" aria-label="Increase quantity">
|
|
|
|
|
<i class="bi bi-plus"></i>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
<p class="cart-item-subtotal">${subtotal}</p>
|
|
|
|
|
</div>
|
|
|
|
|
<button class="cart-item-remove" data-id="${item.id}" aria-label="Remove from cart">
|
|
|
|
|
<i class="bi bi-x-lg"></i>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setupCartItemListeners() {
|
|
|
|
|
// Remove buttons
|
|
|
|
|
this.cartContent.querySelectorAll(".cart-item-remove").forEach((btn) => {
|
|
|
|
|
btn.addEventListener("click", (e) => {
|
|
|
|
|
const id = parseInt(e.currentTarget.dataset.id);
|
|
|
|
|
window.AppState.removeFromCart(id);
|
|
|
|
|
this.render();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Quantity buttons
|
|
|
|
|
this.cartContent.querySelectorAll(".quantity-minus").forEach((btn) => {
|
|
|
|
|
btn.addEventListener("click", (e) => {
|
|
|
|
|
const id = parseInt(e.currentTarget.dataset.id);
|
|
|
|
|
const item = window.AppState.cart.find((item) => item.id === id);
|
|
|
|
|
if (item && item.quantity > 1) {
|
|
|
|
|
window.AppState.updateCartQuantity(id, item.quantity - 1);
|
|
|
|
|
this.render();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.cartContent.querySelectorAll(".quantity-plus").forEach((btn) => {
|
|
|
|
|
btn.addEventListener("click", (e) => {
|
|
|
|
|
const id = parseInt(e.currentTarget.dataset.id);
|
|
|
|
|
const item = window.AppState.cart.find((item) => item.id === id);
|
|
|
|
|
if (item) {
|
|
|
|
|
window.AppState.updateCartQuantity(id, item.quantity + 1);
|
|
|
|
|
this.render();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateFooter(total) {
|
|
|
|
|
const footer = this.cartPanel?.querySelector(".dropdown-foot");
|
|
|
|
|
if (!footer) return;
|
|
|
|
|
|
|
|
|
|
if (total === null) {
|
|
|
|
|
footer.innerHTML =
|
|
|
|
|
'<a href="/shop" class="btn-outline">Continue Shopping</a>';
|
|
|
|
|
} else {
|
|
|
|
|
footer.innerHTML = `
|
|
|
|
|
<div class="cart-total">
|
|
|
|
|
<span>Total:</span>
|
|
|
|
|
<strong>${window.Utils.formatCurrency(total)}</strong>
|
|
|
|
|
</div>
|
|
|
|
|
<a href="/shop" class="btn-text">Continue Shopping</a>
|
|
|
|
|
<button class="btn-primary-full" onclick="alert('Checkout coming soon!')">
|
|
|
|
|
Proceed to Checkout
|
|
|
|
|
</button>
|
|
|
|
|
`;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Wishlist Component
|
|
|
|
|
class Wishlist {
|
|
|
|
|
constructor() {
|
|
|
|
|
this.wishlistToggle = document.getElementById("wishlistToggle");
|
|
|
|
|
this.wishlistPanel = document.getElementById("wishlistPanel");
|
|
|
|
|
this.wishlistContent = document.getElementById("wishlistContent");
|
|
|
|
|
this.wishlistClose = document.getElementById("wishlistClose");
|
|
|
|
|
this.isOpen = false;
|
|
|
|
|
|
|
|
|
|
this.init();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
init() {
|
|
|
|
|
this.setupEventListeners();
|
|
|
|
|
this.render();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setupEventListeners() {
|
|
|
|
|
if (this.wishlistToggle) {
|
|
|
|
|
this.wishlistToggle.addEventListener("click", () => this.toggle());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.wishlistClose) {
|
|
|
|
|
this.wishlistClose.addEventListener("click", () => this.close());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Close when clicking outside
|
|
|
|
|
document.addEventListener("click", (e) => {
|
|
|
|
|
if (this.isOpen && !e.target.closest(".wishlist-dropdown-wrapper")) {
|
|
|
|
|
this.close();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Listen for wishlist updates
|
|
|
|
|
window.addEventListener("wishlist-updated", () => this.render());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
toggle() {
|
|
|
|
|
this.isOpen ? this.close() : this.open();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
open() {
|
|
|
|
|
if (this.wishlistPanel) {
|
|
|
|
|
this.wishlistPanel.classList.add("active");
|
|
|
|
|
this.wishlistPanel.setAttribute("aria-hidden", "false");
|
|
|
|
|
this.isOpen = true;
|
|
|
|
|
this.render();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
close() {
|
|
|
|
|
if (this.wishlistPanel) {
|
|
|
|
|
this.wishlistPanel.classList.remove("active");
|
|
|
|
|
this.wishlistPanel.setAttribute("aria-hidden", "true");
|
|
|
|
|
this.isOpen = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
render() {
|
|
|
|
|
if (!this.wishlistContent) return;
|
|
|
|
|
|
|
|
|
|
const wishlist = window.AppState.wishlist;
|
|
|
|
|
|
|
|
|
|
if (wishlist.length === 0) {
|
|
|
|
|
this.wishlistContent.innerHTML =
|
|
|
|
|
'<p class="empty-state">Your wishlist is empty</p>';
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const html = wishlist
|
|
|
|
|
.map((item) => this.renderWishlistItem(item))
|
|
|
|
|
.join("");
|
|
|
|
|
this.wishlistContent.innerHTML = html;
|
|
|
|
|
|
|
|
|
|
// Add event listeners
|
|
|
|
|
this.setupWishlistItemListeners();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
renderWishlistItem(item) {
|
|
|
|
|
const imageUrl =
|
|
|
|
|
item.imageUrl || item.image_url || "/assets/images/placeholder.jpg";
|
|
|
|
|
const title = window.Utils.escapeHtml(
|
|
|
|
|
item.title || item.name || "Product"
|
|
|
|
|
);
|
|
|
|
|
const price = window.Utils.formatCurrency(item.price || 0);
|
|
|
|
|
|
|
|
|
|
return `
|
|
|
|
|
<div class="wishlist-item" data-id="${item.id}">
|
|
|
|
|
<img src="${imageUrl}" alt="${title}" class="wishlist-item-image" loading="lazy">
|
|
|
|
|
<div class="wishlist-item-details">
|
|
|
|
|
<h4 class="wishlist-item-title">${title}</h4>
|
|
|
|
|
<p class="wishlist-item-price">${price}</p>
|
|
|
|
|
<button class="btn-add-to-cart" data-id="${item.id}">Add to Cart</button>
|
|
|
|
|
</div>
|
|
|
|
|
<button class="wishlist-item-remove" data-id="${item.id}" aria-label="Remove from wishlist">
|
|
|
|
|
<i class="bi bi-x-lg"></i>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setupWishlistItemListeners() {
|
|
|
|
|
// Remove buttons
|
|
|
|
|
this.wishlistContent
|
|
|
|
|
.querySelectorAll(".wishlist-item-remove")
|
|
|
|
|
.forEach((btn) => {
|
|
|
|
|
btn.addEventListener("click", (e) => {
|
|
|
|
|
const id = parseInt(e.currentTarget.dataset.id);
|
|
|
|
|
window.AppState.removeFromWishlist(id);
|
|
|
|
|
this.render();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Add to cart buttons
|
|
|
|
|
this.wishlistContent
|
|
|
|
|
.querySelectorAll(".btn-add-to-cart")
|
|
|
|
|
.forEach((btn) => {
|
|
|
|
|
btn.addEventListener("click", (e) => {
|
|
|
|
|
const id = parseInt(e.currentTarget.dataset.id);
|
|
|
|
|
const item = window.AppState.wishlist.find(
|
|
|
|
|
(item) => item.id === id
|
|
|
|
|
);
|
|
|
|
|
if (item) {
|
|
|
|
|
window.AppState.addToCart(item);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Initialize when DOM is ready
|
|
|
|
|
if (document.readyState === "loading") {
|
|
|
|
|
document.addEventListener("DOMContentLoaded", () => {
|
|
|
|
|
new ShoppingCart();
|
|
|
|
|
new Wishlist();
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
new ShoppingCart();
|
|
|
|
|
new Wishlist();
|
|
|
|
|
}
|
|
|
|
|
})();
|