This commit is contained in:
152
web/static/page_common.js
Normal file
152
web/static/page_common.js
Normal file
@@ -0,0 +1,152 @@
|
||||
const tg = window.Telegram?.WebApp;
|
||||
tg?.ready();
|
||||
tg?.expand();
|
||||
|
||||
const CarPassPage = (() => {
|
||||
const state = { user: null, authConfig: null };
|
||||
|
||||
function authHeaders(extra = {}) {
|
||||
const headers = { ...extra };
|
||||
if (tg?.initData) headers["X-Telegram-Init-Data"] = tg.initData;
|
||||
if (!tg?.initData && state.authConfig?.allow_dev_auth) {
|
||||
headers["X-Dev-Telegram-Id"] = localStorage.getItem("driversDevTelegramId") || "1";
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
async function api(path, options = {}) {
|
||||
const { headers: optionHeaders = {}, ...fetchOptions } = options;
|
||||
const headers = { "Content-Type": "application/json", ...authHeaders(optionHeaders) };
|
||||
if (options.body instanceof FormData) delete headers["Content-Type"];
|
||||
const response = await fetch(`/api${path}`, { ...fetchOptions, headers });
|
||||
if (!response.ok) throw new Error(await response.text() || response.statusText);
|
||||
if (response.status === 204) return null;
|
||||
return response.json();
|
||||
}
|
||||
|
||||
async function loadAuthConfig() {
|
||||
state.authConfig = await api("/users/auth/config");
|
||||
}
|
||||
|
||||
function showAuthOverlay() {
|
||||
document.body.classList.add("auth-required");
|
||||
const link = document.querySelector("#telegramLoginLink");
|
||||
if (state.authConfig?.bot_username && link) {
|
||||
link.href = `https://t.me/${state.authConfig.bot_username}`;
|
||||
link.classList.remove("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureUser() {
|
||||
if (tg?.initData) {
|
||||
state.user = await api("/users/webapp-auth", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ init_data: tg.initData }),
|
||||
});
|
||||
} else if (state.authConfig?.allow_dev_auth) {
|
||||
state.user = await api("/users/me");
|
||||
} else {
|
||||
showAuthOverlay();
|
||||
throw new Error("Требуется вход через Telegram");
|
||||
}
|
||||
document.body.classList.remove("auth-required");
|
||||
document.querySelector("#authOverlay")?.classList.add("hidden");
|
||||
return state.user;
|
||||
}
|
||||
|
||||
function toast(message, tone = "success") {
|
||||
const node = document.querySelector("#toast");
|
||||
if (!node) return;
|
||||
node.textContent = message;
|
||||
node.className = `toast ${tone}`;
|
||||
window.clearTimeout(toast.timer);
|
||||
toast.timer = window.setTimeout(() => node.classList.add("hidden"), 2600);
|
||||
}
|
||||
|
||||
function setBusy(button, busy, label = "Сохраняю...") {
|
||||
if (!button) return;
|
||||
if (busy) {
|
||||
button.dataset.label = button.textContent;
|
||||
button.disabled = true;
|
||||
button.classList.add("is-busy");
|
||||
button.innerHTML = `<span class="spinner"></span><span>${label}</span>`;
|
||||
} else {
|
||||
button.disabled = false;
|
||||
button.classList.remove("is-busy");
|
||||
button.textContent = button.dataset.label || button.textContent;
|
||||
delete button.dataset.label;
|
||||
}
|
||||
}
|
||||
|
||||
async function runAction(button, label, callback) {
|
||||
setBusy(button, true, label);
|
||||
try {
|
||||
const result = await callback();
|
||||
return result;
|
||||
} catch (error) {
|
||||
toast(error.message || "Ошибка", "error");
|
||||
throw error;
|
||||
} finally {
|
||||
setBusy(button, false, label);
|
||||
}
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
return String(value ?? "")
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """);
|
||||
}
|
||||
|
||||
function formData(form) {
|
||||
return Object.fromEntries(new FormData(form).entries());
|
||||
}
|
||||
|
||||
function numberOrNull(value) {
|
||||
return value === "" || value == null ? null : Number(value);
|
||||
}
|
||||
|
||||
function csvList(value) {
|
||||
return value ? value.split(",").map((item) => item.trim()).filter(Boolean) : null;
|
||||
}
|
||||
|
||||
function formatDateTime(value) {
|
||||
if (!value) return "-";
|
||||
const date = new Date(value);
|
||||
if (Number.isNaN(date.getTime())) return String(value).slice(0, 16).replace("T", " ");
|
||||
return date.toLocaleString("ru-RU", { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" });
|
||||
}
|
||||
|
||||
function today() {
|
||||
return new Date().toISOString().slice(0, 10);
|
||||
}
|
||||
|
||||
async function boot(init) {
|
||||
try {
|
||||
await loadAuthConfig();
|
||||
await ensureUser();
|
||||
await init();
|
||||
} catch (error) {
|
||||
if (error.message === "Требуется вход через Telegram") return;
|
||||
console.error(error);
|
||||
toast(error.message || "Ошибка", "error");
|
||||
}
|
||||
}
|
||||
|
||||
document.querySelector("#telegramRetryBtn")?.addEventListener("click", () => window.location.reload());
|
||||
|
||||
return {
|
||||
state,
|
||||
api,
|
||||
boot,
|
||||
toast,
|
||||
runAction,
|
||||
escapeHtml,
|
||||
formData,
|
||||
numberOrNull,
|
||||
csvList,
|
||||
formatDateTime,
|
||||
today,
|
||||
};
|
||||
})();
|
||||
Reference in New Issue
Block a user