1import { getPublicApiUrl } from './site-api.js'; 2import { 3 buildOrderTrackingUrl, 4 clearActiveOrderTracking, 5 listActiveOrderTrackings, 6 saveActiveOrderTracking 7} from './order-confirmation.js'; 8import { getLocale } from './locale.js'; 9import { t } from './i18n.js'; 10 11const POLL_MS = 20000; 12const CHIP_ID = 'header-track-chip'; 13 14let pollTimer = null; 15let inFlight = false; 16let visibilityBound = false; 17 18function shouldSkipPage() { 19 const path = (window.location.pathname || '').replace(/\\/g, '/').toLowerCase(); 20 return /\/sledz\.html$/.test(path) 21 || /\/admin(?:-en)?\.html$/.test(path) 22 || /\/payment\.html$/.test(path); 23} 24 25function escapeHtml(value) { 26 return String(value ?? '') 27 .replaceAll('&', '&') 28 .replaceAll('<', '<') 29 .replaceAll('>', '>') 30 .replaceAll('"', '"'); 31} 32 33function ensureChip() { 34 let el = document.getElementById(CHIP_ID); 35 if (el) return el; 36 37 const actions = document.querySelector('.header__actions'); 38 if (!actions) return null; 39 40 el = document.createElement('a'); 41 el.id = CHIP_ID; 42 el.className = 'header-track'; 43 el.hidden = true; 44 el.setAttribute('aria-label', t('trackFab.label')); 45 el.innerHTML = ` 46 <span class="header-track__stage" aria-hidden="true"> 47 <span class="header-track__road"></span>
48 <span class="header-track__dust"></span> 49 <span class="header-track__scooter">ðµ</span> 50 </span> 51 <span class="header-track__text"> 52 <span class="header-track__title">${escapeHtml(t('trackFab.label'))}</span> 53 <span class="header-track__hint">${escapeHtml(t('trackFab.hint'))}</span> 54 </span> 55 `; 56 57 const cartBtn = actions.querySelector('#cart-btn, .cart-btn'); 58 if (cartBtn) { 59 actions.insertBefore(el, cartBtn); 60 } else { 61 actions.prepend(el); 62 } 63 return el; 64} 65 66function hideChip() { 67 const el = document.getElementById(CHIP_ID); 68 if (!el) return; 69 el.hidden = true; 70 el.removeAttribute('href'); 71 document.body.classList.remove('has-header-track'); 72} 73 74function showChip(href, statusLabel = '') { 75 const el = ensureChip(); 76 if (!el) return; 77 78 el.href = href; 79 el.hidden = false; 80 el.setAttribute('aria-label', t('trackFab.label')); 81 82 const title = el.querySelector('.header-track__title'); 83 if (title) title.textContent = t('trackFab.label'); 84 85 const hint = el.querySelector('.header-track__hint'); 86 if (hint) { 87 hint.textContent = statusLabel || t('trackFab.hint'); 88 } 89 90 document.body.classList.add('has-header-track'); 91} 92 93async function fetchTrackStatus(orderId, token) { 94 const lang = getLocale() === 'en' ? 'en' : 'pl'; 95 const url = `${getPublicApiUrl('track-order.php')}?id=${encodeURIComponent(orderId)}&t=${encodeURIComponent(token)}&lang=${lang}`; 96 const res = await fetch(url, { cache: 'no-store' }); 97 const data = await res.json().catch(() => ({})); 98 return { ok: res.ok, status: res.status, data }; 99} 100 101function resolveHref(entry) { 102 if (entry.url) return entry.url; 103 return buildOrderTrackingUrl(entry.orderId, entry.token, { 104 lang: getLocale() === 'en' ? 'en' : '' 105 }); 106} 107 108async function refreshHeaderTrack() { 109 if (shouldSkipPage() || inFlight) return; 110 inFlight = true; 111 try { 112 const entries = listActiveOrderTrackings(); 113 if (!entries.length) { 114 hideChip(); 115 return; 116 } 117 118 for (const entry of entries) { 119 let result; 120 try { 121 result = await fetchTrackStatus(entry.orderId, entry.token); 122 } catch { 123 continue; 124 } 125 126 if (!result.ok) { 127 if (result.status === 403 || result.status === 404) { 128 clearActiveOrderTracking(entry.orderId); 129 } 130 continue; 131 } 132 133 const data = result.data || {}; 134 if (data.enabled === false) { 135 hideChip(); 136 return; 137 } 138 139 const kitchenStatus = String(data.kitchenStatus || '').toLowerCase(); 140 if (kitchenStatus === 'done') { 141 clearActiveOrderTracking(entry.orderId); 142 continue; 143 } 144 145 const href = resolveHref(entry); 146 saveActiveOrderTracking(entry.orderId, { 147 token: entry.token, 148 url: href, 149 deliveryLocation: entry.deliveryLocation 150 }); 151 152 showChip(href, data.statusLabel || ''); 153 return; 154 } 155 156 hideChip(); 157 } finally { 158 inFlight = false; 159 } 160} 161 162export function initActiveOrderFab() { 163 if (shouldSkipPage()) return; 164 165 ensureChip(); 166 void refreshHeaderTrack(); 167 168 if (pollTimer) window.clearInterval(pollTimer); 169 pollTimer = window.setInterval(() => { 170 void refreshHeaderTrack(); 171 }, POLL_MS); 172 173 if (!visibilityBound) { 174 visibilityBound = true; 175 document.addEventListener('visibilitychange', () => { 176 if (document.visibilityState === 'visible') { 177 void refreshHeaderTrack(); 178 } 179 }); 180 } 181}
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.