503 lines
16 KiB
JavaScript
503 lines
16 KiB
JavaScript
// 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();
|
|
}
|
|
});
|
|
});
|
|
|
|
// 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", {
|
|
credentials: "include",
|
|
});
|
|
const data = await response.json();
|
|
if (data.success) {
|
|
currentSettings = data.settings || {};
|
|
populateSettings();
|
|
}
|
|
} catch (error) {
|
|
console.error("Failed to load settings:", error);
|
|
}
|
|
}
|
|
|
|
function populateSettings() {
|
|
// General Settings
|
|
if (currentSettings.general) {
|
|
document.getElementById("siteName").value =
|
|
currentSettings.general.siteName || "";
|
|
document.getElementById("siteTagline").value =
|
|
currentSettings.general.siteTagline || "";
|
|
document.getElementById("siteEmail").value =
|
|
currentSettings.general.siteEmail || "";
|
|
document.getElementById("sitePhone").value =
|
|
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
|
|
if (currentSettings.homepage) {
|
|
document.getElementById("showHero").checked =
|
|
currentSettings.homepage.showHero !== false;
|
|
document.getElementById("showPromotions").checked =
|
|
currentSettings.homepage.showPromotions !== false;
|
|
document.getElementById("showPortfolio").checked =
|
|
currentSettings.homepage.showPortfolio !== false;
|
|
document.getElementById("showBlog").checked =
|
|
currentSettings.homepage.showBlog !== false;
|
|
}
|
|
|
|
// Product Settings
|
|
if (currentSettings.product) {
|
|
document.getElementById("defaultProductStatus").value =
|
|
currentSettings.product.defaultStatus || "active";
|
|
document.getElementById("productsPerPage").value =
|
|
currentSettings.product.perPage || 12;
|
|
document.getElementById("bestSellerLogic").value =
|
|
currentSettings.product.bestSellerLogic || "manual";
|
|
document.getElementById("enableInventory").checked =
|
|
currentSettings.product.enableInventory !== false;
|
|
document.getElementById("showOutOfStock").checked =
|
|
currentSettings.product.showOutOfStock !== false;
|
|
}
|
|
|
|
// Security Settings
|
|
if (currentSettings.security) {
|
|
document.getElementById("passwordExpiration").value =
|
|
currentSettings.security.passwordExpiration || 90;
|
|
document.getElementById("sessionTimeout").value =
|
|
currentSettings.security.sessionTimeout || 60;
|
|
document.getElementById("loginAttempts").value =
|
|
currentSettings.security.loginAttempts || 5;
|
|
document.getElementById("requireStrongPassword").checked =
|
|
currentSettings.security.requireStrongPassword !== false;
|
|
document.getElementById("enableTwoFactor").checked =
|
|
currentSettings.security.enableTwoFactor || false;
|
|
}
|
|
|
|
// Appearance Settings
|
|
if (currentSettings.appearance) {
|
|
document.getElementById("accentColor").value =
|
|
currentSettings.appearance.accentColor || "#667eea";
|
|
updateColorPreview();
|
|
}
|
|
}
|
|
|
|
// Media Library Functions - Make global for onclick handlers
|
|
window.openMediaLibrary = async function (targetField) {
|
|
console.log("openMediaLibrary called for:", targetField);
|
|
currentMediaTarget = targetField;
|
|
selectedMediaUrl = null;
|
|
|
|
// 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 url = document.getElementById("siteFavicon").value;
|
|
const preview = document.getElementById("faviconPreview");
|
|
if (url) {
|
|
preview.innerHTML = `<img src="${url}" alt="Favicon" />`;
|
|
}
|
|
}
|
|
|
|
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 updateColorPreview() {
|
|
const color = document.getElementById("accentColor").value;
|
|
document.getElementById("colorPreview").style.backgroundColor = color;
|
|
document.getElementById("colorValue").textContent = color;
|
|
}
|
|
|
|
window.saveSettings = async function () {
|
|
console.log("saveSettings called");
|
|
|
|
const settings = {
|
|
general: {
|
|
siteName: document.getElementById("siteName").value,
|
|
siteTagline: document.getElementById("siteTagline").value,
|
|
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,
|
|
showPromotions: document.getElementById("showPromotions").checked,
|
|
showPortfolio: document.getElementById("showPortfolio").checked,
|
|
showBlog: document.getElementById("showBlog").checked,
|
|
},
|
|
product: {
|
|
defaultStatus: document.getElementById("defaultProductStatus").value,
|
|
perPage: parseInt(document.getElementById("productsPerPage").value),
|
|
bestSellerLogic: document.getElementById("bestSellerLogic").value,
|
|
enableInventory: document.getElementById("enableInventory").checked,
|
|
showOutOfStock: document.getElementById("showOutOfStock").checked,
|
|
},
|
|
security: {
|
|
passwordExpiration: parseInt(
|
|
document.getElementById("passwordExpiration").value
|
|
),
|
|
sessionTimeout: parseInt(document.getElementById("sessionTimeout").value),
|
|
loginAttempts: parseInt(document.getElementById("loginAttempts").value),
|
|
requireStrongPassword: document.getElementById("requireStrongPassword")
|
|
.checked,
|
|
enableTwoFactor: document.getElementById("enableTwoFactor").checked,
|
|
},
|
|
appearance: {
|
|
accentColor: document.getElementById("accentColor").value,
|
|
},
|
|
};
|
|
|
|
console.log("Settings to save:", settings);
|
|
|
|
try {
|
|
const response = await fetch("/api/admin/settings", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
credentials: "include",
|
|
body: JSON.stringify(settings),
|
|
});
|
|
|
|
const data = await response.json();
|
|
console.log("Save response:", data);
|
|
|
|
if (data.success) {
|
|
window.showToast("Settings saved successfully!", "success");
|
|
currentSettings = settings;
|
|
} else {
|
|
window.showToast(data.message || "Failed to save settings", "error");
|
|
}
|
|
} catch (error) {
|
|
console.error("Failed to save settings:", error);
|
|
window.showToast("Failed to save settings. Please try again.", "error");
|
|
}
|
|
};
|