1import { 2 customerLogin, 3 customerRegister, 4 customerLogout, 5 customerMe, 6 customerUpdateProfile, 7 customerFetchOrders, 8 customerRepeatOrder, 9 customerCreateReservation, 10 customerRequestPasswordReset, 11 customerRequestPasswordChange, 12 customerResetPassword, 13 customerVerifyEmail, 14 customerResendVerification, 15 customerDeleteAccount, 16 customerSetNewsletter, 17 customerSetBirthday, 18 customerCompleteMissingProfile, 19 getCustomerUser, 20 isCustomerLoggedIn, 21 clearCustomerSession, 22 initCustomerAccountChrome 23} from './customer-auth.js'; 24import { initSocialLogin } from './customer-oauth.js'; 25import { fetchLoyaltyDashboard, loyaltyAction, formatReward } from './loyalty-client.js'; 26import { renderLoyaltyCalendar, syncCalendarAccountTab } from './loyalty-calendar.js'; 27import { 28 buildReferralLink, 29 extractReferralCodeFromSearch, 30 extractAuthTokenFromSearch, 31 formatAccountDate, 32 formatAccountMoney, 33 formatOrderItemsSummary, 34 resolveShopItemDisplay, 35 splitShopByCategory, 36 couponExpiresAt, 37 userNeedsBirthday, 38 userNeedsEmail, 39 userNeedsPhone, 40 userNeedsProfileCompletion, 41 validateRegisterPayload, 42 validatePassword 43} from './account-logic.js'; 44import { 45 composeDeliveryAddress, 46 initAddressAutocompleteInRoot, 47 readAddressPartsFromForm 48} from './address-autocomplete.js'; 49import { 50 renderFortuneWheel, 51 spinFortuneWheelToPrize, 52 getWheelPrizeIndex, 53 isWheelSpinning, 54 resetWheelCelebration 55} from './fortune-wheel.js'; 56import { loadConfig, getConfig, getMenu } from './config.js'; 57import { runOrHardReload, showFatalDataLoadError } from './data-load.js'; 58import { resolvePublicPath } from './locale.js'; 59import { initCookieConsent } from './cookie-consent.js'; 60import { initFooterAuthorCredit } from './footer-author.js'; 61import { 62 subscribeToNewsletter, 63 syncNewsletterStatus, 64 isNewsletterSubscriber, 65 isNewsletterConfigured, 66 getNewsletterCheckboxLabel, 67 getNewsletterConsentText, 68 setNewsletterSubscription, 69 clearNewsletterSubscription 70} from './newsletter.js'; 71 72const $ = (sel, root = document) => root.querySelector(sel); 73 74function escapeHtml(value) { 75 return String(value ?? '') 76 .replaceAll('&', '&') 77 .replaceAll('<', '<') 78 .replaceAll('>', '>') 79 .replaceAll('"', '"'); 80} 81 82let loyaltyCache = null; 83 84function formatMoney(n) { 85 return formatAccountMoney(n); 86} 87 88function formatDate(iso) { 89 return formatAccountDate(iso); 90} 91 92function accountOrderTypeLabel(order) { 93 if (order?.type === 'reservation') return 'Rezerwacja stolika'; 94 if (order?.type === 'table') return 'Stolik'; 95 if (order?.type === 'pickup') return 'Odbiór'; 96 return 'Dostawa'; 97} 98 99function accountOrderStatusLabel(order) { 100 const status = String(order?.status || ''); 101 const map = { 102 completed: 'Zrealizowane', 103 payment_failed: 'PÅatnoÅÄ nieudana', 104 payment_pending: 'Oczekuje na pÅatnoÅÄ', 105 email_failed: 'BÅÄ d e-mail', 106 cancelled: 'Anulowane', 107 reservation_pending: 'Oczekuje na decyzjÄ', 108 reservation_accepted: 'Potwierdzona', 109 reservation_declined: 'Odrzucona' 110 }; 111 return map[status] || status || 'â'; 112} 113 114function showMsg(el, text, isError = false) { 115 if (!el) return; 116 el.hidden = !text; 117 el.textContent = text || ''; 118 el.classList.toggle('is-error', !!isError); 119} 120 121function showPanel(name) { 122 document.querySelectorAll('[data-account-panel]').forEach((panel) => { 123 panel.hidden = panel.getAttribute('data-account-panel') !== name; 124 }); 125} 126 127function showDashTab(tab) { 128 document.querySelectorAll('[data-dash-tab]').forEach((b) => { 129 b.classList.toggle('is-active', b.getAttribute('data-dash-tab') === tab); 130 }); 131 document.querySelectorAll('[data-dash-panel]').forEach((p) => { 132 p.hidden = p.getAttribute('data-dash-panel') !== tab; 133 }); 134} 135 136function fillProfileForm(user) { 137 const form = $('#account-profile-form'); 138 if (!form || !user) return; 139 form.name.value = user.name || ''; 140 form.phone.value = user.phone || ''; 141 const pwdBtn = $('#account-password-change-btn'); 142 if (pwdBtn) { 143 pwdBtn.textContent = user.hasPassword 144 ? 'WyÅlij link do zmiany hasÅa' 145 : 'WyÅlij link do ustawienia hasÅa'; 146 } 147 const emailEl = $('#account-email-display'); 148 if (emailEl) { 149 emailEl.textContent = user.email 150 ? user.email 151 : (userNeedsEmail(user) ? 'Brak â uzupeÅnij w oknie' : ''); 152 } 153 const hello = $('#account-hello'); 154 if (hello) hello.textContent = user.name ? `CzeÅÄ, ${user.name}` : 'Twoje konto'; 155 156 const deletePassWrap = $('#account-delete-password-wrap'); 157 if (deletePassWrap) { 158 deletePassWrap.hidden = user.hasPassword === false; 159 } 160 161 const bday = $('#account-birthday'); 162 const hint = $('#account-birthday-hint'); 163 if (bday) { 164 const value = user.birthday ? String(user.birthday).slice(0, 10) : ''; 165 bday.value = value; 166 bday.readOnly = true; 167 bday.disabled = true; 168 } 169 if (hint) { 170 hint.textContent = user.birthday 171 ? 'Ustawiona przy zakÅadaniu konta â nie można zmieniÄ.' 172 : 'Brak daty â uzupeÅnisz jÄ w oknie, które pojawi siÄ raz.'; 173 } 174} 175 176function showBirthdayModal(visible) { 177 const modal = $('#birthday-modal'); 178 if (!modal) return; 179 modal.hidden = !visible; 180 if (!visible && $('#profile-complete-modal')?.hidden !== false) { 181 document.body.classList.toggle('account-modal-open', false); 182 } else if (visible) { 183 document.body.classList.add('account-modal-open'); 184 } 185 if (visible) { 186 const input = $('#birthday-modal-input'); 187 if (input) { 188 input.value = ''; 189 setTimeout(() => input.focus(), 50); 190 } 191 showMsg($('#birthday-modal-msg'), ''); 192 } 193} 194 195function showProfileCompleteModal(visible, user = null) { 196 const modal = $('#profile-complete-modal'); 197 if (!modal) return; 198 modal.hidden = !visible; 199 document.body.classList.toggle('account-modal-open', !!visible); 200 if (!visible) return; 201 202 const needEmail = userNeedsEmail(user); 203 const needPhone = userNeedsPhone(user); 204 const needBirthday = userNeedsBirthday(user); 205 206 const emailWrap = $('#profile-complete-email-wrap'); 207 const phoneWrap = $('#profile-complete-phone-wrap'); 208 const birthdayWrap = $('#profile-complete-birthday-wrap'); 209 const birthdayNote = $('#profile-complete-birthday-note'); 210 const emailInput = $('#profile-complete-email'); 211 const phoneInput = $('#profile-complete-phone'); 212 const birthdayInput = $('#profile-complete-birthday'); 213 214 if (emailWrap) emailWrap.hidden = !needEmail; 215 if (phoneWrap) phoneWrap.hidden = !needPhone; 216 if (birthdayWrap) birthdayWrap.hidden = !needBirthday; 217 if (birthdayNote) birthdayNote.hidden = !needBirthday; 218 219 if (emailInput) { 220 emailInput.required = needEmail; 221 emailInput.value = ''; 222 } 223 if (phoneInput) { 224 phoneInput.required = needPhone; 225 phoneInput.value = ''; 226 } 227 if (birthdayInput) { 228 birthdayInput.required = needBirthday; 229 birthdayInput.value = ''; 230 } 231 232 showMsg($('#profile-complete-modal-msg'), ''); 233 const first = needEmail ? emailInput : (needPhone ? phoneInput : birthdayInput); 234 if (first) setTimeout(() => first.focus(), 50); 235} 236 237/** UzupeÅnienie e-maila / telefonu / daty urodzenia po Google/FB (gdy brak). */ 238async function ensureProfileComplete(user) { 239 if (!userNeedsProfileCompletion(user)) return user; 240 241 // Stary modal tylko-urodziny: gdy brakuje wyÅÄ cznie birthday i nie ma nowego modala. 242 const hasCombined = !!$('#profile-complete-modal-form'); 243 if (!hasCombined) { 244 return ensureBirthdayOnce(user); 245 } 246 247 return new Promise((resolve) => { 248 const form = $('#profile-complete-modal-form'); 249 const msg = $('#profile-complete-modal-msg'); 250 if (!form) { 251 resolve(user); 252 return; 253 } 254 255 showProfileCompleteModal(true, user); 256 257 const onSubmit = async (e) => { 258 e.preventDefault(); 259 const fd = new FormData(form); 260 showMsg(msg, 'Zapisywanieâ¦');
261 try { 262 const updated = await customerCompleteMissingProfile({ 263 email: String(fd.get('email') || '').trim(), 264 phone: String(fd.get('phone') || '').trim(), 265 birthday: String(fd.get('birthday') || '').trim() 266 }); 267 form.removeEventListener('submit', onSubmit); 268 showProfileCompleteModal(false); 269 fillProfileForm(updated); 270 resolve(updated); 271 } catch (err) { 272 showMsg(msg, err.message || 'Nie udaÅo siÄ zapisaÄ danych.', true); 273 } 274 }; 275 276 form.addEventListener('submit', onSubmit); 277 }); 278} 279 280/** Jednorazowe uzupeÅnienie daty urodzenia (fallback). */ 281async function ensureBirthdayOnce(user) { 282 if (!userNeedsBirthday(user)) return user; 283 return new Promise((resolve) => { 284 const form = $('#birthday-modal-form'); 285 const msg = $('#birthday-modal-msg'); 286 if (!form) { 287 resolve(user); 288 return; 289 } 290 291 showBirthdayModal(true); 292 293 const onSubmit = async (e) => { 294 e.preventDefault(); 295 const birthday = String(new FormData(form).get('birthday') || '').trim(); 296 showMsg(msg, 'Zapisywanieâ¦'); 297 try { 298 const updated = await customerSetBirthday(birthday); 299 form.removeEventListener('submit', onSubmit); 300 showBirthdayModal(false); 301 fillProfileForm(updated); 302 resolve(updated); 303 } catch (err) { 304 showMsg(msg, err.message || 'Nie udaÅo siÄ zapisaÄ daty.', true); 305 } 306 }; 307 308 form.addEventListener('submit', onSubmit); 309 }); 310} 311 312async function renderOrders() { 313 const listEl = $('#account-orders'); 314 const emptyEl = $('#account-orders-empty'); 315 if (!listEl) return; 316 listEl.innerHTML = '<p class="account-muted">Åadowanieâ¦</p>'; 317 if (emptyEl) emptyEl.hidden = true; 318 try { 319 const orders = await customerFetchOrders({ limit: 40 }); 320 if (!orders.length) { 321 listEl.innerHTML = ''; 322 if (emptyEl) emptyEl.hidden = false; 323 return; 324 } 325 listEl.innerHTML = orders.map((order) => { 326 const items = formatOrderItemsSummary(order.items || []); 327 const reservationAt = String(order?.meta?.reservationAt || '').trim(); 328 const reservationRow = (order?.type === 'reservation' && reservationAt) 329 ? `<p class="account-order__items">Termin: ${formatDate(reservationAt)}</p>` 330 : ''; 331 return ` 332 <article class="account-order"> 333 <div class="account-order__top"> 334 <strong>${order.id || ''}</strong> 335 <span>${formatDate(order.createdAt)}</span> 336 </div> 337 <div class="account-order__meta"> 338 <span>${accountOrderTypeLabel(order)}</span> 339 <span>${accountOrderStatusLabel(order)}</span> 340 <span>${formatMoney(order.totals?.total)}</span> 341 </div> 342 ${reservationRow} 343 <p class="account-order__items">${order?.type === 'reservation' ? (order?.meta?.notes || 'â') : (items || 'â')}</p> 344 <div class="account-order__actions"> 345 ${order?.type === 'reservation' ? '' : `<button type="button" class="btn btn-secondary btn-sm" data-repeat-id="${order.id}">Powtórz</button>`} 346 </div> 347 </article>`; 348 }).join(''); 349 listEl.querySelectorAll('[data-repeat-id]').forEach((btn) => { 350 btn.addEventListener('click', async () => { 351 btn.disabled = true; 352 try { 353 const data = await customerRepeatOrder(btn.getAttribute('data-repeat-id')); 354 localStorage.setItem('planeta_repeat_order', JSON.stringify({ 355 items: data.items || [], 356 savedAt: new Date().toISOString() 357 })); 358 window.location.href = 'index.html?repeat=1#menu'; 359 } catch (err) { 360 alert(err.message || 'Nie udaÅo siÄ powtórzyÄ.'); 361 btn.disabled = false; 362 } 363 }); 364 }); 365 } catch (err) { 366 listEl.innerHTML = `<p class="account-msg is-error">${err.message || 'BÅÄ d'}</p>`; 367 } 368} 369 370function bindReservationForm() { 371 const form = $('#account-reservation-form'); 372 const msg = $('#account-reservation-msg'); 373 if (!form) return; 374 form.addEventListener('submit', async (e) => { 375 e.preventDefault(); 376 const fd = new FormData(form); 377 const reservationAt = String(fd.get('reservationAt') || '').trim(); 378 const guests = Number(fd.get('guests') || 2); 379 const notes = String(fd.get('notes') || '').trim(); 380 showMsg(msg, 'WysyÅanie proÅbyâ¦'); 381 try { 382 await customerCreateReservation({ 383 reservationAt, 384 guests, 385 notes, 386 source: 'account_web' 387 }); 388 showMsg(msg, 'WysÅano. Odpowiedź pojawi siÄ na koncie i e-mailu.'); 389 form.reset(); 390 await renderOrders(); 391 } catch (err) { 392 showMsg(msg, err.message || 'Nie udaÅo siÄ wysÅaÄ proÅby.', true); 393 } 394 }); 395} 396 397function renderLoyaltyUI(data) { 398 loyaltyCache = data; 399 const points = data.me?.points ?? 0; 400 const badge = $('#account-points-badge'); 401 if (badge) badge.textContent = `${points} pkt`; 402 403 const summary = $('#points-summary'); 404 if (summary) { 405 summary.textContent = data.enabled 406 ? `Masz ${points} punktów. Za każde 1 zÅ zamówienia dostajesz ${data.pointsPerZloty ?? 1} pkt.` 407 : 'Program lojalnoÅciowy jest obecnie wyÅÄ czony.'; 408 } 409 410 const couponsEl = $('#account-coupons'); 411 if (couponsEl) { 412 const coupons = data.coupons || []; 413 couponsEl.innerHTML = coupons.length 414 ? coupons.map((c) => { 415 const expires = couponExpiresAt(c); 416 const until = expires 417 ? `<span class="account-coupon__until">Do kiedy: ${escapeHtml(formatDate(expires))}</span>` 418 : '<span class="account-coupon__until account-muted">Bez terminu ważnoÅci</span>'; 419 return ` 420 <div class="account-coupon"> 421 <strong>${escapeHtml(c.title || c.code)}</strong>
422 <span>${escapeHtml(formatReward(c))}</span> 423 ${until} 424 <code>${escapeHtml(c.code)}</code> 425 <button type="button" class="btn btn-secondary btn-sm" data-copy-code="${escapeHtml(c.code)}">Kopiuj do koszyka</button> 426 </div>`; 427 }).join('') 428 : '<p class="account-muted">Brak aktywnych kuponów.</p>'; 429 couponsEl.querySelectorAll('[data-copy-code]').forEach((btn) => { 430 btn.addEventListener('click', async () => { 431 const code = btn.getAttribute('data-copy-code'); 432 const coupon = (data.coupons || []).find((c) => c.code === code); 433 try { 434 localStorage.setItem('promoCode', code); 435 } catch {} 436 if (coupon?.rewardType === 'free_item' && coupon.itemId) { 437 const { queueLoyaltyFreeItem } = await import('./loyalty-free-cart.js'); 438 queueLoyaltyFreeItem({ 439 itemId: coupon.itemId, 440 couponCode: coupon.code, 441 title: coupon.title || '', 442 size: coupon.itemSize || null 443 }); 444 } 445 window.location.href = 'index.html#menu'; 446 }); 447 }); 448 } 449 450 const ledgerEl = $('#account-ledger'); 451 if (ledgerEl) { 452 const ledger = Array.isArray(data.ledger) ? data.ledger : []; 453 ledgerEl.innerHTML = ledger.length 454 ? `<ul class="account-ledger">${ledger.map((row) => { 455 const delta = Number(row.delta) || 0; 456 const sign = delta > 0 ? '+' : ''; 457 const cls = delta >= 0 ? 'is-plus' : 'is-minus'; 458 const label = escapeHtml(row.reasonLabel || row.reason || 'Operacja'); 459 const when = escapeHtml(formatDate(row.createdAt)); 460 return `<li class="account-ledger__row"> 461 <span class="account-ledger__delta ${cls}">${sign}${delta} pkt</span> 462 <span class="account-ledger__meta"> 463 <strong>${label}</strong> 464 <span class="account-muted">${when}</span> 465 </span> 466 </li>`; 467 }).join('')}</ul>` 468 : '<p class="account-muted">Brak historii punktów.</p>'; 469 } 470 471 const shopCouponsEl = $('#account-shop-coupons'); 472 const shopMenuEl = $('#account-shop-menu'); 473 if (shopCouponsEl || shopMenuEl) { 474 const menu = getMenu(); 475 const { coupons, menu: menuShop } = splitShopByCategory(data.shop || []); 476 477 const bindRedeem = (root) => { 478 root?.querySelectorAll('[data-redeem]').forEach((btn) => { 479 btn.addEventListener('click', async () => { 480 btn.disabled = true; 481 try { 482 const result = await loyaltyAction('redeemShop', { shopItemId: btn.getAttribute('data-redeem') }); 483 const coupon = result?.coupon || null; 484 if (coupon?.rewardType === 'free_item' && coupon.itemId) { 485 const { queueLoyaltyFreeItem } = await import('./loyalty-free-cart.js'); 486 queueLoyaltyFreeItem({ 487 itemId: coupon.itemId, 488 couponCode: coupon.code, 489 title: coupon.title || '', 490 size: coupon.itemSize || null 491 }); 492 window.location.href = 'index.html#menu'; 493 return; 494 } 495 if (coupon?.code) { 496 try { localStorage.setItem('promoCode', coupon.code); } catch {} 497 } 498 await refreshLoyalty(); 499 alert('Nagroda wymieniona â kupon jest w sekcji âTwoje kuponyâ.'); 500 } catch (err) { 501 alert(err.message || 'Nie udaÅo siÄ wymieniÄ.'); 502 btn.disabled = false; 503 } 504 }); 505 }); 506 }; 507 508 if (shopCouponsEl) { 509 shopCouponsEl.innerHTML = coupons.length 510 ? coupons.map((s) => { 511 const display = resolveShopItemDisplay(s, menu); 512 const detail = s.rewardType === 'percent' 513 ? `-${s.rewardValue}%` 514 : `-${s.rewardValue} zÅ`; 515 return ` 516 <article class="account-shop-item"> 517 <div> 518 <strong>${escapeHtml(display.name)}</strong> 519 <p class="account-muted">${s.costPoints} pkt · ${detail}${s.minOrder ? ` (min. ${s.minOrder} zÅ)` : ''}</p> 520 </div> 521 <button type="button" class="btn btn-primary btn-sm" data-redeem="${escapeHtml(s.id)}" ${points < s.costPoints ? 'disabled' : ''}>WymieÅ</button> 522 </article>`; 523 }).join('') 524 : '<p class="account-muted">Brak kuponów w sklepiku.</p>'; 525 bindRedeem(shopCouponsEl); 526 } 527 528 if (shopMenuEl) { 529 shopMenuEl.innerHTML = menuShop.length 530 ? menuShop.map((s) => { 531 const display = resolveShopItemDisplay(s, menu); 532 const img = display.image 533 ? `<img class="account-shop-item__img" src="${escapeHtml(resolvePublicPath(display.image))}" alt="" loading="lazy">` 534 : `<div class="account-shop-item__img account-shop-item__img--empty" aria-hidden="true"></div>`; 535 return ` 536 <article class="account-shop-item account-shop-item--menu"> 537 ${img} 538 <div class="account-shop-item__body"> 539 <strong>${escapeHtml(display.name)}</strong> 540 <p class="account-muted">
540${s.costPoints} pkt · gratis${s.minOrder ? ` (min. ${s.minOrder} zÅ)` : ''}</p> 541 <button type="button" class="btn btn-primary btn-sm" data-redeem="${escapeHtml(s.id)}" ${points < s.costPoints ? 'disabled' : ''}>WymieÅ</button> 542 </div> 543 </article>`; 544 }).join('') 545 : '<p class="account-muted">Brak pozycji menu w sklepiku.</p>'; 546 bindRedeem(shopMenuEl); 547 } 548 549 document.querySelectorAll('[data-shop-tab]').forEach((btn) => { 550 btn.onclick = () => { 551 const tab = btn.getAttribute('data-shop-tab'); 552 document.querySelectorAll('[data-shop-tab]').forEach((b) => { 553 const on = b === btn; 554 b.classList.toggle('is-active', on); 555 b.setAttribute('aria-selected', on ? 'true' : 'false'); 556 }); 557 document.querySelectorAll('[data-shop-panel]').forEach((panel) => { 558 panel.hidden = panel.getAttribute('data-shop-panel') !== tab; 559 }); 560 }; 561 }); 562 } 563 564 const favEl = $('#account-favorites'); 565 if (favEl) { 566 const favs = data.favorites || []; 567 favEl.innerHTML = favs.length 568 ? `<ul>${favs.map((id) => `<li><a href="index.html#menu">${id}</a> 569 <button type="button" class="btn btn-secondary btn-sm" data-unfav="${id}">UsuÅ</button></li>`).join('')}</ul>` 570 : '<p class="account-muted">Brak ulubionych. Dodaj je z menu na stronie gÅównej.</p>'; 571 favEl.querySelectorAll('[data-unfav]').forEach((btn) => { 572 btn.addEventListener('click', async () => { 573 await loyaltyAction('toggleFavorite', { itemId: btn.getAttribute('data-unfav') }); 574 await refreshLoyalty(); 575 }); 576 }); 577 } 578 579 const addrForm = $('#account-addresses-form'); 580 if (addrForm) { 581 const addrs = data.addresses || []; 582 addrForm.innerHTML = [0, 1, 2].map((i) => { 583 const a = addrs[i] || { label: i === 0 ? 'Dom' : i === 1 ? 'Praca' : `Adres ${i + 1}`, city: '', street: '', number: '', apartment: '' }; 584 return ` 585 <div class="account-addr-slot input-group input-group--address" data-address-group data-addr-slot="${i}"> 586 <label class="account-addr-label">Etykieta 587 <input type="text" name="label${i}" value="${escapeHtml(a.label || '')}" placeholder="Dom / Praca"> 588 </label> 589 <div class="address-label-row"> 590 <span class="address-group-label">Adres dostawy</span> 591 </div> 592 <div class="address-fields"> 593 <div class="address-field address-field--city"> 594 <label for="addr${i}-city">Miasto</label> 595 <input type="text" id="addr${i}-city" name="city${i}" data-address-part="city" 596 value="${escapeHtml(a.city || '')}" placeholder="ElblÄ g" autocomplete="address-level2"> 597 </div> 598 <div class="address-field address-field--street"> 599 <label for="addr${i}-street">Ulica</label> 600 <input type="text" id="addr${i}-street" name="street${i}" data-address-part="street" 601 value="${escapeHtml(a.street || '')}" placeholder="np. DÄ bka" autocomplete="off" autocorrect="off" autocapitalize="words" spellcheck="false" inputmode="text"> 602 </div> 603 <div class="address-field address-field--number"> 604 <label for="addr${i}-number">Numer</label> 605 <input type="text" id="addr${i}-number" name="number${i}" data-address-part="number" 606 value="${escapeHtml(a.number || '')}" placeholder="140" autocomplete="off"> 607 </div> 608 <div class="address-field address-field--apt"> 609 <label for="addr${i}-apt">Nr lokalu</label> 610 <input type="text" id="addr${i}-apt" name="apartment${i}" data-address-part="apartment" 611 value="${escapeHtml(a.apartment || '')}" placeholder="opcjonalnie" autocomplete="off"> 612 </div> 613 </div> 614 <small class="input-hint">Podaj miasto, ulicÄ i numer domu. Numer lokalu jest opcjonalny.</small> 615 </div>`; 616 }).join(''); 617 618 void (async () => { 619 try { 620 await loadConfig(); 621 } catch (err) { 622 console.warn(err); 623 } 624 const config = getConfig(); 625 if (!config) return; 626 addrForm.querySelectorAll('[data-address-group]').forEach((group) => { 627 void initAddressAutocompleteInRoot(group, config); 628 }); 629 })(); 630 } 631 632 const recsEl = $('#account-recs'); 633 if (recsEl) { 634 const recs = data.recommendations || []; 635 recsEl.innerHTML = recs.length 636 ? `<ul>${recs.map((r) => `<li><strong>${r.name}</strong> · zamawiane ${r.count}à · <a href="index.html#menu">Do menu</a></li>`).join('')}</ul>` 637 : '<p class="account-muted">Za maÅo historii, żeby coÅ poleciÄ â zÅóż pierwsze zamówienia.</p>'; 638 } 639 640 const wheelStage = $('#fortune-wheel-stage'); 641 if (wheelStage && !isWheelSpinning()) { 642 const nextPrizes = data.wheel?.prizes || []; 643 const prev = wheelStage._wheelPrizes || []; 644 const same = 645 prev.length === nextPrizes.length 646 && nextPrizes.every((p, i) => p.id === prev[i]?.id && p.label === prev[i]?.label); 647 // Nie przebudowuj koÅa po spinie, jeÅli lista nagród siÄ nie zmieniÅa â zachowaj kÄ t i efekt wygranej 648 if (!same || !wheelStage.querySelector('[data-wheel-root]')) { 649 renderFortuneWheel(wheelStage, nextPrizes); 650 } else { 651 wheelStage._wheelPrizes = nextPrizes; 652 wheelStage.dataset.prizeCount = String(nextPrizes.length || 1); 653 } 654 } 655 const wheelStatus = $('#wheel-status'); 656 if (wheelStatus && data.wheel) { 657 wheelStatus.textContent = data.wheel.enabled 658 ? `Możesz krÄciÄ raz na ${data.wheel.cooldownDays || 7} dni. Wskaźnik u góry pokazuje wygranÄ .`
659 : 'KoÅo fortuny jest wyÅÄ czone.'; 660 } 661 const spinBtn = $('#wheel-spin'); 662 if (spinBtn) { 663 spinBtn.disabled = !data.wheel?.enabled || isWheelSpinning(); 664 } 665 666 const refCode = $('#referral-code'); 667 const refLink = $('#referral-link'); 668 if (refCode) refCode.textContent = data.referral?.code || 'â'; 669 if (refLink) { 670 const href = buildReferralLink(window.location.origin, data.referral?.code || ''); 671 refLink.href = href; 672 refLink.textContent = href; 673 } 674 const refRewards = $('#referral-rewards'); 675 const refRules = $('#referral-rules'); 676 if (refRewards || refRules) { 677 const ref = data.referral || {}; 678 const fmt = (type, value) => { 679 const t = String(type || '').toLowerCase(); 680 const v = Number(value) || 0; 681 if (t === 'percent') return `â${v}%`; 682 if (t === 'fixed') return `â${v.toFixed(0)} zÅ`; 683 return v ? String(v) : 'nagrodÄ lojalnoÅciowÄ '; 684 }; 685 if (refRewards) { 686 refRewards.innerHTML = ref.enabled === false 687 ? 'Program poleceÅ jest obecnie wyÅÄ czony.' 688 : `Ty (za polecenie): <strong>${escapeHtml(fmt(ref.referrerType, ref.referrerValue))}</strong><br> 689 Znajomy (za rejestracjÄ z kodem): <strong>${escapeHtml(fmt(ref.refereeType, ref.refereeValue))}</strong>`; 690 } 691 if (refRules) { 692 const days = Number(ref.validDays) || 30; 693 const min = Number(ref.minOrder) || 0; 694 const cooldown = Number(ref.referrerCooldownHours) || 24; 695 refRules.textContent = ref.enabled === false 696 ? '' 697 : `Kupony ważne ${days} dni${min > 0 ? ` · min. zamówienie ${min.toFixed(0)} zÅ` : ''}. Kod tylko przy rejestracji (raz na konto znajomego). Ty możesz polecaÄ wielu znajomych â Twoja nagroda max raz na ${cooldown} h.`; 698 } 699 } 700 701 const promo = $('#account-personal-promo'); 702 if (promo) { 703 if (data.personalPromo) { 704 promo.hidden = false; 705 promo.innerHTML = `<strong>${data.personalPromo.message}</strong> 706 <button type="button" class="btn btn-primary btn-sm" data-copy-code="${data.personalPromo.code}">Użyj kodu ${data.personalPromo.code}</button>`; 707 promo.querySelector('[data-copy-code]')?.addEventListener('click', () => { 708 try { localStorage.setItem('promoCode', data.personalPromo.code); } catch {} 709 window.location.href = 'index.html#menu'; 710 }); 711 } else { 712 promo.hidden = true; 713 promo.innerHTML = ''; 714 } 715 } 716 717 const reminder = $('#account-reminder'); 718 if (reminder) { 719 if (data.reminder && !data.personalPromo) { 720 reminder.hidden = false; 721 reminder.innerHTML = `<strong>${data.reminder.message}</strong> 722 <a class="btn btn-secondary btn-sm" href="index.html#menu">Zamów teraz</a>`; 723 } else { 724 reminder.hidden = true; 725 reminder.innerHTML = ''; 726 } 727 } 728 729 const bday = $('#account-birthday'); 730 if (bday && data.me?.birthday) { 731 bday.value = String(data.me.birthday).slice(0, 10); 732 bday.readOnly = true; 733 bday.disabled = true; 734 } 735 736 const showCalendar = syncCalendarAccountTab(data); 737 if (!showCalendar && document.querySelector('[data-dash-tab="calendar"].is-active')) { 738 showDashTab('points'); 739 } 740 const calRoot = $('#account-calendar'); 741 if (calRoot && showCalendar) { 742 renderLoyaltyCalendar(calRoot, data.calendar, { 743 onClaimed: async (result) => { 744 const extra = result?.reward?.kind === 'points' 745 ? ` (+${result.reward.points} pkt)` 746 : (result?.reward?.coupon?.code ? ` · kod ${result.reward.coupon.code}` : ''); 747 await refreshLoyalty(); 748 const msg = $('#loycal-msg'); 749 if (msg) { 750 msg.hidden = false; 751 msg.classList.remove('is-error'); 752 msg.textContent = (result?.message || 'Nagroda odebrana.') + extra; 753 } 754 } 755 }); 756 } 757} 758 759async function refreshLoyalty() { 760 try { 761 const data = await fetchLoyaltyDashboard(); 762 renderLoyaltyUI(data); 763 } catch (err) { 764 console.warn('[loyalty]', err); 765 } 766} 767 768async function enterLoggedIn(user) { 769 showPanel('dashboard'); 770 fillProfileForm(user); 771 const completed = await ensureProfileComplete(user || getCustomerUser()); 772 fillProfileForm(completed || user); 773 showDashTab('orders'); 774 await Promise.all([ 775 renderOrders(), 776 refreshLoyalty(), 777 refreshProfileNewsletter(completed || user || getCustomerUser()) 778 ]); 779} 780 781function socialLegalAccepted() { 782 return !!$('#account-social-legal')?.checked; 783} 784 785function syncSocialLegalGate() {
786 const row = $('#account-social-row'); 787 if (!row) return; 788 row.classList.toggle('is-blocked', !socialLegalAccepted()); 789} 790 791async function refreshProfileNewsletter(user) { 792 const statusEl = $('#profile-newsletter-status'); 793 const wrap = $('#profile-newsletter-wrap'); 794 const btn = $('#profile-newsletter-btn'); 795 const unsubBtn = $('#profile-newsletter-unsub'); 796 if (!statusEl) return; 797 798 const email = String(user?.email || getCustomerUser()?.email || '').trim(); 799 if (!email) { 800 statusEl.textContent = 'Brak e-maila na koncie â newsletter niedostÄpny.'; 801 if (wrap) wrap.hidden = true; 802 if (btn) btn.hidden = true; 803 if (unsubBtn) unsubBtn.hidden = true; 804 return; 805 } 806 807 const config = getConfig() || await loadConfig(); 808 if (!isNewsletterConfigured(config)) { 809 statusEl.textContent = 'Newsletter jest obecnie wyÅÄ czony.'; 810 if (wrap) wrap.hidden = true; 811 if (btn) btn.hidden = true; 812 if (unsubBtn) unsubBtn.hidden = true; 813 return; 814 } 815 816 statusEl.textContent = 'Sprawdzanie statusuâ¦'; 817 const subscribed = await syncNewsletterStatus(email) || isNewsletterSubscriber(email); 818 if (subscribed) { 819 statusEl.textContent = 'JesteÅ zapisany do newslettera. Możesz wypisaÄ siÄ poniżej.'; 820 if (wrap) wrap.hidden = true; 821 if (btn) btn.hidden = true; 822 if (unsubBtn) unsubBtn.hidden = false; 823 return; 824 } 825 826 statusEl.textContent = 'Nie jesteÅ jeszcze zapisany â możesz doÅÄ czyÄ poniżej.'; 827 if (wrap) { 828 wrap.hidden = false; 829 const strong = wrap.querySelector('strong'); 830 const small = wrap.querySelector('small'); 831 if (strong) strong.textContent = getNewsletterCheckboxLabel(config); 832 if (small) small.textContent = getNewsletterConsentText(config); 833 } 834 if (btn) btn.hidden = false; 835 if (unsubBtn) unsubBtn.hidden = true; 836} 837 838function showAuthForm(name) { 839 document.querySelectorAll('[data-account-tab]').forEach((b) => { 840 const tab = b.getAttribute('data-account-tab'); 841 b.classList.toggle('is-active', tab === name || (name === 'forgot' && tab === 'login') || (name === 'reset' && tab === 'login')); 842 }); 843 document.querySelectorAll('[data-account-form]').forEach((form) => { 844 form.hidden = form.getAttribute('data-account-form') !== name; 845 }); 846 const verifyBanner = $('#account-verify-banner'); 847 if (verifyBanner) verifyBanner.hidden = true; 848 const loginVerify = $('#account-login-verify'); 849 if (loginVerify && name !== 'login') loginVerify.hidden = true; 850 const registerVerify = $('#account-register-verify'); 851 if (registerVerify && name !== 'register') registerVerify.hidden = true; 852} 853 854function cleanAuthQueryParams() { 855 try { 856 const u = new URL(window.location.href); 857 let changed = false; 858 for (const key of ['verify', 'reset']) { 859 if (u.searchParams.has(key)) { 860 u.searchParams.delete(key); 861 changed = true; 862 } 863 } 864 if (changed) { 865 window.history.replaceState(null, '', u.pathname + (u.search || '') + (u.hash || '')); 866 } 867 } catch {} 868} 869 870async function handleVerifyFromUrl() { 871 const found = extractAuthTokenFromSearch(window.location.search, ['verify']); 872 if (!found?.token) return false; 873 874 showPanel('auth'); 875 showAuthForm('login'); 876 const banner = $('#account-verify-banner'); 877 const msg = $('#account-verify-msg'); 878 if (banner) banner.hidden = false; 879 document.querySelectorAll('[data-account-form]').forEach((form) => { form.hidden = true; }); 880 showMsg(msg, 'Potwierdzanie adresu e-mailâ¦'); 881 882 try { 883 const data = await customerVerifyEmail(found.token); 884 cleanAuthQueryParams(); 885 showMsg(msg, data.message || 'E-mail potwierdzony. JesteÅ zalogowany.'); 886 await enterLoggedIn(data.customer || getCustomerUser()); 887 const ref = ($('#register-ref')?.value || sessionStorage.getItem('planeta_referral_code') || '').trim(); 888 if (ref) { 889 try { await loyaltyAction('applyReferral', { code: ref }); } catch {} 890 } 891 return true; 892 } catch (err) { 893 cleanAuthQueryParams(); 894 showMsg(msg, err.message || 'Link weryfikacji jest nieprawidÅowy lub wygasÅ.', true); 895 showAuthForm('login'); 896 if (banner) banner.hidden = false;
897 return true; 898 } 899} 900 901function handleResetFromUrl() { 902 const found = extractAuthTokenFromSearch(window.location.search, ['reset']); 903 if (!found?.token) return null; 904 showPanel('auth'); 905 showAuthForm('reset'); 906 const form = $('#account-reset-form'); 907 if (form) form.dataset.resetToken = found.token; 908 return found.token; 909} 910 911async function resendVerificationForEmail(email, msgEl, actionsEl) { 912 const normalized = String(email || '').trim(); 913 if (!normalized) { 914 showMsg(msgEl, 'Podaj e-mail, na który mamy wysÅaÄ link.', true); 915 return; 916 } 917 showMsg(msgEl, 'WysyÅanie linkuâ¦'); 918 try { 919 const data = await customerResendVerification(normalized); 920 showMsg(msgEl, data.message || 'JeÅli konto wymaga potwierdzenia, wysÅaliÅmy nowy link.'); 921 if (actionsEl) actionsEl.hidden = false; 922 } catch (err) { 923 showMsg(msgEl, err.message || 'Nie udaÅo siÄ wysÅaÄ linku.', true); 924 } 925} 926 927async function subscribeAccountNewsletter({ email, name = '', phone = '', source = 'account' }) { 928 const result = await subscribeToNewsletter({ email, name, phone, source }); 929 if (!result.ok) { 930 throw new Error(result.error || 'Nie udaÅo siÄ zapisaÄ do newslettera.'); 931 } 932 return result; 933} 934 935function captureReferralFromUrl() { 936 const ref = extractReferralCodeFromSearch(window.location.search); 937 if (ref) { 938 try { sessionStorage.setItem('planeta_referral_code', ref); } catch {} 939 const input = $('#register-ref'); 940 if (input) input.value = ref; 941 } else { 942 try { 943 const saved = sessionStorage.getItem('planeta_referral_code'); 944 if (saved && $('#register-ref')) $('#register-ref').value = saved; 945 } catch {} 946 } 947} 948 949async function boot() { 950 initCookieConsent(); 951 initFooterAuthorCredit(); 952 initCustomerAccountChrome(); 953 captureReferralFromUrl(); 954 try { 955 await runOrHardReload(() => loadConfig()); 956 } catch (err) { 957 showFatalDataLoadError(err); 958 return; 959 } 960 const loginForm = $('#account-login-form'); 961 const registerForm = $('#account-register-form'); 962 const forgotForm = $('#account-forgot-form'); 963 const resetForm = $('#account-reset-form'); 964 const profileForm = $('#account-profile-form'); 965 const logoutBtn = $('#account-logout'); 966 const loginMsg = $('#account-login-msg'); 967 const registerMsg = $('#account-register-msg'); 968 const forgotMsg = $('#account-forgot-msg'); 969 const resetMsg = $('#account-reset-msg'); 970 const profileMsg = $('#account-profile-msg'); 971 const socialMsg = $('#account-social-msg'); 972 let pendingVerifyEmail = ''; 973 bindReservationForm(); 974 975 document.querySelectorAll('[data-account-tab]').forEach((btn) => { 976 btn.addEventListener('click', () => { 977 const tab = btn.getAttribute('data-account-tab'); 978 showAuthForm(tab); 979 showMsg(loginMsg, ''); 980 showMsg(registerMsg, ''); 981 showMsg(forgotMsg, ''); 982 showMsg(resetMsg, ''); 983 const loginVerify = $('#account-login-verify'); 984 if (loginVerify) loginVerify.hidden = true; 985 const registerVerify = $('#account-register-verify'); 986 if (registerVerify) registerVerify.hidden = true; 987 }); 988 }); 989 990 document.querySelectorAll('[data-dash-tab]').forEach((btn) => { 991 btn.addEventListener('click', () => showDashTab(btn.getAttribute('data-dash-tab'))); 992 }); 993 994 void initSocialLogin({ 995 googleContainer: $('#google-login-btn'), 996 facebookButton: $('#facebook-login-btn'), 997 onSuccess: async (data) => { 998 if (!socialLegalAccepted()) { 999 showMsg(socialMsg, 'Zaznacz akceptacjÄ Regulaminu i Polityki prywatnoÅci.', true); 1000 try { 1001 await customerLogout(); 1002 } catch { 1003 clearCustomerSession(); 1004 } 1005 return; 1006 } 1007 try { localStorage.setItem('planeta_account_legal_ok', '1'); } catch {} 1008 showMsg(socialMsg, ''); 1009 await enterLoggedIn(data.customer); 1010 const ref = ($('#register-ref')?.value || sessionStorage.getItem('planeta_referral_code') || '').trim(); 1011 if (ref) { 1012 try { await loyaltyAction('applyReferral', { code: ref }); } catch {} 1013 } 1014 }, 1015 onError: (err) => showMsg(socialMsg, err?.message || 'BÅÄ d', true) 1016 }); 1017 1018 const socialLegal = $('#account-social-legal'); 1019 try { 1020 if (socialLegal && localStorage.getItem('planeta_account_legal_ok') === '1') { 1021 socialLegal.checked = true; 1022 } 1023 } catch {} 1024 socialLegal?.addEventListener('change', () => { 1025 if (socialLegal.checked) {
1026 try { localStorage.setItem('planeta_account_legal_ok', '1'); } catch {} 1027 } 1028 syncSocialLegalGate(); 1029 }); 1030 syncSocialLegalGate(); 1031 1032 // Blokada Google/FB bez akceptacji regulaminu 1033 $('#account-social')?.addEventListener('click', (e) => { 1034 if (socialLegalAccepted()) return; 1035 const target = e.target; 1036 if (!(target instanceof Element)) return; 1037 if (target.closest('#account-social-legal') || target.closest('label[for="account-social-legal"]')) return; 1038 if (target.closest('#google-login-btn') || target.closest('#facebook-login-btn') || target.closest('.account-social__row')) { 1039 e.preventDefault(); 1040 e.stopPropagation(); 1041 showMsg(socialMsg, 'Najpierw zaakceptuj Regulamin i PolitykÄ prywatnoÅci.', true); 1042 } 1043 }, true); 1044 1045 $('#account-forgot-toggle')?.addEventListener('click', () => { 1046 const email = String(loginForm?.email?.value || '').trim(); 1047 showAuthForm('forgot'); 1048 if (forgotForm && email) forgotForm.email.value = email; 1049 showMsg(forgotMsg, ''); 1050 }); 1051 1052 $('#account-forgot-back')?.addEventListener('click', () => { 1053 showAuthForm('login'); 1054 }); 1055 1056 forgotForm?.addEventListener('submit', async (e) => { 1057 e.preventDefault(); 1058 const fd = new FormData(forgotForm); 1059 showMsg(forgotMsg, 'WysyÅanieâ¦'); 1060 try { 1061 const data = await customerRequestPasswordReset(String(fd.get('email') || '')); 1062 showMsg(forgotMsg, data.message || 'JeÅli konto istnieje, wysÅaliÅmy link do resetu.'); 1063 } catch (err) { 1064 showMsg(forgotMsg, err.message || 'Nie udaÅo siÄ wysÅaÄ linku.', true); 1065 } 1066 }); 1067 1068 resetForm?.addEventListener('submit', async (e) => { 1069 e.preventDefault(); 1070 const fd = new FormData(resetForm); 1071 const password = String(fd.get('password') || ''); 1072 const passwordConfirm = String(fd.get('passwordConfirm') || ''); 1073 const token = String(resetForm.dataset.resetToken || '').trim(); 1074 const passErr = validatePassword(password); 1075 if (passErr) { 1076 showMsg(resetMsg, passErr, true); 1077 return; 1078 } 1079 if (password !== passwordConfirm) { 1080 showMsg(resetMsg, 'HasÅa nie sÄ takie same.', true); 1081 return; 1082 } 1083 if (!token) { 1084 showMsg(resetMsg, 'Brak tokenu resetu. PoproÅ o nowy link.', true); 1085 return; 1086 } 1087 showMsg(resetMsg, 'Zapisywanieâ¦'); 1088 try { 1089 const data = await customerResetPassword({ token, password }); 1090 cleanAuthQueryParams(); 1091 delete resetForm.dataset.resetToken; 1092 showMsg(resetMsg, data.message || 'HasÅo zostaÅo zmienione. Zaloguj siÄ nowym hasÅem.'); 1093 setTimeout(() => showAuthForm('login'), 1200); 1094 } catch (err) { 1095 showMsg(resetMsg, err.message || 'Nie udaÅo siÄ zmieniÄ hasÅa.', true); 1096 } 1097 }); 1098 1099 $('#account-resend-verify')?.addEventListener('click', async () => { 1100 const email = pendingVerifyEmail || String(loginForm?.email?.value || '').trim(); 1101 await resendVerificationForEmail(email, loginMsg, $('#account-login-verify')); 1102 }); 1103 1104 $('#account-register-resend')?.addEventListener('click', async () => { 1105 const email = pendingVerifyEmail || String(registerForm?.email?.value || '').trim(); 1106 await resendVerificationForEmail(email, registerMsg, $('#account-register-verify')); 1107 }); 1108 1109 loginForm?.addEventListener('submit', async (e) => { 1110 e.preventDefault(); 1111 const fd = new FormData(loginForm); 1112 const email = String(fd.get('email') || ''); 1113 showMsg(loginMsg, 'Logowanieâ¦'); 1114 const loginVerify = $('#account-login-verify'); 1115 if (loginVerify) loginVerify.hidden = true; 1116 try { 1117 const data = await customerLogin({ 1118 email, 1119 password: String(fd.get('password') || '') 1120 }); 1121 pendingVerifyEmail = ''; 1122 await enterLoggedIn(data.customer); 1123 } catch (err) { 1124 showMsg(loginMsg, err.message || 'Logowanie nieudane.', true); 1125 if (err?.status === 403) { 1126 pendingVerifyEmail = email; 1127 if (loginVerify) loginVerify.hidden = false; 1128 } 1129 } 1130 }); 1131 1132 registerForm?.addEventListener('submit', async (e) => { 1133 e.preventDefault(); 1134 const fd = new FormData(registerForm); 1135 const legalAccepted = !!$('#register-legal-accept')?.checked; 1136 const validated = validateRegisterPayload({ 1137 name: String(fd.get('name') || ''), 1138 email: String(fd.get('email') || ''), 1139 phone: String(fd.get('phone') || ''), 1140 address: String(fd.get('address') || ''), 1141 birthday: String(fd.get('birthday') || ''), 1142 password: String(fd.get('password') || ''), 1143 legalAccepted, 1144 referralCode: String(fd.get('referralCode') || '') 1145 }); 1146 if (!validated.ok) { 1147 showMsg(registerMsg, validated.error || 'UzupeÅnij formularz.', true); 1148 if (!legalAccepted) { 1149 $('#register-legal-accept')?.focus(); 1150 } 1151 return; 1152 } 1153 showMsg(registerMsg, 'Tworzenie kontaâ¦'); 1154 const registerVerify = $('#account-register-verify'); 1155 if (registerVerify) registerVerify.hidden = true; 1156 try { 1157 const { payload } = validated; 1158 const data = await customerRegister({ 1159 name: payload.name, 1160 email: payload.email, 1161 phone: payload.phone, 1162 address: payload.address, 1163 birthday: payload.birthday, 1164 password: payload.password, 1165 legalAccepted: true 1166 }); 1167 if ($('#register-newsletter')?.checked) { 1168 try { 1169 await subscribeAccountNewsletter({ 1170 email: payload.email, 1171 name: payload.name, 1172 phone: payload.phone, 1173 source: 'account_register' 1174 }); 1175 } catch (nlErr) { 1176 console.warn('[newsletter]', nlErr); 1177 } 1178 }
1179 try { localStorage.setItem('planeta_account_legal_ok', '1'); } catch {} 1180 1181 if (data.needsEmailVerification || !data.token) { 1182 pendingVerifyEmail = payload.email; 1183 const mailFailed = data.mailSent === false; 1184 showMsg( 1185 registerMsg, 1186 data.message 1187 || (mailFailed 1188 ? 'Konto utworzone, ale e-mail weryfikacyjny nie wyszedÅ. Kliknij âWyÅlij ponownieâ.' 1189 : 'Konto utworzone. Sprawdź skrzynkÄ e-mail i potwierdź adres, aby siÄ zalogowaÄ.'), 1190 mailFailed 1191 ); 1192 if (registerVerify) registerVerify.hidden = false; 1193 return; 1194 } 1195 1196 await enterLoggedIn(data.customer); 1197 const ref = payload.referralCode; 1198 if (ref) { 1199 try { 1200 const r = await loyaltyAction('applyReferral', { code: ref }); 1201 showMsg(registerMsg, r.message || 'Konto utworzone + kod polecenia.'); 1202 } catch (err) { 1203 showMsg(registerMsg, `Konto OK. Kod polecenia: ${err.message}`, true); 1204 } 1205 } 1206 } catch (err) { 1207 showMsg(registerMsg, err.message || 'Rejestracja nieudana.', true); 1208 } 1209 }); 1210 1211 profileForm?.addEventListener('submit', async (e) => { 1212 e.preventDefault(); 1213 const fd = new FormData(profileForm); 1214 showMsg(profileMsg, 'Zapisywanieâ¦'); 1215 try { 1216 const user = await customerUpdateProfile({ 1217 name: String(fd.get('name') || ''), 1218 phone: String(fd.get('phone') || '') 1219 }); 1220 fillProfileForm(user); 1221 showMsg(profileMsg, 'Zapisano.'); 1222 await refreshProfileNewsletter(user); 1223 await refreshLoyalty(); 1224 } catch (err) { 1225 showMsg(profileMsg, err.message || 'BÅÄ d zapisu.', true); 1226 } 1227 }); 1228 1229 $('#account-password-change-btn')?.addEventListener('click', async () => { 1230 showMsg(profileMsg, 'WysyÅanie linkuâ¦'); 1231 try { 1232 const data = await customerRequestPasswordChange(); 1233 showMsg(profileMsg, data.message || 'Sprawdź e-mail â wysÅaliÅmy link do zmiany hasÅa.'); 1234 } catch (err) { 1235 showMsg(profileMsg, err.message || 'Nie udaÅo siÄ wysÅaÄ linku.', true); 1236 } 1237 }); 1238 1239 $('#profile-newsletter-btn')?.addEventListener('click', async () => { 1240 const user = getCustomerUser(); 1241 const email = String(user?.email || '').trim(); 1242 if (!email) { 1243 showMsg(profileMsg, 'Brak e-maila na koncie.', true); 1244 return; 1245 } 1246 const box = $('#profile-newsletter'); 1247 if (box && !box.checked) { 1248 showMsg(profileMsg, 'Zaznacz zgodÄ na newsletter.', true); 1249 return; 1250 } 1251 showMsg(profileMsg, 'Zapisywanie do newsletteraâ¦'); 1252 try { 1253 await customerSetNewsletter(true); 1254 setNewsletterSubscription({ email, source: 'account_profile' }); 1255 showMsg(profileMsg, 'Zapisano do newslettera.'); 1256 await refreshProfileNewsletter(user); 1257 } catch (err) { 1258 try { 1259 await subscribeAccountNewsletter({ 1260 email, 1261 name: String(user?.name || ''), 1262 phone: String(user?.phone || ''), 1263 source: 'account_profile' 1264 }); 1265 showMsg(profileMsg, 'Zapisano do newslettera.'); 1266 await refreshProfileNewsletter(user); 1267 } catch (fallbackErr) { 1268 showMsg(profileMsg, fallbackErr.message || err.message || 'Nie udaÅo siÄ zapisaÄ.', true); 1269 } 1270 } 1271 }); 1272 1273 $('#profile-newsletter-unsub')?.addEventListener('click', async () => { 1274 const user = getCustomerUser(); 1275 showMsg(profileMsg, 'Wypisywanieâ¦'); 1276 try { 1277 await customerSetNewsletter(false); 1278 clearNewsletterSubscription(); 1279 showMsg(profileMsg, 'Wypisano z newslettera.'); 1280 await refreshProfileNewsletter(user); 1281 } catch (err) { 1282 showMsg(profileMsg, err.message || 'Nie udaÅo siÄ wypisaÄ.', true); 1283 } 1284 }); 1285 1286 $('#account-delete-btn')?.addEventListener('click', async () => { 1287 const confirmVal = String($('#account-delete-confirm')?.value || '').trim(); 1288 const password = String($('#account-delete-password')?.value || ''); 1289 const deleteMsg = $('#account-delete-msg'); 1290 const user = getCustomerUser(); 1291 if (confirmVal.toUpperCase() !== 'USUN') { 1292 showMsg(deleteMsg, 'Aby usunÄ Ä konto, wpisz USUN.', true); 1293 return; 1294 } 1295 if (user?.hasPassword !== false && !password) { 1296 showMsg(deleteMsg, 'Podaj hasÅo, aby usunÄ Ä konto.', true); 1297 return; 1298 } 1299 if (!window.confirm('Na pewno trwale usunÄ Ä konto? Tej operacji nie można cofnÄ Ä.')) { 1300 return; 1301 } 1302 showMsg(deleteMsg, 'Usuwanie kontaâ¦'); 1303 try { 1304 const data = await customerDeleteAccount({ 1305 confirm: 'USUN', 1306 password: user?.hasPassword === false ? '' : password 1307 }); 1308 clearNewsletterSubscription(); 1309 showPanel('auth'); 1310 showAuthForm('login'); 1311 showMsg(loginMsg, data.message || 'Konto zostaÅo usuniÄte.'); 1312 } catch (err) { 1313 showMsg(deleteMsg, err.message || 'Nie udaÅo siÄ usunÄ Ä konta.', true); 1314 } 1315 }); 1316 1317 $('#account-addresses-form')?.addEventListener('submit', async (e) => { 1318 e.preventDefault(); 1319 const form = e.target; 1320 const addresses = [0, 1, 2].map((i) => { 1321 const group = form.querySelector(`[data-addr-slot="${i}"]`); 1322 const parts = group ? readAddressPartsFromForm(group) : { city: '', street: '', number: '', apartment: '' }; 1323 return { 1324 label: String(new FormData(form).get(`label${i}`) || ''), 1325 city: parts.city, 1326 street: parts.street, 1327 number: parts.number, 1328 apartment: parts.apartment, 1329 address: composeDeliveryAddress(parts) 1330 }; 1331 }).filter((a) => a.city || a.street || a.number || a.address); 1332 try { 1333 await loyaltyAction('saveAddresses', { addresses }); 1334 showMsg($('#account-addr-msg'), 'Adresy zapisane.'); 1335 await refreshLoyalty(); 1336 } catch (err) { 1337 showMsg($('#account-addr-msg'), err.message, true); 1338 } 1339 }); 1340
1341 $('#wheel-spin')?.addEventListener('click', async () => { 1342 const btn = $('#wheel-spin'); 1343 if (isWheelSpinning()) return; 1344 btn.disabled = true; 1345 showMsg($('#wheel-msg'), ''); 1346 resetWheelCelebration(); 1347 try { 1348 const data = await loyaltyAction('spinWheel'); 1349 const prizes = loyaltyCache?.wheel?.prizes 1350 || $('#fortune-wheel-stage')?._wheelPrizes 1351 || []; 1352 const count = Math.max(prizes.length, 1); 1353 const idx = Number.isInteger(data.prizeIndex) 1354 ? data.prizeIndex 1355 : getWheelPrizeIndex(prizes, data.prize); 1356 await spinFortuneWheelToPrize(idx, count); 1357 const label = data.prize?.label || 'Wynik'; 1358 const kind = data.reward?.kind; 1359 let extra = ''; 1360 if (kind === 'points') extra = ` (+${data.reward.points} pkt)`; 1361 else if (kind === 'coupon' && data.reward.coupon?.code) { 1362 extra = ` · kod ${data.reward.coupon.code}`; 1363 } else if (kind === 'none') extra = ' â spróbuj za tydzieÅ!'; 1364 showMsg($('#wheel-msg'), `WypadÅo: ${label}${extra}`); 1365 await refreshLoyalty(); 1366 } catch (err) { 1367 showMsg($('#wheel-msg'), err.message, true); 1368 } finally { 1369 btn.disabled = false; 1370 } 1371 }); 1372 1373 // referral-apply (wpisanie kodu po zalogowaniu) usuniÄte â kod tylko przy rejestracji. 1374 1375 logoutBtn?.addEventListener('click', async () => { 1376 await customerLogout(); 1377 showPanel('auth'); 1378 showAuthForm('login'); 1379 }); 1380 1381 const verifiedFromLink = await handleVerifyFromUrl(); 1382 if (verifiedFromLink) return; 1383 1384 const resetToken = handleResetFromUrl(); 1385 if (resetToken) return; 1386 1387 if (isCustomerLoggedIn()) { 1388 try { 1389 const user = await customerMe(); 1390 await enterLoggedIn(user || getCustomerUser()); 1391 } catch (err) { 1392 if (err?.status === 401) clearCustomerSession(); 1393 showPanel('auth'); 1394 showAuthForm('login'); 1395 if (err?.status === 503) showMsg(loginMsg, err.message, true); 1396 } 1397 } else { 1398 showPanel('auth'); 1399 let authTab = 'login'; 1400 try { 1401 const tab = new URLSearchParams(window.location.search).get('tab'); 1402 if (tab === 'register' || tab === 'login' || tab === 'forgot') authTab = tab; 1403 } catch { 1404 /* ignore */ 1405 } 1406 showAuthForm(authTab); 1407 } 1408} 1409 1410boot();
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.