Files
SkyArtShop/website/public/assets/js/components.js

541 lines
17 KiB
JavaScript
Raw Normal View History

2026-01-18 02:22:05 -06:00
/**
* Shared HTML Components
* Eliminates duplication across HTML files
*/
// Component templates
const Components = {
/**
* Render cart drawer HTML
* @returns {string} Cart drawer HTML
*/
cartDrawer: () => `
<div id="cartDrawer" class="cart-drawer">
<div class="cart-drawer-header">
<h3>Shopping Cart</h3>
<button id="closeCart" class="close-cart" aria-label="Close cart">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
<div id="cartItems" class="cart-items"></div>
<div class="cart-footer">
<div class="cart-total">
<span>Total:</span>
<span id="cartTotal">$0.00</span>
</div>
<button id="checkoutBtn" class="btn btn-primary checkout-btn">
Proceed to Checkout
</button>
</div>
</div>
`,
/**
* Render wishlist drawer HTML
* @returns {string} Wishlist drawer HTML
*/
wishlistDrawer: () => `
<div id="wishlistDrawer" class="wishlist-drawer">
<div class="wishlist-drawer-header">
<h3>Wishlist</h3>
<button id="closeWishlist" class="close-wishlist" aria-label="Close wishlist">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
<div id="wishlistItems" class="wishlist-items"></div>
</div>
`,
/**
* Render navbar HTML
* @param {Object} options - Navbar options
* @param {string} options.activePage - Current active page
* @returns {string} Navbar HTML
*/
navbar: ({ activePage = "" } = {}) => {
const isActive = (page) => (page === activePage ? "active" : "");
return `
<nav class="navbar">
<div class="nav-container">
<a href="/" class="nav-logo">
<img src="/assets/images/logo.png" alt="SkyArtShop Logo" class="logo-img" />
<span>SkyArtShop</span>
</a>
<button class="mobile-menu-toggle" id="mobileMenuToggle" aria-label="Toggle menu">
<span></span>
<span></span>
<span></span>
</button>
<div class="nav-menu" id="navMenu">
<a href="/" class="nav-link ${isActive("home")}">Home</a>
<a href="/shop" class="nav-link ${isActive("shop")}">Shop</a>
<a href="/portfolio" class="nav-link ${isActive(
"portfolio"
)}">Portfolio</a>
<a href="/blog" class="nav-link ${isActive("blog")}">Blog</a>
<a href="/about" class="nav-link ${isActive("about")}">About</a>
<a href="/contact" class="nav-link ${isActive(
"contact"
)}">Contact</a>
<div class="nav-actions">
<button id="wishlistBtn" class="icon-btn" aria-label="Wishlist">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path>
</svg>
<span id="wishlistBadge" class="badge">0</span>
</button>
<button id="cartBtn" class="icon-btn" aria-label="Shopping cart">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="9" cy="21" r="1"></circle>
<circle cx="20" cy="21" r="1"></circle>
<path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"></path>
</svg>
<span id="cartBadge" class="badge">0</span>
</button>
</div>
</div>
</div>
</nav>
`;
},
/**
* Render footer HTML
* @returns {string} Footer HTML
*/
footer: () => `
<footer class="footer">
<div class="footer-container">
<div class="footer-section">
<h3>About SkyArtShop</h3>
<p>Your premier destination for unique art pieces and custom designs.</p>
</div>
<div class="footer-section">
<h3>Quick Links</h3>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/shop">Shop</a></li>
<li><a href="/portfolio">Portfolio</a></li>
<li><a href="/blog">Blog</a></li>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</div>
<div class="footer-section">
<h3>Customer Service</h3>
<ul>
<li><a href="/faq">FAQ</a></li>
<li><a href="/shipping-info">Shipping Info</a></li>
<li><a href="/returns">Returns</a></li>
<li><a href="/privacy">Privacy Policy</a></li>
</ul>
</div>
<div class="footer-section">
<h3>Connect With Us</h3>
<div class="social-links">
<a href="#" aria-label="Facebook">
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
<path d="M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z"></path>
</svg>
</a>
<a href="#" aria-label="Instagram">
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
<rect x="2" y="2" width="20" height="20" rx="5" ry="5"></rect>
<path d="M16 11.37A4 4 0 1 1 12.63 8 4 4 0 0 1 16 11.37z"></path>
<line x1="17.5" y1="6.5" x2="17.51" y2="6.5"></line>
</svg>
</a>
<a href="#" aria-label="Twitter">
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
<path d="M23 3a10.9 10.9 0 0 1-3.14 1.53 4.48 4.48 0 0 0-7.86 3v1A10.66 10.66 0 0 1 3 4s-4 9 5 13a11.64 11.64 0 0 1-7 2c9 5 20 0 20-11.5a4.5 4.5 0 0 0-.08-.83A7.72 7.72 0 0 0 23 3z"></path>
</svg>
</a>
</div>
</div>
</div>
<div class="footer-bottom">
<p>&copy; ${new Date().getFullYear()} SkyArtShop. All rights reserved.</p>
</div>
</footer>
`,
/**
* Render notification container
* @returns {string} Notification container HTML
*/
notificationContainer: () => `
<div id="notificationContainer" class="notification-container"></div>
`,
};
/**
* Initialize components in the DOM
* @param {Object} options - Component options
* @param {boolean} options.navbar - Include navbar
* @param {boolean} options.footer - Include footer
* @param {boolean} options.cart - Include cart drawer
* @param {boolean} options.wishlist - Include wishlist drawer
* @param {boolean} options.notifications - Include notifications
* @param {string} options.activePage - Active page for navbar
*/
function initializeComponents({
navbar = true,
footer = true,
cart = true,
wishlist = true,
notifications = true,
activePage = "",
} = {}) {
// Inject components into DOM
if (navbar) {
const navPlaceholder = document.getElementById("navbar-placeholder");
if (navPlaceholder) {
navPlaceholder.innerHTML = Components.navbar({ activePage });
}
}
if (footer) {
const footerPlaceholder = document.getElementById("footer-placeholder");
if (footerPlaceholder) {
footerPlaceholder.innerHTML = Components.footer();
}
}
if (cart) {
const cartPlaceholder = document.getElementById("cart-drawer-placeholder");
if (cartPlaceholder) {
cartPlaceholder.innerHTML = Components.cartDrawer();
}
}
if (wishlist) {
const wishlistPlaceholder = document.getElementById(
"wishlist-drawer-placeholder"
);
if (wishlistPlaceholder) {
wishlistPlaceholder.innerHTML = Components.wishlistDrawer();
}
}
if (notifications) {
const notificationPlaceholder = document.getElementById(
"notification-placeholder"
);
if (notificationPlaceholder) {
notificationPlaceholder.innerHTML = Components.notificationContainer();
}
}
// Initialize mobile menu toggle
initMobileMenu();
// Initialize cart and wishlist interactions
if (cart) initCartDrawer();
if (wishlist) initWishlistDrawer();
}
/**
* Initialize mobile menu functionality
*/
function initMobileMenu() {
const menuToggle = document.getElementById("mobileMenuToggle");
const navMenu = document.getElementById("navMenu");
if (menuToggle && navMenu) {
menuToggle.addEventListener("click", () => {
navMenu.classList.toggle("active");
menuToggle.classList.toggle("active");
});
// Close menu when clicking outside
document.addEventListener("click", (e) => {
if (
!menuToggle.contains(e.target) &&
!navMenu.contains(e.target) &&
navMenu.classList.contains("active")
) {
navMenu.classList.remove("active");
menuToggle.classList.remove("active");
}
});
}
}
/**
* Initialize cart drawer functionality
*/
function initCartDrawer() {
const cartBtn = document.getElementById("cartBtn");
const cartDrawer = document.getElementById("cartDrawer");
const closeCart = document.getElementById("closeCart");
if (cartBtn && cartDrawer) {
cartBtn.addEventListener("click", () => {
cartDrawer.classList.add("active");
document.body.style.overflow = "hidden";
updateCartUI();
});
if (closeCart) {
closeCart.addEventListener("click", () => {
cartDrawer.classList.remove("active");
document.body.style.overflow = "";
});
}
// Close on overlay click
cartDrawer.addEventListener("click", (e) => {
if (e.target === cartDrawer) {
cartDrawer.classList.remove("active");
document.body.style.overflow = "";
}
});
}
// Initialize checkout button
const checkoutBtn = document.getElementById("checkoutBtn");
if (checkoutBtn) {
checkoutBtn.addEventListener("click", () => {
window.location.href = "/checkout";
});
}
}
/**
* Initialize wishlist drawer functionality
*/
function initWishlistDrawer() {
const wishlistBtn = document.getElementById("wishlistBtn");
const wishlistDrawer = document.getElementById("wishlistDrawer");
const closeWishlist = document.getElementById("closeWishlist");
if (wishlistBtn && wishlistDrawer) {
wishlistBtn.addEventListener("click", () => {
wishlistDrawer.classList.add("active");
document.body.style.overflow = "hidden";
updateWishlistUI();
});
if (closeWishlist) {
closeWishlist.addEventListener("click", () => {
wishlistDrawer.classList.remove("active");
document.body.style.overflow = "";
});
}
// Close on overlay click
wishlistDrawer.addEventListener("click", (e) => {
if (e.target === wishlistDrawer) {
wishlistDrawer.classList.remove("active");
document.body.style.overflow = "";
}
});
}
}
/**
* Update cart UI with current items
*/
function updateCartUI() {
if (typeof CartUtils === "undefined") return;
const cart = CartUtils.getCart();
const cartItems = document.getElementById("cartItems");
const cartTotal = document.getElementById("cartTotal");
const cartBadge = document.getElementById("cartBadge");
if (cartBadge) {
cartBadge.textContent = cart.length;
cartBadge.style.display = cart.length > 0 ? "flex" : "none";
}
if (!cartItems) return;
if (cart.length === 0) {
cartItems.innerHTML = '<p class="empty-cart">Your cart is empty</p>';
if (cartTotal) cartTotal.textContent = "$0.00";
return;
}
cartItems.innerHTML = cart
.map(
(item) => `
<div class="cart-item" data-id="${item.id}">
<img src="${item.image}" alt="${item.name}" class="cart-item-image" />
<div class="cart-item-details">
<h4>${item.name}</h4>
<p class="cart-item-price">${formatPrice(item.price)}</p>
<div class="quantity-controls">
<button class="qty-btn" onclick="decreaseQuantity('${
item.id
}')">-</button>
<span class="quantity">${item.quantity}</span>
<button class="qty-btn" onclick="increaseQuantity('${
item.id
}')">+</button>
</div>
</div>
<button class="remove-item" onclick="removeFromCart('${
item.id
}')" aria-label="Remove item">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
`
)
.join("");
if (cartTotal) {
cartTotal.textContent = formatPrice(CartUtils.getCartTotal());
}
}
/**
* Update wishlist UI with current items
*/
function updateWishlistUI() {
if (typeof WishlistUtils === "undefined") return;
const wishlist = WishlistUtils.getWishlist();
const wishlistItems = document.getElementById("wishlistItems");
const wishlistBadge = document.getElementById("wishlistBadge");
if (wishlistBadge) {
wishlistBadge.textContent = wishlist.length;
wishlistBadge.style.display = wishlist.length > 0 ? "flex" : "none";
}
if (!wishlistItems) return;
if (wishlist.length === 0) {
wishlistItems.innerHTML =
'<p class="empty-wishlist">Your wishlist is empty</p>';
return;
}
wishlistItems.innerHTML = wishlist
.map(
(item) => `
<div class="wishlist-item" data-id="${item.id}">
<img src="${item.image}" alt="${
item.name
}" class="wishlist-item-image" />
<div class="wishlist-item-details">
<h4>${item.name}</h4>
<p class="wishlist-item-price">${formatPrice(item.price)}</p>
<button class="btn-sm btn-primary" onclick="moveToCart('${item.id}')">
Add to Cart
</button>
</div>
<button class="remove-item" onclick="removeFromWishlist('${
item.id
}')" aria-label="Remove from wishlist">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
`
)
.join("");
}
// Global functions for inline event handlers
window.increaseQuantity = (productId) => {
if (typeof CartUtils !== "undefined") {
const cart = CartUtils.getCart();
const item = cart.find((item) => item.id === productId);
if (item) {
CartUtils.updateQuantity(productId, item.quantity + 1);
updateCartUI();
}
}
};
window.decreaseQuantity = (productId) => {
if (typeof CartUtils !== "undefined") {
const cart = CartUtils.getCart();
const item = cart.find((item) => item.id === productId);
if (item && item.quantity > 1) {
CartUtils.updateQuantity(productId, item.quantity - 1);
updateCartUI();
}
}
};
window.removeFromCart = (productId) => {
if (typeof CartUtils !== "undefined") {
CartUtils.removeFromCart(productId);
updateCartUI();
showNotification("Item removed from cart", "info");
}
};
window.removeFromWishlist = (productId) => {
if (typeof WishlistUtils !== "undefined") {
WishlistUtils.removeFromWishlist(productId);
updateWishlistUI();
showNotification("Item removed from wishlist", "info");
}
};
window.moveToCart = (productId) => {
if (
typeof WishlistUtils !== "undefined" &&
typeof CartUtils !== "undefined"
) {
const wishlist = WishlistUtils.getWishlist();
const item = wishlist.find((item) => item.id === productId);
if (item) {
CartUtils.addToCart(item);
WishlistUtils.removeFromWishlist(productId);
updateWishlistUI();
updateCartUI();
showNotification("Item moved to cart", "success");
}
}
};
// Initialize on DOM load
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", () => {
// Auto-initialize if data attribute is set
const autoInit = document.body.dataset.autoInitComponents;
if (autoInit !== "false") {
initializeComponents({
activePage: document.body.dataset.activePage || "",
});
}
});
} else {
// Already loaded
const autoInit = document.body.dataset.autoInitComponents;
if (autoInit !== "false") {
initializeComponents({
activePage: document.body.dataset.activePage || "",
});
}
}
// Export for manual initialization
window.Components = Components;
window.initializeComponents = initializeComponents;