updateweb

This commit is contained in:
Local Server
2025-12-14 01:54:40 -06:00
parent dce6460994
commit 61929a5daf
454 changed files with 12193 additions and 42002 deletions

104
website/admin/js/auth.js Normal file
View 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
View 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 = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&#039;",
};
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);
}

View 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
View 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 = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&#039;",
};
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);
}

View 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 = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&#039;",
};
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);
}

View 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 = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&#039;",
};
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);
}

View 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
View 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 = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&#039;",
};
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);
}