fix(14-mobile-modal-fix): Modal rezerwacji działa na mobile/tablet

Sekcja Elementor zawierająca modal miała elementor-hidden-mobile/tablet,
co powodowało display:none na rodzicu. Modal position:fixed wewnątrz
ukrytego elementu miał zerowe wymiary.

Fix: przeniesienie overlay do document.body w initRefs().
Plan Phase 13 (pakiety ochronne) utworzony, BLOCKED — czeka na klienta.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-10 00:05:53 +02:00
parent 9b36f8fec3
commit 6f6c1fcf17
8 changed files with 554 additions and 31 deletions

View File

@@ -86,6 +86,10 @@
function initRefs() {
overlay = document.querySelector('[data-carei-modal]');
// Move overlay to body so it's not trapped inside Elementor hidden sections on mobile/tablet
if (overlay && overlay.parentElement !== document.body) {
document.body.appendChild(overlay);
}
form = document.getElementById('carei-reservation-form');
segmentSelect = document.getElementById('carei-segment');
dateFrom = document.getElementById('carei-date-from');
@@ -166,6 +170,7 @@
initDateLabels();
dataLoaded = true;
}
enforceDateMin();
// Pre-select segment from trigger attribute
var presetSegment = triggerBtn && triggerBtn.getAttribute('segment');
@@ -312,6 +317,65 @@
function showExtras() { if (extrasWrapper) extrasWrapper.style.display = ''; }
function hideExtras() { if (extrasWrapper) extrasWrapper.style.display = 'none'; }
// ─── Past Date Prevention ────────────────────────────────────
function getNowLocal() {
var d = new Date();
return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate()) + 'T' + pad(d.getHours()) + ':' + pad(d.getMinutes());
}
var dateMinListenersBound = false;
function warnPastDate(input, msg) {
var wrap = input.closest('.carei-form__date-wrap') || input.parentNode;
var existing = wrap.querySelector('.carei-form__error-msg');
if (existing) existing.remove();
var span = document.createElement('span');
span.className = 'carei-form__error-msg';
span.textContent = msg;
wrap.appendChild(span);
input.classList.add('carei-form__field--error');
input.value = '';
updateDateEmpty(input);
}
function clearDateError(input) {
var wrap = input.closest('.carei-form__date-wrap') || input.parentNode;
var existing = wrap.querySelector('.carei-form__error-msg');
if (existing) existing.remove();
input.classList.remove('carei-form__field--error');
}
function checkPastAndWarn(input, label) {
if (!input || !input.value) return;
if (input.value < getNowLocal()) {
warnPastDate(input, label + ' — data lub godzina już minęły');
} else {
clearDateError(input);
}
}
function enforceDateMin() {
var now = getNowLocal();
if (dateFrom) dateFrom.setAttribute('min', now);
if (dateTo) dateTo.setAttribute('min', now);
if (!dateMinListenersBound) {
if (dateFrom) {
dateFrom.addEventListener('change', function () {
checkPastAndWarn(dateFrom, 'Rozpoczęcie');
if (dateTo && dateFrom.value) dateTo.setAttribute('min', dateFrom.value);
});
}
if (dateTo) {
dateTo.addEventListener('change', function () {
checkPastAndWarn(dateTo, 'Zakończenie');
});
}
dateMinListenersBound = true;
}
}
// ─── Date Labels ──────────────────────────────────────────────
function initDateLabels() {
@@ -502,8 +566,8 @@
if (!items || items.length === 0) return;
items.forEach(function (item) {
var id = item.id || item.code;
var isSelected = !!selectedCountries[id];
abroadResults.appendChild(buildCountryCard(item, isSelected));
if (selectedCountries[id]) return; // already selected — show only in "added" section
abroadResults.appendChild(buildCountryCard(item, false));
});
}
@@ -595,12 +659,8 @@
{ id: 'carei-pickup-branch', type: 'select', msg: 'Wybierz miejsce odbioru' },
{ id: 'carei-firstname', type: 'input', msg: 'Podaj imię' },
{ id: 'carei-lastname', type: 'input', msg: 'Podaj nazwisko' },
{ id: 'carei-city', type: 'input', msg: 'Podaj miejscowość' },
{ id: 'carei-zipcode', type: 'input', msg: 'Podaj kod pocztowy' },
{ id: 'carei-street', type: 'input', msg: 'Podaj ulicę' },
{ id: 'carei-email', type: 'email', msg: 'Podaj poprawny adres e-mail' },
{ id: 'carei-phone', type: 'phone', msg: 'Podaj numer telefonu (min. 9 cyfr)' },
{ id: 'carei-pesel', type: 'pesel', msg: 'Podaj poprawny PESEL (11 cyfr)' },
{ id: 'carei-privacy', type: 'checkbox', msg: 'Wymagana zgoda na Politykę Prywatności' }
];
@@ -623,6 +683,13 @@
if (hasError) { valid = false; markFieldError(el, f.msg, f.type); }
});
var now = new Date();
if (dateFrom && dateFrom.value && new Date(dateFrom.value) < now) {
valid = false; markFieldError(dateFrom, 'Data lub godzina rozpoczęcia już minęły', 'input');
}
if (dateTo && dateTo.value && new Date(dateTo.value) < now) {
valid = false; markFieldError(dateTo, 'Data lub godzina zakończenia już minęły', 'input');
}
if (dateFrom && dateTo && dateFrom.value && dateTo.value) {
if (new Date(dateTo.value) <= new Date(dateFrom.value)) {
valid = false; markFieldError(dateTo, 'Data zakończenia musi być po dacie rozpoczęcia', 'input');
@@ -711,12 +778,8 @@
returnBranch: (sameReturnCheck && !sameReturnCheck.checked && returnSelect) ? returnSelect.value : '',
firstName: val('carei-firstname'),
lastName: val('carei-lastname'),
city: val('carei-city'),
zipCode: val('carei-zipcode'),
street: val('carei-street'),
email: val('carei-email'),
phone: '+48' + phoneRaw,
pesel: val('carei-pesel'),
message: val('carei-message'),
privacy: document.getElementById('carei-privacy') ? document.getElementById('carei-privacy').checked : false
};
@@ -736,8 +799,8 @@
lastName: fd.lastName,
name: fd.firstName + ' ' + fd.lastName,
isCompany: false,
address: { city: fd.city, zipCode: fd.zipCode, street: fd.street, homeNo: '-' },
pesel: fd.pesel,
address: { city: '-', zipCode: '00-000', street: '-', homeNo: '-' },
pesel: '00000000000',
email: fd.email,
phoneMobile: fd.phone,
paymentMethod: 'GOTÓWKA',
@@ -974,13 +1037,13 @@
returnLocation: { branchName: returnBranch, outOfBranch: 'N' },
carParameters: { categoryName: fd.segment },
priceListId: currentPriceListId,
validTime: 30,
validTime: 1440,
priceItems: getSelectedExtrasForApi(),
drivers: [{
firstName: fd.firstName,
lastName: fd.lastName,
address: { city: fd.city, zipCode: fd.zipCode, street: fd.street },
pesel: fd.pesel,
address: { city: '-', zipCode: '00-000', street: '-' },
pesel: '00000000000',
phone: fd.phone,
email: fd.email
}],
@@ -997,9 +1060,8 @@
apiPost('booking', bookingData).then(function (res) {
if (res && res.success && res.reservationId) {
currentReservationId = res.reservationId;
return apiPost('booking/confirm', { reservationId: res.reservationId }).then(function () {
showSuccessView(res.reservationNo || res.reservationId);
});
showSuccessView(res.reservationNo || res.reservationId);
return;
}
throw new Error(translateRejectReason(res.rejectReason) || 'Rezerwacja nie powiodła się');
}).catch(function (err) {
@@ -1033,7 +1095,7 @@
// ─── Success View ─────────────────────────────────────────────
function showSuccessView(reservationNo) {
if (successNumber) successNumber.textContent = 'Nr rezerwacji: ' + reservationNo;
if (successNumber) successNumber.textContent = 'Nr zamówienia: ' + reservationNo;
transitionStep(summaryOverlay, successView, function () {
announce('Rezerwacja potwierdzona');
var title = successView.querySelector('.carei-success__title');
@@ -1119,6 +1181,22 @@
var searchMapData = null;
// Enforce min dates on search form (no error messages — compact UI)
var now = getNowLocal();
if (searchDateFrom) {
searchDateFrom.setAttribute('min', now);
searchDateFrom.addEventListener('change', function () {
if (searchDateFrom.value && searchDateFrom.value < getNowLocal()) searchDateFrom.value = '';
if (searchDateTo && searchDateFrom.value) searchDateTo.setAttribute('min', searchDateFrom.value);
});
}
if (searchDateTo) {
searchDateTo.setAttribute('min', now);
searchDateTo.addEventListener('change', function () {
if (searchDateTo.value && searchDateTo.value < getNowLocal()) searchDateTo.value = '';
});
}
// Ładowanie danych do mini formularza
function loadSearchData() {
Promise.all([