updateweb
This commit is contained in:
@@ -1,376 +1,306 @@
|
||||
/**
|
||||
* Enhanced Cart and Wishlist Management System
|
||||
* Amazon/eBay-style product display with images and details
|
||||
* Shopping/Products Component
|
||||
* Handles product display, filtering, and interactions
|
||||
*/
|
||||
|
||||
class ShoppingManager {
|
||||
constructor() {
|
||||
this.cart = this.loadFromStorage("skyart_cart") || [];
|
||||
this.wishlist = this.loadFromStorage("skyart_wishlist") || [];
|
||||
this.init();
|
||||
}
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
init() {
|
||||
this.updateAllBadges();
|
||||
this.setupEventListeners();
|
||||
this.renderCart();
|
||||
this.renderWishlist();
|
||||
}
|
||||
class ShoppingPage {
|
||||
constructor() {
|
||||
this.productsContainer = document.getElementById("productsContainer");
|
||||
this.loadingIndicator = document.getElementById("loadingIndicator");
|
||||
this.errorContainer = document.getElementById("errorContainer");
|
||||
this.currentCategory = window.Utils.getUrlParameter("category") || "all";
|
||||
this.currentSort = "newest";
|
||||
this.products = [];
|
||||
|
||||
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;
|
||||
this.init();
|
||||
}
|
||||
}
|
||||
|
||||
saveToStorage(key, data) {
|
||||
try {
|
||||
localStorage.setItem(key, JSON.stringify(data));
|
||||
} catch (e) {
|
||||
console.error("Error saving to storage:", e);
|
||||
async init() {
|
||||
this.setupEventListeners();
|
||||
await this.loadProducts();
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
setupEventListeners() {
|
||||
// Category filters
|
||||
document.querySelectorAll("[data-category]").forEach((btn) => {
|
||||
btn.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
this.currentCategory = e.currentTarget.dataset.category;
|
||||
this.filterProducts();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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");
|
||||
// Sort dropdown
|
||||
const sortSelect = document.getElementById("sortSelect");
|
||||
if (sortSelect) {
|
||||
sortSelect.addEventListener("change", (e) => {
|
||||
this.currentSort = e.target.value;
|
||||
this.filterProducts();
|
||||
});
|
||||
}
|
||||
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();
|
||||
// Search
|
||||
const searchInput = document.getElementById("productSearch");
|
||||
if (searchInput) {
|
||||
searchInput.addEventListener(
|
||||
"input",
|
||||
window.Utils.debounce((e) => {
|
||||
this.searchProducts(e.target.value);
|
||||
}, 300)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add to Wishlist
|
||||
addToWishlist(product) {
|
||||
const exists = this.wishlist.find((item) => item.id === product.id);
|
||||
async loadProducts() {
|
||||
if (!this.productsContainer) return;
|
||||
|
||||
if (!exists) {
|
||||
this.wishlist.push({
|
||||
id: product.id,
|
||||
name: product.name,
|
||||
price: parseFloat(product.price),
|
||||
imageurl: product.imageurl,
|
||||
addedAt: new Date().toISOString(),
|
||||
try {
|
||||
this.showLoading();
|
||||
const response = await window.API.getProducts();
|
||||
this.products = response.products || response.data || [];
|
||||
this.renderProducts(this.products);
|
||||
this.hideLoading();
|
||||
} catch (error) {
|
||||
console.error("Error loading products:", error);
|
||||
this.showError("Failed to load products. Please try again later.");
|
||||
this.hideLoading();
|
||||
}
|
||||
}
|
||||
|
||||
filterProducts() {
|
||||
let filtered = [...this.products];
|
||||
|
||||
// Filter by category
|
||||
if (this.currentCategory && this.currentCategory !== "all") {
|
||||
filtered = filtered.filter(
|
||||
(p) =>
|
||||
p.category?.toLowerCase() === this.currentCategory.toLowerCase()
|
||||
);
|
||||
}
|
||||
|
||||
// Sort products
|
||||
filtered = this.sortProducts(filtered);
|
||||
|
||||
this.renderProducts(filtered);
|
||||
}
|
||||
|
||||
sortProducts(products) {
|
||||
switch (this.currentSort) {
|
||||
case "price-low":
|
||||
return products.sort((a, b) => (a.price || 0) - (b.price || 0));
|
||||
case "price-high":
|
||||
return products.sort((a, b) => (b.price || 0) - (a.price || 0));
|
||||
case "name":
|
||||
return products.sort((a, b) =>
|
||||
(a.title || a.name || "").localeCompare(b.title || b.name || "")
|
||||
);
|
||||
case "newest":
|
||||
default:
|
||||
return products.sort(
|
||||
(a, b) => new Date(b.created_at || 0) - new Date(a.created_at || 0)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
searchProducts(query) {
|
||||
if (!query.trim()) {
|
||||
this.filterProducts();
|
||||
return;
|
||||
}
|
||||
|
||||
const searchTerm = query.toLowerCase();
|
||||
const filtered = this.products.filter((p) => {
|
||||
const title = (p.title || p.name || "").toLowerCase();
|
||||
const description = (p.description || "").toLowerCase();
|
||||
const category = (p.category || "").toLowerCase();
|
||||
|
||||
return (
|
||||
title.includes(searchTerm) ||
|
||||
description.includes(searchTerm) ||
|
||||
category.includes(searchTerm)
|
||||
);
|
||||
});
|
||||
|
||||
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";
|
||||
this.renderProducts(filtered);
|
||||
}
|
||||
|
||||
if (wishlistBadge) {
|
||||
wishlistBadge.textContent = wishlistCount;
|
||||
wishlistBadge.style.display = wishlistCount > 0 ? "flex" : "none";
|
||||
}
|
||||
}
|
||||
renderProducts(products) {
|
||||
if (!this.productsContainer) return;
|
||||
|
||||
// Render Cart
|
||||
renderCart() {
|
||||
const cartContent = document.getElementById("cartContent");
|
||||
const cartSubtotal = document.getElementById("cartSubtotal");
|
||||
if (products.length === 0) {
|
||||
this.productsContainer.innerHTML = `
|
||||
<div class="no-products">
|
||||
<i class="bi bi-inbox" style="font-size: 48px; opacity: 0.5;"></i>
|
||||
<p>No products found</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!cartContent) return;
|
||||
const html = products
|
||||
.map((product) => this.renderProductCard(product))
|
||||
.join("");
|
||||
this.productsContainer.innerHTML = html;
|
||||
|
||||
if (this.cart.length === 0) {
|
||||
cartContent.innerHTML = '<p class="empty-state">Your cart is empty</p>';
|
||||
if (cartSubtotal) cartSubtotal.textContent = "$0.00";
|
||||
return;
|
||||
// Setup product card listeners
|
||||
this.setupProductListeners();
|
||||
}
|
||||
|
||||
const subtotal = this.cart.reduce(
|
||||
(sum, item) => sum + item.price * item.quantity,
|
||||
0
|
||||
);
|
||||
renderProductCard(product) {
|
||||
const id = product.id;
|
||||
const title = window.Utils?.escapeHtml
|
||||
? window.Utils.escapeHtml(product.title || product.name || "Product")
|
||||
: product.title || product.name || "Product";
|
||||
const price = window.Utils?.formatCurrency
|
||||
? window.Utils.formatCurrency(product.price || 0)
|
||||
: `$${parseFloat(product.price || 0).toFixed(2)}`;
|
||||
|
||||
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>
|
||||
// Get image URL from multiple possible sources
|
||||
let imageUrl = "/assets/images/placeholder.jpg";
|
||||
if (
|
||||
product.images &&
|
||||
Array.isArray(product.images) &&
|
||||
product.images.length > 0
|
||||
) {
|
||||
const primaryImg = product.images.find((img) => img.is_primary);
|
||||
imageUrl = primaryImg
|
||||
? primaryImg.image_url
|
||||
: product.images[0].image_url;
|
||||
} else if (product.imageUrl) {
|
||||
imageUrl = product.imageUrl;
|
||||
} else if (product.image_url) {
|
||||
imageUrl = product.image_url;
|
||||
}
|
||||
|
||||
// Get description
|
||||
const description =
|
||||
product.shortdescription ||
|
||||
(product.description
|
||||
? product.description.substring(0, 100) + "..."
|
||||
: "");
|
||||
|
||||
const isInWishlist = window.AppState?.isInWishlist(id) || false;
|
||||
|
||||
return `
|
||||
<article class="product-card" data-id="${id}">
|
||||
<div class="product-image-wrapper">
|
||||
<img src="${imageUrl}" alt="${title}" class="product-image" loading="lazy" onerror="this.src='/assets/images/placeholder.svg'">
|
||||
<button
|
||||
class="wishlist-btn ${isInWishlist ? "active" : ""}"
|
||||
data-id="${id}"
|
||||
aria-label="${
|
||||
isInWishlist ? "Remove from wishlist" : "Add to wishlist"
|
||||
}"
|
||||
>
|
||||
<i class="bi bi-heart${isInWishlist ? "-fill" : ""}"></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("");
|
||||
<div class="product-info">
|
||||
<a href="/product?id=${id}" style="text-decoration: none; color: inherit;">
|
||||
<h3 class="product-title">${title}</h3>
|
||||
</a>
|
||||
${
|
||||
description
|
||||
? `<div class="product-description">${description}</div>`
|
||||
: ""
|
||||
}
|
||||
<p class="product-price">${price}</p>
|
||||
<div class="product-actions">
|
||||
<button class="btn-add-to-cart" data-id="${id}" style="flex: 1;">
|
||||
<i class="bi bi-cart-plus"></i> Add to Cart
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
`;
|
||||
}
|
||||
|
||||
if (cartSubtotal) {
|
||||
cartSubtotal.textContent = `$${subtotal.toFixed(2)}`;
|
||||
setupProductListeners() {
|
||||
// Add to cart buttons
|
||||
this.productsContainer
|
||||
.querySelectorAll(".btn-add-to-cart")
|
||||
.forEach((btn) => {
|
||||
btn.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
const id = parseInt(e.currentTarget.dataset.id);
|
||||
const product = this.products.find((p) => p.id === id);
|
||||
if (product) {
|
||||
window.AppState.addToCart(product);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Wishlist buttons
|
||||
this.productsContainer
|
||||
.querySelectorAll(".wishlist-btn")
|
||||
.forEach((btn) => {
|
||||
btn.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
const id = parseInt(e.currentTarget.dataset.id);
|
||||
const product = this.products.find((p) => p.id === id);
|
||||
if (product) {
|
||||
if (window.AppState.isInWishlist(id)) {
|
||||
window.AppState.removeFromWishlist(id);
|
||||
} else {
|
||||
window.AppState.addToWishlist(product);
|
||||
}
|
||||
this.renderProducts(this.products);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
showLoading() {
|
||||
if (this.loadingIndicator) {
|
||||
this.loadingIndicator.style.display = "flex";
|
||||
}
|
||||
if (this.productsContainer) {
|
||||
this.productsContainer.style.opacity = "0.5";
|
||||
}
|
||||
}
|
||||
|
||||
hideLoading() {
|
||||
if (this.loadingIndicator) {
|
||||
this.loadingIndicator.style.display = "none";
|
||||
}
|
||||
if (this.productsContainer) {
|
||||
this.productsContainer.style.opacity = "1";
|
||||
}
|
||||
}
|
||||
|
||||
showError(message) {
|
||||
if (this.errorContainer) {
|
||||
this.errorContainer.innerHTML = `
|
||||
<div class="error-message" role="alert">
|
||||
<i class="bi bi-exclamation-triangle"></i>
|
||||
<p>${window.Utils.escapeHtml(message)}</p>
|
||||
<button onclick="location.reload()">Retry</button>
|
||||
</div>
|
||||
`;
|
||||
this.errorContainer.style.display = "block";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
// Initialize on shop/products pages
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
if (
|
||||
window.location.pathname.includes("/shop") ||
|
||||
window.location.pathname.includes("/products")
|
||||
) {
|
||||
new ShoppingPage();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (
|
||||
window.location.pathname.includes("/shop") ||
|
||||
window.location.pathname.includes("/products")
|
||||
) {
|
||||
new ShoppingPage();
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user