/** * اسکریپت اصلی تم مطیب * * @package Motayeb * @version 1.0.1 * * تغییرات نسخه ۱.۰.۱: * - persist شدن سبد خرید و علاقهمندیها در localStorage * - persist شدن دارک مود در کوکی (هماهنگ با PHP body_class) * - جلوگیری از XSS با escapeHtml روی همه ورودیهای داینامیک * - guard کردن motayeb_ajax * - بهبود مدیریت خطای fetch * - رفع toggleWishlist و setRating * - استفاده از کلاسهای Tailwind در renderStars */ // ============================================ // Guard متغیرهای سراسری // ============================================ const MOTAYEB_AJAX = (typeof motayeb_ajax !== 'undefined') ? motayeb_ajax : { ajax_url: '/wp-admin/admin-ajax.php', nonce: '', home_url: '/', checkout_url: '/checkout/', cart_url: '/cart/' }; // ============================================ // مدیریت State با localStorage // ============================================ let cart = loadFromStorage('motayeb_cart', []); let wishlist = loadFromStorage('motayeb_wishlist', []); let selectedWeightPrice = 295000; function loadFromStorage(key, fallback) { try { const saved = localStorage.getItem(key); return saved ? JSON.parse(saved) : fallback; } catch (e) { return fallback; } } function saveToStorage(key, value) { try { localStorage.setItem(key, JSON.stringify(value)); } catch (e) { // localStorage ممکنه private mode باشه — نادیده میگیریم } } // ============================================ // ابزارهای کمکی // ============================================ /** * Escape کردن HTML برای جلوگیری از XSS */ function escapeHtml(str) { if (str === null || str === undefined) return ''; return String(str) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } /** * فرمت قیمت — اعداد فارسی + نمایش رایگان برای قیمت صفر */ function formatPrice(n) { n = Number(n) || 0; if (n === 0) return 'رایگان'; return n.toLocaleString('fa-IR'); } /** * درخواست Ajax با مدیریت خطای کامل */ async function motayebFetch(action, params = {}) { const url = new URL(MOTAYEB_AJAX.ajax_url, window.location.origin); url.searchParams.set('action', action); url.searchParams.set('nonce', MOTAYEB_AJAX.nonce); Object.keys(params).forEach(k => url.searchParams.set(k, params[k])); const response = await fetch(url.toString()); if (!response.ok) { throw new Error('HTTP ' + response.status); } const data = await response.json(); return data; } // ============================================ // توابع سبد خرید // ============================================ async function quickAddToCart(id) { try { const data = await motayebFetch('get_product_data', { id: id }); if (data.success) { const p = data.data; const existing = cart.find(x => x.id === p.id); if (existing) { existing.qty++; } else { cart.push({ ...p, qty: 1 }); } saveToStorage('motayeb_cart', cart); updateCartUI(); showToast(escapeHtml(p.name) + ' به سبد خرید اضافه شد', 'success'); } else { showToast('محصول یافت نشد', 'error'); } } catch (err) { console.error('quickAddToCart:', err); showToast('خطا در ارتباط با سرور', 'error'); } } async function addToCartDetail(productId) { const qtyEl = document.getElementById('product-qty'); const qty = parseInt(qtyEl ? qtyEl.textContent : '1') || 1; try { const data = await motayebFetch('get_product_data', { id: productId }); if (data.success) { const p = data.data; const existing = cart.find(x => x.id === p.id); if (existing) { existing.qty += qty; } else { cart.push({ ...p, qty: qty }); } saveToStorage('motayeb_cart', cart); updateCartUI(); showToast(escapeHtml(p.name) + ' به سبد خرید اضافه شد', 'success'); } else { showToast('محصول یافت نشد', 'error'); } } catch (err) { console.error('addToCartDetail:', err); showToast('خطا در ارتباط با سرور', 'error'); } } function removeFromCart(id) { cart = cart.filter(x => x.id !== id); saveToStorage('motayeb_cart', cart); updateCartUI(); } function updateCartQty(id, delta) { const item = cart.find(x => x.id === id); if (!item) return; item.qty += delta; if (item.qty <= 0) { removeFromCart(id); return; } saveToStorage('motayeb_cart', cart); updateCartUI(); } function updateCartUI() { const count = cart.reduce((s, i) => s + i.qty, 0); const total = cart.reduce((s, i) => s + (Number(i.price) || 0) * i.qty, 0); // Badge روی آیکون سبد ['cart-count', 'mobile-cart-count', 'cart-drawer-count'].forEach(id => { const el = document.getElementById(id); if (!el) return; if (id === 'cart-drawer-count') { el.textContent = count; } else { if (count > 0) { el.textContent = count; el.classList.remove('hidden'); } else { el.classList.add('hidden'); } } }); // محتویات drawer const listEl = document.getElementById('cart-items-list'); const emptyEl = document.getElementById('cart-empty'); const footerEl = document.getElementById('cart-drawer-footer'); if (!listEl) return; if (cart.length === 0) { listEl.classList.add('hidden'); if (emptyEl) emptyEl.classList.remove('hidden'); if (footerEl) footerEl.classList.add('hidden'); } else { listEl.classList.remove('hidden'); if (emptyEl) emptyEl.classList.add('hidden'); if (footerEl) footerEl.classList.remove('hidden'); const totalEl = document.getElementById('cart-drawer-total'); if (totalEl) totalEl.textContent = formatPrice(total) + ' تومان'; listEl.innerHTML = cart.map(i => `
سبد خرید خالی است
'; } else { checkoutItems.innerHTML = cart.map(i => `
محصولی یافت نشد
'; } } catch (err) { console.error('handleSearch:', err); results.innerHTML = 'خطا در جستجو. لطفاً دوباره تلاش کنید.
'; } } // ============================================ // گالری محصول — زوم و تغییر تصویر // ============================================ function changeImage(src, btn) { const mainImg = document.getElementById('main-product-img'); if (!mainImg) return; mainImg.src = src; mainImg.setAttribute('data-large_image', src); mainImg.setAttribute('data-src', src); const parentLink = mainImg.closest('a'); if (parentLink) parentLink.href = src; document.querySelectorAll('.gallery-thumb').forEach(t => t.classList.remove('active')); if (btn) btn.classList.add('active'); // بازسازی زوم ووکامرس (اگه jQuery لود شده باشه) if (typeof jQuery !== 'undefined' && typeof jQuery.fn.zoom !== 'undefined') { const $mainImg = jQuery(mainImg); const $wrapper = $mainImg.parent(); $wrapper.trigger('zoom.destroy'); $wrapper.zoom({ url: src, touch: false }); } } // ============================================ // صفحه محصول — انتخاب وزن و تعداد // ============================================ function selectWeight(btn, price) { document.querySelectorAll('.weight-btn').forEach(b => { b.classList.remove('active', 'border-forest-500', 'dark:border-forest-300', 'bg-forest-50', 'dark:bg-forest-900/30', 'text-forest-500', 'dark:text-forest-300'); b.classList.add('border-cream-200', 'dark:border-dark-border'); }); btn.classList.add('active', 'border-forest-500', 'dark:border-forest-300', 'bg-forest-50', 'dark:bg-forest-900/30', 'text-forest-500', 'dark:text-forest-300'); btn.classList.remove('border-cream-200', 'dark:border-dark-border'); selectedWeightPrice = price; const priceEl = document.getElementById('product-price'); if (priceEl) priceEl.textContent = formatPrice(price); } function changeQty(delta) { const el = document.getElementById('product-qty'); if (!el) return; let val = parseInt(el.textContent) + delta; if (val < 1) val = 1; if (val > 10) val = 10; el.textContent = val; } function switchTab(tabId, btn) { document.querySelectorAll('.tab-content').forEach(t => t.classList.add('hidden')); document.querySelectorAll('.tab-btn').forEach(b => { b.classList.remove('tab-active'); b.classList.add('text-cream-500', 'dark:text-dark-muted'); }); const target = document.getElementById('tab-' + tabId); if (target) target.classList.remove('hidden'); if (btn) { btn.classList.add('tab-active'); btn.classList.remove('text-cream-500', 'dark:text-dark-muted'); } } // ============================================ // علاقهمندیها — با persist در localStorage // ============================================ function toggleWishlist(btn, productId) { if (!btn) return; const icon = btn.querySelector('iconify-icon'); if (!icon) return; productId = productId || parseInt(btn.getAttribute('data-product-id')) || 0; const index = wishlist.indexOf(productId); if (index === -1) { // افزودن به علاقهمندیها wishlist.push(productId); icon.setAttribute('icon', 'lucide:heart'); icon.style.color = '#ef4444'; icon.style.fill = '#ef4444'; showToast('به علاقهمندیها اضافه شد', 'success'); } else { // حذف از علاقهمندیها wishlist.splice(index, 1); icon.setAttribute('icon', 'lucide:heart'); icon.style.color = ''; icon.style.fill = ''; showToast('از علاقهمندیها حذف شد', 'info'); } saveToStorage('motayeb_wishlist', wishlist); } // ============================================ // Toast نوتیفیکیشن // ============================================ function showToast(msg, type) { type = type || 'info'; const container = document.getElementById('toast-container'); if (!container) return; const colors = { success: 'bg-forest-500', info: 'bg-earth-400', error: 'bg-red-500' }; const icons = { success: 'lucide:check-circle', info: 'lucide:info', error: 'lucide:alert-circle' }; const toast = document.createElement('div'); toast.className = 'toast flex items-center gap-3 px-5 py-3 ' + (colors[type] || 'bg-earth-400') + ' text-white rounded-2xl shadow-xl text-sm font-medium'; toast.innerHTML = '