forked from hesabix/arc
858 lines
25 KiB
JavaScript
858 lines
25 KiB
JavaScript
/**
|
|
* کاتالوگ عمومی — بدون وابستگی به CDN؛ فقط fetch به REST وردپرس.
|
|
*/
|
|
(function () {
|
|
'use strict';
|
|
|
|
function t(key) {
|
|
var S = window.shabakeTamin || {};
|
|
var i = S.i18n || {};
|
|
return i[key] || key;
|
|
}
|
|
|
|
function absUrl(path) {
|
|
var base = (window.shabakeTamin && window.shabakeTamin.apiPublicBase) || '';
|
|
if (!path) return '';
|
|
if (/^https?:\/\//i.test(path)) return path;
|
|
if (!base) return path;
|
|
return base.replace(/\/$/, '') + (path.charAt(0) === '/' ? path : '/' + path);
|
|
}
|
|
|
|
function noImageUrl() {
|
|
return (window.shabakeTamin && window.shabakeTamin.noImageUrl) || '';
|
|
}
|
|
|
|
function productImageSrc(p) {
|
|
var img = (p && (p.thumbnail_url || p.image_url)) || '';
|
|
return img ? absUrl(img) : noImageUrl();
|
|
}
|
|
|
|
function telHref(num) {
|
|
if (!num) return '';
|
|
return 'tel:' + String(num).replace(/[^\d+]/g, '');
|
|
}
|
|
|
|
function renderSupplierContactBlock(sup, opts) {
|
|
opts = opts || {};
|
|
var compact = !!opts.compact;
|
|
if (!sup) return '';
|
|
var hasContact =
|
|
sup.business_name ||
|
|
sup.address ||
|
|
sup.province ||
|
|
sup.city ||
|
|
sup.country ||
|
|
(sup.show_contact && (sup.phone || sup.mobile));
|
|
if (!hasContact) return '';
|
|
|
|
var loc = [sup.province, sup.city].filter(Boolean).join(' — ');
|
|
var country = sup.country ? String(sup.country) : '';
|
|
var locationLine = [loc, country].filter(Boolean).join(' · ');
|
|
|
|
var parts = [];
|
|
parts.push(
|
|
'<div class="st-supplier-contact' + (compact ? ' st-supplier-contact--compact' : '') + '">'
|
|
);
|
|
if (!compact) {
|
|
parts.push(
|
|
'<div class="st-supplier-contact-title">' + esc(t('supplierContactTitle')) + '</div>'
|
|
);
|
|
}
|
|
if (sup.business_name) {
|
|
parts.push(
|
|
'<div class="st-supplier-contact-name">' + esc(sup.business_name) + '</div>'
|
|
);
|
|
}
|
|
parts.push('<div class="st-supplier-contact-rows">');
|
|
if (sup.address) {
|
|
parts.push(
|
|
'<div class="st-supplier-contact-row"><span class="st-supplier-contact-icon" aria-hidden="true">📍</span><span class="st-supplier-contact-text">' +
|
|
nl2br(sup.address) +
|
|
'</span></div>'
|
|
);
|
|
}
|
|
if (locationLine) {
|
|
parts.push(
|
|
'<div class="st-supplier-contact-row"><span class="st-supplier-contact-icon" aria-hidden="true">🗺</span><span class="st-supplier-contact-text">' +
|
|
esc(locationLine) +
|
|
'</span></div>'
|
|
);
|
|
}
|
|
if (sup.show_contact) {
|
|
if (sup.phone) {
|
|
parts.push(
|
|
'<div class="st-supplier-contact-row"><span class="st-supplier-contact-icon" aria-hidden="true">☎</span><a class="st-supplier-contact-link" href="' +
|
|
esc(telHref(sup.phone)) +
|
|
'">' +
|
|
esc(sup.phone) +
|
|
'</a></div>'
|
|
);
|
|
}
|
|
if (sup.mobile) {
|
|
parts.push(
|
|
'<div class="st-supplier-contact-row"><span class="st-supplier-contact-icon" aria-hidden="true">📱</span><a class="st-supplier-contact-link" href="' +
|
|
esc(telHref(sup.mobile)) +
|
|
'">' +
|
|
esc(sup.mobile) +
|
|
'</a></div>'
|
|
);
|
|
}
|
|
}
|
|
parts.push('</div></div>');
|
|
return parts.join('');
|
|
}
|
|
|
|
function esc(s) {
|
|
var d = document.createElement('div');
|
|
d.textContent = s == null ? '' : String(s);
|
|
return d.innerHTML;
|
|
}
|
|
|
|
function nl2br(s) {
|
|
return esc(s).replace(/\n/g, '<br />');
|
|
}
|
|
|
|
function sanitizeHtmlBasic(html) {
|
|
if (!html) return '';
|
|
var d = document.createElement('div');
|
|
d.innerHTML = String(html);
|
|
var bad = d.querySelectorAll('script,style,iframe,object,embed');
|
|
for (var i = 0; i < bad.length; i++) {
|
|
bad[i].parentNode.removeChild(bad[i]);
|
|
}
|
|
return d.innerHTML;
|
|
}
|
|
|
|
function renderGallery(p) {
|
|
var thumbs = p.gallery_thumbnail_urls || [];
|
|
var full = p.gallery_urls || [];
|
|
if (!thumbs.length) return '';
|
|
var items = '';
|
|
for (var gi = 0; gi < thumbs.length; gi++) {
|
|
var tu = absUrl(thumbs[gi]);
|
|
var fu = absUrl(full[gi] || thumbs[gi]);
|
|
items +=
|
|
'<button type="button" class="st-gallery-thumb" data-full="' +
|
|
esc(fu) +
|
|
'"><img src="' +
|
|
esc(tu) +
|
|
'" alt="" loading="lazy" /></button>';
|
|
}
|
|
return (
|
|
'<div class="st-detail-gallery-wrap"><strong class="st-detail-gallery-label">' +
|
|
esc(t('galleryLabel')) +
|
|
'</strong><div class="st-detail-gallery">' +
|
|
items +
|
|
'</div></div>'
|
|
);
|
|
}
|
|
|
|
function parseCfg(root) {
|
|
var el = root.querySelector('.st-json-config');
|
|
if (!el || !el.textContent) return {};
|
|
try {
|
|
return JSON.parse(el.textContent);
|
|
} catch (e) {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
function fmtPrice(n) {
|
|
if (n === null || n === undefined || n === '') return t('priceNA');
|
|
var x = Number(n);
|
|
if (isNaN(x)) return t('priceNA');
|
|
try {
|
|
return new Intl.NumberFormat('fa-IR').format(x);
|
|
} catch (e) {
|
|
return String(x);
|
|
}
|
|
}
|
|
|
|
function fmtCount(n) {
|
|
if (n === Infinity || n === 'Infinity') {
|
|
return '…';
|
|
}
|
|
var x = Number(n);
|
|
if (isNaN(x)) {
|
|
return String(n);
|
|
}
|
|
try {
|
|
return new Intl.NumberFormat('fa-IR').format(x);
|
|
} catch (e2) {
|
|
return String(x);
|
|
}
|
|
}
|
|
|
|
function errMsg(payload) {
|
|
if (!payload) return t('errorGeneric');
|
|
if (payload.error && typeof payload.error === 'string') return payload.error;
|
|
var r = payload.remote;
|
|
if (r && r.error && r.error.message) return r.error.message;
|
|
if (r && r.detail && r.detail.error && r.detail.error.message) return r.detail.error.message;
|
|
if (r && r.detail && r.detail.message) return r.detail.message;
|
|
if (r && r.detail && typeof r.detail === 'string') return r.detail;
|
|
return t('errorGeneric');
|
|
}
|
|
|
|
function renderSpecsTable(specs) {
|
|
if (!specs || !specs.length) return '';
|
|
var rows = '';
|
|
for (var i = 0; i < specs.length; i++) {
|
|
var s = specs[i] || {};
|
|
var label = s.label || s.key || '';
|
|
var value = s.value || '';
|
|
if (!label || !value) continue;
|
|
rows +=
|
|
'<tr><th scope="row">' +
|
|
esc(label) +
|
|
'</th><td>' +
|
|
esc(value) +
|
|
'</td></tr>';
|
|
}
|
|
if (!rows) return '';
|
|
return (
|
|
'<div class="st-detail-specs-wrap"><strong class="st-detail-specs-label">' +
|
|
esc(t('specsLabel')) +
|
|
'</strong><table class="st-detail-specs"><tbody>' +
|
|
rows +
|
|
'</tbody></table></div>'
|
|
);
|
|
}
|
|
|
|
function initRoot(root) {
|
|
var cfg = parseCfg(root);
|
|
var rest = (window.shabakeTamin && window.shabakeTamin.restBase) || '';
|
|
var configured = window.shabakeTamin && window.shabakeTamin.configured;
|
|
var grid = root.querySelector('.st-grid');
|
|
var statusEl = root.querySelector('.st-catalog-status');
|
|
var loadBtn = root.querySelector('.st-loadmore');
|
|
var searchInp = root.querySelector('.st-search-input');
|
|
var searchBtn = root.querySelector('.st-search-btn');
|
|
var statsEl = root.querySelector('.st-pub-result-stats');
|
|
|
|
if (searchInp) searchInp.placeholder = t('search');
|
|
|
|
var skip = 0;
|
|
var take = cfg.take || 20;
|
|
var total = Infinity;
|
|
var loading = false;
|
|
var q = '';
|
|
|
|
function updateResultStats() {
|
|
if (!statsEl) return;
|
|
var tpl = t('resultCountTemplate');
|
|
var totStr = total === Infinity ? '…' : fmtCount(total);
|
|
statsEl.textContent = tpl.replace('{shown}', fmtCount(skip)).replace('{total}', totStr);
|
|
}
|
|
|
|
function setStatus( msg, isErr, isInfo ) {
|
|
if (!statusEl) return;
|
|
if (!msg) {
|
|
statusEl.hidden = true;
|
|
statusEl.textContent = '';
|
|
statusEl.classList.remove('st-is-error', 'st-is-info');
|
|
return;
|
|
}
|
|
statusEl.hidden = false;
|
|
statusEl.textContent = msg;
|
|
statusEl.classList.toggle('st-is-error', !!isErr);
|
|
statusEl.classList.toggle('st-is-info', !!isInfo && !isErr);
|
|
}
|
|
|
|
function setLoading(on) {
|
|
root.classList.toggle('st-is-loading', !!on);
|
|
}
|
|
|
|
var provInput = root.querySelector('.st-filter-province');
|
|
var cityInput = root.querySelector('.st-filter-city');
|
|
var brandInput = root.querySelector('.st-filter-brand');
|
|
var filterBtn = root.querySelector('.st-filter-apply');
|
|
|
|
if (brandInput) brandInput.placeholder = t('brandFilterPlaceholder');
|
|
|
|
function effectiveProvince() {
|
|
if (provInput) return String(provInput.value || '').trim();
|
|
return cfg.province != null && cfg.province !== '' ? String(cfg.province) : '';
|
|
}
|
|
|
|
function effectiveCity() {
|
|
if (cityInput) return String(cityInput.value || '').trim();
|
|
return cfg.city != null && cfg.city !== '' ? String(cfg.city) : '';
|
|
}
|
|
|
|
function effectiveBrand() {
|
|
if (brandInput) return String(brandInput.value || '').trim();
|
|
return cfg.brand != null && cfg.brand !== '' ? String(cfg.brand) : '';
|
|
}
|
|
|
|
function params() {
|
|
var p = new URLSearchParams();
|
|
p.set('skip', String(skip));
|
|
p.set('take', String(take));
|
|
if (cfg.businessId) p.set('business_id', String(cfg.businessId));
|
|
if (cfg.categoryId) p.set('category_id', String(cfg.categoryId));
|
|
var pv = effectiveProvince();
|
|
var cv = effectiveCity();
|
|
var bv = effectiveBrand();
|
|
if (pv) p.set('province', pv);
|
|
if (cv) p.set('city', cv);
|
|
if (bv) p.set('brand', bv);
|
|
if (q) p.set('search', q);
|
|
return p.toString();
|
|
}
|
|
|
|
function renderItem(row) {
|
|
var p = row.product || {};
|
|
var sup = row.supplier || {};
|
|
var uuid = p.catalog_public_uuid || '';
|
|
var imgSrc = productImageSrc(p);
|
|
var price = fmtPrice(p.base_sales_price);
|
|
var card = document.createElement('article');
|
|
card.className = 'st-card';
|
|
card.dataset.uuid = uuid;
|
|
card.dataset.businessId = String(row.business_id || '');
|
|
|
|
var html = '';
|
|
html += '<div class="st-card-img-wrap">';
|
|
html +=
|
|
'<img class="st-card-img' +
|
|
(imgSrc === noImageUrl() ? ' st-card-img--fallback' : '') +
|
|
'" src="' +
|
|
esc(imgSrc) +
|
|
'" alt="" loading="lazy" />';
|
|
html += '</div>';
|
|
html += '<div class="st-card-body">';
|
|
html += '<h3 class="st-card-title">' + esc(p.name || '') + '</h3>';
|
|
if (p.short_description) {
|
|
html += '<div class="st-card-short">' + esc(p.short_description) + '</div>';
|
|
}
|
|
if (p.brand || p.model) {
|
|
html += '<div class="st-card-brand">' + esc([p.brand, p.model].filter(Boolean).join(' — ')) + '</div>';
|
|
}
|
|
if (p.category_name) html += '<div class="st-card-meta">' + esc(p.category_name) + '</div>';
|
|
html += '<div class="st-card-price">' + esc(price) + '</div>';
|
|
html += renderSupplierContactBlock(sup, { compact: true });
|
|
html += '<div class="st-card-actions">';
|
|
if (cfg.showProductDetails !== false) {
|
|
html += '<button type="button" class="st-btn-details button">' + esc(t('details')) + '</button>';
|
|
}
|
|
html += '<button type="button" class="st-btn-contact button">' + esc(t('contact')) + '</button>';
|
|
html += '</div></div>';
|
|
card.innerHTML = html;
|
|
return card;
|
|
}
|
|
|
|
function appendItems(items) {
|
|
if (!grid) return;
|
|
for (var i = 0; i < items.length; i++) {
|
|
grid.appendChild(renderItem(items[i]));
|
|
}
|
|
Array.prototype.forEach.call(grid.querySelectorAll('.st-btn-contact'), function (btn) {
|
|
btn.addEventListener('click', onContactClick);
|
|
});
|
|
Array.prototype.forEach.call(grid.querySelectorAll('.st-btn-details'), function (btn) {
|
|
btn.addEventListener('click', onDetailClick);
|
|
});
|
|
}
|
|
|
|
function onContactClick(ev) {
|
|
ev.preventDefault();
|
|
ev.stopPropagation();
|
|
var card = ev.target.closest('.st-card');
|
|
if (!card) return;
|
|
openContactModal({
|
|
businessId: parseInt(card.dataset.businessId, 10),
|
|
productUuid: card.dataset.uuid || null,
|
|
});
|
|
}
|
|
|
|
function onDetailClick(ev) {
|
|
ev.preventDefault();
|
|
ev.stopPropagation();
|
|
var card = ev.target.closest('.st-card');
|
|
if (!card) return;
|
|
var id = card.dataset.uuid || '';
|
|
if (id) openProductDetail(id);
|
|
}
|
|
|
|
function openProductDetail(uuid) {
|
|
if (!uuid || !configured) {
|
|
if (!configured) window.alert(t('notConfigured'));
|
|
return;
|
|
}
|
|
var overlay = document.createElement('div');
|
|
overlay.className = 'st-modal-overlay st-detail-overlay';
|
|
overlay.setAttribute('role', 'dialog');
|
|
overlay.setAttribute('aria-modal', 'true');
|
|
overlay.innerHTML =
|
|
'<div class="st-modal st-detail-modal">' +
|
|
'<button type="button" class="st-modal-close" aria-label="' +
|
|
esc(t('close')) +
|
|
'">×</button>' +
|
|
'<div class="st-detail-loading">' +
|
|
esc(t('loading')) +
|
|
'</div></div>';
|
|
document.body.appendChild(overlay);
|
|
|
|
function closeDetail() {
|
|
overlay.remove();
|
|
}
|
|
overlay.querySelector('.st-modal-close').addEventListener('click', closeDetail);
|
|
overlay.addEventListener('click', function (e) {
|
|
if (e.target === overlay) closeDetail();
|
|
});
|
|
|
|
fetch(rest + 'product/' + encodeURIComponent(uuid), { credentials: 'same-origin' })
|
|
.then(function (r) {
|
|
return r.json().then(function (j) {
|
|
return { ok: r.ok, json: j };
|
|
});
|
|
})
|
|
.then(function (x) {
|
|
var j = x.json;
|
|
if (!x.ok || !j.ok || !j.data || !j.data.item) throw new Error(errMsg(j));
|
|
var row = j.data.item;
|
|
var p = row.product || {};
|
|
var sup = row.supplier || {};
|
|
var bid = parseInt(row.business_id, 10) || 0;
|
|
var imgSrc = productImageSrc(p);
|
|
var parts = [];
|
|
parts.push(
|
|
'<button type="button" class="st-modal-close" aria-label="' +
|
|
esc(t('close')) +
|
|
'">×</button>'
|
|
);
|
|
parts.push('<h2 class="st-modal-title st-detail-title">' + esc(p.name || '') + '</h2>');
|
|
parts.push(
|
|
'<div class="st-detail-img-wrap" id="st-detail-main-img-wrap"><img class="st-detail-img' +
|
|
(imgSrc === noImageUrl() ? ' st-detail-img--fallback' : '') +
|
|
'" id="st-detail-main-img" src="' +
|
|
esc(imgSrc) +
|
|
'" alt="" /></div>'
|
|
);
|
|
parts.push(renderGallery(p));
|
|
if (p.category_name) {
|
|
parts.push('<div class="st-detail-meta">' + esc(p.category_name) + '</div>');
|
|
}
|
|
parts.push('<div class="st-detail-price">' + esc(fmtPrice(p.base_sales_price)) + '</div>');
|
|
parts.push(renderSupplierContactBlock(sup));
|
|
if (p.main_unit) {
|
|
parts.push(
|
|
'<div class="st-detail-unit"><strong>' +
|
|
esc(t('unitLabel')) +
|
|
'</strong> ' +
|
|
esc(p.main_unit) +
|
|
'</div>'
|
|
);
|
|
}
|
|
if (p.min_order_qty != null && p.min_order_qty !== '') {
|
|
parts.push(
|
|
'<div class="st-detail-min-order"><strong>' +
|
|
esc(t('minOrderLabel')) +
|
|
'</strong> ' +
|
|
esc(fmtCount(p.min_order_qty)) +
|
|
'</div>'
|
|
);
|
|
}
|
|
if (p.lead_time_days != null && p.lead_time_days !== '') {
|
|
parts.push(
|
|
'<div class="st-detail-lead-time"><strong>' +
|
|
esc(t('leadTimeLabel')) +
|
|
'</strong> ' +
|
|
esc(fmtCount(p.lead_time_days)) +
|
|
' ' +
|
|
esc(t('daysSuffix')) +
|
|
'</div>'
|
|
);
|
|
}
|
|
if (p.brand || p.model) {
|
|
var bm = [p.brand, p.model].filter(Boolean).join(' — ');
|
|
parts.push(
|
|
'<div class="st-detail-brand"><strong>' +
|
|
esc(t('brandModelLabel')) +
|
|
'</strong> ' +
|
|
esc(bm) +
|
|
'</div>'
|
|
);
|
|
}
|
|
if (p.country_of_origin) {
|
|
parts.push(
|
|
'<div class="st-detail-origin"><strong>' +
|
|
esc(t('originLabel')) +
|
|
'</strong> ' +
|
|
esc(p.country_of_origin) +
|
|
'</div>'
|
|
);
|
|
}
|
|
if (p.video_url) {
|
|
parts.push(
|
|
'<div class="st-detail-video"><a href="' +
|
|
esc(p.video_url) +
|
|
'" target="_blank" rel="noopener noreferrer">' +
|
|
esc(t('videoLabel')) +
|
|
'</a></div>'
|
|
);
|
|
}
|
|
if (p.updated_at) {
|
|
parts.push(
|
|
'<div class="st-detail-updated"><strong>' +
|
|
esc(t('updatedLabel')) +
|
|
'</strong> ' +
|
|
esc(p.updated_at) +
|
|
'</div>'
|
|
);
|
|
}
|
|
if (p.short_description) {
|
|
parts.push(
|
|
'<div class="st-detail-short-wrap"><strong class="st-detail-short-label">' +
|
|
esc(t('shortDescriptionLabel')) +
|
|
'</strong><div class="st-detail-short">' +
|
|
nl2br(p.short_description) +
|
|
'</div></div>'
|
|
);
|
|
}
|
|
if (p.description) {
|
|
parts.push(
|
|
'<div class="st-detail-desc-wrap"><strong class="st-detail-desc-label">' +
|
|
esc(t('descriptionLabel')) +
|
|
'</strong><div class="st-detail-desc">' +
|
|
nl2br(p.description) +
|
|
'</div></div>'
|
|
);
|
|
}
|
|
if (p.expert_review) {
|
|
parts.push(
|
|
'<div class="st-detail-expert-wrap"><strong class="st-detail-expert-label">' +
|
|
esc(t('expertReviewLabel')) +
|
|
'</strong><div class="st-detail-expert st-detail-expert-html"></div></div>'
|
|
);
|
|
}
|
|
parts.push(renderSpecsTable(p.specifications));
|
|
parts.push('<div class="st-detail-actions">');
|
|
if (bid > 0) {
|
|
parts.push(
|
|
'<button type="button" class="button st-detail-contact">' +
|
|
esc(t('contact')) +
|
|
'</button>'
|
|
);
|
|
}
|
|
parts.push('</div>');
|
|
|
|
var modal = overlay.querySelector('.st-detail-modal');
|
|
modal.innerHTML = parts.join('');
|
|
var expertEl = modal.querySelector('.st-detail-expert-html');
|
|
if (expertEl && p.expert_review) {
|
|
expertEl.innerHTML = sanitizeHtmlBasic(p.expert_review);
|
|
}
|
|
Array.prototype.forEach.call(modal.querySelectorAll('.st-gallery-thumb'), function (btn) {
|
|
btn.addEventListener('click', function () {
|
|
var main = modal.querySelector('#st-detail-main-img');
|
|
var full = btn.getAttribute('data-full');
|
|
if (main && full) main.src = full;
|
|
});
|
|
});
|
|
modal.querySelector('.st-modal-close').addEventListener('click', closeDetail);
|
|
var cbtn = modal.querySelector('.st-detail-contact');
|
|
if (cbtn) {
|
|
cbtn.addEventListener('click', function () {
|
|
closeDetail();
|
|
openContactModal({ businessId: bid, productUuid: uuid });
|
|
});
|
|
}
|
|
})
|
|
.catch(function (e) {
|
|
var modal = overlay.querySelector('.st-detail-modal');
|
|
modal.innerHTML =
|
|
'<button type="button" class="st-modal-close" aria-label="' +
|
|
esc(t('close')) +
|
|
'">×</button>' +
|
|
'<p class="st-detail-error">' +
|
|
esc(e.message || t('detailLoadError')) +
|
|
'</p>';
|
|
modal.querySelector('.st-modal-close').addEventListener('click', closeDetail);
|
|
});
|
|
}
|
|
|
|
function openContactModal(ctx) {
|
|
if (!ctx.businessId || isNaN(ctx.businessId) || ctx.businessId < 1) {
|
|
window.alert(t('errorGeneric'));
|
|
return;
|
|
}
|
|
var overlay = document.createElement('div');
|
|
overlay.className = 'st-modal-overlay';
|
|
overlay.innerHTML =
|
|
'<div class="st-modal" role="dialog" aria-modal="true">' +
|
|
'<button type="button" class="st-modal-close" aria-label="' +
|
|
esc(t('close')) +
|
|
'">×</button>' +
|
|
'<h2 class="st-modal-title">' +
|
|
esc(t('contact')) +
|
|
'</h2>' +
|
|
'<form class="st-contact-form">' +
|
|
'<label>' +
|
|
esc(t('name')) +
|
|
'<input type="text" name="sender_name" required maxlength="200" /></label>' +
|
|
'<label>' +
|
|
esc(t('contactField')) +
|
|
'<input type="text" name="sender_contact" required maxlength="200" /></label>' +
|
|
'<label>' +
|
|
esc(t('message')) +
|
|
'<textarea name="message" required maxlength="2000" rows="4"></textarea></label>' +
|
|
'<div class="st-captcha-row">' +
|
|
'<img class="st-captcha-img" alt="" />' +
|
|
'<button type="button" class="st-captcha-refresh">' +
|
|
esc(t('refreshCaptcha')) +
|
|
'</button></div>' +
|
|
'<label>' +
|
|
esc(t('captcha')) +
|
|
'<input type="text" name="captcha_code" required maxlength="16" autocomplete="off" /></label>' +
|
|
'<input type="hidden" name="captcha_id" />' +
|
|
'<div class="st-form-status" hidden></div>' +
|
|
'<button type="submit" class="button st-submit">' +
|
|
esc(t('send')) +
|
|
'</button>' +
|
|
'</form></div>';
|
|
|
|
document.body.appendChild(overlay);
|
|
|
|
var form = overlay.querySelector('.st-contact-form');
|
|
var capImg = overlay.querySelector('.st-captcha-img');
|
|
var capId = form.querySelector('input[name="captcha_id"]');
|
|
var formStatus = overlay.querySelector('.st-form-status');
|
|
|
|
function close() {
|
|
overlay.remove();
|
|
}
|
|
overlay.querySelector('.st-modal-close').addEventListener('click', close);
|
|
overlay.addEventListener('click', function (e) {
|
|
if (e.target === overlay) close();
|
|
});
|
|
|
|
function loadCaptcha() {
|
|
return fetch(rest + 'captcha', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
credentials: 'same-origin',
|
|
body: '{}',
|
|
})
|
|
.then(function (r) {
|
|
return r.json().then(function (j) {
|
|
return { ok: r.ok, json: j };
|
|
});
|
|
})
|
|
.then(function (x) {
|
|
var j = x.json;
|
|
if (!x.ok || !j.ok || !j.data) throw new Error(errMsg(j));
|
|
capId.value = j.data.captcha_id || '';
|
|
var b64 = j.data.image_base64 || '';
|
|
capImg.src = 'data:image/png;base64,' + b64;
|
|
});
|
|
}
|
|
|
|
overlay.querySelector('.st-captcha-refresh').addEventListener('click', function () {
|
|
loadCaptcha().catch(function (e) {
|
|
formStatus.hidden = false;
|
|
formStatus.textContent = e.message || t('errorGeneric');
|
|
});
|
|
});
|
|
|
|
loadCaptcha().catch(function (e) {
|
|
formStatus.hidden = false;
|
|
formStatus.textContent = e.message || t('errorGeneric');
|
|
});
|
|
|
|
form.addEventListener('submit', function (e) {
|
|
e.preventDefault();
|
|
formStatus.hidden = true;
|
|
var fd = new FormData(form);
|
|
var body = {
|
|
business_id: ctx.businessId,
|
|
sender_name: fd.get('sender_name'),
|
|
sender_contact: fd.get('sender_contact'),
|
|
message: fd.get('message'),
|
|
captcha_id: fd.get('captcha_id'),
|
|
captcha_code: fd.get('captcha_code'),
|
|
};
|
|
if (ctx.productUuid) body.product_catalog_uuid = ctx.productUuid;
|
|
|
|
fetch(rest + 'contact', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
credentials: 'same-origin',
|
|
body: JSON.stringify(body),
|
|
})
|
|
.then(function (r) {
|
|
return r.json().then(function (j) {
|
|
return { ok: r.ok, json: j };
|
|
});
|
|
})
|
|
.then(function (x) {
|
|
if (x.json && x.json.ok && x.json.data && x.json.data.saved) {
|
|
formStatus.hidden = false;
|
|
formStatus.classList.remove('st-is-error');
|
|
formStatus.textContent = t('sentOk');
|
|
form.reset();
|
|
loadCaptcha();
|
|
return;
|
|
}
|
|
throw new Error(errMsg(x.json));
|
|
})
|
|
.catch(function (err) {
|
|
formStatus.hidden = false;
|
|
formStatus.classList.add('st-is-error');
|
|
formStatus.textContent = err.message || t('errorGeneric');
|
|
loadCaptcha();
|
|
});
|
|
});
|
|
}
|
|
|
|
function updateSupplierBanner(items, reset) {
|
|
var bannerEl = root.querySelector('.st-supplier-banner');
|
|
if (!bannerEl) return;
|
|
if (!reset) return;
|
|
var sup = null;
|
|
if (cfg.businessId && items.length && items[0].supplier) {
|
|
sup = items[0].supplier;
|
|
} else if (items.length) {
|
|
var bid = items[0].business_id;
|
|
var allSame = true;
|
|
for (var bi = 0; bi < items.length; bi++) {
|
|
if (items[bi].business_id !== bid) {
|
|
allSame = false;
|
|
break;
|
|
}
|
|
}
|
|
if (allSame && items[0].supplier) {
|
|
sup = items[0].supplier;
|
|
}
|
|
}
|
|
if (!sup) {
|
|
bannerEl.hidden = true;
|
|
bannerEl.innerHTML = '';
|
|
return;
|
|
}
|
|
var html = renderSupplierContactBlock(sup);
|
|
if (!html) {
|
|
bannerEl.hidden = true;
|
|
bannerEl.innerHTML = '';
|
|
return;
|
|
}
|
|
bannerEl.innerHTML = html;
|
|
bannerEl.hidden = false;
|
|
}
|
|
|
|
function load(reset) {
|
|
if (!configured) {
|
|
setStatus(t('notConfigured'), true);
|
|
return;
|
|
}
|
|
if (loading) return;
|
|
loading = true;
|
|
if (reset) {
|
|
skip = 0;
|
|
total = Infinity;
|
|
if (grid) grid.innerHTML = '';
|
|
var bannerEl = root.querySelector('.st-supplier-banner');
|
|
if (bannerEl) {
|
|
bannerEl.hidden = true;
|
|
bannerEl.innerHTML = '';
|
|
}
|
|
}
|
|
setStatus('');
|
|
setLoading(true);
|
|
if (loadBtn) loadBtn.disabled = true;
|
|
|
|
fetch(rest + 'catalog?' + params(), { credentials: 'same-origin' })
|
|
.then(function (r) {
|
|
return r.json().then(function (j) {
|
|
return { ok: r.ok, json: j };
|
|
});
|
|
})
|
|
.then(function (x) {
|
|
var j = x.json;
|
|
if (!x.ok || !j.ok) throw new Error(errMsg(j));
|
|
var data = j.data || {};
|
|
var items = data.items || [];
|
|
total = typeof data.total_count === 'number' ? data.total_count : total;
|
|
appendItems(items);
|
|
updateSupplierBanner(items, reset);
|
|
skip += items.length;
|
|
if (loadBtn) {
|
|
loadBtn.hidden = skip >= total;
|
|
loadBtn.disabled = false;
|
|
loadBtn.textContent = t('loadMore');
|
|
}
|
|
if (reset && items.length === 0) {
|
|
setStatus(t('emptyResults'), false, true);
|
|
}
|
|
if (statsEl) {
|
|
updateResultStats();
|
|
}
|
|
})
|
|
.catch(function (e) {
|
|
setStatus(e.message || t('errorGeneric'), true, false);
|
|
if (loadBtn) loadBtn.disabled = false;
|
|
})
|
|
.then(function () {
|
|
loading = false;
|
|
setLoading(false);
|
|
});
|
|
}
|
|
|
|
if (loadBtn) {
|
|
loadBtn.addEventListener('click', function () {
|
|
load(false);
|
|
});
|
|
}
|
|
|
|
function doSearch() {
|
|
q = searchInp ? String(searchInp.value || '').trim() : '';
|
|
load(true);
|
|
}
|
|
|
|
function doApplyFilters() {
|
|
load(true);
|
|
}
|
|
|
|
if (filterBtn) {
|
|
filterBtn.addEventListener('click', doApplyFilters);
|
|
}
|
|
function bindEnterApply(el) {
|
|
if (!el) return;
|
|
el.addEventListener('keydown', function (e) {
|
|
if (e.key === 'Enter') {
|
|
e.preventDefault();
|
|
doApplyFilters();
|
|
}
|
|
});
|
|
}
|
|
bindEnterApply(provInput);
|
|
bindEnterApply(cityInput);
|
|
bindEnterApply(brandInput);
|
|
|
|
if (searchBtn && searchInp) {
|
|
searchBtn.addEventListener('click', doSearch);
|
|
searchInp.addEventListener('keydown', function (e) {
|
|
if (e.key === 'Enter') {
|
|
e.preventDefault();
|
|
doSearch();
|
|
}
|
|
});
|
|
}
|
|
|
|
load(true);
|
|
}
|
|
|
|
function boot() {
|
|
var roots = document.querySelectorAll('.st-catalog-root[data-st-root="1"]');
|
|
for (var i = 0; i < roots.length; i++) {
|
|
initRoot(roots[i]);
|
|
}
|
|
}
|
|
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', boot);
|
|
} else {
|
|
boot();
|
|
}
|
|
})();
|