`,
)
.join("");
}
function addTeamMember() {
const newMember = {
id: null,
name: "",
position: "",
bio: "",
image_url: "",
display_order: aboutTeamMembers.length,
};
aboutTeamMembers.push(newMember);
displayTeamMembersInEditor();
}
function updateTeamMember(index, field, value) {
if (aboutTeamMembers[index]) {
aboutTeamMembers[index][field] = value;
}
}
function removeTeamMemberFromAbout(index) {
const member = aboutTeamMembers[index];
// If member has an ID, track it for deletion from database
if (member && member.id) {
deletedTeamMemberIds.push(member.id);
}
aboutTeamMembers.splice(index, 1);
displayTeamMembersInEditor();
}
function selectImageForMember(index) {
currentMediaPicker = { purpose: "teamMember", index };
// Initialize if not already
initPagesMediaLibrary();
if (pagesMediaLibrary) {
pagesMediaLibrary.open();
}
}
function handleMediaSelection(media) {
if (!currentMediaPicker) return;
// Handle About editor image insertion
if (currentMediaPicker === "aboutEditorImage") {
handleAboutEditorImageSelection(media);
currentMediaPicker = null;
return;
}
// Handle main page editor image insertion
if (currentMediaPicker === "pageEditorImage") {
handlePageEditorImageSelection(media);
currentMediaPicker = null;
return;
}
if (currentMediaPicker.purpose === "teamMember") {
const index = currentMediaPicker.index;
if (aboutTeamMembers[index]) {
aboutTeamMembers[index].image_url = media.path;
displayTeamMembersInEditor();
}
}
currentMediaPicker = null;
}
async function saveTeamMembers() {
try {
// First, delete any removed team members from the database
for (const memberId of deletedTeamMemberIds) {
try {
await fetch(`/api/admin/team-members/${memberId}`, {
method: "DELETE",
credentials: "include",
});
console.log(`Deleted team member ${memberId}`);
} catch (err) {
console.error(`Failed to delete team member ${memberId}:`, err);
}
}
// Clear the deleted IDs array after processing
deletedTeamMemberIds = [];
// Save or update each team member
for (const member of aboutTeamMembers) {
if (!member.name || !member.position) continue; // Skip incomplete members
const payload = {
name: member.name,
position: member.position,
bio: member.bio || "",
image_url: member.image_url || "",
display_order: parseInt(member.display_order) || 0,
};
if (member.id) {
// Update existing
await fetch(`/api/admin/team-members/${member.id}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
credentials: "include",
body: JSON.stringify(payload),
});
} else {
// Create new
const response = await fetch("/api/admin/team-members", {
method: "POST",
headers: { "Content-Type": "application/json" },
credentials: "include",
body: JSON.stringify(payload),
});
const data = await response.json();
if (data.success && data.teamMember) {
member.id = data.teamMember.id; // Update with new ID
}
}
}
console.log("Team members saved successfully");
} catch (error) {
console.error("Error saving team members:", error);
}
}
async function savePage() {
const id = document.getElementById("pageId").value;
const slug = document.getElementById("pageSlug").value;
let formData = {
title: document.getElementById("pageTitle").value,
slug: slug,
metatitle: document.getElementById("pageMetaTitle").value,
metadescription: document.getElementById("pageMetaDescription").value,
ispublished: document.getElementById("pagePublished").checked,
};
// Check if this is About page with team members
if (
(slug === "about" || slug === "page-about") &&
document.getElementById("aboutWithTeamFields").style.display !== "none"
) {
// Get About content from editor
const contentDelta = aboutContentEditor.getContents();
formData.content = JSON.stringify(contentDelta);
// Save team members separately
await saveTeamMembers();
}
// Check if this is contact page with structured fields visible
else if (
slug === "contact" &&
document.getElementById("contactStructuredFields").style.display !== "none"
) {
// Collect structured data
const pagedata = {
header: {
title: document.getElementById("contactHeaderTitle").value,
subtitle: document.getElementById("contactHeaderSubtitle").value,
},
contactInfo: {
phone: document.getElementById("contactPhone").value,
email: document.getElementById("contactEmail").value,
address: document.getElementById("contactAddress").value,
},
businessHours: [],
};
// Collect business hours
const hourRows = document.getElementById("businessHoursList").children;
for (let row of hourRows) {
const days = row.querySelector('[data-field="days"]').value;
const hours = row.querySelector('[data-field="hours"]').value;
if (days && hours) {
pagedata.businessHours.push({ days, hours });
}
}
// Generate HTML from structured data
const generatedHTML = generateContactHTML(pagedata);
formData.pagedata = pagedata;
formData.content = generatedHTML; // Store HTML in content field
formData.contenthtml = generatedHTML; // Also in contenthtml
}
// Check if this is privacy/shipping/returns page with structured fields
else if (
(slug.includes("privacy") ||
slug.includes("shipping") ||
slug.includes("return")) &&
document.getElementById("privacyContentSection").style.display !== "none"
) {
// Collect structured privacy data
const pagedata = {
header: {
title: document.getElementById("privacyHeaderTitle").value,
},
lastUpdated: document.getElementById("privacyLastUpdated").value,
sections: [],
};
// Collect sections
const sectionDivs = document.getElementById(
"privacySectionsContainer",
).children;
for (let i = 0; i < sectionDivs.length; i++) {
const sectionDiv = sectionDivs[i];
const title = sectionDiv.querySelector('[data-field="title"]').value;
const editor = privacySectionEditors[i];
if (editor && title) {
// Get content as Delta JSON
const contentDelta = editor.getContents();
const contentHTML = editor.root.innerHTML;
pagedata.sections.push({
title: title,
content: JSON.stringify(contentDelta), // Store as Delta
contentHTML: contentHTML, // Also store rendered HTML
});
}
}
// Store the structured data
formData.pagedata = pagedata;
// Also generate and store as content for backwards compatibility
const generatedHTML = generatePrivacyHTML(pagedata);
formData.content = generatedHTML;
formData.contenthtml = generatedHTML;
} else {
// Use Quill editor content for other pages
const contentDelta = quillEditor.getContents();
const contentHTML = quillEditor.root.innerHTML;
formData.content = JSON.stringify(contentDelta); // Store as Delta for editing
formData.contenthtml = contentHTML; // Store rendered HTML for display
if (contentDelta.ops.length === 0) {
showError("Please fill in all required fields");
return;
}
}
if (!formData.title || !formData.slug) {
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");
}
}
function generateContactHTML(pagedata) {
const { header, contactInfo, businessHours } = pagedata;
// Generate business hours HTML
const businessHoursHTML = businessHours
.map(
(hour) => `
${escapeHtml(
hour.days,
)}
${escapeHtml(hour.hours)}
`,
)
.join("");
return `
${escapeHtml(header.title)}
${escapeHtml(header.subtitle)}
Phone
${escapeHtml(
contactInfo.phone,
)}
Email
${escapeHtml(
contactInfo.email,
)}
Location
${escapeHtml(
contactInfo.address,
)}
Business Hours
${businessHoursHTML}
`;
}
function generatePrivacyHTML(pagedata) {
const { header, lastUpdated, sections } = pagedata;
// Generate sections HTML
const sectionsHTML = sections
.map((section) => {
// Parse the content Delta if it's stored as JSON
let contentHTML = section.contentHTML;
if (!contentHTML && section.content) {
try {
// If we don't have contentHTML, try to get it from the content field
contentHTML = section.content;
} catch {
contentHTML = section.content;
}
}
return `
${escapeHtml(section.title)}
${contentHTML}
`;
})
.join("");
return `
${lastUpdated ? `
Last updated: ${escapeHtml(lastUpdated)}
` : ""}
${sectionsHTML}
`;
}
async function deletePage(id, title) {
// Show custom confirmation modal instead of browser confirm
showConfirmation(
`Are you sure you want to delete "${escapeHtml(
title,
)}"?
` +
`This action cannot be undone.`,
async () => {
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, "");
}
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) {
showNotification(message, "success");
}
function showError(message) {
showNotification(message, "error");
}
function showNotification(message, type) {
const modal = new bootstrap.Modal(
document.getElementById("notificationModal"),
);
const modalContent = document.getElementById("notificationModalContent");
const modalHeader = document.getElementById("notificationModalHeader");
const modalIcon = document.getElementById("notificationModalIcon");
const modalTitleText = document.getElementById("notificationModalTitleText");
const modalBody = document.getElementById("notificationModalBody");
// Configure based on type
if (type === "success") {
modalContent.classList.remove("border-danger");
modalContent.classList.add("border-success");
modalContent.style.borderWidth = "3px";
modalHeader.classList.remove("bg-danger", "text-white");
modalHeader.classList.add("bg-success", "text-white");
modalIcon.className = "bi bi-check-circle-fill me-2";
modalTitleText.textContent = "Success";
modalBody.innerHTML = `