1import { getConfig } from './config.js'; 2import { isCustomerLoggedIn } from './customer-auth.js'; 3import { t } from './i18n.js'; 4import { resolvePublicPath, isEnglish } from './locale.js'; 5import { trackAbImpression, trackAbRegisterClick } from './ab-tests.js'; 6 7const STORAGE_KEY = 'planeta_account_promo_day'; 8const DEFAULT_DELAY_SECONDS = 5; 9const TRIGGERS = new Set(['disabled', 'delay', 'open_cart', 'after_order']); 10const BLOCKING_BODY_CLASSES = [ 11 'author-contact-open', 12 'account-modal-open', 13 'gallery-panel-open' 14]; 15 16let delayTimer = null; 17let retryTimer = null; 18 19export function normalizeAccountPromoPopup(raw = null) { 20 const src = raw && typeof raw === 'object' ? raw : {}; 21 let trigger = String(src.trigger || 'delay').trim(); 22 if (!TRIGGERS.has(trigger)) trigger = 'delay'; 23 const delaySeconds = Math.min( 24 120, 25 Math.max(1, Number(src.delaySeconds) || DEFAULT_DELAY_SECONDS) 26 ); 27 return { trigger, delaySeconds }; 28} 29 30export function getAccountPromoPopupSettings(config = getConfig()) { 31 return normalizeAccountPromoPopup(config?.accountPromoPopup); 32} 33 34function todayKey() { 35 const d = new Date(); 36 const y = d.getFullYear(); 37 const m = String(d.getMonth() + 1).padStart(2, '0'); 38 const day = String(d.getDate()).padStart(2, '0'); 39 return `${y}-${m}-${day}`; 40} 41 42function wasShownToday() { 43 try { 44 return localStorage.getItem(STORAGE_KEY) === todayKey(); 45 } catch { 46 return true; 47 } 48} 49 50function markShownToday() { 51 try { 52 localStorage.setItem(STORAGE_KEY, todayKey()); 53 } catch { 54 // ignore 55 } 56} 57 58function hasBlockingOverlay() { 59 if (BLOCKING_BODY_CLASSES.some((cls) => document.body.classList.contains(cls))) { 60 return true; 61 } 62 const productModal = document.getElementById('product-modal'); 63 if (productModal && !productModal.hidden) return true; 64 return false; 65} 66 67function registerHref() { 68 return `${resolvePublicPath('konto.html')}?tab=register`; 69} 70 71function escapeHtml(value) { 72 return String(value ?? '') 73 .replaceAll('&', '&') 74 .replaceAll('<', '<') 75 .replaceAll('>', '>') 76 .replaceAll('"', '"'); 77} 78 79function escapeAttr(value) { 80 return escapeHtml(value).replaceAll("'", '''); 81} 82 83function buildMarkup(config = getConfig()) { 84 const ab = config?._abAccountPromo; 85 const english = isEnglish(); 86 const abTitle = ab?.titlePl 87 ? (english ? (ab.titleEn || ab.titlePl) : ab.titlePl) 88 : t('accountPromo.title'); 89 const abDesc = ab?.descPl 90 ? (english ? (ab.descEn || ab.descPl) : ab.descPl) 91 : t('accountPromo.desc'); 92 return ` 93 <div class="account-promo-backdrop" data-account-promo-dismiss tabindex="-1"></div> 94 <div 95 class="account-promo" 96 role="dialog" 97 aria-modal="true" 98 aria-labelledby="account-promo-title" 99 aria-describedby="account-promo-desc" 100 > 101 <button type="button" class="account-promo__close" data-account-promo-dismiss aria-label="${escapeAttr(t('accountPromo.close'))}">â</button> 102 <div class="account-promo__sparkles" aria-hidden="true">â¨</div> 103 <div class="account-promo__emoji" aria-hidden="true"> 104 <span class="account-promo__emoji-wheel">ð¡</span> 105 <span class="account-promo__emoji-pizza">ð</span> 106 </div> 107 <h2 id="account-promo-title" class="account-promo__title">${escapeHtml(abTitle)}</h2> 108 <p id="account-promo-desc" class="account-promo__desc">${escapeHtml(abDesc)}</p> 109 <ul class="account-promo__perks" aria-label="${escapeAttr(t('accountPromo.perksLabel'))}"> 110 <li><span aria-hidden="true">ð¯</span> ${escapeHtml(t('accountPromo.perk1'))}</li> 111 <li><span aria-hidden="true">ð¸</span> ${escapeHtml(t('accountPromo.perk2'))}</li> 112 <li><span aria-hidden="true">ð</span> ${escapeHtml(t('accountPromo.perk3'))}</li> 113 </ul> 114 <div class="account-promo__actions"> 115 <a class="btn btn-primary account-promo__cta" href="${escapeAttr(registerHref())}"> 116 ${escapeHtml(t('accountPromo.cta'))} 117 </a> 118 <button type="button" class="account-promo__later" data-account-promo-dismiss> 119 ${escapeHtml(t('accountPromo.later'))} 120 </button> 121 </div> 122 </div> 123 `; 124} 125 126function closePromo(root) { 127 if (!root || root.dataset.closing === '1') return; 128 root.dataset.closing = '1'; 129 markShownToday(); 130 root.classList.remove('is-visible'); 131 document.body.classList.remove('account-promo-open');
132 const onEnd = () => { 133 root.removeEventListener('transitionend', onEnd); 134 root.remove(); 135 }; 136 root.addEventListener('transitionend', onEnd); 137 window.setTimeout(() => { 138 if (root.isConnected) root.remove(); 139 }, 400); 140} 141 142function openPromo() { 143 if (document.getElementById('account-promo-root')) return; 144 if (isCustomerLoggedIn() || wasShownToday()) return; 145 146 const root = document.createElement('div'); 147 root.id = 'account-promo-root'; 148 root.className = 'account-promo-root'; 149 root.innerHTML = buildMarkup(getConfig()); 150 document.body.appendChild(root); 151 document.body.classList.add('account-promo-open'); 152 153 const onDismiss = (e) => { 154 if (e.target.closest('[data-account-promo-dismiss]')) { 155 e.preventDefault(); 156 closePromo(root); 157 } 158 }; 159 root.addEventListener('click', onDismiss); 160 161 const onKey = (e) => { 162 if (e.key === 'Escape') { 163 closePromo(root); 164 document.removeEventListener('keydown', onKey); 165 } 166 }; 167 document.addEventListener('keydown', onKey); 168 169 root.querySelector('.account-promo__cta')?.addEventListener('click', () => { 170 markShownToday(); 171 trackAbRegisterClick(); 172 }); 173 174 trackAbImpression('account_popup'); 175 176 requestAnimationFrame(() => { 177 requestAnimationFrame(() => root.classList.add('is-visible')); 178 }); 179 180 root.querySelector('.account-promo__cta')?.focus(); 181} 182 183function tryShowPromo() { 184 if (isCustomerLoggedIn() || wasShownToday()) return; 185 if (hasBlockingOverlay()) { 186 if (retryTimer) window.clearTimeout(retryTimer); 187 retryTimer = window.setTimeout(tryShowPromo, 1500); 188 return; 189 } 190 openPromo(); 191} 192 193function canShowForTrigger(expectedTrigger, config = getConfig()) { 194 const settings = getAccountPromoPopupSettings(config); 195 if (settings.trigger !== expectedTrigger) return false; 196 if (isCustomerLoggedIn() || wasShownToday()) return false; 197 return true; 198} 199 200/** Po zaÅadowaniu strony â tylko gdy trigger = delay. */ 201export function initAccountPromoPopup(config = getConfig()) { 202 const settings = getAccountPromoPopupSettings(config); 203 if (settings.trigger === 'disabled') return; 204 if (settings.trigger === 'open_cart') return; 205 if (settings.trigger === 'after_order') return; 206 if (!canShowForTrigger('delay', config)) return; 207 208 if (delayTimer) window.clearTimeout(delayTimer); 209 delayTimer = window.setTimeout(() => { 210 tryShowPromo(); 211 }, settings.delaySeconds * 1000); 212} 213 214/** WywoÅaj przy otwarciu koszyka â tylko gdy trigger = open_cart. */ 215export function triggerAccountPromoOnCartOpen(config = getConfig()) { 216 if (!canShowForTrigger('open_cart', config)) return; 217 // Krótka pauza, żeby upsell produktowy zdÄ Å¼yÅ siÄ otworzyÄ (wtedy poczekamy). 218 window.setTimeout(() => tryShowPromo(), 600); 219} 220 221/** Strona âdziÄkujemyâ / po udanym zamówieniu â tylko gdy trigger = after_order. */ 222export function triggerAccountPromoAfterOrder(config = getConfig()) { 223 if (!canShowForTrigger('after_order', config)) return; 224 window.setTimeout(() => tryShowPromo(), 1600); 225}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.