updateweb
This commit is contained in:
@@ -1,8 +1,32 @@
|
||||
// Settings Management JavaScript
|
||||
|
||||
let currentSettings = {};
|
||||
let mediaLibraryModal;
|
||||
let currentMediaTarget = null;
|
||||
let selectedMediaUrl = null;
|
||||
let allMedia = [];
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
// Initialize modal
|
||||
const modalElement = document.getElementById("mediaLibraryModal");
|
||||
if (modalElement) {
|
||||
mediaLibraryModal = new bootstrap.Modal(modalElement);
|
||||
}
|
||||
|
||||
// Setup media search
|
||||
const searchInput = document.getElementById("mediaSearch");
|
||||
if (searchInput) {
|
||||
searchInput.addEventListener("input", filterMedia);
|
||||
}
|
||||
|
||||
const typeFilter = document.getElementById("mediaTypeFilter");
|
||||
if (typeFilter) {
|
||||
typeFilter.addEventListener("change", filterMedia);
|
||||
}
|
||||
|
||||
// Load saved theme
|
||||
loadTheme();
|
||||
|
||||
checkAuth().then((authenticated) => {
|
||||
if (authenticated) {
|
||||
loadSettings();
|
||||
@@ -10,6 +34,128 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
});
|
||||
});
|
||||
|
||||
// Toast Notification System - Make global for onclick handlers
|
||||
window.showToast = function (message, type = "success") {
|
||||
const container = document.getElementById("toastContainer");
|
||||
if (!container) {
|
||||
console.error("Toast container not found!");
|
||||
return;
|
||||
}
|
||||
|
||||
const icons = {
|
||||
success: "bi-check-circle-fill",
|
||||
error: "bi-x-circle-fill",
|
||||
warning: "bi-exclamation-triangle-fill",
|
||||
info: "bi-info-circle-fill",
|
||||
};
|
||||
|
||||
const toast = document.createElement("div");
|
||||
toast.className = `toast toast-${type}`;
|
||||
toast.innerHTML = `
|
||||
<div class="toast-icon">
|
||||
<i class="bi ${icons[type] || icons.info}"></i>
|
||||
</div>
|
||||
<div class="toast-message">${message}</div>
|
||||
<button class="toast-close" onclick="window.closeToast(this)">
|
||||
<i class="bi bi-x"></i>
|
||||
</button>
|
||||
`;
|
||||
|
||||
container.appendChild(toast);
|
||||
|
||||
// Trigger animation
|
||||
setTimeout(() => toast.classList.add("toast-show"), 10);
|
||||
|
||||
// Add visual feedback for success saves
|
||||
if (type === "success" && message.includes("saved")) {
|
||||
const saveBtn = document.querySelector('button[onclick*="saveSettings"]');
|
||||
if (saveBtn) {
|
||||
const originalBg = saveBtn.style.background;
|
||||
const originalTransform = saveBtn.style.transform;
|
||||
saveBtn.style.background =
|
||||
"linear-gradient(135deg, #10b981 0%, #059669 100%)";
|
||||
saveBtn.style.transform = "scale(1.05)";
|
||||
saveBtn.innerHTML = '<i class="bi bi-check-lg"></i> Saved!';
|
||||
setTimeout(() => {
|
||||
saveBtn.style.background = originalBg;
|
||||
saveBtn.style.transform = originalTransform;
|
||||
saveBtn.innerHTML = '<i class="bi bi-save"></i> Save All Settings';
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
|
||||
// Auto remove after 5 seconds
|
||||
setTimeout(() => {
|
||||
toast.classList.add("toast-hide");
|
||||
setTimeout(() => toast.remove(), 300);
|
||||
}, 5000);
|
||||
};
|
||||
|
||||
window.closeToast = function (button) {
|
||||
const toast = button.closest(".toast");
|
||||
toast.classList.add("toast-hide");
|
||||
setTimeout(() => toast.remove(), 300);
|
||||
};
|
||||
|
||||
// Theme Management - Make global for onclick handlers
|
||||
function loadTheme() {
|
||||
const savedTheme = localStorage.getItem("adminTheme") || "light";
|
||||
applyTheme(savedTheme);
|
||||
}
|
||||
|
||||
window.selectTheme = function (theme) {
|
||||
console.log("selectTheme called with:", theme);
|
||||
|
||||
// Update UI
|
||||
document.querySelectorAll(".theme-selector .theme-option").forEach((el) => {
|
||||
el.classList.remove("active");
|
||||
});
|
||||
event.target.closest(".theme-option").classList.add("active");
|
||||
|
||||
// Save and apply theme
|
||||
localStorage.setItem("adminTheme", theme);
|
||||
applyTheme(theme);
|
||||
window.showToast(`Theme changed to ${theme} mode`, "success");
|
||||
};
|
||||
|
||||
function applyTheme(theme) {
|
||||
console.log("applyTheme called with:", theme);
|
||||
const body = document.body;
|
||||
|
||||
if (theme === "dark") {
|
||||
body.classList.add("dark-mode");
|
||||
body.classList.remove("light-mode");
|
||||
} else if (theme === "light") {
|
||||
body.classList.add("light-mode");
|
||||
body.classList.remove("dark-mode");
|
||||
} else if (theme === "auto") {
|
||||
// Check system preference
|
||||
const prefersDark = window.matchMedia(
|
||||
"(prefers-color-scheme: dark)"
|
||||
).matches;
|
||||
if (prefersDark) {
|
||||
body.classList.add("dark-mode");
|
||||
body.classList.remove("light-mode");
|
||||
} else {
|
||||
body.classList.add("light-mode");
|
||||
body.classList.remove("dark-mode");
|
||||
}
|
||||
}
|
||||
|
||||
// Update active state in UI
|
||||
const themeOptions = document.querySelectorAll(
|
||||
".theme-selector .theme-option"
|
||||
);
|
||||
themeOptions.forEach((option, index) => {
|
||||
const themes = ["light", "dark", "auto"];
|
||||
if (themes[index] === theme) {
|
||||
option.classList.add("active");
|
||||
} else {
|
||||
option.classList.remove("active");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function loadSettings() {
|
||||
try {
|
||||
const response = await fetch("/api/admin/settings", {
|
||||
@@ -38,6 +184,22 @@ function populateSettings() {
|
||||
currentSettings.general.sitePhone || "";
|
||||
document.getElementById("timezone").value =
|
||||
currentSettings.general.timezone || "UTC";
|
||||
|
||||
// Logo and Favicon
|
||||
if (currentSettings.general.siteLogo) {
|
||||
document.getElementById("siteLogo").value =
|
||||
currentSettings.general.siteLogo;
|
||||
document.getElementById(
|
||||
"logoPreview"
|
||||
).innerHTML = `<img src="${currentSettings.general.siteLogo}" alt="Logo" />`;
|
||||
}
|
||||
if (currentSettings.general.siteFavicon) {
|
||||
document.getElementById("siteFavicon").value =
|
||||
currentSettings.general.siteFavicon;
|
||||
document.getElementById(
|
||||
"faviconPreview"
|
||||
).innerHTML = `<img src="${currentSettings.general.siteFavicon}" alt="Favicon" />`;
|
||||
}
|
||||
}
|
||||
|
||||
// Homepage Settings
|
||||
@@ -88,45 +250,184 @@ function populateSettings() {
|
||||
}
|
||||
}
|
||||
|
||||
function previewLogo() {
|
||||
const fileInput = document.getElementById("siteLogo");
|
||||
const preview = document.getElementById("logoPreview");
|
||||
// Media Library Functions - Make global for onclick handlers
|
||||
window.openMediaLibrary = async function (targetField) {
|
||||
console.log("openMediaLibrary called for:", targetField);
|
||||
currentMediaTarget = targetField;
|
||||
selectedMediaUrl = null;
|
||||
|
||||
if (fileInput.files && fileInput.files[0]) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = function (e) {
|
||||
preview.innerHTML = `<img src="${e.target.result}" alt="Logo" />`;
|
||||
};
|
||||
reader.readAsDataURL(fileInput.files[0]);
|
||||
// Load media files
|
||||
try {
|
||||
const response = await fetch("/api/admin/uploads", {
|
||||
credentials: "include",
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
allMedia = data.files || [];
|
||||
renderMediaGrid(allMedia);
|
||||
mediaLibraryModal.show();
|
||||
} else {
|
||||
showToast(data.message || "Failed to load media library", "error");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load media library:", error);
|
||||
showToast("Failed to load media library. Please try again.", "error");
|
||||
}
|
||||
};
|
||||
|
||||
function renderMediaGrid(media) {
|
||||
const grid = document.getElementById("mediaGrid");
|
||||
if (media.length === 0) {
|
||||
grid.innerHTML = `
|
||||
<div class="text-center py-5" style="grid-column: 1/-1;">
|
||||
<i class="bi bi-inbox fs-1 text-muted"></i>
|
||||
<p class="text-muted">No media files found</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
grid.innerHTML = media
|
||||
.map(
|
||||
(file) => `
|
||||
<div class="media-item" data-url="${
|
||||
file.path
|
||||
}" style="cursor: pointer; border: 3px solid transparent; border-radius: 8px; overflow: hidden; transition: all 0.3s;">
|
||||
${
|
||||
file.mimetype?.startsWith("image/")
|
||||
? `<img src="${file.path}" alt="${
|
||||
file.originalName || file.filename
|
||||
}" style="width: 100%; height: 150px; object-fit: cover;" />`
|
||||
: `<div style="width: 100%; height: 150px; background: #f8f9fa; display: flex; align-items: center; justify-content: center;">
|
||||
<i class="bi bi-file-earmark fs-1 text-muted"></i>
|
||||
</div>`
|
||||
}
|
||||
<div style="padding: 8px; font-size: 12px; text-align: center; background: white;">
|
||||
<div style="font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${
|
||||
file.originalName || file.filename
|
||||
}</div>
|
||||
<div style="color: #6c757d; font-size: 11px;">${formatFileSize(
|
||||
file.size
|
||||
)}</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
)
|
||||
.join("");
|
||||
|
||||
// Add click listeners to all media items
|
||||
document.querySelectorAll(".media-item").forEach((item) => {
|
||||
item.addEventListener("click", function () {
|
||||
selectMedia(this.dataset.url);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function selectMedia(url) {
|
||||
// Remove previous selection
|
||||
document.querySelectorAll(".media-item").forEach((el) => {
|
||||
el.style.border = "3px solid transparent";
|
||||
});
|
||||
|
||||
// Mark current selection - find the clicked item
|
||||
document.querySelectorAll(".media-item").forEach((el) => {
|
||||
if (el.dataset.url === url) {
|
||||
el.style.border = "3px solid #667eea";
|
||||
el.style.background = "#f8f9fa";
|
||||
}
|
||||
});
|
||||
|
||||
selectedMediaUrl = url;
|
||||
}
|
||||
|
||||
window.selectMediaFile = function () {
|
||||
if (!selectedMediaUrl) {
|
||||
window.showToast("Please select a media file", "warning");
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the selected URL to the target field
|
||||
document.getElementById(currentMediaTarget).value = selectedMediaUrl;
|
||||
|
||||
// Update preview
|
||||
if (currentMediaTarget === "siteLogo") {
|
||||
document.getElementById(
|
||||
"logoPreview"
|
||||
).innerHTML = `<img src="${selectedMediaUrl}" alt="Logo" />`;
|
||||
} else if (currentMediaTarget === "siteFavicon") {
|
||||
document.getElementById(
|
||||
"faviconPreview"
|
||||
).innerHTML = `<img src="${selectedMediaUrl}" alt="Favicon" />`;
|
||||
}
|
||||
|
||||
// Close modal
|
||||
mediaLibraryModal.hide();
|
||||
window.showToast("Image selected successfully", "success");
|
||||
};
|
||||
|
||||
function filterMedia() {
|
||||
const searchTerm = document.getElementById("mediaSearch").value.toLowerCase();
|
||||
const typeFilter = document.getElementById("mediaTypeFilter").value;
|
||||
|
||||
let filtered = allMedia;
|
||||
|
||||
// Filter by search term
|
||||
if (searchTerm) {
|
||||
filtered = filtered.filter(
|
||||
(file) =>
|
||||
file.filename.toLowerCase().includes(searchTerm) ||
|
||||
file.originalName?.toLowerCase().includes(searchTerm)
|
||||
);
|
||||
}
|
||||
|
||||
// Filter by type
|
||||
if (typeFilter !== "all") {
|
||||
filtered = filtered.filter((file) => {
|
||||
if (typeFilter === "image") return file.mimetype?.startsWith("image/");
|
||||
if (typeFilter === "video") return file.mimetype?.startsWith("video/");
|
||||
if (typeFilter === "document")
|
||||
return (
|
||||
file.mimetype?.includes("pdf") ||
|
||||
file.mimetype?.includes("document") ||
|
||||
file.mimetype?.includes("text")
|
||||
);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
renderMediaGrid(filtered);
|
||||
}
|
||||
|
||||
function formatFileSize(bytes) {
|
||||
if (!bytes) return "0 B";
|
||||
const k = 1024;
|
||||
const sizes = ["B", "KB", "MB", "GB"];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + " " + sizes[i];
|
||||
}
|
||||
|
||||
function previewLogo() {
|
||||
const url = document.getElementById("siteLogo").value;
|
||||
const preview = document.getElementById("logoPreview");
|
||||
if (url) {
|
||||
preview.innerHTML = `<img src="${url}" alt="Logo" />`;
|
||||
}
|
||||
}
|
||||
|
||||
function previewFavicon() {
|
||||
const fileInput = document.getElementById("siteFavicon");
|
||||
const url = document.getElementById("siteFavicon").value;
|
||||
const preview = document.getElementById("faviconPreview");
|
||||
|
||||
if (fileInput.files && fileInput.files[0]) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = function (e) {
|
||||
preview.innerHTML = `<img src="${e.target.result}" alt="Favicon" />`;
|
||||
};
|
||||
reader.readAsDataURL(fileInput.files[0]);
|
||||
if (url) {
|
||||
preview.innerHTML = `<img src="${url}" alt="Favicon" />`;
|
||||
}
|
||||
}
|
||||
|
||||
function selectLayout(layout) {
|
||||
window.selectLayout = function (layout) {
|
||||
document.querySelectorAll(".theme-selector .theme-option").forEach((el) => {
|
||||
el.classList.remove("active");
|
||||
});
|
||||
event.target.closest(".theme-option").classList.add("active");
|
||||
}
|
||||
|
||||
function selectTheme(theme) {
|
||||
document.querySelectorAll(".theme-selector .theme-option").forEach((el) => {
|
||||
el.classList.remove("active");
|
||||
});
|
||||
event.target.closest(".theme-option").classList.add("active");
|
||||
}
|
||||
};
|
||||
|
||||
function updateColorPreview() {
|
||||
const color = document.getElementById("accentColor").value;
|
||||
@@ -134,7 +435,9 @@ function updateColorPreview() {
|
||||
document.getElementById("colorValue").textContent = color;
|
||||
}
|
||||
|
||||
async function saveSettings() {
|
||||
window.saveSettings = async function () {
|
||||
console.log("saveSettings called");
|
||||
|
||||
const settings = {
|
||||
general: {
|
||||
siteName: document.getElementById("siteName").value,
|
||||
@@ -142,6 +445,8 @@ async function saveSettings() {
|
||||
siteEmail: document.getElementById("siteEmail").value,
|
||||
sitePhone: document.getElementById("sitePhone").value,
|
||||
timezone: document.getElementById("timezone").value,
|
||||
siteLogo: document.getElementById("siteLogo").value,
|
||||
siteFavicon: document.getElementById("siteFavicon").value,
|
||||
},
|
||||
homepage: {
|
||||
showHero: document.getElementById("showHero").checked,
|
||||
@@ -171,6 +476,8 @@ async function saveSettings() {
|
||||
},
|
||||
};
|
||||
|
||||
console.log("Settings to save:", settings);
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/admin/settings", {
|
||||
method: "POST",
|
||||
@@ -180,34 +487,16 @@ async function saveSettings() {
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
console.log("Save response:", data);
|
||||
|
||||
if (data.success) {
|
||||
showSuccess("Settings saved successfully!");
|
||||
window.showToast("Settings saved successfully!", "success");
|
||||
currentSettings = settings;
|
||||
} else {
|
||||
showError(data.message || "Failed to save settings");
|
||||
window.showToast(data.message || "Failed to save settings", "error");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to save settings:", error);
|
||||
showError("Failed to save settings");
|
||||
window.showToast("Failed to save settings. Please try again.", "error");
|
||||
}
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
try {
|
||||
const response = await fetch("/api/admin/logout", {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
});
|
||||
if (response.ok) window.location.href = "/admin/login.html";
|
||||
} catch (error) {
|
||||
console.error("Logout failed:", error);
|
||||
}
|
||||
}
|
||||
|
||||
function showSuccess(message) {
|
||||
alert(message);
|
||||
}
|
||||
|
||||
function showError(message) {
|
||||
alert("Error: " + message);
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user