motayeb/assets/js/main.js
Kazem Alghasi c16648678d - install local Tailwind
- fix more problems
2026-09-23 00:32:16 +03:30

673 lines
25 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* اسکریپت اصلی تم مطیب
*
* @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, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}
/**
* فرمت قیمت — اعداد فارسی + نمایش رایگان برای قیمت صفر
*/
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 => `
<div class="flex gap-3 bg-cream-50 dark:bg-dark-bg rounded-xl p-3">
<img src="${escapeHtml(i.img && i.img !== 'default-product' ? i.img : 'https://picsum.photos/seed/' + i.id + '/100/100.jpg')}" alt="${escapeHtml(i.name)}" class="w-16 h-16 rounded-lg object-cover flex-shrink-0">
<div class="flex-1 min-w-0">
<h4 class="font-bold text-sm line-clamp-1">${escapeHtml(i.name)}</h4>
<div class="text-xs text-cream-500 dark:text-dark-muted mt-0.5">${formatPrice(i.price)} تومان</div>
<div class="flex items-center justify-between mt-2">
<div class="flex items-center border border-cream-200 dark:border-dark-border rounded-lg overflow-hidden">
<button onclick="updateCartQty(${i.id},-1)" class="w-7 h-7 flex items-center justify-center hover:bg-cream-200 dark:hover:bg-dark-card transition text-xs" aria-label="کاهش"><iconify-icon icon="lucide:minus" width="12"></iconify-icon></button>
<span class="w-8 h-7 flex items-center justify-center text-xs font-bold border-x border-cream-200 dark:border-dark-border">${i.qty}</span>
<button onclick="updateCartQty(${i.id},1)" class="w-7 h-7 flex items-center justify-center hover:bg-cream-200 dark:hover:bg-dark-card transition text-xs" aria-label="افزایش"><iconify-icon icon="lucide:plus" width="12"></iconify-icon></button>
</div>
<button onclick="removeFromCart(${i.id})" class="text-red-400 hover:text-red-500 transition" aria-label="حذف"><iconify-icon icon="lucide:trash-2" width="16"></iconify-icon></button>
</div>
</div>
</div>
`).join('');
}
// صفحه checkout
const checkoutItems = document.getElementById('checkout-items');
if (checkoutItems) {
if (cart.length === 0) {
checkoutItems.innerHTML = '<p class="text-sm text-cream-500 dark:text-dark-muted text-center py-4">سبد خرید خالی است</p>';
} else {
checkoutItems.innerHTML = cart.map(i => `
<div class="flex items-center gap-3">
<img src="${escapeHtml(i.img && i.img !== 'default-product' ? i.img : 'https://picsum.photos/seed/' + i.id + '/60/60.jpg')}" alt="${escapeHtml(i.name)}" class="w-12 h-12 rounded-lg object-cover">
<div class="flex-1 min-w-0">
<div class="text-sm font-medium line-clamp-1">${escapeHtml(i.name)}</div>
<div class="text-xs text-cream-500 dark:text-dark-muted">${i.qty} عدد</div>
</div>
<div class="text-sm font-bold whitespace-nowrap">${formatPrice(Number(i.price) * i.qty)}</div>
</div>
`).join('');
}
const subtotalEl = document.getElementById('checkout-subtotal');
const totalEl = document.getElementById('checkout-total');
if (subtotalEl) subtotalEl.textContent = formatPrice(total) + ' تومان';
if (totalEl) totalEl.textContent = formatPrice(total) + ' تومان';
}
}
// ============================================
// توابع UI — جستجو، سبد، منو
// ============================================
function openSearch() {
const overlay = document.getElementById('search-overlay');
if (!overlay) return;
overlay.classList.remove('hidden');
setTimeout(() => {
const input = document.getElementById('search-input');
if (input) input.focus();
}, 100);
}
function closeSearch() {
const overlay = document.getElementById('search-overlay');
if (overlay) overlay.classList.add('hidden');
const input = document.getElementById('search-input');
if (input) input.value = '';
const results = document.getElementById('search-results');
if (results) results.innerHTML = '';
const suggestions = document.getElementById('search-suggestions');
if (suggestions) suggestions.classList.remove('hidden');
}
function openCart() {
const drawer = document.getElementById('cart-drawer');
if (drawer) drawer.classList.remove('hidden');
}
function closeCart() {
const drawer = document.getElementById('cart-drawer');
if (drawer) drawer.classList.add('hidden');
}
function openMobileMenu() {
const menu = document.getElementById('mobile-menu');
if (menu) menu.classList.remove('hidden');
}
function closeMobileMenu() {
const menu = document.getElementById('mobile-menu');
if (menu) menu.classList.add('hidden');
}
function toggleLang() {
showToast('نسخه انگلیسی به زودی اضافه می‌شود', 'info');
}
function goToCheckout() {
closeCart();
if (MOTAYEB_AJAX.checkout_url) {
window.location.href = MOTAYEB_AJAX.checkout_url;
} else {
window.location.href = '/checkout/';
}
}
// ============================================
// دارک مود — هماهنگ با PHP body_class
// ============================================
/**
* تغییر حالت تاریک.
* - کلاس dark روی <html> (برای Tailwind dark:)
* - کلاس dark-mode روی <body> (برای CSS variables قدیمی)
* - ذخیره در کوکی (برای خواندن سمت سرور در functions.php)
*/
function toggleDark() {
const html = document.documentElement;
const body = document.body;
const isDark = html.classList.toggle('dark');
body.classList.toggle('dark-mode', isDark);
// ذخیره در کوکی — ۱ سال اعتبار
const value = isDark ? 'on' : 'off';
document.cookie = 'motayeb_darkmode=' + value +
';path=/;max-age=31536000;SameSite=Lax';
}
// ============================================
// جستجوی زنده
// ============================================
async function handleSearch(val) {
const results = document.getElementById('search-results');
const suggestions = document.getElementById('search-suggestions');
if (!results) return;
if (val.length < 2) {
results.innerHTML = '';
if (suggestions) suggestions.classList.remove('hidden');
return;
}
if (suggestions) suggestions.classList.add('hidden');
try {
const data = await motayebFetch('search_products', { term: val });
if (data.success && data.data.length > 0) {
results.innerHTML = data.data.map(p => {
const imgSrc = p.image || p.img || ('https://picsum.photos/seed/' + p.id + '/60/60.jpg');
const permalink = p.permalink || '#';
return `
<a href="${escapeHtml(permalink)}" onclick="closeSearch()" class="flex items-center gap-3 w-full p-3 rounded-xl hover:bg-cream-50 dark:hover:bg-dark-bg transition text-right">
<img src="${escapeHtml(imgSrc)}" alt="${escapeHtml(p.name)}" class="w-12 h-12 rounded-lg object-cover">
<div class="flex-1 min-w-0">
<div class="font-bold text-sm line-clamp-1">${escapeHtml(p.name)}</div>
<div class="text-xs text-cream-500 dark:text-dark-muted">${escapeHtml(p.category || 'محصول')}</div>
</div>
<div class="text-sm font-bold text-forest-500 dark:text-forest-300 whitespace-nowrap">${formatPrice(p.price)}</div>
</a>
`;
}).join('');
} else {
results.innerHTML = '<p class="text-sm text-cream-500 dark:text-dark-muted text-center py-6">محصولی یافت نشد</p>';
}
} catch (err) {
console.error('handleSearch:', err);
results.innerHTML = '<p class="text-sm text-cream-500 dark:text-dark-muted text-center py-6">خطا در جستجو. لطفاً دوباره تلاش کنید.</p>';
}
}
// ============================================
// گالری محصول — زوم و تغییر تصویر
// ============================================
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 = '<iconify-icon icon="' + (icons[type] || 'lucide:info') + '" width="20"></iconify-icon><span>' + escapeHtml(msg) + '</span>';
container.appendChild(toast);
setTimeout(() => {
toast.style.opacity = '0';
toast.style.transition = 'opacity 0.3s';
setTimeout(() => toast.remove(), 300);
}, 3000);
}
// ============================================
// فرم‌ها
// ============================================
function handleNewsletter(e) {
e.preventDefault();
const email = document.getElementById('newsletter-email');
if (email && email.value) {
showToast(email.value + ' با موفقیت ثبت شد!', 'success');
email.value = '';
}
return false;
}
function handleContact(e) {
e.preventDefault();
showToast('پیام شما با موفقیت ارسال شد. به زودی پاسخ می‌دهیم.', 'success');
e.target.reset();
return false;
}
// ============================================
// سیستم امتیازدهی ستاره‌ای
// ============================================
let currentRating = 0;
/**
* تعیین امتیاز با کلیک روی ستاره
*/
function setRating(rating) {
currentRating = rating;
renderStars(rating);
// تنظیم فیلد مخفی برای ارسال با فرم
const hiddenInput = document.getElementById('rating-input');
if (hiddenInput) hiddenInput.value = rating;
}
/**
* نمایش ستاره‌ها با امتیاز داده‌شده
* @param {number} rating - امتیاز از ۱ تا ۵
* @param {string} containerId - شناسه ظرف (پیش‌فرض: star-rating)
*/
function renderStars(rating, containerId) {
containerId = containerId || 'star-rating';
const container = document.getElementById(containerId);
if (!container) return;
container.innerHTML = '';
for (let i = 1; i <= 5; i++) {
const star = document.createElement('i');
if (i <= rating) {
// ستاره پر — طلایی (کلاس‌های Tailwind/FontAwesome)
star.className = 'fa-solid fa-star text-honey-400 cursor-pointer transition';
} else {
// ستاره خالی — خاکستری
star.className = 'fa-regular fa-star text-cream-300 dark:text-dark-muted cursor-pointer transition';
}
star.dataset.rating = i;
star.setAttribute('role', 'button');
star.setAttribute('aria-label', 'امتیاز ' + i + ' از ۵');
star.addEventListener('click', function() {
setRating(parseInt(this.dataset.rating));
});
star.addEventListener('mouseenter', function() {
renderStars(parseInt(this.dataset.rating), containerId);
});
container.appendChild(star);
}
// وقتی موس از ظرف خارج شد، به امتیاز فعلی برگرد
container.onmouseleave = function() {
renderStars(currentRating, containerId);
};
}
// ============================================
// رویدادهای صفحه کلید
// ============================================
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
closeSearch();
closeCart();
closeMobileMenu();
}
// Ctrl/Cmd + K برای باز کردن جستجو
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
e.preventDefault();
openSearch();
}
});
// ============================================
// تغییر هدر هنگام اسکرول
// ============================================
window.addEventListener('scroll', function() {
const header = document.getElementById('header');
if (!header) return;
if (window.scrollY > 100) {
header.classList.add('shadow-lg');
} else {
header.classList.remove('shadow-lg');
}
}, { passive: true });
// ============================================
// مقداردهی اولیه — همه در یک listener
// ============================================
document.addEventListener('DOMContentLoaded', function() {
// ۱. به‌روزرسانی UI سبد خرید از localStorage
updateCartUI();
// ۲. تنظیم گالری محصول (اگه در صفحه محصول هستیم)
const mainImg = document.getElementById('main-product-img');
if (mainImg) {
const firstThumb = document.querySelector('.gallery-thumb');
if (firstThumb) firstThumb.classList.add('active');
}
// ۳. رندر ستاره‌های امتیازدهی (اگه ظرف وجود داشته باشه)
const starContainer = document.getElementById('star-rating');
if (starContainer) {
// خواندن امتیاز فعلی از فیلد مخفی (اگه ویرایش نظر باشه)
const hiddenInput = document.getElementById('rating-input');
currentRating = hiddenInput ? parseInt(hiddenInput.value) || 0 : 0;
renderStars(currentRating);
}
// ۴. علامت‌گذاری آیکون‌های علاقه‌مندی که قبلاً انتخاب شده‌ان
document.querySelectorAll('[data-wishlist-btn]').forEach(btn => {
const productId = parseInt(btn.getAttribute('data-product-id')) || 0;
if (wishlist.indexOf(productId) !== -1) {
const icon = btn.querySelector('iconify-icon');
if (icon) {
icon.style.color = '#ef4444';
icon.style.fill = '#ef4444';
}
}
});
});
// AJAX Add to Cart برای صفحه محصول
document.addEventListener('DOMContentLoaded', function() {
var form = document.querySelector('form.cart');
if (!form) return;
form.addEventListener('submit', function(e) {
e.preventDefault();
var formData = new FormData(form);
var productId = formData.get('add-to-cart');
var quantity = formData.get('quantity') || 1;
fetch(MOTAYEB_AJAX.ajax_url, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: 'action=woocommerce_add_to_cart&product_id=' + productId + '&quantity=' + quantity
})
.then(r => r.json())
.then(data => {
if (data.fragments) {
// به‌روزرسانی fragment های ووکامرس (مثل شمارنده سبد)
Object.keys(data.fragments).forEach(function(key) {
var el = document.querySelector(key);
if (el) el.innerHTML = data.fragments[key];
});
showToast('به سبد خرید اضافه شد', 'success');
}
})
.catch(err => showToast('خطا در افزودن', 'error'));
});
});