Fix: Restore website functionality - all pages and APIs working
This commit is contained in:
287
website/public/assets/js/accessibility-enhanced.js
Normal file
287
website/public/assets/js/accessibility-enhanced.js
Normal file
@@ -0,0 +1,287 @@
|
||||
/**
|
||||
* Accessibility Enhancements
|
||||
* WCAG 2.1 AA Compliant
|
||||
*/
|
||||
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
const A11y = {
|
||||
init() {
|
||||
this.addSkipLink();
|
||||
this.enhanceFocusManagement();
|
||||
this.addARIALabels();
|
||||
this.improveKeyboardNav();
|
||||
this.addLiveRegions();
|
||||
this.enhanceFormAccessibility();
|
||||
console.log("[A11y] Accessibility enhancements loaded");
|
||||
},
|
||||
|
||||
// Add skip to main content link
|
||||
addSkipLink() {
|
||||
if (document.querySelector(".skip-link")) return;
|
||||
|
||||
const skipLink = document.createElement("a");
|
||||
skipLink.href = "#main-content";
|
||||
skipLink.className = "skip-link";
|
||||
skipLink.textContent = "Skip to main content";
|
||||
skipLink.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
const main = document.querySelector("#main-content, main");
|
||||
if (main) {
|
||||
main.setAttribute("tabindex", "-1");
|
||||
main.focus();
|
||||
}
|
||||
});
|
||||
|
||||
document.body.insertBefore(skipLink, document.body.firstChild);
|
||||
},
|
||||
|
||||
// Enhance focus management
|
||||
enhanceFocusManagement() {
|
||||
// Trap focus in modals
|
||||
document.addEventListener("keydown", (e) => {
|
||||
if (e.key !== "Tab") return;
|
||||
|
||||
const modal = document.querySelector(
|
||||
'.modal.active, .dropdown[style*="display: flex"]'
|
||||
);
|
||||
if (!modal) return;
|
||||
|
||||
const focusable = modal.querySelectorAll(
|
||||
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
||||
);
|
||||
|
||||
if (focusable.length === 0) return;
|
||||
|
||||
const first = focusable[0];
|
||||
const last = focusable[focusable.length - 1];
|
||||
|
||||
if (e.shiftKey && document.activeElement === first) {
|
||||
e.preventDefault();
|
||||
last.focus();
|
||||
} else if (!e.shiftKey && document.activeElement === last) {
|
||||
e.preventDefault();
|
||||
first.focus();
|
||||
}
|
||||
});
|
||||
|
||||
// Focus visible styles
|
||||
const style = document.createElement("style");
|
||||
style.textContent = `
|
||||
*:focus-visible {
|
||||
outline: 3px solid #667eea !important;
|
||||
outline-offset: 2px !important;
|
||||
}
|
||||
|
||||
button:focus-visible,
|
||||
a:focus-visible,
|
||||
input:focus-visible,
|
||||
select:focus-visible,
|
||||
textarea:focus-visible {
|
||||
outline: 3px solid #667eea !important;
|
||||
outline-offset: 2px !important;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
},
|
||||
|
||||
// Add ARIA labels to interactive elements
|
||||
addARIALabels() {
|
||||
// Cart button
|
||||
const cartBtn = document.querySelector("#cart-btn");
|
||||
if (cartBtn && !cartBtn.hasAttribute("aria-label")) {
|
||||
cartBtn.setAttribute("aria-label", "Shopping cart");
|
||||
cartBtn.setAttribute("aria-haspopup", "true");
|
||||
}
|
||||
|
||||
// Wishlist button
|
||||
const wishlistBtn = document.querySelector("#wishlist-btn");
|
||||
if (wishlistBtn && !wishlistBtn.hasAttribute("aria-label")) {
|
||||
wishlistBtn.setAttribute("aria-label", "Wishlist");
|
||||
wishlistBtn.setAttribute("aria-haspopup", "true");
|
||||
}
|
||||
|
||||
// Mobile menu toggle
|
||||
const menuToggle = document.querySelector(".mobile-menu-toggle");
|
||||
if (menuToggle && !menuToggle.hasAttribute("aria-label")) {
|
||||
menuToggle.setAttribute("aria-label", "Open navigation menu");
|
||||
menuToggle.setAttribute("aria-expanded", "false");
|
||||
}
|
||||
|
||||
// Add ARIA labels to product cards
|
||||
document.querySelectorAll(".product-card").forEach((card, index) => {
|
||||
if (!card.hasAttribute("role")) {
|
||||
card.setAttribute("role", "article");
|
||||
}
|
||||
|
||||
const title = card.querySelector("h3, .product-title");
|
||||
if (title && !title.id) {
|
||||
title.id = `product-title-${index}`;
|
||||
card.setAttribute("aria-labelledby", title.id);
|
||||
}
|
||||
});
|
||||
|
||||
// Add labels to icon-only buttons
|
||||
document.querySelectorAll("button:not([aria-label])").forEach((btn) => {
|
||||
const icon = btn.querySelector('i[class*="bi-"]');
|
||||
if (icon && !btn.textContent.trim()) {
|
||||
const iconClass = icon.className;
|
||||
let label = "Button";
|
||||
|
||||
if (iconClass.includes("cart")) label = "Add to cart";
|
||||
else if (iconClass.includes("heart")) label = "Add to wishlist";
|
||||
else if (iconClass.includes("trash")) label = "Remove";
|
||||
else if (iconClass.includes("plus")) label = "Increase";
|
||||
else if (iconClass.includes("minus") || iconClass.includes("dash"))
|
||||
label = "Decrease";
|
||||
else if (iconClass.includes("close") || iconClass.includes("x"))
|
||||
label = "Close";
|
||||
|
||||
btn.setAttribute("aria-label", label);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// Improve keyboard navigation
|
||||
improveKeyboardNav() {
|
||||
// Dropdown keyboard support
|
||||
document.querySelectorAll("[data-dropdown-toggle]").forEach((toggle) => {
|
||||
toggle.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
toggle.click();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Product card keyboard navigation
|
||||
document.querySelectorAll(".product-card").forEach((card) => {
|
||||
const link = card.querySelector("a");
|
||||
if (link) {
|
||||
card.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Enter" && e.target === card) {
|
||||
link.click();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Quantity input keyboard support
|
||||
document.querySelectorAll(".quantity-input").forEach((input) => {
|
||||
input.addEventListener("keydown", (e) => {
|
||||
if (e.key === "ArrowUp") {
|
||||
e.preventDefault();
|
||||
const newValue = parseInt(input.value || 1) + 1;
|
||||
if (newValue <= 99) {
|
||||
input.value = newValue;
|
||||
input.dispatchEvent(new Event("change"));
|
||||
}
|
||||
} else if (e.key === "ArrowDown") {
|
||||
e.preventDefault();
|
||||
const newValue = parseInt(input.value || 1) - 1;
|
||||
if (newValue >= 1) {
|
||||
input.value = newValue;
|
||||
input.dispatchEvent(new Event("change"));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
// Add live regions for dynamic content
|
||||
addLiveRegions() {
|
||||
// Create announcement region
|
||||
if (!document.querySelector("#a11y-announcements")) {
|
||||
const announcer = document.createElement("div");
|
||||
announcer.id = "a11y-announcements";
|
||||
announcer.setAttribute("role", "status");
|
||||
announcer.setAttribute("aria-live", "polite");
|
||||
announcer.setAttribute("aria-atomic", "true");
|
||||
announcer.className = "sr-only";
|
||||
document.body.appendChild(announcer);
|
||||
}
|
||||
|
||||
// Announce cart/wishlist updates
|
||||
window.addEventListener("cart-updated", (e) => {
|
||||
this.announce(`Cart updated. ${e.detail.length} items in cart.`);
|
||||
});
|
||||
|
||||
window.addEventListener("wishlist-updated", (e) => {
|
||||
this.announce(
|
||||
`Wishlist updated. ${e.detail.length} items in wishlist.`
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
announce(message) {
|
||||
const announcer = document.querySelector("#a11y-announcements");
|
||||
if (announcer) {
|
||||
announcer.textContent = "";
|
||||
setTimeout(() => {
|
||||
announcer.textContent = message;
|
||||
}, 100);
|
||||
}
|
||||
},
|
||||
|
||||
// Enhance form accessibility
|
||||
enhanceFormAccessibility() {
|
||||
// Add required indicators
|
||||
document
|
||||
.querySelectorAll(
|
||||
"input[required], select[required], textarea[required]"
|
||||
)
|
||||
.forEach((field) => {
|
||||
const label = document.querySelector(`label[for="${field.id}"]`);
|
||||
if (label && !label.querySelector(".required-indicator")) {
|
||||
const indicator = document.createElement("span");
|
||||
indicator.className = "required-indicator";
|
||||
indicator.textContent = " *";
|
||||
indicator.setAttribute("aria-label", "required");
|
||||
label.appendChild(indicator);
|
||||
}
|
||||
});
|
||||
|
||||
// Add error message associations
|
||||
document.querySelectorAll(".error-message").forEach((error, index) => {
|
||||
if (!error.id) {
|
||||
error.id = `error-${index}`;
|
||||
}
|
||||
|
||||
const field = error.previousElementSibling;
|
||||
if (
|
||||
field &&
|
||||
(field.tagName === "INPUT" ||
|
||||
field.tagName === "SELECT" ||
|
||||
field.tagName === "TEXTAREA")
|
||||
) {
|
||||
field.setAttribute("aria-describedby", error.id);
|
||||
field.setAttribute("aria-invalid", "true");
|
||||
}
|
||||
});
|
||||
|
||||
// Add autocomplete attributes
|
||||
document.querySelectorAll('input[type="email"]').forEach((field) => {
|
||||
if (!field.hasAttribute("autocomplete")) {
|
||||
field.setAttribute("autocomplete", "email");
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelectorAll('input[type="tel"]').forEach((field) => {
|
||||
if (!field.hasAttribute("autocomplete")) {
|
||||
field.setAttribute("autocomplete", "tel");
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
// Initialize on DOM ready
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", () => A11y.init());
|
||||
} else {
|
||||
A11y.init();
|
||||
}
|
||||
|
||||
// Export for external use
|
||||
window.A11y = A11y;
|
||||
})();
|
||||
@@ -84,7 +84,8 @@
|
||||
closeId: "cartClose",
|
||||
wrapperClass: ".cart-dropdown-wrapper",
|
||||
eventName: "cart-updated",
|
||||
emptyMessage: '<p class="empty-state"><i class="bi bi-cart-x"></i><br>Your cart is empty</p>'
|
||||
emptyMessage:
|
||||
'<p class="empty-state"><i class="bi bi-cart-x"></i><br>Your cart is empty</p>',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -97,9 +98,10 @@
|
||||
}
|
||||
|
||||
const cart = window.AppState.cart;
|
||||
|
||||
|
||||
if (!Array.isArray(cart)) {
|
||||
this.content.innerHTML = '<p class="empty-state">Error loading cart</p>';
|
||||
this.content.innerHTML =
|
||||
'<p class="empty-state">Error loading cart</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -115,19 +117,24 @@
|
||||
this.updateFooter(null);
|
||||
return;
|
||||
}
|
||||
|
||||
this.content.innerHTML = validItems.map(item => this.renderCartItem(item)).join("");
|
||||
|
||||
this.content.innerHTML = validItems
|
||||
.map((item) => this.renderCartItem(item))
|
||||
.join("");
|
||||
this.setupCartItemListeners();
|
||||
|
||||
|
||||
const total = this._calculateTotal(validItems);
|
||||
this.updateFooter(total);
|
||||
} catch (error) {
|
||||
this.content.innerHTML = '<p class="empty-state">Error loading cart</p>';
|
||||
this.content.innerHTML =
|
||||
'<p class="empty-state">Error loading cart</p>';
|
||||
}
|
||||
}
|
||||
|
||||
_filterValidItems(items) {
|
||||
return items.filter(item => item && item.id && typeof item.price !== 'undefined');
|
||||
return items.filter(
|
||||
(item) => item && item.id && typeof item.price !== "undefined"
|
||||
);
|
||||
}
|
||||
|
||||
_calculateTotal(items) {
|
||||
@@ -137,7 +144,7 @@
|
||||
return items.reduce((sum, item) => {
|
||||
const price = parseFloat(item.price) || 0;
|
||||
const quantity = parseInt(item.quantity) || 0;
|
||||
return sum + (price * quantity);
|
||||
return sum + price * quantity;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
@@ -145,13 +152,13 @@
|
||||
try {
|
||||
// Validate item and Utils availability
|
||||
if (!item || !item.id) {
|
||||
return '';
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
if (!window.Utils) {
|
||||
return '<p class="error-message">Error loading item</p>';
|
||||
}
|
||||
|
||||
|
||||
// Sanitize and validate item data with defensive checks
|
||||
const imageUrl =
|
||||
item.imageurl ||
|
||||
@@ -164,7 +171,7 @@
|
||||
const price = parseFloat(item.price) || 0;
|
||||
const quantity = Math.max(1, parseInt(item.quantity) || 1);
|
||||
const subtotal = price * quantity;
|
||||
|
||||
|
||||
const priceFormatted = window.Utils.formatCurrency(price);
|
||||
const subtotalFormatted = window.Utils.formatCurrency(subtotal);
|
||||
|
||||
@@ -191,7 +198,7 @@
|
||||
</div>
|
||||
`;
|
||||
} catch (error) {
|
||||
return '';
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,19 +238,20 @@
|
||||
this._handleAction(e, () => {
|
||||
const id = e.currentTarget.dataset.id;
|
||||
if (!window.AppState?.cart) return;
|
||||
|
||||
|
||||
const item = window.AppState.cart.find(
|
||||
(item) => String(item.id) === String(id)
|
||||
);
|
||||
|
||||
|
||||
if (!item || !window.AppState.updateCartQuantity) return;
|
||||
|
||||
const newQuantity = delta > 0
|
||||
? Math.min(item.quantity + delta, 999)
|
||||
: Math.max(item.quantity + delta, 1);
|
||||
|
||||
|
||||
const newQuantity =
|
||||
delta > 0
|
||||
? Math.min(item.quantity + delta, 999)
|
||||
: Math.max(item.quantity + delta, 1);
|
||||
|
||||
if (delta < 0 && item.quantity <= 1) return;
|
||||
|
||||
|
||||
window.AppState.updateCartQuantity(id, newQuantity);
|
||||
this.render();
|
||||
});
|
||||
@@ -291,28 +299,10 @@
|
||||
closeId: "wishlistClose",
|
||||
wrapperClass: ".wishlist-dropdown-wrapper",
|
||||
eventName: "wishlist-updated",
|
||||
emptyMessage: '<p class="empty-state"><i class="bi bi-heart"></i><br>Your wishlist is empty</p>'
|
||||
emptyMessage:
|
||||
'<p class="empty-state"><i class="bi bi-heart"></i><br>Your wishlist is empty</p>',
|
||||
});
|
||||
}
|
||||
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.content) return;
|
||||
|
||||
818
website/public/assets/js/main-enhanced.js
Normal file
818
website/public/assets/js/main-enhanced.js
Normal file
@@ -0,0 +1,818 @@
|
||||
/**
|
||||
* Enhanced Main Application JavaScript
|
||||
* Production-Ready with No Console Errors
|
||||
* Proper State Management & API Integration
|
||||
*/
|
||||
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
// Production mode check
|
||||
const isDevelopment =
|
||||
window.location.hostname === "localhost" ||
|
||||
window.location.hostname === "127.0.0.1";
|
||||
|
||||
// Safe console wrapper
|
||||
const logger = {
|
||||
log: (...args) => isDevelopment && console.log(...args),
|
||||
error: (...args) => console.error(...args),
|
||||
warn: (...args) => isDevelopment && console.warn(...args),
|
||||
info: (...args) => isDevelopment && console.info(...args),
|
||||
};
|
||||
|
||||
// ========================================
|
||||
// GLOBAL STATE MANAGEMENT
|
||||
// ========================================
|
||||
window.AppState = {
|
||||
cart: [],
|
||||
wishlist: [],
|
||||
products: [],
|
||||
settings: null,
|
||||
user: null,
|
||||
_saveCartTimeout: null,
|
||||
_saveWishlistTimeout: null,
|
||||
_initialized: false,
|
||||
|
||||
// Initialize state
|
||||
init() {
|
||||
if (this._initialized) {
|
||||
logger.warn("[AppState] Already initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("[AppState] Initializing...");
|
||||
this.loadCart();
|
||||
this.loadWishlist();
|
||||
this.updateUI();
|
||||
this._initialized = true;
|
||||
logger.info(
|
||||
"[AppState] Initialized - Cart:",
|
||||
this.cart.length,
|
||||
"items, Wishlist:",
|
||||
this.wishlist.length,
|
||||
"items"
|
||||
);
|
||||
|
||||
// Dispatch ready event
|
||||
window.dispatchEvent(new CustomEvent("appstate-ready"));
|
||||
},
|
||||
|
||||
// ========================================
|
||||
// CART MANAGEMENT
|
||||
// ========================================
|
||||
loadCart() {
|
||||
try {
|
||||
const saved = localStorage.getItem("cart");
|
||||
this.cart = saved ? JSON.parse(saved) : [];
|
||||
|
||||
// Validate cart items
|
||||
this.cart = this.cart.filter((item) => item && item.id && item.price);
|
||||
} catch (error) {
|
||||
logger.error("Error loading cart:", error);
|
||||
this.cart = [];
|
||||
}
|
||||
},
|
||||
|
||||
saveCart() {
|
||||
if (this._saveCartTimeout) {
|
||||
clearTimeout(this._saveCartTimeout);
|
||||
}
|
||||
|
||||
this._saveCartTimeout = setTimeout(() => {
|
||||
try {
|
||||
localStorage.setItem("cart", JSON.stringify(this.cart));
|
||||
this.updateUI();
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("cart-updated", { detail: this.cart })
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error("Error saving cart:", error);
|
||||
this.showNotification("Error saving cart", "error");
|
||||
}
|
||||
}, 100);
|
||||
},
|
||||
|
||||
addToCart(product, quantity = 1) {
|
||||
if (!product || !product.id) {
|
||||
logger.error("[AppState] Invalid product:", product);
|
||||
this.showNotification("Invalid product", "error");
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const existing = this.cart.find((item) => item.id === product.id);
|
||||
if (existing) {
|
||||
existing.quantity = (existing.quantity || 1) + quantity;
|
||||
logger.info("[AppState] Updated cart quantity:", existing);
|
||||
} else {
|
||||
this.cart.push({
|
||||
...product,
|
||||
quantity,
|
||||
addedAt: new Date().toISOString(),
|
||||
});
|
||||
logger.info("[AppState] Added to cart:", product.name);
|
||||
}
|
||||
|
||||
this.saveCart();
|
||||
this.showNotification(
|
||||
`${product.name || "Item"} added to cart`,
|
||||
"success"
|
||||
);
|
||||
return true;
|
||||
} catch (error) {
|
||||
logger.error("[AppState] Error adding to cart:", error);
|
||||
this.showNotification("Error adding to cart", "error");
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
removeFromCart(productId) {
|
||||
if (!productId) {
|
||||
logger.error("[AppState] Invalid productId");
|
||||
return false;
|
||||
}
|
||||
|
||||
const initialLength = this.cart.length;
|
||||
this.cart = this.cart.filter((item) => item.id !== productId);
|
||||
|
||||
if (this.cart.length < initialLength) {
|
||||
this.saveCart();
|
||||
this.showNotification("Removed from cart", "info");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
updateCartQuantity(productId, quantity) {
|
||||
const item = this.cart.find((item) => item.id === productId);
|
||||
if (item) {
|
||||
item.quantity = Math.max(1, parseInt(quantity) || 1);
|
||||
this.saveCart();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
clearCart() {
|
||||
this.cart = [];
|
||||
this.saveCart();
|
||||
this.showNotification("Cart cleared", "info");
|
||||
},
|
||||
|
||||
getCartTotal() {
|
||||
return this.cart.reduce((sum, item) => {
|
||||
const price = parseFloat(item.price) || 0;
|
||||
const quantity = parseInt(item.quantity) || 1;
|
||||
return sum + price * quantity;
|
||||
}, 0);
|
||||
},
|
||||
|
||||
getCartCount() {
|
||||
return this.cart.reduce(
|
||||
(sum, item) => sum + (parseInt(item.quantity) || 1),
|
||||
0
|
||||
);
|
||||
},
|
||||
|
||||
// ========================================
|
||||
// WISHLIST MANAGEMENT
|
||||
// ========================================
|
||||
loadWishlist() {
|
||||
try {
|
||||
const saved = localStorage.getItem("wishlist");
|
||||
this.wishlist = saved ? JSON.parse(saved) : [];
|
||||
|
||||
// Validate wishlist items
|
||||
this.wishlist = this.wishlist.filter((item) => item && item.id);
|
||||
} catch (error) {
|
||||
logger.error("Error loading wishlist:", error);
|
||||
this.wishlist = [];
|
||||
}
|
||||
},
|
||||
|
||||
saveWishlist() {
|
||||
if (this._saveWishlistTimeout) {
|
||||
clearTimeout(this._saveWishlistTimeout);
|
||||
}
|
||||
|
||||
this._saveWishlistTimeout = setTimeout(() => {
|
||||
try {
|
||||
localStorage.setItem("wishlist", JSON.stringify(this.wishlist));
|
||||
this.updateUI();
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("wishlist-updated", { detail: this.wishlist })
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error("Error saving wishlist:", error);
|
||||
}
|
||||
}, 100);
|
||||
},
|
||||
|
||||
addToWishlist(product) {
|
||||
if (!product || !product.id) {
|
||||
logger.error("[AppState] Invalid product:", product);
|
||||
this.showNotification("Invalid product", "error");
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const exists = this.wishlist.some((item) => item.id === product.id);
|
||||
if (exists) {
|
||||
this.showNotification("Already in wishlist", "info");
|
||||
return false;
|
||||
}
|
||||
|
||||
this.wishlist.push({
|
||||
...product,
|
||||
addedAt: new Date().toISOString(),
|
||||
});
|
||||
this.saveWishlist();
|
||||
this.showNotification(
|
||||
`${product.name || "Item"} added to wishlist`,
|
||||
"success"
|
||||
);
|
||||
return true;
|
||||
} catch (error) {
|
||||
logger.error("[AppState] Error adding to wishlist:", error);
|
||||
this.showNotification("Error adding to wishlist", "error");
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
removeFromWishlist(productId) {
|
||||
if (!productId) return false;
|
||||
|
||||
const initialLength = this.wishlist.length;
|
||||
this.wishlist = this.wishlist.filter((item) => item.id !== productId);
|
||||
|
||||
if (this.wishlist.length < initialLength) {
|
||||
this.saveWishlist();
|
||||
this.showNotification("Removed from wishlist", "info");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
isInWishlist(productId) {
|
||||
return this.wishlist.some((item) => item.id === productId);
|
||||
},
|
||||
|
||||
getWishlistCount() {
|
||||
return this.wishlist.length;
|
||||
},
|
||||
|
||||
// ========================================
|
||||
// UI UPDATES
|
||||
// ========================================
|
||||
updateUI() {
|
||||
this.updateCartBadge();
|
||||
this.updateWishlistBadge();
|
||||
this.updateCartDropdown();
|
||||
this.updateWishlistDropdown();
|
||||
},
|
||||
|
||||
updateCartBadge() {
|
||||
const badges = document.querySelectorAll(
|
||||
".cart-count, .cart-badge, #cartCount"
|
||||
);
|
||||
const count = this.getCartCount();
|
||||
|
||||
badges.forEach((badge) => {
|
||||
badge.textContent = count;
|
||||
if (count > 0) {
|
||||
badge.classList.add("show");
|
||||
} else {
|
||||
badge.classList.remove("show");
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
updateWishlistBadge() {
|
||||
const badges = document.querySelectorAll(
|
||||
".wishlist-count, .wishlist-badge, #wishlistCount"
|
||||
);
|
||||
const count = this.getWishlistCount();
|
||||
|
||||
badges.forEach((badge) => {
|
||||
badge.textContent = count;
|
||||
if (count > 0) {
|
||||
badge.classList.add("show");
|
||||
} else {
|
||||
badge.classList.remove("show");
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
updateCartDropdown() {
|
||||
const container = document.querySelector("#cart-items");
|
||||
if (!container) return;
|
||||
|
||||
if (this.cart.length === 0) {
|
||||
container.innerHTML = `
|
||||
<div class="empty-state">
|
||||
<i class="bi bi-cart-x"></i>
|
||||
<p>Your cart is empty</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const totalEl = document.querySelector(".cart-total-value");
|
||||
if (totalEl) totalEl.textContent = "$0.00";
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = this.cart
|
||||
.map((item) => this.renderCartItem(item))
|
||||
.join("");
|
||||
|
||||
const totalEl = document.querySelector(".cart-total-value");
|
||||
if (totalEl) {
|
||||
totalEl.textContent = `$${this.getCartTotal().toFixed(2)}`;
|
||||
}
|
||||
|
||||
this.attachCartEventListeners();
|
||||
},
|
||||
|
||||
updateWishlistDropdown() {
|
||||
const container = document.querySelector("#wishlist-items");
|
||||
if (!container) return;
|
||||
|
||||
if (this.wishlist.length === 0) {
|
||||
container.innerHTML = `
|
||||
<div class="empty-state">
|
||||
<i class="bi bi-heart"></i>
|
||||
<p>Your wishlist is empty</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = this.wishlist
|
||||
.map((item) => this.renderWishlistItem(item))
|
||||
.join("");
|
||||
this.attachWishlistEventListeners();
|
||||
},
|
||||
|
||||
renderCartItem(item) {
|
||||
const price = parseFloat(item.price) || 0;
|
||||
const quantity = parseInt(item.quantity) || 1;
|
||||
const imageUrl = this.getProductImage(item);
|
||||
const name = this.sanitizeHTML(item.name || "Product");
|
||||
|
||||
return `
|
||||
<div class="cart-item" data-product-id="${item.id}">
|
||||
<div class="cart-item-image">
|
||||
<img src="${imageUrl}" alt="${name}" loading="lazy" onerror="this.src='/assets/img/placeholder.jpg'">
|
||||
</div>
|
||||
<div class="cart-item-info">
|
||||
<div class="cart-item-title">${name}</div>
|
||||
<div class="cart-item-price">$${price.toFixed(2)}</div>
|
||||
<div class="cart-item-controls">
|
||||
<button class="btn-quantity" data-action="decrease" aria-label="Decrease quantity">
|
||||
<i class="bi bi-dash"></i>
|
||||
</button>
|
||||
<input type="number" class="quantity-input" value="${quantity}" min="1" max="99" aria-label="Quantity">
|
||||
<button class="btn-quantity" data-action="increase" aria-label="Increase quantity">
|
||||
<i class="bi bi-plus"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn-remove" data-action="remove" aria-label="Remove from cart">
|
||||
<i class="bi bi-x-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
|
||||
renderWishlistItem(item) {
|
||||
const price = parseFloat(item.price) || 0;
|
||||
const imageUrl = this.getProductImage(item);
|
||||
const name = this.sanitizeHTML(item.name || "Product");
|
||||
|
||||
return `
|
||||
<div class="wishlist-item" data-product-id="${item.id}">
|
||||
<div class="wishlist-item-image">
|
||||
<img src="${imageUrl}" alt="${name}" loading="lazy" onerror="this.src='/assets/img/placeholder.jpg'">
|
||||
</div>
|
||||
<div class="wishlist-item-info">
|
||||
<div class="wishlist-item-title">${name}</div>
|
||||
<div class="wishlist-item-price">$${price.toFixed(2)}</div>
|
||||
<button class="btn-add-to-cart" data-product-id="${item.id}">
|
||||
<i class="bi bi-cart-plus"></i> Add to Cart
|
||||
</button>
|
||||
</div>
|
||||
<button class="btn-remove" data-action="remove" aria-label="Remove from wishlist">
|
||||
<i class="bi bi-x-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
|
||||
attachCartEventListeners() {
|
||||
document.querySelectorAll(".cart-item").forEach((item) => {
|
||||
const productId = item.dataset.productId;
|
||||
|
||||
// Quantity controls
|
||||
const decreaseBtn = item.querySelector('[data-action="decrease"]');
|
||||
const increaseBtn = item.querySelector('[data-action="increase"]');
|
||||
const quantityInput = item.querySelector(".quantity-input");
|
||||
|
||||
if (decreaseBtn) {
|
||||
decreaseBtn.addEventListener("click", () => {
|
||||
const currentQty = parseInt(quantityInput.value) || 1;
|
||||
if (currentQty > 1) {
|
||||
quantityInput.value = currentQty - 1;
|
||||
this.updateCartQuantity(productId, currentQty - 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (increaseBtn) {
|
||||
increaseBtn.addEventListener("click", () => {
|
||||
const currentQty = parseInt(quantityInput.value) || 1;
|
||||
if (currentQty < 99) {
|
||||
quantityInput.value = currentQty + 1;
|
||||
this.updateCartQuantity(productId, currentQty + 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (quantityInput) {
|
||||
quantityInput.addEventListener("change", (e) => {
|
||||
const newQty = parseInt(e.target.value) || 1;
|
||||
this.updateCartQuantity(productId, newQty);
|
||||
});
|
||||
}
|
||||
|
||||
// Remove button
|
||||
const removeBtn = item.querySelector('[data-action="remove"]');
|
||||
if (removeBtn) {
|
||||
removeBtn.addEventListener("click", () => {
|
||||
this.removeFromCart(productId);
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
attachWishlistEventListeners() {
|
||||
document.querySelectorAll(".wishlist-item").forEach((item) => {
|
||||
const productId = item.dataset.productId;
|
||||
|
||||
// Add to cart button
|
||||
const addBtn = item.querySelector(".btn-add-to-cart");
|
||||
if (addBtn) {
|
||||
addBtn.addEventListener("click", () => {
|
||||
const product = this.wishlist.find((p) => p.id === productId);
|
||||
if (product) {
|
||||
this.addToCart(product);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Remove button
|
||||
const removeBtn = item.querySelector('[data-action="remove"]');
|
||||
if (removeBtn) {
|
||||
removeBtn.addEventListener("click", () => {
|
||||
this.removeFromWishlist(productId);
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// ========================================
|
||||
// NOTIFICATIONS
|
||||
// ========================================
|
||||
showNotification(message, type = "info") {
|
||||
if (!message) return;
|
||||
|
||||
let container = document.querySelector(".notification-container");
|
||||
|
||||
if (!container) {
|
||||
container = document.createElement("div");
|
||||
container.className = "notification-container";
|
||||
document.body.appendChild(container);
|
||||
}
|
||||
|
||||
const notification = document.createElement("div");
|
||||
notification.className = `notification ${type}`;
|
||||
|
||||
const icon =
|
||||
type === "success"
|
||||
? "check-circle-fill"
|
||||
: type === "error"
|
||||
? "exclamation-circle-fill"
|
||||
: "info-circle-fill";
|
||||
|
||||
notification.innerHTML = `
|
||||
<i class="bi bi-${icon}"></i>
|
||||
<span class="notification-message">${this.sanitizeHTML(message)}</span>
|
||||
`;
|
||||
|
||||
container.appendChild(notification);
|
||||
|
||||
setTimeout(() => {
|
||||
notification.style.animation = "slideOut 0.3s ease forwards";
|
||||
setTimeout(() => notification.remove(), 300);
|
||||
}, 3000);
|
||||
},
|
||||
|
||||
// ========================================
|
||||
// API INTEGRATION
|
||||
// ========================================
|
||||
async fetchProducts() {
|
||||
try {
|
||||
const response = await fetch("/api/products");
|
||||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||||
|
||||
const data = await response.json();
|
||||
if (data.success && Array.isArray(data.products)) {
|
||||
this.products = data.products;
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("products-loaded", { detail: this.products })
|
||||
);
|
||||
return this.products;
|
||||
}
|
||||
|
||||
throw new Error("Invalid API response");
|
||||
} catch (error) {
|
||||
logger.error("Error fetching products:", error);
|
||||
this.showNotification("Error loading products", "error");
|
||||
return [];
|
||||
}
|
||||
},
|
||||
|
||||
async fetchSettings() {
|
||||
try {
|
||||
const response = await fetch("/api/settings");
|
||||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||||
|
||||
const data = await response.json();
|
||||
if (data.success && data.settings) {
|
||||
this.settings = data.settings;
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("settings-loaded", { detail: this.settings })
|
||||
);
|
||||
return this.settings;
|
||||
}
|
||||
|
||||
throw new Error("Invalid API response");
|
||||
} catch (error) {
|
||||
logger.error("Error fetching settings:", error);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
// ========================================
|
||||
// UTILITY METHODS
|
||||
// ========================================
|
||||
getProductImage(product) {
|
||||
if (!product) return "/assets/img/placeholder.jpg";
|
||||
|
||||
// Check various image properties
|
||||
if (product.image_url) return product.image_url;
|
||||
if (product.imageUrl) return product.imageUrl;
|
||||
if (
|
||||
product.images &&
|
||||
Array.isArray(product.images) &&
|
||||
product.images.length > 0
|
||||
) {
|
||||
return (
|
||||
product.images[0].image_url ||
|
||||
product.images[0].url ||
|
||||
"/assets/img/placeholder.jpg"
|
||||
);
|
||||
}
|
||||
if (product.thumbnail) return product.thumbnail;
|
||||
|
||||
return "/assets/img/placeholder.jpg";
|
||||
},
|
||||
|
||||
sanitizeHTML(str) {
|
||||
if (!str) return "";
|
||||
const div = document.createElement("div");
|
||||
div.textContent = str;
|
||||
return div.innerHTML;
|
||||
},
|
||||
|
||||
formatPrice(price) {
|
||||
const num = parseFloat(price) || 0;
|
||||
return `$${num.toFixed(2)}`;
|
||||
},
|
||||
|
||||
debounce(func, wait) {
|
||||
let timeout;
|
||||
return function executedFunction(...args) {
|
||||
const later = () => {
|
||||
clearTimeout(timeout);
|
||||
func(...args);
|
||||
};
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
// ========================================
|
||||
// DROPDOWN MANAGEMENT
|
||||
// ========================================
|
||||
window.DropdownManager = {
|
||||
activeDropdown: null,
|
||||
|
||||
init() {
|
||||
this.attachEventListeners();
|
||||
logger.info("[DropdownManager] Initialized");
|
||||
},
|
||||
|
||||
attachEventListeners() {
|
||||
// Cart toggle
|
||||
const cartBtn = document.querySelector("#cart-btn");
|
||||
if (cartBtn) {
|
||||
cartBtn.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
this.toggle("cart");
|
||||
});
|
||||
}
|
||||
|
||||
// Wishlist toggle
|
||||
const wishlistBtn = document.querySelector("#wishlist-btn");
|
||||
if (wishlistBtn) {
|
||||
wishlistBtn.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
this.toggle("wishlist");
|
||||
});
|
||||
}
|
||||
|
||||
// Close on outside click
|
||||
document.addEventListener("click", (e) => {
|
||||
if (!e.target.closest(".dropdown") && !e.target.closest(".icon-btn")) {
|
||||
this.closeAll();
|
||||
}
|
||||
});
|
||||
|
||||
// Close on escape key
|
||||
document.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Escape") {
|
||||
this.closeAll();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
toggle(type) {
|
||||
const dropdown = document.querySelector(`#${type}-dropdown`);
|
||||
if (!dropdown) return;
|
||||
|
||||
if (this.activeDropdown === dropdown) {
|
||||
this.close(dropdown);
|
||||
} else {
|
||||
this.closeAll();
|
||||
this.open(dropdown, type);
|
||||
}
|
||||
},
|
||||
|
||||
open(dropdown, type) {
|
||||
dropdown.style.display = "flex";
|
||||
this.activeDropdown = dropdown;
|
||||
|
||||
// Update content
|
||||
if (type === "cart") {
|
||||
window.AppState.updateCartDropdown();
|
||||
} else if (type === "wishlist") {
|
||||
window.AppState.updateWishlistDropdown();
|
||||
}
|
||||
},
|
||||
|
||||
close(dropdown) {
|
||||
if (dropdown) {
|
||||
dropdown.style.display = "none";
|
||||
}
|
||||
this.activeDropdown = null;
|
||||
},
|
||||
|
||||
closeAll() {
|
||||
document.querySelectorAll(".dropdown").forEach((dropdown) => {
|
||||
dropdown.style.display = "none";
|
||||
});
|
||||
this.activeDropdown = null;
|
||||
},
|
||||
};
|
||||
|
||||
// ========================================
|
||||
// MOBILE MENU
|
||||
// ========================================
|
||||
window.MobileMenu = {
|
||||
menu: null,
|
||||
overlay: null,
|
||||
isOpen: false,
|
||||
|
||||
init() {
|
||||
this.menu = document.querySelector(".mobile-menu");
|
||||
this.overlay = document.querySelector(".mobile-menu-overlay");
|
||||
|
||||
if (!this.menu || !this.overlay) {
|
||||
logger.warn("[MobileMenu] Elements not found");
|
||||
return;
|
||||
}
|
||||
|
||||
this.attachEventListeners();
|
||||
logger.info("[MobileMenu] Initialized");
|
||||
},
|
||||
|
||||
attachEventListeners() {
|
||||
// Toggle button
|
||||
const toggleBtn = document.querySelector(".mobile-menu-toggle");
|
||||
if (toggleBtn) {
|
||||
toggleBtn.addEventListener("click", () => this.toggle());
|
||||
}
|
||||
|
||||
// Close button
|
||||
const closeBtn = document.querySelector(".mobile-menu-close");
|
||||
if (closeBtn) {
|
||||
closeBtn.addEventListener("click", () => this.close());
|
||||
}
|
||||
|
||||
// Overlay click
|
||||
if (this.overlay) {
|
||||
this.overlay.addEventListener("click", () => this.close());
|
||||
}
|
||||
|
||||
// Menu links
|
||||
this.menu.querySelectorAll("a").forEach((link) => {
|
||||
link.addEventListener("click", () => {
|
||||
setTimeout(() => this.close(), 100);
|
||||
});
|
||||
});
|
||||
|
||||
// Escape key
|
||||
document.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Escape" && this.isOpen) {
|
||||
this.close();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
toggle() {
|
||||
this.isOpen ? this.close() : this.open();
|
||||
},
|
||||
|
||||
open() {
|
||||
this.menu.classList.add("active");
|
||||
this.overlay.classList.add("active");
|
||||
this.isOpen = true;
|
||||
document.body.style.overflow = "hidden";
|
||||
},
|
||||
|
||||
close() {
|
||||
this.menu.classList.remove("active");
|
||||
this.overlay.classList.remove("active");
|
||||
this.isOpen = false;
|
||||
document.body.style.overflow = "";
|
||||
},
|
||||
};
|
||||
|
||||
// ========================================
|
||||
// INITIALIZATION
|
||||
// ========================================
|
||||
function initialize() {
|
||||
logger.info("[App] Initializing...");
|
||||
|
||||
// Initialize state
|
||||
if (window.AppState) {
|
||||
window.AppState.init();
|
||||
}
|
||||
|
||||
// Initialize dropdown manager
|
||||
if (window.DropdownManager) {
|
||||
window.DropdownManager.init();
|
||||
}
|
||||
|
||||
// Initialize mobile menu
|
||||
if (window.MobileMenu) {
|
||||
window.MobileMenu.init();
|
||||
}
|
||||
|
||||
// Fetch initial data
|
||||
if (window.AppState.fetchSettings) {
|
||||
window.AppState.fetchSettings();
|
||||
}
|
||||
|
||||
logger.info("[App] Initialization complete");
|
||||
}
|
||||
|
||||
// Run on DOM ready
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", initialize);
|
||||
} else {
|
||||
initialize();
|
||||
}
|
||||
|
||||
// Export for debugging in development
|
||||
if (isDevelopment) {
|
||||
window.DEBUG = {
|
||||
AppState: window.AppState,
|
||||
DropdownManager: window.DropdownManager,
|
||||
MobileMenu: window.MobileMenu,
|
||||
logger,
|
||||
};
|
||||
}
|
||||
})();
|
||||
@@ -6,7 +6,7 @@
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
console.log('[main.js] Loading...');
|
||||
console.log("[main.js] Loading...");
|
||||
|
||||
// Global state management
|
||||
window.AppState = {
|
||||
@@ -20,12 +20,18 @@
|
||||
|
||||
// Initialize state from localStorage
|
||||
init() {
|
||||
console.log('[AppState] Initializing...');
|
||||
console.log('[AppState] window.AppState exists:', !!window.AppState);
|
||||
console.log("[AppState] Initializing...");
|
||||
console.log("[AppState] window.AppState exists:", !!window.AppState);
|
||||
this.loadCart();
|
||||
this.loadWishlist();
|
||||
this.updateUI();
|
||||
console.log('[AppState] Initialized - Cart:', this.cart.length, 'items, Wishlist:', this.wishlist.length, 'items');
|
||||
console.log(
|
||||
"[AppState] Initialized - Cart:",
|
||||
this.cart.length,
|
||||
"items, Wishlist:",
|
||||
this.wishlist.length,
|
||||
"items"
|
||||
);
|
||||
},
|
||||
|
||||
// Cart management
|
||||
@@ -55,18 +61,26 @@
|
||||
},
|
||||
|
||||
addToCart(product, quantity = 1) {
|
||||
console.log('[AppState] addToCart called:', product, 'quantity:', quantity);
|
||||
console.log(
|
||||
"[AppState] addToCart called:",
|
||||
product,
|
||||
"quantity:",
|
||||
quantity
|
||||
);
|
||||
const existing = this.cart.find((item) => item.id === product.id);
|
||||
if (existing) {
|
||||
console.log('[AppState] Product exists in cart, updating quantity');
|
||||
console.log("[AppState] Product exists in cart, updating quantity");
|
||||
existing.quantity += quantity;
|
||||
} else {
|
||||
console.log('[AppState] Adding new product to cart');
|
||||
console.log("[AppState] Adding new product to cart");
|
||||
this.cart.push({ ...product, quantity });
|
||||
}
|
||||
console.log('[AppState] Cart after add:', this.cart);
|
||||
console.log("[AppState] Cart after add:", this.cart);
|
||||
this.saveCart();
|
||||
this.showNotification(`${product.name || product.title || 'Item'} added to cart`, "success");
|
||||
this.showNotification(
|
||||
`${product.name || product.title || "Item"} added to cart`,
|
||||
"success"
|
||||
);
|
||||
},
|
||||
|
||||
removeFromCart(productId) {
|
||||
@@ -77,9 +91,11 @@
|
||||
|
||||
updateCartQuantity(productId, quantity) {
|
||||
const item = this.cart.find((item) => item.id === productId);
|
||||
|
||||
// Dispatch custom event for cart dropdown
|
||||
window.dispatchEvent(new CustomEvent('cart-updated', { detail: this.cart }));
|
||||
|
||||
// Dispatch custom event for cart dropdown
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("cart-updated", { detail: this.cart })
|
||||
);
|
||||
if (item) {
|
||||
item.quantity = Math.max(1, quantity);
|
||||
this.saveCart();
|
||||
@@ -118,18 +134,20 @@
|
||||
},
|
||||
|
||||
addToWishlist(product) {
|
||||
if (!this.wishlist.find(`${product.name || product.title || 'Item'} added to wishlist`, "success");
|
||||
|
||||
// Dispatch custom event for wishlist dropdown
|
||||
window.dispatchEvent(new CustomEvent('wishlist-updated', { detail: this.wishlist }));
|
||||
} else {
|
||||
this.showNotification("Already in wishlist", "info.id)) {
|
||||
if (!this.wishlist.find((item) => item.id === product.id)) {
|
||||
this.wishlist.push(product);
|
||||
this.saveWishlist();
|
||||
|
||||
// Dispatch custom event for wishlist dropdown
|
||||
window.dispatchEvent(new CustomEvent('wishlist-updated', { detail: this.wishlist }));
|
||||
this.showNotification("Added to wishlist", "success");
|
||||
this.showNotification(
|
||||
`${product.name || product.title || "Item"} added to wishlist`,
|
||||
"success"
|
||||
);
|
||||
|
||||
// Dispatch custom event for wishlist dropdown
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("wishlist-updated", { detail: this.wishlist })
|
||||
);
|
||||
} else {
|
||||
this.showNotification("Already in wishlist", "info");
|
||||
}
|
||||
},
|
||||
|
||||
@@ -151,32 +169,44 @@
|
||||
|
||||
updateCartUI() {
|
||||
const count = this.getCartCount();
|
||||
console.log('[AppState] Updating cart UI, count:', count);
|
||||
console.log("[AppState] Updating cart UI, count:", count);
|
||||
const badge = document.getElementById("cartCount");
|
||||
if (badge) {
|
||||
badge.textContent = count;
|
||||
badge.style.display = count > 0 ? "flex" : "none";
|
||||
console.log('[AppState] Cart badge updated');
|
||||
if (count > 0) {
|
||||
badge.classList.add("show");
|
||||
} else {
|
||||
badge.classList.remove("show");
|
||||
}
|
||||
console.log("[AppState] Cart badge updated");
|
||||
} else {
|
||||
console.warn('[AppState] Cart badge element not found');
|
||||
console.warn("[AppState] Cart badge element not found");
|
||||
}
|
||||
// Also trigger cart dropdown update
|
||||
window.dispatchEvent(new CustomEvent('cart-updated', { detail: this.cart }));
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("cart-updated", { detail: this.cart })
|
||||
);
|
||||
},
|
||||
|
||||
updateWishlistUI() {
|
||||
const count = this.wishlist.length;
|
||||
console.log('[AppState] Updating wishlist UI, count:', count);
|
||||
console.log("[AppState] Updating wishlist UI, count:", count);
|
||||
const badge = document.getElementById("wishlistCount");
|
||||
if (badge) {
|
||||
badge.textContent = count;
|
||||
badge.style.display = count > 0 ? "flex" : "none";
|
||||
console.log('[AppState] Wishlist badge updated');
|
||||
if (count > 0) {
|
||||
badge.classList.add("show");
|
||||
} else {
|
||||
badge.classList.remove("show");
|
||||
}
|
||||
console.log("[AppState] Wishlist badge updated");
|
||||
} else {
|
||||
console.warn('[AppState] Wishlist badge element not found');
|
||||
console.warn("[AppState] Wishlist badge element not found");
|
||||
}
|
||||
// Also trigger wishlist dropdown update
|
||||
window.dispatchEvent(new CustomEvent('wishlist-updated', { detail: this.wishlist }));
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("wishlist-updated", { detail: this.wishlist })
|
||||
);
|
||||
},
|
||||
|
||||
// Notifications
|
||||
@@ -341,14 +371,17 @@
|
||||
};
|
||||
|
||||
// Initialize on DOM ready
|
||||
console.log('[main.js] Script loaded, document.readyState:', document.readyState);
|
||||
console.log(
|
||||
"[main.js] Script loaded, document.readyState:",
|
||||
document.readyState
|
||||
);
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
console.log('[main.js] DOMContentLoaded fired');
|
||||
console.log("[main.js] DOMContentLoaded fired");
|
||||
window.AppState.init();
|
||||
});
|
||||
} else {
|
||||
console.log('[main.js] DOM already loaded, initializing immediately');
|
||||
console.log("[main.js] DOM already loaded, initializing immediately");
|
||||
window.AppState.init();
|
||||
}
|
||||
|
||||
|
||||
@@ -359,7 +359,11 @@
|
||||
if (cartBadge) {
|
||||
const count = this.getCartCount();
|
||||
cartBadge.textContent = count;
|
||||
cartBadge.style.display = count > 0 ? "flex" : "none";
|
||||
if (count > 0) {
|
||||
cartBadge.classList.add("show");
|
||||
} else {
|
||||
cartBadge.classList.remove("show");
|
||||
}
|
||||
}
|
||||
|
||||
// Update wishlist badge
|
||||
@@ -367,7 +371,11 @@
|
||||
if (wishlistBadge) {
|
||||
const count = this.wishlist.length;
|
||||
wishlistBadge.textContent = count;
|
||||
wishlistBadge.style.display = count > 0 ? "flex" : "none";
|
||||
if (count > 0) {
|
||||
wishlistBadge.classList.add("show");
|
||||
} else {
|
||||
wishlistBadge.classList.remove("show");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -463,14 +471,14 @@
|
||||
const footer = document.querySelector("#cartPanel .dropdown-foot");
|
||||
if (!footer) return;
|
||||
|
||||
if (total === 0) {
|
||||
if (total === 0 || total === null) {
|
||||
footer.innerHTML =
|
||||
'<a href="/shop" class="btn-outline">Continue Shopping</a>';
|
||||
} else {
|
||||
footer.innerHTML = `
|
||||
<div class="cart-total">
|
||||
<span>Total:</span>
|
||||
<strong>$${total.toFixed(2)}</strong>
|
||||
<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!')">
|
||||
|
||||
@@ -226,21 +226,33 @@
|
||||
|
||||
// Update badges on state changes
|
||||
window.StateManager.on("cartUpdated", () => {
|
||||
const badge = document.querySelector(".cart-badge");
|
||||
if (badge) {
|
||||
const count = window.StateManager.getCartCount();
|
||||
badge.textContent = count;
|
||||
badge.style.display = count > 0 ? "flex" : "none";
|
||||
}
|
||||
const badges = document.querySelectorAll(".cart-badge, #cartCount");
|
||||
const count = window.StateManager.getCartCount();
|
||||
badges.forEach((badge) => {
|
||||
if (badge) {
|
||||
badge.textContent = count;
|
||||
if (count > 0) {
|
||||
badge.classList.add("show");
|
||||
} else {
|
||||
badge.classList.remove("show");
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
window.StateManager.on("wishlistUpdated", () => {
|
||||
const badge = document.querySelector(".wishlist-badge");
|
||||
if (badge) {
|
||||
const count = window.StateManager.getWishlist().length;
|
||||
badge.textContent = count;
|
||||
badge.style.display = count > 0 ? "flex" : "none";
|
||||
}
|
||||
const badges = document.querySelectorAll(".wishlist-badge, #wishlistCount");
|
||||
const count = window.StateManager.getWishlist().length;
|
||||
badges.forEach((badge) => {
|
||||
if (badge) {
|
||||
badge.textContent = count;
|
||||
if (count > 0) {
|
||||
badge.classList.add("show");
|
||||
} else {
|
||||
badge.classList.remove("show");
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Initialize badges
|
||||
|
||||
Reference in New Issue
Block a user