Naprawa lokalizacji - odczytu
This commit is contained in:
@@ -71,33 +71,191 @@ body {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40px;
|
||||
padding: 24px 20px;
|
||||
text-align: center;
|
||||
transition: opacity 0.5s ease, visibility 0.5s;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.geo-shell {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.geo-icon {
|
||||
font-size: 80px;
|
||||
margin-bottom: 16px;
|
||||
font-size: 64px;
|
||||
margin-bottom: 12px;
|
||||
animation: bounce 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes bounce {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(-15px); }
|
||||
50% { transform: translateY(-10px); }
|
||||
}
|
||||
|
||||
.geo-text h2 {
|
||||
.geo-title {
|
||||
font-family: 'Playfair Display', serif;
|
||||
margin: 0 0 12px;
|
||||
margin: 0 0 10px;
|
||||
color: var(--primary);
|
||||
font-size: 1.65rem;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.geo-lead {
|
||||
color: var(--text-muted);
|
||||
font-size: 15px;
|
||||
line-height: 1.45;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.geo-status {
|
||||
color: var(--text-muted);
|
||||
font-size: 14px;
|
||||
line-height: 1.45;
|
||||
margin: 14px 0 0;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.geo-status:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.geo-status.is-error {
|
||||
color: #ff8a8a;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.geo-status.is-info {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.geo-msg {
|
||||
.geo-actions {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1.2fr;
|
||||
gap: 10px;
|
||||
margin-top: 20px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.geo-btn {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 2px;
|
||||
min-height: 76px;
|
||||
padding: 12px 10px;
|
||||
border-radius: 14px;
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
line-height: 1.25;
|
||||
transition: transform 0.15s ease, box-shadow 0.15s ease;
|
||||
}
|
||||
|
||||
.geo-btn:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.geo-btn-main {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.geo-btn-sub {
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.geo-btn-menu {
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border: 1.5px solid rgba(226, 176, 126, 0.25);
|
||||
color: var(--text-muted);
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.geo-btn-menu .geo-btn-main {
|
||||
color: var(--text-main);
|
||||
}
|
||||
|
||||
.geo-btn-locate {
|
||||
border: none;
|
||||
box-shadow: 0 4px 18px rgba(226, 176, 126, 0.28);
|
||||
}
|
||||
|
||||
.geo-btn-locate .geo-btn-main {
|
||||
font-size: 13px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.geo-btn-locate:disabled {
|
||||
opacity: 0.65;
|
||||
cursor: wait;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.geo-instructions {
|
||||
margin-top: 16px;
|
||||
padding: 14px 16px;
|
||||
background: var(--surface-light);
|
||||
border: 1px solid rgba(226, 176, 126, 0.2);
|
||||
border-radius: 12px;
|
||||
font-size: 13px;
|
||||
line-height: 1.55;
|
||||
color: #d1d5db;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.geo-instructions.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.geo-wifi-callout {
|
||||
margin: 20px 0 0;
|
||||
padding: 16px 18px;
|
||||
background: rgba(226, 176, 126, 0.1);
|
||||
border: 1.5px solid rgba(226, 176, 126, 0.35);
|
||||
border-radius: 14px;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 16px;
|
||||
color: #e8e8ea;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.geo-wifi-callout-title {
|
||||
margin: 0 0 10px;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.geo-wifi-callout p {
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
|
||||
.geo-wifi-network,
|
||||
.geo-wifi-password {
|
||||
font-size: 15px;
|
||||
color: var(--text-main);
|
||||
}
|
||||
|
||||
.geo-wifi-list {
|
||||
margin: 8px 0 0;
|
||||
padding-left: 20px;
|
||||
color: #d1d5db;
|
||||
}
|
||||
|
||||
.geo-wifi-list li {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.geo-wifi-list li:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 360px) {
|
||||
.geo-actions {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* --- MAIN LAYOUT --- */
|
||||
@@ -907,6 +1065,10 @@ header {
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.menu-only-banner.is-hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.menu-only-banner-btn {
|
||||
flex-shrink: 0;
|
||||
border: 1px solid rgba(226, 176, 126, 0.45);
|
||||
@@ -944,6 +1106,10 @@ header {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#menuOnlyBanner.is-hidden + .menu-search-container {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
#menuSearchInput {
|
||||
width: 100%;
|
||||
background: var(--surface);
|
||||
|
||||
@@ -7,6 +7,9 @@ window.kitchenAnimations = [
|
||||
];
|
||||
window.selectedAnimationHtml = null;
|
||||
|
||||
const MENU_ASSET_VERSION =
|
||||
window.MENU_ASSET_VERSION || window.APP_ASSET_VERSION || "1";
|
||||
|
||||
const params = new URLSearchParams(location.search);
|
||||
let hashParam = (params.get("h") || "").trim();
|
||||
const isStaffPreview = params.get("preview") === "staff";
|
||||
@@ -454,7 +457,10 @@ function updateNavAccessState() {
|
||||
if (el) el.classList.toggle("nav-locked", locked);
|
||||
});
|
||||
const banner = document.getElementById("menuOnlyBanner");
|
||||
if (banner) banner.classList.toggle("hidden", appAccessLevel !== "menu");
|
||||
if (banner) {
|
||||
banner.classList.remove("hidden");
|
||||
banner.classList.toggle("is-hidden", appAccessLevel !== "menu");
|
||||
}
|
||||
}
|
||||
|
||||
function showBottomNav() {
|
||||
@@ -479,46 +485,156 @@ async function resolveTableLabel() {
|
||||
}
|
||||
}
|
||||
|
||||
function isIOSDevice() {
|
||||
return /iPad|iPhone|iPod/.test(navigator.userAgent) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);
|
||||
}
|
||||
|
||||
const GEO_GATE_LABELS = {
|
||||
status: "status zamówienia",
|
||||
waiter: "wezwanie kelnera",
|
||||
bill: "prośbę o rachunek",
|
||||
};
|
||||
|
||||
const GEO_DEFAULT_LEAD =
|
||||
"Przeglądaj menu od razu — albo potwierdź, że jesteś u nas, aby wezwać kelnera, śledzić zamówienie i poprosić o rachunek.";
|
||||
|
||||
function setGeoLead(html) {
|
||||
const el = document.getElementById("geoLead");
|
||||
if (el) el.innerHTML = html;
|
||||
}
|
||||
|
||||
function setGeoStatus(html, { error = false, info = false } = {}) {
|
||||
const el = document.getElementById("geoMsg");
|
||||
if (!el) return;
|
||||
el.innerHTML = html || "";
|
||||
el.classList.toggle("is-error", error);
|
||||
el.classList.toggle("is-info", info);
|
||||
}
|
||||
|
||||
function showGeoInstructions(html) {
|
||||
const el = document.getElementById("geoInstructions");
|
||||
if (!el) return;
|
||||
el.innerHTML = html || "";
|
||||
el.classList.toggle("hidden", !html);
|
||||
}
|
||||
|
||||
function hideGeoInstructions() {
|
||||
showGeoInstructions("");
|
||||
}
|
||||
|
||||
function setGeoActionBusy(busy) {
|
||||
const btn = document.getElementById("geoActionBtn");
|
||||
if (!btn) return;
|
||||
btn.disabled = false;
|
||||
btn.setAttribute("aria-busy", busy ? "true" : "false");
|
||||
if (busy) {
|
||||
setGeoActionLabel("Sprawdzanie…");
|
||||
}
|
||||
}
|
||||
|
||||
function isGeoPermissionDenied(error) {
|
||||
return Number(error?.code) === 1;
|
||||
}
|
||||
|
||||
function setGeoActionLabel(text) {
|
||||
const btn = document.getElementById("geoActionBtn");
|
||||
if (!btn) return;
|
||||
const main = btn.querySelector(".geo-btn-main");
|
||||
if (main) main.textContent = text;
|
||||
else btn.textContent = text;
|
||||
}
|
||||
|
||||
function getGeoPermissionInstructions() {
|
||||
if (isIOSDevice()) {
|
||||
return `<b>iPhone (Safari):</b><br>
|
||||
1. Kliknij <b>aA</b> po lewej stronie paska adresu.<br>
|
||||
2. Wybierz <b>Ustawienia witryny</b>.<br>
|
||||
3. Ustaw <b>Położenie</b> na „Zapytaj” lub „Pozwalaj”.<br>
|
||||
4. Odśwież stronę.<br><br>
|
||||
<i>Lokalizacja działa tylko przez bezpieczne <b>https://</b>.</i>`;
|
||||
}
|
||||
|
||||
return `<b>Android / Chrome:</b><br>
|
||||
1. Kliknij ikonę <b>kłódki</b> obok adresu strony.<br>
|
||||
2. W <b>Uprawnieniach</b> zmień Lokalizację na „Zezwalaj”.<br>
|
||||
3. Odśwież stronę.`;
|
||||
}
|
||||
|
||||
let geoMenuButtonMode = "menu_only";
|
||||
|
||||
window.handleGeoMenuClick = function () {
|
||||
if (geoMenuButtonMode === "back_to_menu") {
|
||||
document.getElementById("geoScreen")?.classList.add("hidden");
|
||||
return;
|
||||
}
|
||||
enterMenuOnlyMode();
|
||||
};
|
||||
|
||||
window.retryGeolocation = function () {
|
||||
if (shouldBypassGeolocationHost()) {
|
||||
bypassGeolocation("trusted_host", { host: window.location.hostname });
|
||||
return;
|
||||
}
|
||||
|
||||
initGeolocationAfterBypassChecks({ userInitiated: true }).catch((err) => {
|
||||
console.error("[GEO] retry failed", err);
|
||||
showGeoPermissionBlockedState();
|
||||
});
|
||||
};
|
||||
|
||||
function bindGeoScreenButtons() {
|
||||
document.getElementById("geoMenuOnlyBtn")?.addEventListener("click", (event) => {
|
||||
event.preventDefault();
|
||||
handleGeoMenuClick();
|
||||
});
|
||||
|
||||
document.getElementById("geoActionBtn")?.addEventListener("click", (event) => {
|
||||
event.preventDefault();
|
||||
retryGeolocation();
|
||||
});
|
||||
}
|
||||
|
||||
function configureGeoSecondaryButton(mode) {
|
||||
const menuOnlyBtn = document.getElementById("geoMenuOnlyBtn");
|
||||
if (!menuOnlyBtn) return;
|
||||
|
||||
geoMenuButtonMode = mode;
|
||||
const mainEl = menuOnlyBtn.querySelector(".geo-btn-main");
|
||||
const subEl = menuOnlyBtn.querySelector(".geo-btn-sub");
|
||||
|
||||
if (mode === "back_to_menu") {
|
||||
menuOnlyBtn.style.display = "";
|
||||
menuOnlyBtn.textContent = "Wróć do menu";
|
||||
menuOnlyBtn.onclick = () => {
|
||||
document.getElementById("geoScreen")?.classList.add("hidden");
|
||||
};
|
||||
if (mainEl) mainEl.textContent = "Wróć do menu";
|
||||
if (subEl) {
|
||||
subEl.textContent = "";
|
||||
subEl.style.display = "none";
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
menuOnlyBtn.style.display = "";
|
||||
menuOnlyBtn.textContent = "Przeglądaj menu bez lokalizacji";
|
||||
menuOnlyBtn.onclick = () => enterMenuOnlyMode();
|
||||
if (mainEl) mainEl.textContent = "Przejdź do menu";
|
||||
if (subEl) {
|
||||
subEl.textContent = "bez lokalizacji";
|
||||
subEl.style.display = "";
|
||||
}
|
||||
}
|
||||
|
||||
function showGeoGateForAction(action) {
|
||||
const geoScreen = document.getElementById("geoScreen");
|
||||
const loadingScreen = document.getElementById("loadingScreen");
|
||||
const geoMsg = document.getElementById("geoMsg");
|
||||
const geoActionBtn = document.getElementById("geoActionBtn");
|
||||
|
||||
if (loadingScreen) loadingScreen.classList.add("hidden");
|
||||
if (geoScreen) geoScreen.classList.remove("hidden");
|
||||
|
||||
const feature = GEO_GATE_LABELS[action] || "tę funkcję";
|
||||
if (geoMsg) {
|
||||
geoMsg.innerHTML = `Aby skorzystać z <b>${feature}</b>, potwierdź, że jesteś w restauracji.<br><br>Prosimy o zgodę na dostęp do lokalizacji.`;
|
||||
}
|
||||
setGeoLead(`Menu masz już otwarte. Aby skorzystać z <b>${feature}</b>, potwierdź krótko, że jesteś w restauracji.`);
|
||||
setGeoStatus("");
|
||||
hideGeoInstructions();
|
||||
if (geoActionBtn) {
|
||||
geoActionBtn.disabled = false;
|
||||
geoActionBtn.textContent = "Sprawdź lokalizację";
|
||||
setGeoActionBusy(false);
|
||||
setGeoActionLabel("Sprawdź lokalizację");
|
||||
}
|
||||
configureGeoSecondaryButton(appAccessLevel === "menu" ? "back_to_menu" : "menu_only");
|
||||
}
|
||||
@@ -1322,7 +1438,7 @@ window.fetchGUS = async function () {
|
||||
// --- DYNAMIC MENU LOADING ---
|
||||
async function loadMenu() {
|
||||
try {
|
||||
const response = await fetch('menu.json');
|
||||
const response = await fetch(`menu.json?v=${encodeURIComponent(MENU_ASSET_VERSION)}`);
|
||||
if (!response.ok) throw new Error('Nie udało się załadować menu');
|
||||
const menuData = await response.json();
|
||||
window.menuDataRaw = menuData;
|
||||
@@ -1467,9 +1583,13 @@ window.confirmInvoice = async function () {
|
||||
};
|
||||
|
||||
// --- GEOLOCATION LOGIC ---
|
||||
const RESTAURANT_LAT = 50.5624963;
|
||||
const RESTAURANT_LNG = 22.0608059;
|
||||
const MAX_DISTANCE_METERS = 200;
|
||||
// Dwa punkty odniesienia: OSM (adres budynku) i pin Google Maps (z nim porównują goście w Maps).
|
||||
const RESTAURANT_LOCATIONS = [
|
||||
{ lat: 50.5622609, lng: 22.0606303, source: "osm" },
|
||||
{ lat: 50.567953, lng: 22.061045, source: "google_maps" },
|
||||
];
|
||||
const MAX_DISTANCE_METERS = 300;
|
||||
const MAX_ACCURACY_BONUS = 150;
|
||||
|
||||
function haversineDistance(lat1, lon1, lat2, lon2) {
|
||||
const R = 6371e3;
|
||||
@@ -1485,6 +1605,76 @@ function haversineDistance(lat1, lon1, lat2, lon2) {
|
||||
return R * c;
|
||||
}
|
||||
|
||||
function distanceToRestaurant(lat, lng) {
|
||||
return Math.min(
|
||||
...RESTAURANT_LOCATIONS.map(({ lat: rLat, lng: rLng }) =>
|
||||
haversineDistance(rLat, rLng, lat, lng)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function isInsideRestaurantGeofence(distanceMeters, accuracyMeters) {
|
||||
const accuracyBonus = Math.min(Math.max(Number(accuracyMeters) || 0, 0), MAX_ACCURACY_BONUS);
|
||||
return distanceMeters <= MAX_DISTANCE_METERS + accuracyBonus;
|
||||
}
|
||||
|
||||
function requestRestaurantGeolocation() {
|
||||
const geoOptions = {
|
||||
enableHighAccuracy: true,
|
||||
timeout: 15000,
|
||||
maximumAge: 0,
|
||||
};
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let bestSample = null;
|
||||
let watchId = null;
|
||||
let settled = false;
|
||||
|
||||
const finish = (result) => {
|
||||
if (settled) return;
|
||||
settled = true;
|
||||
if (watchId != null) navigator.geolocation.clearWatch(watchId);
|
||||
clearTimeout(timeoutId);
|
||||
resolve(result);
|
||||
};
|
||||
|
||||
const fail = (error) => {
|
||||
if (settled) return;
|
||||
settled = true;
|
||||
if (watchId != null) navigator.geolocation.clearWatch(watchId);
|
||||
clearTimeout(timeoutId);
|
||||
reject(error);
|
||||
};
|
||||
|
||||
const handlePosition = (position) => {
|
||||
const dist = distanceToRestaurant(position.coords.latitude, position.coords.longitude);
|
||||
const accuracy = position.coords.accuracy;
|
||||
const sample = { position, dist, accuracy };
|
||||
|
||||
if (!bestSample || dist < bestSample.dist) {
|
||||
bestSample = sample;
|
||||
}
|
||||
|
||||
if (isInsideRestaurantGeofence(dist, accuracy)) {
|
||||
finish({ passed: true, ...sample });
|
||||
}
|
||||
};
|
||||
|
||||
const timeoutId = setTimeout(() => {
|
||||
if (bestSample) {
|
||||
finish({
|
||||
passed: isInsideRestaurantGeofence(bestSample.dist, bestSample.accuracy),
|
||||
...bestSample,
|
||||
});
|
||||
return;
|
||||
}
|
||||
fail({ code: 3, message: "Geolocation timeout" });
|
||||
}, 12000);
|
||||
|
||||
watchId = navigator.geolocation.watchPosition(handlePosition, fail, geoOptions);
|
||||
});
|
||||
}
|
||||
|
||||
function startApp() {
|
||||
appAccessLevel = "full";
|
||||
updateNavAccessState();
|
||||
@@ -1512,28 +1702,30 @@ function startApp() {
|
||||
function showGeoConsentScreen() {
|
||||
const geoScreen = document.getElementById("geoScreen");
|
||||
const loadingScreen = document.getElementById("loadingScreen");
|
||||
const geoMsg = document.getElementById("geoMsg");
|
||||
const geoActionBtn = document.getElementById("geoActionBtn");
|
||||
const menuOnlyBtn = document.getElementById("geoMenuOnlyBtn");
|
||||
|
||||
loadingScreen.classList.add("hidden");
|
||||
geoScreen.classList.remove("hidden");
|
||||
|
||||
if (geoMsg) {
|
||||
geoMsg.innerHTML = `Aby zapewnić bezpieczeństwo Twojego zamówienia, musimy upewnić się, że znajdujesz się na terenie restauracji.<br><br>Prosimy o udzielenie zgody na dostęp do lokalizacji w przeglądarce.`;
|
||||
}
|
||||
setGeoLead(GEO_DEFAULT_LEAD);
|
||||
setGeoStatus("");
|
||||
hideGeoInstructions();
|
||||
|
||||
if (geoActionBtn) {
|
||||
geoActionBtn.disabled = false;
|
||||
geoActionBtn.textContent = "Udziel zgody / Sprawdź";
|
||||
}
|
||||
if (menuOnlyBtn) {
|
||||
configureGeoSecondaryButton("menu_only");
|
||||
setGeoActionBusy(false);
|
||||
setGeoActionLabel("Zgoda, sprawdź lokalizację");
|
||||
}
|
||||
configureGeoSecondaryButton("menu_only");
|
||||
}
|
||||
|
||||
function isIOSDevice() {
|
||||
return /iPad|iPhone|iPod/.test(navigator.userAgent) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);
|
||||
function showGeoPermissionBlockedState() {
|
||||
setGeoStatus("Przeglądarka zablokowała dostęp do lokalizacji.", { error: true });
|
||||
showGeoInstructions(
|
||||
`${getGeoPermissionInstructions()}<br><br>Po zmianie ustawień <b>odśwież stronę</b>, a potem kliknij „Spróbuj ponownie”.`
|
||||
);
|
||||
setGeoActionBusy(false);
|
||||
setGeoActionLabel("Spróbuj ponownie");
|
||||
configureGeoSecondaryButton(appAccessLevel === "menu" ? "back_to_menu" : "menu_only");
|
||||
}
|
||||
|
||||
function shouldBypassGeolocationHost() {
|
||||
@@ -1541,135 +1733,189 @@ function shouldBypassGeolocationHost() {
|
||||
return bypassHosts.includes(window.location.hostname);
|
||||
}
|
||||
|
||||
window.initGeolocation = function () {
|
||||
if (shouldBypassGeolocationHost()) {
|
||||
console.warn("Bypassing geolocation for trusted host.");
|
||||
trackEvent("geo_bypass_host", { host: window.location.hostname, reason: "trusted_host" });
|
||||
async function checkGeoBypassByClientIp() {
|
||||
try {
|
||||
const controller = typeof AbortController !== "undefined" ? new AbortController() : null;
|
||||
const timeoutId = controller
|
||||
? setTimeout(() => controller.abort(), 4000)
|
||||
: null;
|
||||
|
||||
const res = await fetch("../api/geo_bypass.php", {
|
||||
credentials: "same-origin",
|
||||
cache: "no-store",
|
||||
signal: controller?.signal,
|
||||
});
|
||||
|
||||
if (timeoutId) clearTimeout(timeoutId);
|
||||
|
||||
const data = await res.json();
|
||||
return data.status === "success" && data.bypassGeo === true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function bypassGeolocation(reason, extra = {}) {
|
||||
trackEvent('geo_bypass_host', { reason, ...extra });
|
||||
if (appAccessLevel === 'menu') {
|
||||
unlockFullApp();
|
||||
} else {
|
||||
startApp();
|
||||
}
|
||||
}
|
||||
|
||||
async function queryGeolocationPermissionState() {
|
||||
if (!navigator.permissions?.query) {
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
try {
|
||||
const status = await navigator.permissions.query({ name: "geolocation" });
|
||||
return status.state;
|
||||
} catch {
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
async function bootstrapGeolocation() {
|
||||
if (shouldBypassGeolocationHost()) {
|
||||
bypassGeolocation('trusted_host', { host: window.location.hostname });
|
||||
return;
|
||||
}
|
||||
|
||||
if (await checkGeoBypassByClientIp()) {
|
||||
bypassGeolocation('trusted_ip');
|
||||
return;
|
||||
}
|
||||
|
||||
showGeoConsentScreen();
|
||||
}
|
||||
|
||||
window.initGeolocation = function () {
|
||||
if (shouldBypassGeolocationHost()) {
|
||||
console.warn("Bypassing geolocation for trusted host.");
|
||||
bypassGeolocation("trusted_host", { host: window.location.hostname });
|
||||
return;
|
||||
}
|
||||
|
||||
checkGeoBypassByClientIp().then((bypassByIp) => {
|
||||
if (bypassByIp) {
|
||||
console.warn("Bypassing geolocation for trusted client IP.");
|
||||
bypassGeolocation("trusted_ip");
|
||||
return;
|
||||
}
|
||||
initGeolocationAfterBypassChecks();
|
||||
});
|
||||
};
|
||||
|
||||
async function initGeolocationAfterBypassChecks(options = {}) {
|
||||
const geoScreen = document.getElementById("geoScreen");
|
||||
const loadingScreen = document.getElementById("loadingScreen");
|
||||
const geoMsg = document.getElementById("geoMsg");
|
||||
const geoActionBtn = document.getElementById("geoActionBtn");
|
||||
const menuOnlyBtn = document.getElementById("geoMenuOnlyBtn");
|
||||
|
||||
const bypassHosts = ['localhost', '127.0.0.1', '192.168.20.84'];
|
||||
if (window.location.protocol === 'http:' && bypassHosts.includes(window.location.hostname)) {
|
||||
console.warn("Bypassing geolocation on local HTTP environment.");
|
||||
trackEvent("geo_bypass_host", { host: window.location.hostname, reason: "local_http" });
|
||||
unlockFullApp();
|
||||
bypassGeolocation("local_http", { host: window.location.hostname });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!window.isSecureContext) {
|
||||
geoMsg.innerHTML = `<b style="color: #ff6b6b;">Ta strona nie jest uruchomiona w bezpiecznym trybie HTTPS.</b><br><br>
|
||||
Przeglądarki mobilne blokują geolokalizację bez pytania, jeśli adres nie zaczyna się od <b>https://</b>.<br><br>
|
||||
Otwórz aplikację przez HTTPS i spróbuj ponownie.<br><br>
|
||||
<b>Masz problem z lokalizacją?</b> Połącz się z <b>HotSpot Karczmy</b>, a wtedy wejdziesz do aplikacji bez geolokalizacji.<br>
|
||||
Hasło: <b>karczmabiesiada</b>`;
|
||||
if (geoActionBtn) {
|
||||
geoActionBtn.disabled = false;
|
||||
geoActionBtn.textContent = "Spróbuj ponownie";
|
||||
}
|
||||
if (menuOnlyBtn) configureGeoSecondaryButton(appAccessLevel === "menu" ? "back_to_menu" : "menu_only");
|
||||
return;
|
||||
}
|
||||
|
||||
loadingScreen.classList.add("hidden");
|
||||
geoScreen.classList.remove("hidden");
|
||||
loadingScreen?.classList.add("hidden");
|
||||
geoScreen?.classList.remove("hidden");
|
||||
configureGeoSecondaryButton(appAccessLevel === "menu" ? "back_to_menu" : "menu_only");
|
||||
|
||||
if (!navigator.geolocation) {
|
||||
geoMsg.innerHTML = "Twoja przeglądarka nie wspiera geolokalizacji. Aplikacja wymaga nowszej przeglądarki.";
|
||||
if (!window.isSecureContext) {
|
||||
setGeoLead(GEO_DEFAULT_LEAD);
|
||||
setGeoStatus("Ta strona wymaga bezpiecznego połączenia HTTPS.", { error: true });
|
||||
showGeoInstructions("Przeglądarki mobilne blokują geolokalizację bez <b>https://</b>. Otwórz aplikację przez HTTPS i spróbuj ponownie.");
|
||||
setGeoActionBusy(false);
|
||||
setGeoActionLabel("Spróbuj ponownie");
|
||||
return;
|
||||
}
|
||||
|
||||
geoMsg.innerHTML = "Sprawdzamy Twoją lokalizację...";
|
||||
trackEvent("geo_check_started");
|
||||
if (geoActionBtn) {
|
||||
geoActionBtn.disabled = true;
|
||||
geoActionBtn.textContent = "Sprawdzanie...";
|
||||
if (!navigator.geolocation) {
|
||||
setGeoLead(GEO_DEFAULT_LEAD);
|
||||
setGeoStatus("Twoja przeglądarka nie wspiera geolokalizacji.", { error: true });
|
||||
hideGeoInstructions();
|
||||
setGeoActionBusy(false);
|
||||
return;
|
||||
}
|
||||
|
||||
navigator.geolocation.getCurrentPosition(
|
||||
(position) => {
|
||||
const dist = haversineDistance(
|
||||
RESTAURANT_LAT, RESTAURANT_LNG,
|
||||
position.coords.latitude, position.coords.longitude
|
||||
);
|
||||
setGeoLead(GEO_DEFAULT_LEAD);
|
||||
setGeoStatus("Sprawdzamy Twoją lokalizację…", { info: true });
|
||||
hideGeoInstructions();
|
||||
setGeoActionBusy(true);
|
||||
|
||||
const accuracy = position.coords.accuracy;
|
||||
console.log(`[GEO] Lat: ${position.coords.latitude}, Lng: ${position.coords.longitude}, Dist: ${dist}m, Accuracy: ${accuracy}m`);
|
||||
const permissionState = await queryGeolocationPermissionState();
|
||||
if (permissionState === "denied") {
|
||||
showGeoPermissionBlockedState();
|
||||
return;
|
||||
}
|
||||
|
||||
if (dist <= MAX_DISTANCE_METERS) {
|
||||
trackEvent("geo_check_passed", { distanceMeters: Math.round(dist), accuracyMeters: Math.round(accuracy) });
|
||||
unlockFullApp();
|
||||
// setTimeout(() => showToast(`Lokalizacja zweryfikowana (Dystans: ${Math.round(dist)}m, Dokładność: ${Math.round(accuracy)}m)`), 2000);
|
||||
} else {
|
||||
trackEvent("geo_check_failed", { reason: "outside_restaurant", distanceMeters: Math.round(dist), accuracyMeters: Math.round(accuracy) });
|
||||
if (geoActionBtn) {
|
||||
geoActionBtn.disabled = false;
|
||||
geoActionBtn.textContent = "Spróbuj ponownie";
|
||||
}
|
||||
configureGeoSecondaryButton(appAccessLevel === "menu" ? "back_to_menu" : "menu_only");
|
||||
geoMsg.innerHTML = `Wydaje się, że jesteś poza restauracją (ok. ${Math.round(dist)}m od nas).<br>Nasza aplikacja działa tylko na miejscu.<br><br>
|
||||
<small style="color: #888;">Debug: Twoja odległość: ${Math.round(dist)}m, Dokładność sygnału: ${Math.round(accuracy)}m</small><br><br>
|
||||
Jeśli to błąd GPS lub słaby sygnał, spróbuj ponownie za chwilę.`;
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
trackEvent("geo_check_failed", { reason: "browser_error", code: error.code || null, message: String(error.message || "") });
|
||||
if (geoActionBtn) {
|
||||
geoActionBtn.disabled = false;
|
||||
geoActionBtn.textContent = "Spróbuj ponownie";
|
||||
}
|
||||
configureGeoSecondaryButton(appAccessLevel === "menu" ? "back_to_menu" : "menu_only");
|
||||
const deniedBecauseInsecure = /secure origins|only secure|https/i.test(String(error.message || ""));
|
||||
if (deniedBecauseInsecure) {
|
||||
geoMsg.innerHTML = `<b style="color: #ff6b6b;">Przeglądarka zablokowała lokalizację z powodu braku HTTPS.</b><br><br>
|
||||
Geolokalizacja działa tylko na bezpiecznym adresie <b>https://</b> (lub localhost).<br>
|
||||
Otwórz aplikację przez HTTPS i spróbuj ponownie.<br><br>
|
||||
<b>Masz problem z lokalizacją?</b> Połącz się z <b>HotSpot Karczmy</b>, a wtedy wejdziesz do aplikacji bez geolokalizacji.<br>
|
||||
Hasło: <b>karczmabiesiada</b>`;
|
||||
} else if (error.code === error.PERMISSION_DENIED) {
|
||||
const isIOS = isIOSDevice();
|
||||
|
||||
let instructions = '';
|
||||
if (isIOS) {
|
||||
instructions = `<b>Instrukcja dla iPhone (Safari):</b><br>
|
||||
1. Kliknij ikonę <b>"aA"</b> po lewej stronie paska adresu.<br>
|
||||
2. Wybierz <b>"Ustawienia witryny"</b> (Website Settings).<br>
|
||||
3. Zmień opcję <b>"Położenie"</b> (Location) na "Zapytaj" lub "Pozwalaj".<br>
|
||||
4. Odśwież stronę.<br><br>
|
||||
<i>Uwaga: Na urządzeniach Apple lokalizacja działa WYŁĄCZNIE, gdy adres strony zaczyna się od bezpiecznego <b>https://</b>. Jeżeli jesteś na http://, system zablokuje to automatycznie.</i><br><br>
|
||||
<b>Masz problem z lokalizacją?</b> Połącz się z <b>HotSpot Karczmy</b>, a wtedy wejdziesz do aplikacji bez geolokalizacji.<br>
|
||||
Hasło: <b>karczmabiesiada</b>`;
|
||||
} else {
|
||||
instructions = `<b>Instrukcja dla Android / Chrome:</b><br>
|
||||
1. Kliknij ikonkę <b>kłódki / ustawień</b> 🔒 obok adresu strony na górze przeglądarki.<br>
|
||||
2. Znajdź <b>Uprawnienia</b> (Lokalizacja) i zmień z "Zablokuj" na "Zezwalaj".<br>
|
||||
3. Odśwież stronę.<br><br>
|
||||
<b>Masz problem z lokalizacją?</b> Połącz się z <b>HotSpot Karczmy</b>, a wtedy wejdziesz do aplikacji bez geolokalizacji.<br>
|
||||
Hasło: <b>karczmabiesiada</b>`;
|
||||
}
|
||||
trackEvent("geo_check_started");
|
||||
|
||||
geoMsg.innerHTML = `<b style="color: #ff6b6b;">Przeglądarka zablokowała dostęp do lokalizacji.</b><br><br>
|
||||
Bez tego nie możemy zweryfikować, czy jesteś w restauracji.<br><br>
|
||||
${instructions}`;
|
||||
} else {
|
||||
geoMsg.innerHTML = "Nie udało się pobrać lokalizacji. Sprawdź zasięg lub włącz GPS i spróbuj ponownie.";
|
||||
}
|
||||
},
|
||||
{ enableHighAccuracy: true, timeout: 15000, maximumAge: 0 }
|
||||
);
|
||||
try {
|
||||
const result = await requestRestaurantGeolocation();
|
||||
const dist = result.dist;
|
||||
const accuracy = result.accuracy;
|
||||
|
||||
console.log(
|
||||
`[GEO] Lat: ${result.position.coords.latitude}, Lng: ${result.position.coords.longitude}, ` +
|
||||
`MinDist: ${Math.round(dist)}m, Accuracy: ${Math.round(accuracy)}m`
|
||||
);
|
||||
|
||||
if (result.passed) {
|
||||
trackEvent("geo_check_passed", {
|
||||
distanceMeters: Math.round(dist),
|
||||
accuracyMeters: Math.round(accuracy),
|
||||
});
|
||||
unlockFullApp();
|
||||
return;
|
||||
}
|
||||
|
||||
trackEvent("geo_check_failed", {
|
||||
reason: "outside_restaurant",
|
||||
distanceMeters: Math.round(dist),
|
||||
accuracyMeters: Math.round(accuracy),
|
||||
});
|
||||
setGeoActionBusy(false);
|
||||
setGeoActionLabel("Spróbuj ponownie");
|
||||
configureGeoSecondaryButton(appAccessLevel === "menu" ? "back_to_menu" : "menu_only");
|
||||
setGeoStatus(
|
||||
`Wygląda na to, że jesteś poza restauracją (ok. ${Math.round(dist)} m, dokładność GPS: ±${Math.round(accuracy)} m).`,
|
||||
{ error: true }
|
||||
);
|
||||
showGeoInstructions(
|
||||
"Przeglądarka często podaje inną lokalizację niż aplikacja Map Google. " +
|
||||
"Spróbuj ponownie na zewnątrz lub bliżej okna — albo przejdź do menu bez lokalizacji."
|
||||
);
|
||||
} catch (error) {
|
||||
trackEvent("geo_check_failed", {
|
||||
reason: "browser_error",
|
||||
code: error.code || null,
|
||||
message: String(error.message || ""),
|
||||
});
|
||||
setGeoActionBusy(false);
|
||||
setGeoActionLabel("Spróbuj ponownie");
|
||||
configureGeoSecondaryButton(appAccessLevel === "menu" ? "back_to_menu" : "menu_only");
|
||||
const deniedBecauseInsecure = /secure origins|only secure|https/i.test(String(error.message || ""));
|
||||
|
||||
if (deniedBecauseInsecure) {
|
||||
setGeoStatus("Geolokalizacja wymaga HTTPS.", { error: true });
|
||||
showGeoInstructions("Otwórz aplikację przez bezpieczny adres <b>https://</b> i spróbuj ponownie.");
|
||||
} else if (isGeoPermissionDenied(error)) {
|
||||
showGeoPermissionBlockedState();
|
||||
} else {
|
||||
setGeoStatus("Nie udało się pobrać lokalizacji.", { error: true });
|
||||
showGeoInstructions("Sprawdź zasięg, włącz GPS i spróbuj ponownie.");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
bindGeoScreenButtons();
|
||||
|
||||
if (shouldBypassGeolocationHost()) {
|
||||
startApp();
|
||||
} else if (isIOSDevice()) {
|
||||
showGeoConsentScreen();
|
||||
bypassGeolocation("trusted_host", { host: window.location.hostname });
|
||||
} else {
|
||||
initGeolocation();
|
||||
bootstrapGeolocation();
|
||||
}
|
||||
Reference in New Issue
Block a user