updateweb
This commit is contained in:
104
website/admin/js/auth.js
Normal file
104
website/admin/js/auth.js
Normal file
@@ -0,0 +1,104 @@
|
||||
// Shared Authentication Utility for Admin Panel
|
||||
// Include this file in all admin pages to handle authentication
|
||||
|
||||
// Global authentication state
|
||||
window.adminAuth = {
|
||||
user: null,
|
||||
isAuthenticated: false,
|
||||
};
|
||||
|
||||
// Check authentication and redirect if needed
|
||||
async function checkAuth() {
|
||||
try {
|
||||
const response = await fetch("/api/admin/session", {
|
||||
credentials: "include",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
redirectToLogin();
|
||||
return false;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
if (!data.authenticated) {
|
||||
redirectToLogin();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Store user data
|
||||
window.adminAuth.user = data.user;
|
||||
window.adminAuth.isAuthenticated = true;
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("Authentication check failed:", error);
|
||||
redirectToLogin();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Redirect to login page
|
||||
function redirectToLogin() {
|
||||
if (window.location.pathname !== "/admin/login.html") {
|
||||
window.location.href = "/admin/login.html";
|
||||
}
|
||||
}
|
||||
|
||||
// Logout function
|
||||
async function logout() {
|
||||
try {
|
||||
const response = await fetch("/api/admin/logout", {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
window.adminAuth.user = null;
|
||||
window.adminAuth.isAuthenticated = false;
|
||||
window.location.href = "/admin/login.html";
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Logout failed:", error);
|
||||
window.location.href = "/admin/login.html";
|
||||
}
|
||||
}
|
||||
|
||||
// Show success notification
|
||||
function showSuccess(message) {
|
||||
const alert = document.createElement("div");
|
||||
alert.className =
|
||||
"alert alert-success alert-dismissible fade show position-fixed";
|
||||
alert.style.cssText =
|
||||
"top: 20px; right: 20px; z-index: 9999; min-width: 300px;";
|
||||
alert.innerHTML = `
|
||||
${message}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
`;
|
||||
document.body.appendChild(alert);
|
||||
setTimeout(() => alert.remove(), 5000);
|
||||
}
|
||||
|
||||
// Show error notification
|
||||
function showError(message) {
|
||||
const alert = document.createElement("div");
|
||||
alert.className =
|
||||
"alert alert-danger alert-dismissible fade show position-fixed";
|
||||
alert.style.cssText =
|
||||
"top: 20px; right: 20px; z-index: 9999; min-width: 300px;";
|
||||
alert.innerHTML = `
|
||||
${message}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
`;
|
||||
document.body.appendChild(alert);
|
||||
setTimeout(() => alert.remove(), 5000);
|
||||
}
|
||||
|
||||
// Auto-check authentication when this script loads
|
||||
// Only run if we're not on the login page
|
||||
if (window.location.pathname !== "/admin/login.html") {
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
checkAuth();
|
||||
});
|
||||
}
|
||||
233
website/admin/js/blog.js
Normal file
233
website/admin/js/blog.js
Normal file
@@ -0,0 +1,233 @@
|
||||
// Blog Management JavaScript
|
||||
|
||||
let postsData = [];
|
||||
let postModal;
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
postModal = new bootstrap.Modal(document.getElementById("postModal"));
|
||||
checkAuth().then((authenticated) => {
|
||||
if (authenticated) {
|
||||
loadPosts();
|
||||
}
|
||||
});
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
if (urlParams.get("action") === "create") {
|
||||
showCreatePost();
|
||||
}
|
||||
|
||||
// Auto-generate slug from title
|
||||
document.getElementById("postTitle")?.addEventListener("input", function () {
|
||||
if (!document.getElementById("postId").value) {
|
||||
document.getElementById("postSlug").value = slugify(this.value);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
async function loadPosts() {
|
||||
try {
|
||||
const response = await fetch("/api/admin/blog", { credentials: "include" });
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
postsData = data.posts;
|
||||
renderPosts(postsData);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load posts:", error);
|
||||
}
|
||||
}
|
||||
|
||||
function renderPosts(posts) {
|
||||
const tbody = document.getElementById("postsTableBody");
|
||||
if (posts.length === 0) {
|
||||
tbody.innerHTML = `
|
||||
<tr><td colspan="7" class="text-center p-4">
|
||||
<i class="bi bi-inbox" style="font-size: 3rem; color: #ccc;"></i>
|
||||
<p class="mt-3 text-muted">No blog posts found</p>
|
||||
<button class="btn btn-primary" onclick="showCreatePost()">
|
||||
<i class="bi bi-plus-circle"></i> Create Your First Post
|
||||
</button>
|
||||
</td></tr>`;
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = posts
|
||||
.map(
|
||||
(p) => `
|
||||
<tr>
|
||||
<td>${p.id}</td>
|
||||
<td><strong>${escapeHtml(p.title)}</strong></td>
|
||||
<td><code>${escapeHtml(p.slug)}</code></td>
|
||||
<td>${escapeHtml((p.excerpt || "").substring(0, 40))}...</td>
|
||||
<td><span class="badge ${
|
||||
p.ispublished ? "badge-success" : "badge-warning"
|
||||
}">
|
||||
${p.ispublished ? "Published" : "Draft"}</span></td>
|
||||
<td>${formatDate(p.createdat)}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-info" onclick="editPost(${p.id})">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-danger" onclick="deletePost(${
|
||||
p.id
|
||||
}, '${escapeHtml(p.title)}')">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>`
|
||||
)
|
||||
.join("");
|
||||
}
|
||||
|
||||
function filterPosts() {
|
||||
const searchTerm = document.getElementById("searchInput").value.toLowerCase();
|
||||
const filtered = postsData.filter(
|
||||
(p) =>
|
||||
p.title.toLowerCase().includes(searchTerm) ||
|
||||
p.slug.toLowerCase().includes(searchTerm)
|
||||
);
|
||||
renderPosts(filtered);
|
||||
}
|
||||
|
||||
function showCreatePost() {
|
||||
document.getElementById("modalTitle").textContent = "Create Blog Post";
|
||||
document.getElementById("postForm").reset();
|
||||
document.getElementById("postId").value = "";
|
||||
document.getElementById("postPublished").checked = false;
|
||||
postModal.show();
|
||||
}
|
||||
|
||||
async function editPost(id) {
|
||||
try {
|
||||
const response = await fetch(`/api/admin/blog/${id}`, {
|
||||
credentials: "include",
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
const post = data.post;
|
||||
document.getElementById("modalTitle").textContent = "Edit Blog Post";
|
||||
document.getElementById("postId").value = post.id;
|
||||
document.getElementById("postTitle").value = post.title;
|
||||
document.getElementById("postSlug").value = post.slug;
|
||||
document.getElementById("postExcerpt").value = post.excerpt || "";
|
||||
document.getElementById("postContent").value = post.content || "";
|
||||
document.getElementById("postMetaTitle").value = post.metatitle || "";
|
||||
document.getElementById("postMetaDescription").value =
|
||||
post.metadescription || "";
|
||||
document.getElementById("postPublished").checked = post.ispublished;
|
||||
postModal.show();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load post:", error);
|
||||
showError("Failed to load post details");
|
||||
}
|
||||
}
|
||||
|
||||
async function savePost() {
|
||||
const id = document.getElementById("postId").value;
|
||||
const formData = {
|
||||
title: document.getElementById("postTitle").value,
|
||||
slug: document.getElementById("postSlug").value,
|
||||
excerpt: document.getElementById("postExcerpt").value,
|
||||
content: document.getElementById("postContent").value,
|
||||
metatitle: document.getElementById("postMetaTitle").value,
|
||||
metadescription: document.getElementById("postMetaDescription").value,
|
||||
ispublished: document.getElementById("postPublished").checked,
|
||||
};
|
||||
|
||||
if (!formData.title || !formData.slug || !formData.content) {
|
||||
showError("Please fill in all required fields");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const url = id ? `/api/admin/blog/${id}` : "/api/admin/blog";
|
||||
const method = id ? "PUT" : "POST";
|
||||
const response = await fetch(url, {
|
||||
method: method,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
credentials: "include",
|
||||
body: JSON.stringify(formData),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
showSuccess(
|
||||
id ? "Post updated successfully" : "Post created successfully"
|
||||
);
|
||||
postModal.hide();
|
||||
loadPosts();
|
||||
} else {
|
||||
showError(data.message || "Failed to save post");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to save post:", error);
|
||||
showError("Failed to save post");
|
||||
}
|
||||
}
|
||||
|
||||
async function deletePost(id, title) {
|
||||
if (!confirm(`Are you sure you want to delete "${title}"?`)) return;
|
||||
try {
|
||||
const response = await fetch(`/api/admin/blog/${id}`, {
|
||||
method: "DELETE",
|
||||
credentials: "include",
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
showSuccess("Post deleted successfully");
|
||||
loadPosts();
|
||||
} else {
|
||||
showError(data.message || "Failed to delete post");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to delete post:", error);
|
||||
showError("Failed to delete post");
|
||||
}
|
||||
}
|
||||
|
||||
function slugify(text) {
|
||||
return text
|
||||
.toLowerCase()
|
||||
.replace(/[^\w\s-]/g, "")
|
||||
.replace(/[\s_-]+/g, "-")
|
||||
.replace(/^-+|-+$/g, "");
|
||||
}
|
||||
|
||||
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 escapeHtml(text) {
|
||||
const map = {
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
'"': """,
|
||||
"'": "'",
|
||||
};
|
||||
return text.replace(/[&<>"']/g, (m) => map[m]);
|
||||
}
|
||||
|
||||
function formatDate(dateString) {
|
||||
return new Date(dateString).toLocaleDateString("en-US", {
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
});
|
||||
}
|
||||
|
||||
function showSuccess(message) {
|
||||
alert(message);
|
||||
}
|
||||
function showError(message) {
|
||||
alert("Error: " + message);
|
||||
}
|
||||
196
website/admin/js/homepage.js
Normal file
196
website/admin/js/homepage.js
Normal file
@@ -0,0 +1,196 @@
|
||||
// Homepage Editor JavaScript
|
||||
|
||||
let homepageData = {};
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
checkAuth().then((authenticated) => {
|
||||
if (authenticated) {
|
||||
loadHomepageSettings();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
async function loadHomepageSettings() {
|
||||
try {
|
||||
const response = await fetch("/api/admin/homepage/settings", {
|
||||
credentials: "include",
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
homepageData = data.settings || {};
|
||||
populateFields();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load homepage settings:", error);
|
||||
}
|
||||
}
|
||||
|
||||
function populateFields() {
|
||||
// Hero Section
|
||||
if (homepageData.hero) {
|
||||
document.getElementById("heroEnabled").checked =
|
||||
homepageData.hero.enabled !== false;
|
||||
document.getElementById("heroHeadline").value =
|
||||
homepageData.hero.headline || "";
|
||||
document.getElementById("heroSubheading").value =
|
||||
homepageData.hero.subheading || "";
|
||||
document.getElementById("heroDescription").value =
|
||||
homepageData.hero.description || "";
|
||||
document.getElementById("heroCtaText").value =
|
||||
homepageData.hero.ctaText || "";
|
||||
document.getElementById("heroCtaLink").value =
|
||||
homepageData.hero.ctaLink || "";
|
||||
toggleSection("hero");
|
||||
}
|
||||
|
||||
// Promotion Section
|
||||
if (homepageData.promotion) {
|
||||
document.getElementById("promotionEnabled").checked =
|
||||
homepageData.promotion.enabled !== false;
|
||||
document.getElementById("promotionTitle").value =
|
||||
homepageData.promotion.title || "";
|
||||
document.getElementById("promotionDescription").value =
|
||||
homepageData.promotion.description || "";
|
||||
toggleSection("promotion");
|
||||
}
|
||||
|
||||
// Portfolio Section
|
||||
if (homepageData.portfolio) {
|
||||
document.getElementById("portfolioEnabled").checked =
|
||||
homepageData.portfolio.enabled !== false;
|
||||
document.getElementById("portfolioTitle").value =
|
||||
homepageData.portfolio.title || "";
|
||||
document.getElementById("portfolioDescription").value =
|
||||
homepageData.portfolio.description || "";
|
||||
document.getElementById("portfolioCount").value =
|
||||
homepageData.portfolio.count || 6;
|
||||
toggleSection("portfolio");
|
||||
}
|
||||
}
|
||||
|
||||
function toggleSection(sectionName) {
|
||||
const enabled = document.getElementById(`${sectionName}Enabled`).checked;
|
||||
const section = document.getElementById(`${sectionName}Section`);
|
||||
const content = section.querySelector(".section-content");
|
||||
|
||||
if (enabled) {
|
||||
section.classList.remove("disabled");
|
||||
content
|
||||
.querySelectorAll("input, textarea, button, select")
|
||||
.forEach((el) => {
|
||||
el.disabled = false;
|
||||
});
|
||||
} else {
|
||||
section.classList.add("disabled");
|
||||
content
|
||||
.querySelectorAll("input, textarea, button, select")
|
||||
.forEach((el) => {
|
||||
el.disabled = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function previewImage(sectionName) {
|
||||
const fileInput =
|
||||
document.getElementById(`${sectionName}Background`) ||
|
||||
document.getElementById(`${sectionName}Image`);
|
||||
const preview = document.getElementById(`${sectionName}Preview`);
|
||||
|
||||
if (fileInput.files && fileInput.files[0]) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = function (e) {
|
||||
preview.classList.remove("empty");
|
||||
preview.innerHTML = `<img src="${e.target.result}" alt="Preview" />`;
|
||||
};
|
||||
reader.readAsDataURL(fileInput.files[0]);
|
||||
}
|
||||
}
|
||||
|
||||
function setLayout(sectionName, layout) {
|
||||
const buttons = document.querySelectorAll(
|
||||
`#${sectionName}Section .alignment-btn`
|
||||
);
|
||||
buttons.forEach((btn) => btn.classList.remove("active"));
|
||||
event.target.closest(".alignment-btn").classList.add("active");
|
||||
}
|
||||
|
||||
function setImagePosition(sectionName, position) {
|
||||
const buttons = event.target
|
||||
.closest(".alignment-selector")
|
||||
.querySelectorAll(".alignment-btn");
|
||||
buttons.forEach((btn) => btn.classList.remove("active"));
|
||||
event.target.closest(".alignment-btn").classList.add("active");
|
||||
}
|
||||
|
||||
function setTextAlignment(sectionName, alignment) {
|
||||
const buttons = event.target
|
||||
.closest(".alignment-selector")
|
||||
.querySelectorAll(".alignment-btn");
|
||||
buttons.forEach((btn) => btn.classList.remove("active"));
|
||||
event.target.closest(".alignment-btn").classList.add("active");
|
||||
}
|
||||
|
||||
async function saveHomepage() {
|
||||
const settings = {
|
||||
hero: {
|
||||
enabled: document.getElementById("heroEnabled").checked,
|
||||
headline: document.getElementById("heroHeadline").value,
|
||||
subheading: document.getElementById("heroSubheading").value,
|
||||
description: document.getElementById("heroDescription").value,
|
||||
ctaText: document.getElementById("heroCtaText").value,
|
||||
ctaLink: document.getElementById("heroCtaLink").value,
|
||||
},
|
||||
promotion: {
|
||||
enabled: document.getElementById("promotionEnabled").checked,
|
||||
title: document.getElementById("promotionTitle").value,
|
||||
description: document.getElementById("promotionDescription").value,
|
||||
},
|
||||
portfolio: {
|
||||
enabled: document.getElementById("portfolioEnabled").checked,
|
||||
title: document.getElementById("portfolioTitle").value,
|
||||
description: document.getElementById("portfolioDescription").value,
|
||||
count: parseInt(document.getElementById("portfolioCount").value) || 6,
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/admin/homepage/settings", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
credentials: "include",
|
||||
body: JSON.stringify(settings),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
showSuccess(
|
||||
"Homepage settings saved successfully! Changes are now live."
|
||||
);
|
||||
} else {
|
||||
showError(data.message || "Failed to save homepage settings");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to save homepage:", error);
|
||||
showError("Failed to save homepage settings");
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
232
website/admin/js/pages.js
Normal file
232
website/admin/js/pages.js
Normal file
@@ -0,0 +1,232 @@
|
||||
// Pages Management JavaScript
|
||||
|
||||
let pagesData = [];
|
||||
let pageModal;
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
pageModal = new bootstrap.Modal(document.getElementById("pageModal"));
|
||||
checkAuth().then((authenticated) => {
|
||||
if (authenticated) {
|
||||
loadPages();
|
||||
}
|
||||
});
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
if (urlParams.get("action") === "create") {
|
||||
showCreatePage();
|
||||
}
|
||||
|
||||
// Auto-generate slug from title
|
||||
document.getElementById("pageTitle")?.addEventListener("input", function () {
|
||||
if (!document.getElementById("pageId").value) {
|
||||
document.getElementById("pageSlug").value = slugify(this.value);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
async function loadPages() {
|
||||
try {
|
||||
const response = await fetch("/api/admin/pages", {
|
||||
credentials: "include",
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
pagesData = data.pages;
|
||||
renderPages(pagesData);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load pages:", error);
|
||||
}
|
||||
}
|
||||
|
||||
function renderPages(pages) {
|
||||
const tbody = document.getElementById("pagesTableBody");
|
||||
if (pages.length === 0) {
|
||||
tbody.innerHTML = `
|
||||
<tr><td colspan="6" class="text-center p-4">
|
||||
<i class="bi bi-inbox" style="font-size: 3rem; color: #ccc;"></i>
|
||||
<p class="mt-3 text-muted">No custom pages found</p>
|
||||
<button class="btn btn-primary" onclick="showCreatePage()">
|
||||
<i class="bi bi-plus-circle"></i> Create Your First Page
|
||||
</button>
|
||||
</td></tr>`;
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = pages
|
||||
.map(
|
||||
(p) => `
|
||||
<tr>
|
||||
<td>${p.id}</td>
|
||||
<td><strong>${escapeHtml(p.title)}</strong></td>
|
||||
<td><code>${escapeHtml(p.slug)}</code></td>
|
||||
<td><span class="badge ${
|
||||
p.ispublished ? "badge-success" : "badge-warning"
|
||||
}">
|
||||
${p.ispublished ? "Published" : "Draft"}</span></td>
|
||||
<td>${formatDate(p.createdat)}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-info" onclick="editPage(${p.id})">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-danger" onclick="deletePage(${
|
||||
p.id
|
||||
}, '${escapeHtml(p.title)}')">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>`
|
||||
)
|
||||
.join("");
|
||||
}
|
||||
|
||||
function filterPages() {
|
||||
const searchTerm = document.getElementById("searchInput").value.toLowerCase();
|
||||
const filtered = pagesData.filter(
|
||||
(p) =>
|
||||
p.title.toLowerCase().includes(searchTerm) ||
|
||||
p.slug.toLowerCase().includes(searchTerm)
|
||||
);
|
||||
renderPages(filtered);
|
||||
}
|
||||
|
||||
function showCreatePage() {
|
||||
document.getElementById("modalTitle").textContent = "Create Custom Page";
|
||||
document.getElementById("pageForm").reset();
|
||||
document.getElementById("pageId").value = "";
|
||||
document.getElementById("pagePublished").checked = true;
|
||||
pageModal.show();
|
||||
}
|
||||
|
||||
async function editPage(id) {
|
||||
try {
|
||||
const response = await fetch(`/api/admin/pages/${id}`, {
|
||||
credentials: "include",
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
const page = data.page;
|
||||
document.getElementById("modalTitle").textContent = "Edit Custom Page";
|
||||
document.getElementById("pageId").value = page.id;
|
||||
document.getElementById("pageTitle").value = page.title;
|
||||
document.getElementById("pageSlug").value = page.slug;
|
||||
document.getElementById("pageContent").value = page.content || "";
|
||||
document.getElementById("pageMetaTitle").value = page.metatitle || "";
|
||||
document.getElementById("pageMetaDescription").value =
|
||||
page.metadescription || "";
|
||||
document.getElementById("pagePublished").checked = page.ispublished;
|
||||
pageModal.show();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load page:", error);
|
||||
showError("Failed to load page details");
|
||||
}
|
||||
}
|
||||
|
||||
async function savePage() {
|
||||
const id = document.getElementById("pageId").value;
|
||||
const formData = {
|
||||
title: document.getElementById("pageTitle").value,
|
||||
slug: document.getElementById("pageSlug").value,
|
||||
content: document.getElementById("pageContent").value,
|
||||
metatitle: document.getElementById("pageMetaTitle").value,
|
||||
metadescription: document.getElementById("pageMetaDescription").value,
|
||||
ispublished: document.getElementById("pagePublished").checked,
|
||||
};
|
||||
|
||||
if (!formData.title || !formData.slug || !formData.content) {
|
||||
showError("Please fill in all required fields");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const url = id ? `/api/admin/pages/${id}` : "/api/admin/pages";
|
||||
const method = id ? "PUT" : "POST";
|
||||
const response = await fetch(url, {
|
||||
method: method,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
credentials: "include",
|
||||
body: JSON.stringify(formData),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
showSuccess(
|
||||
id ? "Page updated successfully" : "Page created successfully"
|
||||
);
|
||||
pageModal.hide();
|
||||
loadPages();
|
||||
} else {
|
||||
showError(data.message || "Failed to save page");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to save page:", error);
|
||||
showError("Failed to save page");
|
||||
}
|
||||
}
|
||||
|
||||
async function deletePage(id, title) {
|
||||
if (!confirm(`Are you sure you want to delete "${title}"?`)) return;
|
||||
try {
|
||||
const response = await fetch(`/api/admin/pages/${id}`, {
|
||||
method: "DELETE",
|
||||
credentials: "include",
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
showSuccess("Page deleted successfully");
|
||||
loadPages();
|
||||
} else {
|
||||
showError(data.message || "Failed to delete page");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to delete page:", error);
|
||||
showError("Failed to delete page");
|
||||
}
|
||||
}
|
||||
|
||||
function slugify(text) {
|
||||
return text
|
||||
.toLowerCase()
|
||||
.replace(/[^\w\s-]/g, "")
|
||||
.replace(/[\s_-]+/g, "-")
|
||||
.replace(/^-+|-+$/g, "");
|
||||
}
|
||||
|
||||
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 escapeHtml(text) {
|
||||
const map = {
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
'"': """,
|
||||
"'": "'",
|
||||
};
|
||||
return text.replace(/[&<>"']/g, (m) => map[m]);
|
||||
}
|
||||
|
||||
function formatDate(dateString) {
|
||||
return new Date(dateString).toLocaleDateString("en-US", {
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
});
|
||||
}
|
||||
|
||||
function showSuccess(message) {
|
||||
alert(message);
|
||||
}
|
||||
function showError(message) {
|
||||
alert("Error: " + message);
|
||||
}
|
||||
213
website/admin/js/portfolio.js
Normal file
213
website/admin/js/portfolio.js
Normal file
@@ -0,0 +1,213 @@
|
||||
// Portfolio Management JavaScript
|
||||
|
||||
let projectsData = [];
|
||||
let projectModal;
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
projectModal = new bootstrap.Modal(document.getElementById("projectModal"));
|
||||
checkAuth().then((authenticated) => {
|
||||
if (authenticated) {
|
||||
loadProjects();
|
||||
}
|
||||
});
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
if (urlParams.get("action") === "create") {
|
||||
showCreateProject();
|
||||
}
|
||||
});
|
||||
|
||||
async function loadProjects() {
|
||||
try {
|
||||
const response = await fetch("/api/admin/portfolio/projects", {
|
||||
credentials: "include",
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
projectsData = data.projects;
|
||||
renderProjects(projectsData);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load projects:", error);
|
||||
}
|
||||
}
|
||||
|
||||
function renderProjects(projects) {
|
||||
const tbody = document.getElementById("projectsTableBody");
|
||||
if (projects.length === 0) {
|
||||
tbody.innerHTML = `
|
||||
<tr><td colspan="7" class="text-center p-4">
|
||||
<i class="bi bi-inbox" style="font-size: 3rem; color: #ccc;"></i>
|
||||
<p class="mt-3 text-muted">No projects found</p>
|
||||
<button class="btn btn-primary" onclick="showCreateProject()">
|
||||
<i class="bi bi-plus-circle"></i> Add Your First Project
|
||||
</button>
|
||||
</td></tr>`;
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = projects
|
||||
.map(
|
||||
(p) => `
|
||||
<tr>
|
||||
<td>${p.id}</td>
|
||||
<td><strong>${escapeHtml(p.title)}</strong></td>
|
||||
<td>${escapeHtml((p.description || "").substring(0, 50))}...</td>
|
||||
<td>${p.category || "-"}</td>
|
||||
<td><span class="badge ${p.isactive ? "badge-success" : "badge-danger"}">
|
||||
${p.isactive ? "Active" : "Inactive"}</span></td>
|
||||
<td>${formatDate(p.createdat)}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-info" onclick="editProject(${p.id})">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-danger" onclick="deleteProject(${
|
||||
p.id
|
||||
}, '${escapeHtml(p.title)}')">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>`
|
||||
)
|
||||
.join("");
|
||||
}
|
||||
|
||||
function filterProjects() {
|
||||
const searchTerm = document.getElementById("searchInput").value.toLowerCase();
|
||||
const filtered = projectsData.filter((p) =>
|
||||
p.title.toLowerCase().includes(searchTerm)
|
||||
);
|
||||
renderProjects(filtered);
|
||||
}
|
||||
|
||||
function showCreateProject() {
|
||||
document.getElementById("modalTitle").textContent = "Add Portfolio Project";
|
||||
document.getElementById("projectForm").reset();
|
||||
document.getElementById("projectId").value = "";
|
||||
document.getElementById("projectActive").checked = true;
|
||||
projectModal.show();
|
||||
}
|
||||
|
||||
async function editProject(id) {
|
||||
try {
|
||||
const response = await fetch(`/api/admin/portfolio/projects/${id}`, {
|
||||
credentials: "include",
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
const project = data.project;
|
||||
document.getElementById("modalTitle").textContent =
|
||||
"Edit Portfolio Project";
|
||||
document.getElementById("projectId").value = project.id;
|
||||
document.getElementById("projectTitle").value = project.title;
|
||||
document.getElementById("projectDescription").value =
|
||||
project.description || "";
|
||||
document.getElementById("projectCategory").value = project.category || "";
|
||||
document.getElementById("projectActive").checked = project.isactive;
|
||||
projectModal.show();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load project:", error);
|
||||
showError("Failed to load project details");
|
||||
}
|
||||
}
|
||||
|
||||
async function saveProject() {
|
||||
const id = document.getElementById("projectId").value;
|
||||
const formData = {
|
||||
title: document.getElementById("projectTitle").value,
|
||||
description: document.getElementById("projectDescription").value,
|
||||
category: document.getElementById("projectCategory").value,
|
||||
isactive: document.getElementById("projectActive").checked,
|
||||
};
|
||||
|
||||
if (!formData.title || !formData.description) {
|
||||
showError("Please fill in all required fields");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const url = id
|
||||
? `/api/admin/portfolio/projects/${id}`
|
||||
: "/api/admin/portfolio/projects";
|
||||
const method = id ? "PUT" : "POST";
|
||||
const response = await fetch(url, {
|
||||
method: method,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
credentials: "include",
|
||||
body: JSON.stringify(formData),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
showSuccess(
|
||||
id ? "Project updated successfully" : "Project created successfully"
|
||||
);
|
||||
projectModal.hide();
|
||||
loadProjects();
|
||||
} else {
|
||||
showError(data.message || "Failed to save project");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to save project:", error);
|
||||
showError("Failed to save project");
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteProject(id, name) {
|
||||
if (!confirm(`Are you sure you want to delete "${name}"?`)) return;
|
||||
try {
|
||||
const response = await fetch(`/api/admin/portfolio/projects/${id}`, {
|
||||
method: "DELETE",
|
||||
credentials: "include",
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
showSuccess("Project deleted successfully");
|
||||
loadProjects();
|
||||
} else {
|
||||
showError(data.message || "Failed to delete project");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to delete project:", error);
|
||||
showError("Failed to delete project");
|
||||
}
|
||||
}
|
||||
|
||||
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 escapeHtml(text) {
|
||||
const map = {
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
'"': """,
|
||||
"'": "'",
|
||||
};
|
||||
return text.replace(/[&<>"']/g, (m) => map[m]);
|
||||
}
|
||||
|
||||
function formatDate(dateString) {
|
||||
return new Date(dateString).toLocaleDateString("en-US", {
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
});
|
||||
}
|
||||
|
||||
function showSuccess(message) {
|
||||
alert(message);
|
||||
}
|
||||
function showError(message) {
|
||||
alert("Error: " + message);
|
||||
}
|
||||
270
website/admin/js/products.js
Normal file
270
website/admin/js/products.js
Normal file
@@ -0,0 +1,270 @@
|
||||
// Products Management JavaScript
|
||||
|
||||
let productsData = [];
|
||||
let productModal;
|
||||
|
||||
// Initialize on page load
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
// Initialize Bootstrap modal
|
||||
productModal = new bootstrap.Modal(document.getElementById("productModal"));
|
||||
|
||||
// Check authentication (from auth.js)
|
||||
checkAuth().then((authenticated) => {
|
||||
if (authenticated) {
|
||||
loadProducts();
|
||||
}
|
||||
});
|
||||
|
||||
// Check if we should open create modal
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
if (urlParams.get("action") === "create") {
|
||||
showCreateProduct();
|
||||
}
|
||||
});
|
||||
|
||||
// Load all products
|
||||
async function loadProducts() {
|
||||
try {
|
||||
const response = await fetch("/api/admin/products", {
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
productsData = data.products;
|
||||
renderProducts(productsData);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load products:", error);
|
||||
showError("Failed to load products");
|
||||
}
|
||||
}
|
||||
|
||||
// Render products table
|
||||
function renderProducts(products) {
|
||||
const tbody = document.getElementById("productsTableBody");
|
||||
|
||||
if (products.length === 0) {
|
||||
tbody.innerHTML = `
|
||||
<tr>
|
||||
<td colspan="8" class="text-center p-4">
|
||||
<i class="bi bi-inbox" style="font-size: 3rem; color: #ccc;"></i>
|
||||
<p class="mt-3 text-muted">No products found</p>
|
||||
<button class="btn btn-primary" onclick="showCreateProduct()">
|
||||
<i class="bi bi-plus-circle"></i> Add Your First Product
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = products
|
||||
.map(
|
||||
(product) => `
|
||||
<tr>
|
||||
<td>${product.id}</td>
|
||||
<td><strong>${escapeHtml(product.name)}</strong></td>
|
||||
<td>$${parseFloat(product.price).toFixed(2)}</td>
|
||||
<td>${product.stockquantity || 0}</td>
|
||||
<td>
|
||||
<span class="badge ${
|
||||
product.isactive ? "badge-success" : "badge-danger"
|
||||
}">
|
||||
${product.isactive ? "Active" : "Inactive"}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
${
|
||||
product.isbestseller
|
||||
? '<span class="badge badge-warning">⭐ Best Seller</span>'
|
||||
: "-"
|
||||
}
|
||||
</td>
|
||||
<td>${formatDate(product.createdat)}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-info" onclick="editProduct(${
|
||||
product.id
|
||||
})">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-danger" onclick="deleteProduct(${
|
||||
product.id
|
||||
}, '${escapeHtml(product.name)}')">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`
|
||||
)
|
||||
.join("");
|
||||
}
|
||||
|
||||
// Filter products
|
||||
function filterProducts() {
|
||||
const searchTerm = document.getElementById("searchInput").value.toLowerCase();
|
||||
const filtered = productsData.filter((product) =>
|
||||
product.name.toLowerCase().includes(searchTerm)
|
||||
);
|
||||
renderProducts(filtered);
|
||||
}
|
||||
|
||||
// Show create product modal
|
||||
function showCreateProduct() {
|
||||
document.getElementById("modalTitle").textContent = "Add New Product";
|
||||
document.getElementById("productForm").reset();
|
||||
document.getElementById("productId").value = "";
|
||||
document.getElementById("productActive").checked = true;
|
||||
productModal.show();
|
||||
}
|
||||
|
||||
// Edit product
|
||||
async function editProduct(id) {
|
||||
try {
|
||||
const response = await fetch(`/api/admin/products/${id}`, {
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
const product = data.product;
|
||||
|
||||
document.getElementById("modalTitle").textContent = "Edit Product";
|
||||
document.getElementById("productId").value = product.id;
|
||||
document.getElementById("productName").value = product.name;
|
||||
document.getElementById("productDescription").value =
|
||||
product.description || "";
|
||||
document.getElementById("productPrice").value = product.price;
|
||||
document.getElementById("productStock").value =
|
||||
product.stockquantity || 0;
|
||||
document.getElementById("productCategory").value = product.category || "";
|
||||
document.getElementById("productActive").checked = product.isactive;
|
||||
document.getElementById("productBestSeller").checked =
|
||||
product.isbestseller || false;
|
||||
|
||||
productModal.show();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load product:", error);
|
||||
showError("Failed to load product details");
|
||||
}
|
||||
}
|
||||
|
||||
// Save product
|
||||
async function saveProduct() {
|
||||
const id = document.getElementById("productId").value;
|
||||
const formData = {
|
||||
name: document.getElementById("productName").value,
|
||||
description: document.getElementById("productDescription").value,
|
||||
price: parseFloat(document.getElementById("productPrice").value),
|
||||
stockquantity: parseInt(document.getElementById("productStock").value) || 0,
|
||||
category: document.getElementById("productCategory").value,
|
||||
isactive: document.getElementById("productActive").checked,
|
||||
isbestseller: document.getElementById("productBestSeller").checked,
|
||||
};
|
||||
|
||||
// Validation
|
||||
if (!formData.name || !formData.price) {
|
||||
showError("Please fill in all required fields");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const url = id ? `/api/admin/products/${id}` : "/api/admin/products";
|
||||
const method = id ? "PUT" : "POST";
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: method,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
credentials: "include",
|
||||
body: JSON.stringify(formData),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
showSuccess(
|
||||
id ? "Product updated successfully" : "Product created successfully"
|
||||
);
|
||||
productModal.hide();
|
||||
loadProducts();
|
||||
} else {
|
||||
showError(data.message || "Failed to save product");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to save product:", error);
|
||||
showError("Failed to save product");
|
||||
}
|
||||
}
|
||||
|
||||
// Delete product
|
||||
async function deleteProduct(id, name) {
|
||||
if (!confirm(`Are you sure you want to delete "${name}"?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/admin/products/${id}`, {
|
||||
method: "DELETE",
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
showSuccess("Product deleted successfully");
|
||||
loadProducts();
|
||||
} else {
|
||||
showError(data.message || "Failed to delete product");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to delete product:", error);
|
||||
showError("Failed to delete product");
|
||||
}
|
||||
}
|
||||
|
||||
// Logout function
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// Utility functions
|
||||
function escapeHtml(text) {
|
||||
const map = {
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
'"': """,
|
||||
"'": "'",
|
||||
};
|
||||
return text.replace(/[&<>"']/g, (m) => map[m]);
|
||||
}
|
||||
|
||||
function formatDate(dateString) {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleDateString("en-US", {
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
});
|
||||
}
|
||||
|
||||
function showSuccess(message) {
|
||||
// Simple alert for now - can be replaced with toast notification
|
||||
alert(message);
|
||||
}
|
||||
|
||||
function showError(message) {
|
||||
alert("Error: " + message);
|
||||
}
|
||||
213
website/admin/js/settings.js
Normal file
213
website/admin/js/settings.js
Normal file
@@ -0,0 +1,213 @@
|
||||
// Settings Management JavaScript
|
||||
|
||||
let currentSettings = {};
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
checkAuth().then((authenticated) => {
|
||||
if (authenticated) {
|
||||
loadSettings();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
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";
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
||||
function previewLogo() {
|
||||
const fileInput = document.getElementById("siteLogo");
|
||||
const preview = document.getElementById("logoPreview");
|
||||
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
||||
function previewFavicon() {
|
||||
const fileInput = document.getElementById("siteFavicon");
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
||||
function selectLayout(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;
|
||||
document.getElementById("colorPreview").style.backgroundColor = color;
|
||||
document.getElementById("colorValue").textContent = color;
|
||||
}
|
||||
|
||||
async function saveSettings() {
|
||||
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,
|
||||
},
|
||||
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,
|
||||
},
|
||||
};
|
||||
|
||||
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();
|
||||
if (data.success) {
|
||||
showSuccess("Settings saved successfully!");
|
||||
currentSettings = settings;
|
||||
} else {
|
||||
showError(data.message || "Failed to save settings");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to save settings:", error);
|
||||
showError("Failed to save settings");
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
352
website/admin/js/users.js
Normal file
352
website/admin/js/users.js
Normal file
@@ -0,0 +1,352 @@
|
||||
// User Management JavaScript
|
||||
|
||||
let usersData = [];
|
||||
let userModal;
|
||||
let passwordModal;
|
||||
|
||||
const rolePermissions = {
|
||||
Cashier: ["View Products", "Process Orders", "View Customers"],
|
||||
Accountant: [
|
||||
"View Products",
|
||||
"View Orders",
|
||||
"View Reports",
|
||||
"View Financial Data",
|
||||
],
|
||||
Admin: [
|
||||
"Manage Products",
|
||||
"Manage Portfolio",
|
||||
"Manage Blog",
|
||||
"Manage Pages",
|
||||
"Manage Users",
|
||||
"View Reports",
|
||||
],
|
||||
MasterAdmin: [
|
||||
"Full System Access",
|
||||
"Manage Settings",
|
||||
"Manage Users",
|
||||
"Manage All Content",
|
||||
"View Logs",
|
||||
"System Configuration",
|
||||
],
|
||||
};
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
userModal = new bootstrap.Modal(document.getElementById("userModal"));
|
||||
passwordModal = new bootstrap.Modal(document.getElementById("passwordModal"));
|
||||
checkAuth().then((authenticated) => {
|
||||
if (authenticated) {
|
||||
loadUsers();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
async function loadUsers() {
|
||||
try {
|
||||
const response = await fetch("/api/admin/users", {
|
||||
credentials: "include",
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
usersData = data.users;
|
||||
renderUsers(usersData);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load users:", error);
|
||||
}
|
||||
}
|
||||
|
||||
function renderUsers(users) {
|
||||
const tbody = document.getElementById("usersTableBody");
|
||||
if (users.length === 0) {
|
||||
tbody.innerHTML = `
|
||||
<tr><td colspan="8" class="text-center p-4">
|
||||
<i class="bi bi-inbox" style="font-size: 3rem; color: #ccc;"></i>
|
||||
<p class="mt-3 text-muted">No users found</p>
|
||||
<button class="btn btn-primary" onclick="showCreateUser()">
|
||||
<i class="bi bi-person-plus"></i> Create First User
|
||||
</button>
|
||||
</td></tr>`;
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = users
|
||||
.map(
|
||||
(u) => `
|
||||
<tr>
|
||||
<td>${u.id}</td>
|
||||
<td><strong>${escapeHtml(u.name)}</strong></td>
|
||||
<td>${escapeHtml(u.email)}</td>
|
||||
<td>@${escapeHtml(u.username)}</td>
|
||||
<td><span class="role-badge role-${u.role
|
||||
.toLowerCase()
|
||||
.replace(" ", "-")}">${u.role}</span></td>
|
||||
<td><span class="badge ${u.isactive ? "badge-success" : "badge-danger"}">
|
||||
${u.isactive ? "Active" : "Disabled"}</span></td>
|
||||
<td>${formatDate(u.createdat)}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-info" onclick="editUser(${
|
||||
u.id
|
||||
})" title="Edit User">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-warning" onclick="showChangePassword(${
|
||||
u.id
|
||||
}, '${escapeHtml(u.name)}')" title="Change Password">
|
||||
<i class="bi bi-key"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-danger" onclick="deleteUser(${
|
||||
u.id
|
||||
}, '${escapeHtml(u.name)}')" title="Delete User">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>`
|
||||
)
|
||||
.join("");
|
||||
}
|
||||
|
||||
function filterUsers() {
|
||||
const searchTerm = document.getElementById("searchInput").value.toLowerCase();
|
||||
const filtered = usersData.filter(
|
||||
(u) =>
|
||||
u.name.toLowerCase().includes(searchTerm) ||
|
||||
u.email.toLowerCase().includes(searchTerm) ||
|
||||
u.username.toLowerCase().includes(searchTerm)
|
||||
);
|
||||
renderUsers(filtered);
|
||||
}
|
||||
|
||||
function showCreateUser() {
|
||||
document.getElementById("modalTitle").textContent = "Create New User";
|
||||
document.getElementById("userForm").reset();
|
||||
document.getElementById("userId").value = "";
|
||||
document.getElementById("userActive").checked = true;
|
||||
document.getElementById("userPasswordNeverExpires").checked = false;
|
||||
document.getElementById("userRole").value = "Cashier";
|
||||
updatePermissionsPreview();
|
||||
userModal.show();
|
||||
}
|
||||
|
||||
async function editUser(id) {
|
||||
try {
|
||||
const response = await fetch(`/api/admin/users/${id}`, {
|
||||
credentials: "include",
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
const user = data.user;
|
||||
document.getElementById("modalTitle").textContent = "Edit User";
|
||||
document.getElementById("userId").value = user.id;
|
||||
document.getElementById("userName").value = user.name;
|
||||
document.getElementById("userUsername").value = user.username;
|
||||
document.getElementById("userEmail").value = user.email;
|
||||
document.getElementById("userPassword").value = "";
|
||||
document.getElementById("userPasswordConfirm").value = "";
|
||||
document.getElementById("userRole").value = user.role;
|
||||
document.getElementById("userActive").checked = user.isactive;
|
||||
document.getElementById("userPasswordNeverExpires").checked =
|
||||
user.passwordneverexpires || false;
|
||||
updatePermissionsPreview();
|
||||
userModal.show();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load user:", error);
|
||||
showError("Failed to load user details");
|
||||
}
|
||||
}
|
||||
|
||||
async function saveUser() {
|
||||
const id = document.getElementById("userId").value;
|
||||
const password = document.getElementById("userPassword").value;
|
||||
const passwordConfirm = document.getElementById("userPasswordConfirm").value;
|
||||
|
||||
// Password validation (only for new users or when changing password)
|
||||
if (!id || password) {
|
||||
if (!password) {
|
||||
showError("Password is required for new users");
|
||||
return;
|
||||
}
|
||||
if (password !== passwordConfirm) {
|
||||
showError("Passwords do not match");
|
||||
return;
|
||||
}
|
||||
if (password.length < 8) {
|
||||
showError("Password must be at least 8 characters long");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const formData = {
|
||||
name: document.getElementById("userName").value,
|
||||
username: document.getElementById("userUsername").value,
|
||||
email: document.getElementById("userEmail").value,
|
||||
role: document.getElementById("userRole").value,
|
||||
isactive: document.getElementById("userActive").checked,
|
||||
passwordneverexpires: document.getElementById("userPasswordNeverExpires")
|
||||
.checked,
|
||||
};
|
||||
|
||||
if (password) {
|
||||
formData.password = password;
|
||||
}
|
||||
|
||||
if (!formData.name || !formData.username || !formData.email) {
|
||||
showError("Please fill in all required fields");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const url = id ? `/api/admin/users/${id}` : "/api/admin/users";
|
||||
const method = id ? "PUT" : "POST";
|
||||
const response = await fetch(url, {
|
||||
method: method,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
credentials: "include",
|
||||
body: JSON.stringify(formData),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
showSuccess(
|
||||
id ? "User updated successfully" : "User created successfully"
|
||||
);
|
||||
userModal.hide();
|
||||
loadUsers();
|
||||
} else {
|
||||
showError(data.message || "Failed to save user");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to save user:", error);
|
||||
showError("Failed to save user");
|
||||
}
|
||||
}
|
||||
|
||||
function showChangePassword(id, name) {
|
||||
document.getElementById("passwordUserId").value = id;
|
||||
document.getElementById("passwordUserName").value = name;
|
||||
document.getElementById("passwordUserDisplay").textContent = name;
|
||||
document.getElementById("passwordForm").reset();
|
||||
passwordModal.show();
|
||||
}
|
||||
|
||||
async function changePassword() {
|
||||
const id = document.getElementById("passwordUserId").value;
|
||||
const newPassword = document.getElementById("newPassword").value;
|
||||
const confirmPassword = document.getElementById("confirmNewPassword").value;
|
||||
|
||||
if (!newPassword || !confirmPassword) {
|
||||
showError("Please enter and confirm the new password");
|
||||
return;
|
||||
}
|
||||
|
||||
if (newPassword !== confirmPassword) {
|
||||
showError("Passwords do not match");
|
||||
return;
|
||||
}
|
||||
|
||||
if (newPassword.length < 8) {
|
||||
showError("Password must be at least 8 characters long");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/admin/users/${id}/password`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
credentials: "include",
|
||||
body: JSON.stringify({ password: newPassword }),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
showSuccess("Password changed successfully");
|
||||
passwordModal.hide();
|
||||
} else {
|
||||
showError(data.message || "Failed to change password");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to change password:", error);
|
||||
showError("Failed to change password");
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteUser(id, name) {
|
||||
if (
|
||||
!confirm(
|
||||
`Are you sure you want to delete user "${name}"? This action cannot be undone.`
|
||||
)
|
||||
)
|
||||
return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/admin/users/${id}`, {
|
||||
method: "DELETE",
|
||||
credentials: "include",
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
showSuccess("User deleted successfully");
|
||||
loadUsers();
|
||||
} else {
|
||||
showError(data.message || "Failed to delete user");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to delete user:", error);
|
||||
showError("Failed to delete user");
|
||||
}
|
||||
}
|
||||
|
||||
function updatePermissionsPreview() {
|
||||
const role = document.getElementById("userRole").value;
|
||||
const permissions = rolePermissions[role] || [];
|
||||
const container = document.getElementById("permissionsPreview");
|
||||
|
||||
container.innerHTML = permissions
|
||||
.map(
|
||||
(perm) => `
|
||||
<div class="permission-item active">
|
||||
<i class="bi bi-check-circle-fill" style="color: #10b981; margin-right: 8px;"></i>
|
||||
<span>${perm}</span>
|
||||
</div>
|
||||
`
|
||||
)
|
||||
.join("");
|
||||
}
|
||||
|
||||
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 escapeHtml(text) {
|
||||
const map = {
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
'"': """,
|
||||
"'": "'",
|
||||
};
|
||||
return text.replace(/[&<>"']/g, (m) => map[m]);
|
||||
}
|
||||
|
||||
function formatDate(dateString) {
|
||||
return new Date(dateString).toLocaleDateString("en-US", {
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
});
|
||||
}
|
||||
|
||||
function showSuccess(message) {
|
||||
alert(message);
|
||||
}
|
||||
function showError(message) {
|
||||
alert("Error: " + message);
|
||||
}
|
||||
Reference in New Issue
Block a user