mirror of
https://github.com/wowlikon/LiB.git
synced 2026-02-04 04:31:09 +00:00
Добавление зарплаты для ролей, добавление статусов книг, обновление фронтэнда
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
const Utils = {
|
||||
escapeHtml: (text) => {
|
||||
if (!text) return "";
|
||||
return text.replace(/[&<>"']/g, function (m) {
|
||||
return {
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
'"': """,
|
||||
"'": "'",
|
||||
}[m];
|
||||
});
|
||||
},
|
||||
|
||||
showToast: (message, type = "info") => {
|
||||
const container = document.getElementById("toast-container");
|
||||
if (!container) return;
|
||||
|
||||
const el = document.createElement("div");
|
||||
const colors =
|
||||
type === "error"
|
||||
? "bg-red-500"
|
||||
: type === "success"
|
||||
? "bg-green-500"
|
||||
: "bg-blue-500";
|
||||
el.className = `${colors} text-white px-6 py-3 rounded shadow-lg transform transition-all duration-300 translate-y-10 opacity-0 mb-3`;
|
||||
el.textContent = message;
|
||||
|
||||
container.appendChild(el);
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
el.classList.remove("translate-y-10", "opacity-0");
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
el.classList.add("translate-y-10", "opacity-0");
|
||||
setTimeout(() => el.remove(), 300);
|
||||
}, 3000);
|
||||
},
|
||||
|
||||
getGravatarUrl: async (email) => {
|
||||
if (!email) return "";
|
||||
const msgBuffer = new TextEncoder().encode(email.trim().toLowerCase());
|
||||
const hashBuffer = await crypto.subtle.digest("SHA-256", msgBuffer);
|
||||
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
||||
const hashHex = hashArray
|
||||
.map((b) => b.toString(16).padStart(2, "0"))
|
||||
.join("");
|
||||
return `https://www.gravatar.com/avatar/${hashHex}?d=identicon&s=200`;
|
||||
},
|
||||
};
|
||||
|
||||
const Api = {
|
||||
async request(endpoint, options = {}) {
|
||||
const token = localStorage.getItem("access_token");
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
...options.headers,
|
||||
};
|
||||
|
||||
if (token) {
|
||||
headers["Authorization"] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
const config = { ...options, headers };
|
||||
|
||||
try {
|
||||
const response = await fetch(endpoint, config);
|
||||
if (response.status === 401) {
|
||||
Auth.logout();
|
||||
return null;
|
||||
}
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
throw new Error(errorData.detail || `Error ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
get(endpoint) {
|
||||
return this.request(endpoint, { method: "GET" });
|
||||
},
|
||||
|
||||
post(endpoint, body) {
|
||||
return this.request(endpoint, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
},
|
||||
|
||||
postForm(endpoint, formData) {
|
||||
return this.request(endpoint, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||
body: formData.toString(),
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
const Auth = {
|
||||
logout: () => {
|
||||
localStorage.removeItem("access_token");
|
||||
localStorage.removeItem("refresh_token");
|
||||
window.location.reload();
|
||||
},
|
||||
|
||||
init: async () => {
|
||||
const token = localStorage.getItem("access_token");
|
||||
if (!token) return;
|
||||
|
||||
try {
|
||||
const user = await Api.get("/api/auth/me");
|
||||
if (user) {
|
||||
document.dispatchEvent(new CustomEvent("auth:login", { detail: user }));
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Auth check failed", e);
|
||||
}
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user