Add website file management workflow and deployment script

This commit is contained in:
Local Server
2025-12-13 22:59:42 -06:00
parent f3c1157d7e
commit dce6460994
35 changed files with 8858 additions and 0 deletions

155
website/assets/js/admin.js Normal file
View File

@@ -0,0 +1,155 @@
// Sky Art Shop - Admin Panel Functions
// Delete confirmation with fetch
function deleteWithConfirmation(url, itemName, redirectUrl) {
if (!confirm(`Are you sure you want to delete ${itemName}?`)) {
return false;
}
fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
})
.then((response) => {
if (response.ok) {
window.location.href = redirectUrl || window.location.href;
} else {
alert("Delete failed. Please try again.");
}
})
.catch((error) => {
alert("Error: " + error.message);
});
return false;
}
// Image upload with preview
function uploadImageWithPreview(fileInputId, previewId, urlInputId) {
const input = document.getElementById(fileInputId);
const file = input.files[0];
if (!file) {
alert("Please select a file");
return;
}
const formData = new FormData();
formData.append("file", file);
fetch("/admin/upload/image", {
method: "POST",
body: formData,
})
.then((response) => response.json())
.then((result) => {
if (result.success) {
document.getElementById(urlInputId).value = result.url;
const preview = document.getElementById(previewId);
if (preview) {
preview.src = result.url;
preview.style.display = "block";
}
showAdminNotification("Image uploaded successfully!", "success");
} else {
showAdminNotification("Upload failed: " + result.message, "error");
}
})
.catch((error) => {
showAdminNotification("Upload error: " + error.message, "error");
});
}
// Show admin notification
function showAdminNotification(message, type = "success") {
const notification = document.createElement("div");
notification.className = `alert alert-${
type === "success" ? "success" : "danger"
} alert-dismissible fade show`;
notification.style.cssText =
"position: fixed; top: 20px; right: 20px; z-index: 9999; min-width: 300px;";
notification.innerHTML = `
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
document.body.appendChild(notification);
setTimeout(() => {
notification.classList.remove("show");
setTimeout(() => notification.remove(), 150);
}, 3000);
}
// Copy to clipboard
function copyToClipboard(text) {
navigator.clipboard
.writeText(text)
.then(() => {
showAdminNotification("Copied to clipboard!", "success");
})
.catch(() => {
showAdminNotification("Failed to copy", "error");
});
}
// Auto-generate slug from title
function generateSlugFromTitle(titleInputId, slugInputId) {
const titleInput = document.getElementById(titleInputId);
const slugInput = document.getElementById(slugInputId);
if (titleInput && slugInput) {
titleInput.addEventListener("input", function () {
if (slugInput.value === "" || slugInput.dataset.auto === "true") {
slugInput.value = titleInput.value
.toLowerCase()
.replace(/[^a-z0-9]+/g, "-")
.replace(/(^-|-$)/g, "");
slugInput.dataset.auto = "true";
}
});
slugInput.addEventListener("input", function () {
slugInput.dataset.auto = "false";
});
}
}
// Form validation helper
function validateForm(formId) {
const form = document.getElementById(formId);
if (!form) return true;
const requiredFields = form.querySelectorAll("[required]");
let isValid = true;
requiredFields.forEach((field) => {
if (!field.value.trim()) {
field.classList.add("is-invalid");
isValid = false;
} else {
field.classList.remove("is-invalid");
}
});
if (!isValid) {
showAdminNotification("Please fill in all required fields", "error");
}
return isValid;
}
// Initialize tooltips
document.addEventListener("DOMContentLoaded", function () {
// Bootstrap tooltips
var tooltipTriggerList = [].slice.call(
document.querySelectorAll('[data-bs-toggle="tooltip"]')
);
if (typeof bootstrap !== "undefined") {
tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl);
});
}
});

378
website/assets/js/cart.js Normal file
View File

@@ -0,0 +1,378 @@
// Sky Art Shop - Shopping Cart Functions
// Add item to cart
function addToCart(id, name, price, imageUrl = null) {
// Get existing cart from localStorage
let cart = JSON.parse(localStorage.getItem("cart") || "[]");
// Check if item already exists
const existingItem = cart.find((item) => item.id === id);
if (existingItem) {
existingItem.quantity++;
// Update imageUrl if it was null before
if (!existingItem.imageUrl && imageUrl) {
existingItem.imageUrl = imageUrl;
}
} else {
cart.push({ id, name, price, quantity: 1, imageUrl });
}
// Save cart
localStorage.setItem("cart", JSON.stringify(cart));
console.log("Cart updated:", cart);
// Show confirmation
showCartNotification(`${name} added to cart!`);
updateCartCount();
}
// Remove item from cart
function removeFromCart(id) {
let cart = JSON.parse(localStorage.getItem("cart") || "[]");
cart = cart.filter((item) => item.id !== id);
localStorage.setItem("cart", JSON.stringify(cart));
updateCartCount();
}
// Update cart item quantity
function updateCartQuantity(id, quantity) {
let cart = JSON.parse(localStorage.getItem("cart") || "[]");
const item = cart.find((item) => item.id === id);
if (item) {
item.quantity = quantity;
if (quantity <= 0) {
cart = cart.filter((item) => item.id !== id);
}
}
localStorage.setItem("cart", JSON.stringify(cart));
updateCartCount();
}
// Get cart items
function getCart() {
return JSON.parse(localStorage.getItem("cart") || "[]");
}
// Get cart total
function getCartTotal() {
const cart = getCart();
return cart.reduce((total, item) => total + item.price * item.quantity, 0);
}
// Update cart count badge
function updateCartCount() {
const cart = getCart();
const count = cart.reduce((total, item) => total + item.quantity, 0);
// Update old badge (if exists)
const badge = document.getElementById("cart-count");
if (badge) {
badge.textContent = count;
badge.style.display = count > 0 ? "inline" : "none";
}
// Update navbar cart badge
const navCartBadge = document.querySelector("#cartBtn .badge");
if (navCartBadge) {
navCartBadge.textContent = count;
navCartBadge.style.display = count > 0 ? "block" : "none";
}
}
// Show cart notification
function showCartNotification(message) {
const notification = document.createElement("div");
notification.className = "cart-notification";
notification.textContent = message;
notification.style.cssText = `
position: fixed;
top: 80px;
right: 20px;
background: #4CAF50;
color: white;
padding: 15px 25px;
border-radius: 5px;
box-shadow: 0 4px 6px rgba(0,0,0,0.2);
z-index: 10000;
animation: slideInFromTop 0.3s ease;
`;
document.body.appendChild(notification);
setTimeout(() => {
notification.style.animation = "slideOut 0.3s ease";
setTimeout(() => notification.remove(), 300);
}, 3000);
}
// Clear entire cart
function clearCart() {
localStorage.removeItem("cart");
updateCartCount();
}
// ====================================
// Wishlist Functions
// ====================================
// Add item to wishlist
function addToWishlist(id, name, price, imageUrl) {
let wishlist = JSON.parse(localStorage.getItem("wishlist") || "[]");
const existingItem = wishlist.find((item) => item.id === id);
if (existingItem) {
showWishlistNotification(`${name} is already in your wishlist!`);
return;
}
wishlist.push({ id, name, price, imageUrl });
localStorage.setItem("wishlist", JSON.stringify(wishlist));
console.log("Wishlist updated:", wishlist);
showWishlistNotification(`${name} added to wishlist!`);
updateWishlistCount();
}
// Remove item from wishlist
function removeFromWishlist(id) {
let wishlist = JSON.parse(localStorage.getItem("wishlist") || "[]");
wishlist = wishlist.filter((item) => item.id !== id);
localStorage.setItem("wishlist", JSON.stringify(wishlist));
updateWishlistCount();
}
// Get wishlist items
function getWishlist() {
return JSON.parse(localStorage.getItem("wishlist") || "[]");
}
// Update wishlist count badge
function updateWishlistCount() {
const wishlist = getWishlist();
const count = wishlist.length;
const navWishlistBadge = document.querySelector("#wishlistBtn .badge");
const wishlistIcon = document.querySelector("#wishlistBtn i");
if (navWishlistBadge) {
navWishlistBadge.textContent = count;
navWishlistBadge.style.display = count > 0 ? "block" : "none";
}
// Change heart icon based on wishlist status
if (wishlistIcon) {
if (count > 0) {
wishlistIcon.className = "bi bi-heart-fill";
wishlistIcon.style.color = "#e74c3c";
} else {
wishlistIcon.className = "bi bi-heart";
wishlistIcon.style.color = "";
}
}
}
// Show wishlist notification
function showWishlistNotification(message) {
const notification = document.createElement("div");
notification.className = "wishlist-notification";
notification.textContent = message;
notification.style.cssText = `
position: fixed;
top: 80px;
right: 20px;
background: #E91E63;
color: white;
padding: 15px 25px;
border-radius: 5px;
box-shadow: 0 4px 6px rgba(0,0,0,0.2);
z-index: 10000;
animation: slideInFromTop 0.3s ease;
`;
document.body.appendChild(notification);
setTimeout(() => {
notification.style.animation = "slideOut 0.3s ease";
setTimeout(() => notification.remove(), 300);
}, 3000);
}
// Clear entire wishlist
function clearWishlist() {
localStorage.removeItem("wishlist");
updateWishlistCount();
}
// ====================================
// Dropdown Functions
// ====================================
// Render cart dropdown
function renderCartDropdown() {
const cart = getCart();
const cartItems = document.getElementById("cartItems");
const cartTotal = document.getElementById("cartTotal");
if (!cartItems) return;
if (cart.length === 0) {
cartItems.innerHTML = '<p class="empty-message">Your cart is empty</p>';
if (cartTotal) cartTotal.textContent = "$0.00";
return;
}
console.log("Rendering cart:", cart);
cartItems.innerHTML = cart
.map((item) => {
const imgSrc = item.imageUrl || "/assets/images/placeholder.jpg";
console.log("Cart item image URL:", imgSrc);
return `
<div class="dropdown-item">
<img src="${imgSrc}" alt="${
item.name
}" class="dropdown-item-image" onerror="this.src='/assets/images/placeholder.jpg'">
<div class="dropdown-item-info">
<div class="dropdown-item-name">${item.name}</div>
<div class="dropdown-item-details">
<span class="dropdown-item-quantity">Qty: ${
item.quantity
}</span>
<span class="dropdown-item-price">$${(
item.price * item.quantity
).toFixed(2)}</span>
</div>
</div>
<button class="dropdown-item-remove" onclick="removeFromCartDropdown('${
item.id
}')">
<i class="bi bi-x"></i>
</button>
</div>
`;
})
.join("");
if (cartTotal) {
const total = getCartTotal();
cartTotal.textContent = `$${total.toFixed(2)}`;
}
}
// Render wishlist dropdown
function renderWishlistDropdown() {
const wishlist = getWishlist();
const wishlistItems = document.getElementById("wishlistItems");
if (!wishlistItems) return;
if (wishlist.length === 0) {
wishlistItems.innerHTML =
'<p class="empty-message">Your wishlist is empty</p>';
return;
}
console.log("Rendering wishlist:", wishlist);
wishlistItems.innerHTML = wishlist
.map((item) => {
const imgSrc = item.imageUrl || "/assets/images/placeholder.jpg";
console.log("Wishlist item image URL:", imgSrc);
return `
<div class="dropdown-item">
<img src="${imgSrc}" alt="${
item.name
}" class="dropdown-item-image" onerror="this.src='/assets/images/placeholder.jpg'">
<div class="dropdown-item-info">
<div class="dropdown-item-name">${item.name}</div>
<div class="dropdown-item-details">
<span class="dropdown-item-price">$${item.price.toFixed(
2
)}</span>
</div>
</div>
<button class="dropdown-item-remove" onclick="removeFromWishlistDropdown('${
item.id
}')">
<i class="bi bi-x"></i>
</button>
</div>
`;
})
.join("");
}
// Remove from cart via dropdown
function removeFromCartDropdown(id) {
removeFromCart(id);
renderCartDropdown();
updateCartCount();
}
// Remove from wishlist via dropdown
function removeFromWishlistDropdown(id) {
removeFromWishlist(id);
renderWishlistDropdown();
updateWishlistCount();
}
// Toggle dropdown visibility
function toggleDropdown(dropdownId) {
const dropdown = document.getElementById(dropdownId);
if (!dropdown) return;
// Close other dropdowns
document.querySelectorAll(".icon-dropdown").forEach((d) => {
if (d.id !== dropdownId) {
d.classList.remove("show");
}
});
dropdown.classList.toggle("show");
// Render content when opening
if (dropdown.classList.contains("show")) {
if (dropdownId === "cartDropdown") {
renderCartDropdown();
} else if (dropdownId === "wishlistDropdown") {
renderWishlistDropdown();
}
}
}
// Close cart/wishlist dropdowns when clicking outside
document.addEventListener("click", function (e) {
if (
!e.target.closest(".dropdown-container") &&
!e.target.closest(".nav-toggle")
) {
document.querySelectorAll(".icon-dropdown").forEach((d) => {
d.classList.remove("show");
});
}
});
// Initialize cart and wishlist count on page load
document.addEventListener("DOMContentLoaded", function () {
updateCartCount();
updateWishlistCount();
// Add click handlers for dropdown toggles
const cartBtn = document.getElementById("cartBtn");
const wishlistBtn = document.getElementById("wishlistBtn");
if (cartBtn) {
cartBtn.addEventListener("click", function (e) {
e.preventDefault();
toggleDropdown("cartDropdown");
});
}
if (wishlistBtn) {
wishlistBtn.addEventListener("click", function (e) {
e.preventDefault();
toggleDropdown("wishlistDropdown");
});
}
});

427
website/assets/js/main.js Normal file
View File

@@ -0,0 +1,427 @@
// Sky Art Shop - Main JavaScript File
// ====================================
// Mobile Navigation Toggle
// ====================================
document.addEventListener("DOMContentLoaded", function () {
const navToggle = document.querySelector(".nav-toggle");
const navMenu = document.querySelector("#navDropdown");
if (navToggle && navMenu) {
// Hover to open dropdown
navToggle.addEventListener("mouseenter", function () {
navMenu.classList.add("active");
this.setAttribute("aria-expanded", "true");
const spans = this.querySelectorAll("span");
spans[0].style.transform = "rotate(45deg) translate(7px, 7px)";
spans[1].style.opacity = "0";
spans[2].style.transform = "rotate(-45deg) translate(7px, -7px)";
});
// Keep dropdown open when hovering over it
navMenu.addEventListener("mouseenter", function () {
this.classList.add("active");
});
// Close when mouse leaves both hamburger and dropdown
navToggle.addEventListener("mouseleave", function (e) {
// Delay closing to allow moving to dropdown
setTimeout(() => {
if (!navMenu.matches(":hover") && !navToggle.matches(":hover")) {
navMenu.classList.remove("active");
navToggle.setAttribute("aria-expanded", "false");
const spans = navToggle.querySelectorAll("span");
spans[0].style.transform = "none";
spans[1].style.opacity = "1";
spans[2].style.transform = "none";
}
}, 200);
});
navMenu.addEventListener("mouseleave", function () {
setTimeout(() => {
if (!navMenu.matches(":hover") && !navToggle.matches(":hover")) {
navMenu.classList.remove("active");
navToggle.setAttribute("aria-expanded", "false");
const spans = navToggle.querySelectorAll("span");
spans[0].style.transform = "none";
spans[1].style.opacity = "1";
spans[2].style.transform = "none";
}
}, 200);
});
// Click to toggle (for mobile/touch)
navToggle.addEventListener("click", function (e) {
e.stopPropagation();
const isActive = navMenu.classList.toggle("active");
this.setAttribute("aria-expanded", isActive ? "true" : "false");
// Animate hamburger menu
const spans = this.querySelectorAll("span");
if (isActive) {
spans[0].style.transform = "rotate(45deg) translate(7px, 7px)";
spans[1].style.opacity = "0";
spans[2].style.transform = "rotate(-45deg) translate(7px, -7px)";
} else {
spans[0].style.transform = "none";
spans[1].style.opacity = "1";
spans[2].style.transform = "none";
}
});
// Close dropdown when clicking on a link
const dropdownLinks = navMenu.querySelectorAll("a");
dropdownLinks.forEach((link) => {
link.addEventListener("click", function () {
navMenu.classList.remove("active");
navToggle.setAttribute("aria-expanded", "false");
const spans = navToggle.querySelectorAll("span");
spans[0].style.transform = "none";
spans[1].style.opacity = "1";
spans[2].style.transform = "none";
});
});
// Close dropdown when clicking outside
document.addEventListener("click", function (event) {
// Don't close if clicking on cart/wishlist dropdowns
if (
event.target.closest(".dropdown-container") ||
event.target.closest(".icon-dropdown")
) {
return;
}
const isClickInside =
navToggle.contains(event.target) || navMenu.contains(event.target);
if (!isClickInside && navMenu.classList.contains("active")) {
navMenu.classList.remove("active");
navToggle.setAttribute("aria-expanded", "false");
const spans = navToggle.querySelectorAll("span");
spans[0].style.transform = "none";
spans[1].style.opacity = "1";
spans[2].style.transform = "none";
}
});
}
});
// ====================================
// Smooth Scrolling for Anchor Links
// ====================================
document.querySelectorAll('a[href^="#"]').forEach((anchor) => {
anchor.addEventListener("click", function (e) {
const href = this.getAttribute("href");
if (href !== "#" && href !== "#instagram" && href !== "#wishlist") {
e.preventDefault();
const target = document.querySelector(href);
if (target) {
target.scrollIntoView({
behavior: "smooth",
block: "start",
});
}
}
});
});
// ====================================
// Shop Page Filtering
// ====================================
const categoryFilter = document.getElementById("category-filter");
const sortFilter = document.getElementById("sort-filter");
if (categoryFilter) {
categoryFilter.addEventListener("change", function () {
const selectedCategory = this.value;
const productCards = document.querySelectorAll(".product-card");
productCards.forEach((card) => {
if (selectedCategory === "all") {
card.style.display = "block";
} else {
const cardCategory = card.getAttribute("data-category");
if (cardCategory === selectedCategory) {
card.style.display = "block";
} else {
card.style.display = "none";
}
}
});
});
}
if (sortFilter) {
sortFilter.addEventListener("change", function () {
const sortValue = this.value;
const productsGrid = document.querySelector(".products-grid");
const productCards = Array.from(document.querySelectorAll(".product-card"));
if (sortValue === "price-low") {
productCards.sort((a, b) => {
const priceA = parseFloat(
a.querySelector(".price").textContent.replace("$", "")
);
const priceB = parseFloat(
b.querySelector(".price").textContent.replace("$", "")
);
return priceA - priceB;
});
} else if (sortValue === "price-high") {
productCards.sort((a, b) => {
const priceA = parseFloat(
a.querySelector(".price").textContent.replace("$", "")
);
const priceB = parseFloat(
b.querySelector(".price").textContent.replace("$", "")
);
return priceB - priceA;
});
}
// Re-append sorted cards
productCards.forEach((card) => {
productsGrid.appendChild(card);
});
});
}
// ====================================
// Add to Cart Functionality (Basic)
// ====================================
document.querySelectorAll(".product-card .btn").forEach((button) => {
button.addEventListener("click", function (e) {
e.preventDefault();
// Get product details
const productCard = this.closest(".product-card");
const productName = productCard.querySelector("h3").textContent;
const productPrice = productCard.querySelector(".price").textContent;
// Show notification
showNotification(`${productName} added to cart!`);
// You can expand this to actually store cart items
// For example, using localStorage or sending to a server
});
});
// ====================================
// Contact Form Handling
// ====================================
const contactForm = document.getElementById("contactForm");
if (contactForm) {
contactForm.addEventListener("submit", function (e) {
e.preventDefault();
// Get form values
const name = document.getElementById("name").value;
const email = document.getElementById("email").value;
const phone = document.getElementById("phone").value;
const subject = document.getElementById("subject").value;
const message = document.getElementById("message").value;
// Basic validation
if (!name || !email || !subject || !message) {
showNotification("Please fill in all required fields!", "error");
return;
}
// Email validation
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
showNotification("Please enter a valid email address!", "error");
return;
}
// Here you would typically send the form data to a server
// For now, we'll just show a success message
showNotification(
"Thank you! Your message has been sent. We'll get back to you soon.",
"success"
);
// Reset form
contactForm.reset();
});
}
// ====================================
// Notification System
// ====================================
function showNotification(message, type = "success") {
// Create notification element
const notification = document.createElement("div");
notification.className = `notification notification-${type}`;
notification.textContent = message;
// Style the notification
notification.style.cssText = `
position: fixed;
top: 100px;
right: 20px;
background-color: ${type === "success" ? "#4CAF50" : "#F44336"};
color: white;
padding: 15px 25px;
border-radius: 5px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
z-index: 10000;
animation: slideIn 0.3s ease-out;
`;
// Add animation
const style = document.createElement("style");
style.textContent = `
@keyframes slideIn {
from {
transform: translateX(400px);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes slideOut {
from {
transform: translateX(0);
opacity: 1;
}
to {
transform: translateX(400px);
opacity: 0;
}
}
`;
document.head.appendChild(style);
// Add to page
document.body.appendChild(notification);
// Remove after 3 seconds
setTimeout(() => {
notification.style.animation = "slideOut 0.3s ease-out";
setTimeout(() => {
notification.remove();
}, 300);
}, 3000);
}
// ====================================
// Image Lazy Loading (Optional Enhancement)
// ====================================
if ("IntersectionObserver" in window) {
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src || img.src;
img.classList.add("loaded");
observer.unobserve(img);
}
});
});
document.querySelectorAll("img").forEach((img) => {
imageObserver.observe(img);
});
}
// ====================================
// Scroll to Top Button
// ====================================
function createScrollToTopButton() {
const button = document.createElement("button");
button.innerHTML = "↑";
button.className = "scroll-to-top";
button.style.cssText = `
position: fixed;
bottom: 30px;
right: 30px;
width: 50px;
height: 50px;
background-color: #6B4E9B;
color: white;
border: none;
border-radius: 50%;
font-size: 24px;
cursor: pointer;
display: none;
z-index: 1000;
transition: all 0.3s ease;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
`;
document.body.appendChild(button);
// Show/hide button based on scroll position
window.addEventListener("scroll", () => {
if (window.pageYOffset > 300) {
button.style.display = "block";
} else {
button.style.display = "none";
}
});
// Scroll to top when clicked
button.addEventListener("click", () => {
window.scrollTo({
top: 0,
behavior: "smooth",
});
});
// Hover effect
button.addEventListener("mouseenter", () => {
button.style.backgroundColor = "#5a3e82";
button.style.transform = "translateY(-3px)";
});
button.addEventListener("mouseleave", () => {
button.style.backgroundColor = "#6B4E9B";
button.style.transform = "translateY(0)";
});
}
// Initialize scroll to top button
createScrollToTopButton();
// ====================================
// Portfolio Gallery Hover Effects
// ====================================
document.querySelectorAll(".portfolio-category").forEach((category) => {
category.addEventListener("mouseenter", function () {
this.style.transition = "all 0.3s ease";
});
});
// ====================================
// Active Navigation Link Highlighting
// ====================================
function highlightActiveNavLink() {
const currentPage = window.location.pathname.split("/").pop() || "index.html";
const navLinks = document.querySelectorAll(".nav-menu a");
navLinks.forEach((link) => {
const linkPage = link.getAttribute("href").split("/").pop().split("#")[0];
if (linkPage === currentPage) {
link.classList.add("active");
}
});
}
highlightActiveNavLink();
// ====================================
// Print console message
// ====================================
console.log(
"%c Sky Art Shop Website ",
"background: #6B4E9B; color: white; font-size: 20px; padding: 10px;"
);
console.log("Welcome to Sky Art Shop! 🎨");

View File

@@ -0,0 +1,376 @@
/**
* Enhanced Cart and Wishlist Management System
* Amazon/eBay-style product display with images and details
*/
class ShoppingManager {
constructor() {
this.cart = this.loadFromStorage("skyart_cart") || [];
this.wishlist = this.loadFromStorage("skyart_wishlist") || [];
this.init();
}
init() {
this.updateAllBadges();
this.setupEventListeners();
this.renderCart();
this.renderWishlist();
}
loadFromStorage(key) {
try {
const data = localStorage.getItem(key);
return data ? JSON.parse(data) : null;
} catch (e) {
console.error("Error loading from storage:", e);
return null;
}
}
saveToStorage(key, data) {
try {
localStorage.setItem(key, JSON.stringify(data));
} catch (e) {
console.error("Error saving to storage:", e);
}
}
setupEventListeners() {
// Cart toggle
const cartToggle = document.getElementById("cartToggle");
const cartPanel = document.getElementById("cartPanel");
const cartClose = document.getElementById("cartClose");
if (cartToggle) {
cartToggle.addEventListener("click", (e) => {
e.stopPropagation();
cartPanel?.classList.toggle("active");
document.getElementById("wishlistPanel")?.classList.remove("active");
});
}
if (cartClose) {
cartClose.addEventListener("click", (e) => {
e.stopPropagation();
cartPanel?.classList.remove("active");
});
}
// Wishlist toggle
const wishlistToggle = document.getElementById("wishlistToggle");
const wishlistPanel = document.getElementById("wishlistPanel");
const wishlistClose = document.getElementById("wishlistClose");
if (wishlistToggle) {
wishlistToggle.addEventListener("click", (e) => {
e.stopPropagation();
wishlistPanel?.classList.toggle("active");
cartPanel?.classList.remove("active");
});
}
if (wishlistClose) {
wishlistClose.addEventListener("click", (e) => {
e.stopPropagation();
wishlistPanel?.classList.remove("active");
});
}
// Mobile menu
const mobileToggle = document.getElementById("mobileMenuToggle");
const mobileMenu = document.getElementById("mobileMenu");
const mobileClose = document.getElementById("mobileMenuClose");
if (mobileToggle) {
mobileToggle.addEventListener("click", () => {
mobileMenu?.classList.toggle("active");
document.body.style.overflow = mobileMenu?.classList.contains("active")
? "hidden"
: "";
});
}
if (mobileClose) {
mobileClose.addEventListener("click", () => {
mobileMenu?.classList.remove("active");
document.body.style.overflow = "";
});
}
// Close dropdowns on outside click
document.addEventListener("click", (e) => {
if (!e.target.closest(".cart-dropdown-wrapper")) {
cartPanel?.classList.remove("active");
}
if (!e.target.closest(".wishlist-dropdown-wrapper")) {
wishlistPanel?.classList.remove("active");
}
});
}
// Add to Cart
addToCart(product, quantity = 1) {
const existingItem = this.cart.find((item) => item.id === product.id);
if (existingItem) {
existingItem.quantity += quantity;
} else {
this.cart.push({
id: product.id,
name: product.name,
price: parseFloat(product.price),
imageurl: product.imageurl,
quantity: quantity,
addedAt: new Date().toISOString(),
});
}
this.saveToStorage("skyart_cart", this.cart);
this.updateAllBadges();
this.renderCart();
this.showNotification(`${product.name} added to cart!`, "success");
}
// Remove from Cart
removeFromCart(productId) {
this.cart = this.cart.filter((item) => item.id !== productId);
this.saveToStorage("skyart_cart", this.cart);
this.updateAllBadges();
this.renderCart();
this.showNotification("Item removed from cart", "info");
}
// Update Cart Quantity
updateCartQuantity(productId, quantity) {
const item = this.cart.find((item) => item.id === productId);
if (item) {
if (quantity <= 0) {
this.removeFromCart(productId);
} else {
item.quantity = quantity;
this.saveToStorage("skyart_cart", this.cart);
this.updateAllBadges();
this.renderCart();
}
}
}
// Add to Wishlist
addToWishlist(product) {
const exists = this.wishlist.find((item) => item.id === product.id);
if (!exists) {
this.wishlist.push({
id: product.id,
name: product.name,
price: parseFloat(product.price),
imageurl: product.imageurl,
addedAt: new Date().toISOString(),
});
this.saveToStorage("skyart_wishlist", this.wishlist);
this.updateAllBadges();
this.renderWishlist();
this.showNotification(`${product.name} added to wishlist!`, "success");
} else {
this.showNotification("Already in wishlist", "info");
}
}
// Remove from Wishlist
removeFromWishlist(productId) {
this.wishlist = this.wishlist.filter((item) => item.id !== productId);
this.saveToStorage("skyart_wishlist", this.wishlist);
this.updateAllBadges();
this.renderWishlist();
this.showNotification("Item removed from wishlist", "info");
}
// Move from Wishlist to Cart
moveToCart(productId) {
const item = this.wishlist.find((item) => item.id === productId);
if (item) {
this.addToCart(item, 1);
this.removeFromWishlist(productId);
}
}
// Update All Badges
updateAllBadges() {
const cartCount = this.cart.reduce((sum, item) => sum + item.quantity, 0);
const wishlistCount = this.wishlist.length;
const cartBadge = document.getElementById("cartCount");
const wishlistBadge = document.getElementById("wishlistCount");
if (cartBadge) {
cartBadge.textContent = cartCount;
cartBadge.style.display = cartCount > 0 ? "flex" : "none";
}
if (wishlistBadge) {
wishlistBadge.textContent = wishlistCount;
wishlistBadge.style.display = wishlistCount > 0 ? "flex" : "none";
}
}
// Render Cart
renderCart() {
const cartContent = document.getElementById("cartContent");
const cartSubtotal = document.getElementById("cartSubtotal");
if (!cartContent) return;
if (this.cart.length === 0) {
cartContent.innerHTML = '<p class="empty-state">Your cart is empty</p>';
if (cartSubtotal) cartSubtotal.textContent = "$0.00";
return;
}
const subtotal = this.cart.reduce(
(sum, item) => sum + item.price * item.quantity,
0
);
cartContent.innerHTML = this.cart
.map(
(item) => `
<div class="cart-item" data-product-id="${item.id}">
<div class="cart-item-image">
<img src="${item.imageurl || "/assets/images/placeholder.jpg"}"
alt="${item.name}"
onerror="this.src='/assets/images/placeholder.jpg'" />
</div>
<div class="cart-item-details">
<h4 class="cart-item-name">${item.name}</h4>
<p class="cart-item-price">$${item.price.toFixed(2)}</p>
<div class="cart-item-quantity">
<button class="qty-btn" onclick="shoppingManager.updateCartQuantity('${
item.id
}', ${item.quantity - 1})">
<i class="bi bi-dash"></i>
</button>
<span class="qty-value">${item.quantity}</span>
<button class="qty-btn" onclick="shoppingManager.updateCartQuantity('${
item.id
}', ${item.quantity + 1})">
<i class="bi bi-plus"></i>
</button>
</div>
</div>
<div class="cart-item-actions">
<button class="cart-item-remove" onclick="shoppingManager.removeFromCart('${
item.id
}')" title="Remove">
<i class="bi bi-trash"></i>
</button>
<p class="cart-item-total">$${(item.price * item.quantity).toFixed(
2
)}</p>
</div>
</div>
`
)
.join("");
if (cartSubtotal) {
cartSubtotal.textContent = `$${subtotal.toFixed(2)}`;
}
}
// Render Wishlist
renderWishlist() {
const wishlistContent = document.getElementById("wishlistContent");
if (!wishlistContent) return;
if (this.wishlist.length === 0) {
wishlistContent.innerHTML =
'<p class="empty-state">Your wishlist is empty</p>';
return;
}
wishlistContent.innerHTML = this.wishlist
.map(
(item) => `
<div class="wishlist-item" data-product-id="${item.id}">
<div class="wishlist-item-image">
<img src="${item.imageurl || "/assets/images/placeholder.jpg"}"
alt="${item.name}"
onerror="this.src='/assets/images/placeholder.jpg'" />
</div>
<div class="wishlist-item-details">
<h4 class="wishlist-item-name">${item.name}</h4>
<p class="wishlist-item-price">$${item.price.toFixed(2)}</p>
<button class="btn-move-to-cart" onclick="shoppingManager.moveToCart('${
item.id
}')">
<i class="bi bi-cart-plus"></i> Add to Cart
</button>
</div>
<button class="wishlist-item-remove" onclick="shoppingManager.removeFromWishlist('${
item.id
}')" title="Remove">
<i class="bi bi-x-lg"></i>
</button>
</div>
`
)
.join("");
}
// Show Notification
showNotification(message, type = "info") {
const notification = document.createElement("div");
notification.className = `notification notification-${type}`;
notification.innerHTML = `
<i class="bi bi-${
type === "success" ? "check-circle" : "info-circle"
}"></i>
<span>${message}</span>
`;
document.body.appendChild(notification);
setTimeout(() => notification.classList.add("show"), 10);
setTimeout(() => {
notification.classList.remove("show");
setTimeout(() => notification.remove(), 300);
}, 3000);
}
// Get Cart Total
getCartTotal() {
return this.cart.reduce((sum, item) => sum + item.price * item.quantity, 0);
}
// Get Cart Count
getCartCount() {
return this.cart.reduce((sum, item) => sum + item.quantity, 0);
}
// Clear Cart
clearCart() {
this.cart = [];
this.saveToStorage("skyart_cart", this.cart);
this.updateAllBadges();
this.renderCart();
}
}
// Initialize Shopping Manager
const shoppingManager = new ShoppingManager();
// Make it globally available
window.shoppingManager = shoppingManager;
// Navigation active state
document.addEventListener("DOMContentLoaded", () => {
const currentPage = window.location.pathname.split("/").pop() || "home.html";
document.querySelectorAll(".nav-link, .mobile-link").forEach((link) => {
const linkPage = link.getAttribute("href")?.split("/").pop();
if (linkPage === currentPage) {
link.classList.add("active");
}
});
});