// Custom Pages Admin - Redesigned
// ================================
console.log("=== pages-new.js loading ===");
// State variables
let pages = [];
let pageModal = null;
let pageContentEditor = null;
let aboutContentEditor = null;
let privacyMainEditor = null;
let currentPageSlug = null;
let teamMembers = [];
let deletedTeamMemberIds = [];
let faqItems = [];
let returnsSections = [];
let shippingSections = [];
let privacySections = [];
let pagesMediaLibrary = null;
let currentMediaCallback = null;
// Quill editor instances for sections
let privacyEditors = [];
let shippingEditors = [];
let returnsEditors = [];
// Page icons mapping
const pageIcons = {
about: "bi-info-circle",
contact: "bi-envelope",
faq: "bi-question-circle",
returns: "bi-arrow-return-left",
"shipping-info": "bi-truck",
privacy: "bi-shield-lock",
terms: "bi-file-earmark-text",
default: "bi-file-text",
};
// ========================================
// Custom Modal Functions
// ========================================
// Show confirmation modal (returns Promise)
function showConfirmModal(
title,
message,
confirmText = "Delete",
type = "danger",
) {
return new Promise((resolve) => {
const overlay = document.getElementById("confirmModalOverlay");
const icon = document.getElementById("confirmModalIcon");
const titleEl = document.getElementById("confirmModalTitle");
const messageEl = document.getElementById("confirmModalMessage");
const confirmBtn = document.getElementById("confirmDeleteBtn");
const cancelBtn = document.getElementById("confirmCancelBtn");
// Set content
titleEl.textContent = title;
messageEl.textContent = message;
confirmBtn.textContent = confirmText;
// Set icon style based on type
if (type === "danger") {
icon.innerHTML = '';
icon.className = "confirm-modal-icon";
confirmBtn.className = "btn btn-confirm-delete";
} else if (type === "warning") {
icon.innerHTML = '';
icon.className = "confirm-modal-icon warning";
confirmBtn.className = "btn btn-confirm-delete";
}
// Show modal
overlay.classList.add("active");
// Handle confirm
const handleConfirm = () => {
overlay.classList.remove("active");
cleanup();
resolve(true);
};
// Handle cancel
const handleCancel = () => {
overlay.classList.remove("active");
cleanup();
resolve(false);
};
// Handle overlay click
const handleOverlayClick = (e) => {
if (e.target === overlay) {
handleCancel();
}
};
// Cleanup listeners
const cleanup = () => {
confirmBtn.removeEventListener("click", handleConfirm);
cancelBtn.removeEventListener("click", handleCancel);
overlay.removeEventListener("click", handleOverlayClick);
};
// Add listeners
confirmBtn.addEventListener("click", handleConfirm);
cancelBtn.addEventListener("click", handleCancel);
overlay.addEventListener("click", handleOverlayClick);
});
}
// Show result modal (success/error popup)
function showResultModal(type, title, message) {
return new Promise((resolve) => {
const overlay = document.getElementById("resultModalOverlay");
const icon = document.getElementById("resultModalIcon");
const titleEl = document.getElementById("resultModalTitle");
const messageEl = document.getElementById("resultModalMessage");
const okBtn = document.getElementById("resultOkBtn");
// Set content
titleEl.textContent = title;
messageEl.textContent = message;
// Set icon based on type
if (type === "success") {
icon.innerHTML = '';
icon.className = "result-modal-icon success";
} else if (type === "error") {
icon.innerHTML = '';
icon.className = "result-modal-icon error";
}
// Show modal
overlay.classList.add("active");
// Handle OK
const handleOk = () => {
overlay.classList.remove("active");
okBtn.removeEventListener("click", handleOk);
overlay.removeEventListener("click", handleOverlayClick);
resolve();
};
// Handle overlay click
const handleOverlayClick = (e) => {
if (e.target === overlay) {
handleOk();
}
};
// Add listeners
okBtn.addEventListener("click", handleOk);
overlay.addEventListener("click", handleOverlayClick);
});
}
// Initialize on page load
document.addEventListener("DOMContentLoaded", () => {
console.log("=== DOMContentLoaded fired ===");
// Initialize page modal first
pageModal = new bootstrap.Modal(document.getElementById("pageModal"));
// Try to initialize editors (will fail if modal hidden, that's ok)
try {
initializeEditors();
} catch (error) {
console.warn("Editor initialization delayed:", error);
}
// Try to initialize media library
try {
initializeMediaLibrary();
} catch (error) {
console.warn("Media library initialization delayed:", error);
}
// ALWAYS load pages - this is critical
console.log("=== About to call loadPages() ===");
loadPages();
console.log("=== loadPages() call completed ===");
});
// Initialize Media Library
function initializeMediaLibrary() {
pagesMediaLibrary = new MediaLibrary({
selectMode: true,
multiple: false,
onSelect: handleMediaSelection,
});
}
// Handle media selection
function handleMediaSelection(media) {
if (!currentMediaCallback) return;
// Media object has url or path property
const imageUrl = media.url || media.path || "";
if (imageUrl && currentMediaCallback) {
currentMediaCallback(imageUrl);
}
currentMediaCallback = null;
}
// Initialize Quill editors
function initializeEditors() {
// Check if editors already exist
if (pageContentEditor && aboutContentEditor) {
return;
}
// Check if elements exist and are visible
const pageEditorEl = document.getElementById("pageContentEditor");
const aboutEditorEl = document.getElementById("aboutContentEditor");
if (!pageEditorEl || !aboutEditorEl) {
console.warn("Editor elements not found yet");
return;
}
const toolbarOptions = [
[{ header: [1, 2, 3, 4, 5, 6, false] }],
["bold", "italic", "underline", "strike"],
[{ color: [] }, { background: [] }],
[{ list: "ordered" }, { list: "bullet" }],
[{ align: [] }],
["blockquote", "code-block"],
["link", "image"],
["clean"],
];
try {
// Main content editor
pageContentEditor = new Quill("#pageContentEditor", {
theme: "snow",
modules: {
toolbar: toolbarOptions,
},
placeholder: "Write your page content here...",
});
// About page editor
aboutContentEditor = new Quill("#aboutContentEditor", {
theme: "snow",
modules: {
toolbar: toolbarOptions,
},
placeholder: "Write about your business...",
});
// Add image handlers
setupImageHandler(pageContentEditor);
setupImageHandler(aboutContentEditor);
console.log("Editors initialized successfully");
} catch (error) {
console.error("Error initializing editors:", error);
pageContentEditor = null;
aboutContentEditor = null;
}
}
// Setup image handler for Quill editor
function setupImageHandler(editor) {
const toolbar = editor.getModule("toolbar");
toolbar.addHandler("image", () => {
openMediaLibrary((imageUrl) => {
const range = editor.getSelection(true);
editor.insertEmbed(range.index, "image", imageUrl);
});
});
}
// Open media library
function openMediaLibrary(callback) {
if (pagesMediaLibrary) {
currentMediaCallback = callback;
pagesMediaLibrary.open();
} else if (typeof MediaLibrary !== "undefined") {
// Fallback: create new instance
currentMediaCallback = callback;
pagesMediaLibrary = new MediaLibrary({
selectMode: true,
multiple: false,
onSelect: handleMediaSelection,
});
pagesMediaLibrary.open();
} else {
// Fallback to simple URL input
const url = prompt("Enter image URL:");
if (url) callback(url);
}
}
// Load pages from API
async function loadPages() {
console.log("loadPages() called");
try {
console.log("Fetching pages from API...");
const response = await fetch("/api/admin/pages", {
credentials: "include",
});
console.log("Response status:", response.status);
if (!response.ok) throw new Error("Failed to load pages");
const data = await response.json();
console.log("Pages data received:", data);
pages = data.pages || [];
console.log("Total pages:", pages.length);
renderPages();
} catch (error) {
console.error("Error loading pages:", error);
showToast("Failed to load pages", "error");
document.getElementById("pagesContainer").innerHTML = `
Error Loading Pages
Unable to fetch pages from server
`;
}
}
// Render pages grid
function renderPages() {
console.log("renderPages() called with", pages.length, "pages");
const container = document.getElementById("pagesContainer");
if (!container) {
console.error("pagesContainer element not found!");
return;
}
const searchTerm =
document.getElementById("searchInput")?.value?.toLowerCase() || "";
const filteredPages = pages.filter(
(page) =>
page.title.toLowerCase().includes(searchTerm) ||
page.slug.toLowerCase().includes(searchTerm),
);
console.log("Filtered pages:", filteredPages.length);
if (filteredPages.length === 0) {
container.innerHTML = `
${searchTerm ? "No Matching Pages" : "No Pages Yet"}
${searchTerm ? "Try a different search term" : "Create your first custom page"}
${
!searchTerm
? `
`
: ""
}
`;
return;
}
container.innerHTML = filteredPages
.map((page) => {
const icon = pageIcons[page.slug] || pageIcons.default;
const isPublished = page.ispublished !== false;
return `
/${page.slug}
${isPublished ? "Published" : "Draft"}
`;
})
.join("");
}
// Filter pages based on search
function filterPages() {
renderPages();
}
// Refresh pages
function refreshPages() {
document.getElementById("pagesContainer").innerHTML = `
`;
loadPages();
}
// Delete page
async function deletePage(pageId, pageTitle) {
const confirmed = await showConfirmModal(
"Delete Page",
`Are you sure you want to delete "${pageTitle}"? This action cannot be undone.`,
"Delete",
"danger",
);
if (!confirmed) {
return;
}
showSaving(true);
try {
const response = await fetch(`/api/admin/pages/${pageId}`, {
method: "DELETE",
credentials: "include",
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || "Failed to delete page");
}
showSaving(false);
// Show success modal
await showResultModal(
"success",
"Page Deleted",
`"${pageTitle}" has been deleted successfully.`,
);
// Reload pages list
loadPages();
// Invalidate cache
try {
await fetch("/api/admin/cache/invalidate", {
method: "POST",
headers: { "Content-Type": "application/json" },
credentials: "include",
body: JSON.stringify({ patterns: ["pages", "page:"] }),
});
} catch (e) {
console.log("Cache invalidation skipped");
}
} catch (error) {
console.error("Error deleting page:", error);
showSaving(false);
await showResultModal(
"error",
"Delete Failed",
error.message || "Failed to delete page. Please try again.",
);
}
}
// Show create page modal
function showCreatePage() {
// Initialize editors if not already done
if (!pageContentEditor || !aboutContentEditor) {
initializeEditors();
}
currentPageSlug = null;
deletedTeamMemberIds = [];
teamMembers = [];
faqItems = [];
returnsSections = [];
shippingSections = [];
privacySections = [];
// Reset form
document.getElementById("pageForm").reset();
document.getElementById("pageId").value = "";
document.getElementById("pageSlugOriginal").value = "";
document.getElementById("pageIdBadge").style.display = "none";
// Reset editors
if (pageContentEditor) pageContentEditor.setContents([]);
if (aboutContentEditor) aboutContentEditor.setContents([]);
// Show regular content section
showContentSection("regular");
// Update modal title
document.getElementById("modalTitleText").textContent = "Create New Page";
document.getElementById("saveButtonText").textContent = "Create Page";
pageModal.show();
}
// Edit existing page
async function editPage(pageId) {
// Initialize editors if not already done
if (!pageContentEditor || !aboutContentEditor) {
initializeEditors();
}
// First, fetch the full page data
let page;
try {
const response = await fetch(`/api/admin/pages/${pageId}`, {
credentials: "include",
});
if (!response.ok) throw new Error("Failed to load page");
const data = await response.json();
page = data.page;
} catch (error) {
console.error("Error loading page:", error);
showToast("Failed to load page details", "error");
return;
}
if (!page) return;
currentPageSlug = page.slug;
deletedTeamMemberIds = [];
teamMembers = [];
faqItems = [];
returnsSections = [];
shippingSections = [];
privacySections = [];
// Populate form fields
document.getElementById("pageId").value = page.id;
document.getElementById("pageSlugOriginal").value = page.slug;
document.getElementById("pageTitle").value = page.title || "";
document.getElementById("pageSlug").value = page.slug || "";
document.getElementById("pageMetaTitle").value = page.metatitle || "";
document.getElementById("pageMetaDescription").value =
page.metadescription || "";
document.getElementById("pagePublished").checked = page.ispublished !== false;
// Show page ID
document.getElementById("pageIdBadge").style.display = "flex";
document.getElementById("pageIdDisplay").textContent = page.id;
// Update modal title
document.getElementById("modalTitleText").textContent = `Edit: ${page.title}`;
document.getElementById("saveButtonText").textContent = "Save Changes";
// Handle different page types
if (page.slug === "about") {
showContentSection("about");
await loadAboutPageContent(page);
} else if (page.slug === "contact") {
showContentSection("contact");
loadContactPageContent(page);
} else if (page.slug === "faq") {
showContentSection("faq");
loadFaqPageContent(page);
} else if (page.slug === "returns") {
showContentSection("returns");
loadReturnsPageContent(page);
} else if (page.slug === "shipping-info") {
showContentSection("shipping");
loadShippingPageContent(page);
} else if (page.slug === "privacy") {
showContentSection("privacy");
loadPrivacyPageContent(page);
} else {
showContentSection("regular");
loadRegularPageContent(page);
}
pageModal.show();
}
// Show appropriate content section
function showContentSection(type) {
document.getElementById("regularContentSection").style.display =
type === "regular" ? "block" : "none";
document.getElementById("aboutContentSection").style.display =
type === "about" ? "block" : "none";
document.getElementById("contactContentSection").style.display =
type === "contact" ? "block" : "none";
document.getElementById("faqContentSection").style.display =
type === "faq" ? "block" : "none";
document.getElementById("returnsContentSection").style.display =
type === "returns" ? "block" : "none";
document.getElementById("shippingContentSection").style.display =
type === "shipping" ? "block" : "none";
document.getElementById("privacyContentSection").style.display =
type === "privacy" ? "block" : "none";
}
// Load regular page content
function loadRegularPageContent(page) {
try {
const content = page.pagecontent
? JSON.parse(page.pagecontent)
: { ops: [] };
pageContentEditor.setContents(content);
} catch {
pageContentEditor.setText(page.pagecontent || "");
}
}
// Load about page content
async function loadAboutPageContent(page) {
try {
const content = page.pagecontent
? JSON.parse(page.pagecontent)
: { ops: [] };
aboutContentEditor.setContents(content);
} catch {
aboutContentEditor.setText(page.pagecontent || "");
}
// Load team members
await loadTeamMembers();
}
// Load team members
async function loadTeamMembers() {
try {
const response = await fetch("/api/admin/team-members", {
credentials: "include",
});
if (response.ok) {
const data = await response.json();
teamMembers = data.teamMembers || data || [];
} else {
teamMembers = [];
}
renderTeamMembers();
} catch (error) {
console.error("Error loading team members:", error);
teamMembers = [];
renderTeamMembers();
}
}
// Render team members grid
function renderTeamMembers() {
const grid = document.getElementById("teamMembersGrid");
const membersHtml = teamMembers
.map(
(member, index) => `
`,
)
.join("");
grid.innerHTML =
membersHtml +
`
`;
}
// Add new team member
function addTeamMember() {
teamMembers.push({
name: "",
position: "",
bio: "",
image_url: "",
display_order: teamMembers.length,
});
renderTeamMembers();
}
// Remove team member
async function removeTeamMember(index) {
const member = teamMembers[index];
const confirmed = await showConfirmModal(
"Remove Team Member",
"Are you sure you want to remove this team member?",
"Remove",
"warning",
);
if (confirmed) {
// Track ID for deletion from database
if (member.id && typeof member.id === "number") {
deletedTeamMemberIds.push(member.id);
console.log("Marked for deletion:", member.id);
}
teamMembers.splice(index, 1);
renderTeamMembers();
showToast("Team member removed. Save to apply changes.", "info");
}
}
// Update team member field
function updateTeamMember(index, field, value) {
if (teamMembers[index]) {
teamMembers[index][field] = value;
}
}
// Select team member image
function selectTeamMemberImage(index) {
openMediaLibrary((imageUrl) => {
if (teamMembers[index]) {
teamMembers[index].image_url = imageUrl;
renderTeamMembers();
}
});
}
// Load contact page content
function loadContactPageContent(page) {
let pageData = {};
try {
if (typeof page.pagedata === "string") {
pageData = JSON.parse(page.pagedata);
} else {
pageData = page.pagedata || {};
}
} catch {
pageData = {};
}
// Populate header fields
document.getElementById("contactHeaderTitle").value =
pageData.header?.title || "";
document.getElementById("contactHeaderSubtitle").value =
pageData.header?.subtitle || "";
// Populate contact details
document.getElementById("contactPhone").value = pageData.phone || "";
document.getElementById("contactEmail").value = pageData.email || "";
document.getElementById("contactAddress").value = pageData.address || "";
// Populate business hours
const hoursContainer = document.getElementById("businessHoursContainer");
hoursContainer.innerHTML = "";
const hours = pageData.businessHours || [];
hours.forEach((hour, index) => {
addBusinessHourRow(hour.day, hour.hours, index);
});
if (hours.length === 0) {
addBusinessHour();
}
}
// Load FAQ page content
function loadFaqPageContent(page) {
let pageData = {};
try {
if (typeof page.pagedata === "string") {
pageData = JSON.parse(page.pagedata);
} else {
pageData = page.pagedata || {};
}
} catch {
pageData = {};
}
// Populate header fields
document.getElementById("faqHeaderTitle").value =
pageData.header?.title || "Frequently Asked Questions";
document.getElementById("faqHeaderSubtitle").value =
pageData.header?.subtitle || "Quick answers to common questions";
// Load FAQ items
faqItems = pageData.items || [];
renderFaqItems();
}
// Render FAQ items
function renderFaqItems() {
const container = document.getElementById("faqItemsContainer");
if (faqItems.length === 0) {
container.innerHTML =
'No FAQ items yet. Click "Add Question" to create one.
';
return;
}
container.innerHTML = faqItems
.map(
(item, index) => `
`,
)
.join("");
}
// Add new FAQ item
function addFaqItem() {
faqItems.push({
question: "",
answer: "",
});
renderFaqItems();
}
// Remove FAQ item
async function removeFaqItem(index) {
const confirmed = await showConfirmModal(
"Remove FAQ Item",
"Are you sure you want to remove this FAQ item?",
"Remove",
"warning",
);
if (confirmed) {
faqItems.splice(index, 1);
renderFaqItems();
showToast("FAQ item removed.", "info");
}
}
// Update FAQ item
function updateFaqItem(index, field, value) {
if (faqItems[index]) {
faqItems[index][field] = value;
}
}
// Load Returns page content
function loadReturnsPageContent(page) {
let pageData = {};
try {
if (typeof page.pagedata === "string") {
pageData = JSON.parse(page.pagedata);
} else {
pageData = page.pagedata || {};
}
} catch {
pageData = {};
}
// Populate header fields
document.getElementById("returnsHeaderTitle").value =
pageData.header?.title || "Returns & Refunds";
document.getElementById("returnsHeaderSubtitle").value =
pageData.header?.subtitle || "Our hassle-free return policy";
document.getElementById("returnsHighlight").value = pageData.highlight || "";
// Load sections
returnsSections = pageData.sections || [];
renderReturnsSections();
}
// Render Returns sections
function renderReturnsSections() {
const container = document.getElementById("returnsSectionsContainer");
if (returnsSections.length === 0) {
container.innerHTML =
'No sections yet. Click "Add Section" to create one.
';
returnsEditors = [];
return;
}
// Clear existing editors
returnsEditors = [];
container.innerHTML = returnsSections
.map(
(section, index) => `
${(section.listItems || [])
.map(
(item, i) => `
`,
)
.join("")}
`,
)
.join("");
// Initialize Quill editors for each section after DOM is ready
setTimeout(() => {
returnsSections.forEach((section, index) => {
const editorId = `returnsContentEditor_${index}`;
const editorElement = document.getElementById(editorId);
if (editorElement) {
const quill = new Quill(`#${editorId}`, {
theme: "snow",
modules: {
toolbar: [
[{ header: [2, 3, false] }],
["bold", "italic", "underline"],
[{ list: "ordered" }, { list: "bullet" }],
["link"],
["clean"],
],
},
});
if (section.content) {
quill.clipboard.dangerouslyPasteHTML(section.content);
}
quill.on("text-change", () => {
returnsSections[index].content = quill.root.innerHTML;
});
returnsEditors[index] = quill;
}
});
}, 100);
}
// Returns section functions
function addReturnsSection() {
returnsSections.push({ title: "", content: "", listItems: [] });
renderReturnsSections();
}
async function removeReturnsSection(index) {
const confirmed = await showConfirmModal(
"Remove Section",
"Are you sure you want to remove this returns section?",
"Remove",
"warning",
);
if (confirmed) {
returnsSections.splice(index, 1);
renderReturnsSections();
}
}
function updateReturnsSection(index, field, value) {
if (returnsSections[index]) {
returnsSections[index][field] = value;
}
}
function addReturnsSectionListItem(sectionIndex) {
if (!returnsSections[sectionIndex].listItems) {
returnsSections[sectionIndex].listItems = [];
}
returnsSections[sectionIndex].listItems.push("");
renderReturnsSections();
}
function removeReturnsSectionListItem(sectionIndex, itemIndex) {
returnsSections[sectionIndex].listItems.splice(itemIndex, 1);
renderReturnsSections();
}
function updateReturnsSectionListItem(sectionIndex, itemIndex, value) {
returnsSections[sectionIndex].listItems[itemIndex] = value;
}
// Load Shipping page content
function loadShippingPageContent(page) {
let pageData = {};
try {
if (typeof page.pagedata === "string") {
pageData = JSON.parse(page.pagedata);
} else {
pageData = page.pagedata || {};
}
} catch {
pageData = {};
}
// Populate header fields
document.getElementById("shippingHeaderTitle").value =
pageData.header?.title || "Shipping Information";
document.getElementById("shippingHeaderSubtitle").value =
pageData.header?.subtitle || "Fast, reliable delivery to your door";
// Load sections
shippingSections = pageData.sections || [];
renderShippingSections();
}
// Render Shipping sections
function renderShippingSections() {
const container = document.getElementById("shippingSectionsContainer");
if (shippingSections.length === 0) {
container.innerHTML =
'No sections yet. Click "Add Section" to create one.
';
shippingEditors = [];
return;
}
// Clear existing editors
shippingEditors = [];
container.innerHTML = shippingSections
.map(
(section, index) => `
${(section.listItems || [])
.map(
(item, i) => `
`,
)
.join("")}
`,
)
.join("");
// Initialize Quill editors for each section after DOM is ready
setTimeout(() => {
shippingSections.forEach((section, index) => {
const editorId = `shippingContentEditor_${index}`;
const editorElement = document.getElementById(editorId);
if (editorElement) {
const quill = new Quill(`#${editorId}`, {
theme: "snow",
modules: {
toolbar: [
[{ header: [2, 3, false] }],
["bold", "italic", "underline"],
[{ list: "ordered" }, { list: "bullet" }],
["link"],
["clean"],
],
},
});
if (section.content) {
quill.clipboard.dangerouslyPasteHTML(section.content);
}
quill.on("text-change", () => {
shippingSections[index].content = quill.root.innerHTML;
});
shippingEditors[index] = quill;
}
});
}, 100);
}
// Shipping section functions
function addShippingSection() {
shippingSections.push({ title: "", content: "", listItems: [] });
renderShippingSections();
}
async function removeShippingSection(index) {
const confirmed = await showConfirmModal(
"Remove Section",
"Are you sure you want to remove this shipping section?",
"Remove",
"warning",
);
if (confirmed) {
shippingSections.splice(index, 1);
renderShippingSections();
}
}
function updateShippingSection(index, field, value) {
if (shippingSections[index]) {
shippingSections[index][field] = value;
}
}
function addShippingSectionListItem(sectionIndex) {
if (!shippingSections[sectionIndex].listItems) {
shippingSections[sectionIndex].listItems = [];
}
shippingSections[sectionIndex].listItems.push("");
renderShippingSections();
}
function removeShippingSectionListItem(sectionIndex, itemIndex) {
shippingSections[sectionIndex].listItems.splice(itemIndex, 1);
renderShippingSections();
}
function updateShippingSectionListItem(sectionIndex, itemIndex, value) {
shippingSections[sectionIndex].listItems[itemIndex] = value;
}
// Load Privacy page content
function loadPrivacyPageContent(page) {
let pageData = {};
try {
if (typeof page.pagedata === "string") {
pageData = JSON.parse(page.pagedata);
} else {
pageData = page.pagedata || {};
}
} catch {
pageData = {};
}
// Populate header fields
document.getElementById("privacyHeaderTitle").value =
pageData.header?.title || "Privacy Policy";
document.getElementById("privacyHeaderSubtitle").value =
pageData.header?.subtitle || "How we protect and use your information";
document.getElementById("privacyLastUpdated").value =
pageData.lastUpdated || "";
// Populate contact box fields
document.getElementById("privacyContactTitle").value =
pageData.contactBox?.title || "Privacy Questions?";
document.getElementById("privacyContactMessage").value =
pageData.contactBox?.message ||
"If you have any questions about this Privacy Policy, please contact us:";
document.getElementById("privacyContactEmail").value =
pageData.contactBox?.email || "privacy@skyartshop.com";
document.getElementById("privacyContactPhone").value =
pageData.contactBox?.phone || "(555) 123-4567";
document.getElementById("privacyContactAddress").value =
pageData.contactBox?.address || "123 Creative Lane, City, ST 12345";
// Convert old sections format to new mainContent format if needed
let mainContent = pageData.mainContent || "";
// If no mainContent but has sections (old format), convert them
if (!mainContent && pageData.sections && pageData.sections.length > 0) {
console.log("Converting old sections format to new mainContent format");
mainContent = pageData.sections
.map((section) => {
let html = "";
if (section.title) {
html += `${section.title}
`;
}
if (section.content) {
html += section.content;
}
return html;
})
.join("");
}
console.log(
"Loading privacy content into editor, length:",
mainContent.length,
);
// Initialize main content editor
initializePrivacyMainEditor(mainContent);
}
// Initialize Privacy main content editor
function initializePrivacyMainEditor(content) {
const editorElement = document.getElementById("privacyMainContentEditor");
if (!editorElement) {
console.error("privacyMainContentEditor element not found!");
return;
}
// Check if editor already exists
if (privacyMainEditor) {
console.log("Privacy editor already exists, updating content");
if (content) {
privacyMainEditor.clipboard.dangerouslyPasteHTML(content);
}
return;
}
// Clear any existing editor
editorElement.innerHTML = "";
// Create new Quill editor using dedicated variable
privacyMainEditor = new Quill("#privacyMainContentEditor", {
theme: "snow",
modules: {
toolbar: [
[{ header: [1, 2, 3, false] }],
["bold", "italic", "underline", "strike"],
[{ list: "ordered" }, { list: "bullet" }],
[{ indent: "-1" }, { indent: "+1" }],
["link"],
["clean"],
],
},
});
// Set content if exists
if (content) {
privacyMainEditor.clipboard.dangerouslyPasteHTML(content);
}
console.log(
"Privacy editor initialized with content length:",
content ? content.length : 0,
);
}
// Add business hour row
function addBusinessHour() {
const container = document.getElementById("businessHoursContainer");
const index = container.children.length;
addBusinessHourRow("", "", index);
}
function addBusinessHourRow(day, hours, index) {
const container = document.getElementById("businessHoursContainer");
const row = document.createElement("div");
row.className = "hours-row";
row.innerHTML = `
`;
container.appendChild(row);
}
// Remove business hour row
function removeBusinessHour(btn) {
btn.closest(".hours-row").remove();
}
// Save page
async function savePage() {
const pageId = document.getElementById("pageId").value;
const slug = document
.getElementById("pageSlug")
.value.toLowerCase()
.replace(/\s+/g, "-");
showSaving(true);
try {
// First, handle team member deletions if on about page
if (currentPageSlug === "about" && deletedTeamMemberIds.length > 0) {
console.log("Deleting team members:", deletedTeamMemberIds);
for (const memberId of deletedTeamMemberIds) {
try {
const deleteResponse = await fetch(
`/api/admin/team-members/${memberId}`,
{
method: "DELETE",
credentials: "include",
},
);
if (deleteResponse.ok) {
console.log(`Deleted team member ${memberId}`);
} else {
console.error(`Failed to delete team member ${memberId}`);
}
} catch (err) {
console.error(`Error deleting team member ${memberId}:`, err);
}
}
deletedTeamMemberIds = [];
}
// Build page data based on page type
let pagecontent = "";
let pagedata = null;
if (slug === "about" || currentPageSlug === "about") {
pagecontent = JSON.stringify(aboutContentEditor.getContents());
// Save team members
await saveTeamMembers();
} else if (slug === "contact" || currentPageSlug === "contact") {
pagedata = buildContactPageData();
pagecontent = JSON.stringify({ ops: [] });
} else if (slug === "faq" || currentPageSlug === "faq") {
pagedata = buildFaqPageData();
pagecontent = JSON.stringify({ ops: [] });
} else if (slug === "returns" || currentPageSlug === "returns") {
pagedata = buildReturnsPageData();
pagecontent = JSON.stringify({ ops: [] });
} else if (
slug === "shipping-info" ||
currentPageSlug === "shipping-info"
) {
pagedata = buildShippingPageData();
pagecontent = JSON.stringify({ ops: [] });
} else if (slug === "privacy" || currentPageSlug === "privacy") {
pagedata = buildPrivacyPageData();
pagecontent = JSON.stringify({ ops: [] });
} else {
pagecontent = JSON.stringify(pageContentEditor.getContents());
}
// Build request body
const body = {
title: document.getElementById("pageTitle").value,
slug: slug,
content: pagecontent,
contenthtml: pagecontent,
metatitle: document.getElementById("pageMetaTitle").value,
metadescription: document.getElementById("pageMetaDescription").value,
ispublished: document.getElementById("pagePublished").checked,
};
if (pagedata) {
body.pagedata = pagedata;
}
// Make API request
const url = pageId ? `/api/admin/pages/${pageId}` : "/api/admin/pages";
const method = pageId ? "PUT" : "POST";
const response = await fetch(url, {
method,
headers: { "Content-Type": "application/json" },
credentials: "include",
body: JSON.stringify(body),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || "Failed to save page");
}
const savedPage = await response.json();
showSaving(false);
pageModal.hide();
// Show success modal
await showResultModal(
"success",
"Page Saved",
`Page has been ${pageId ? "updated" : "created"} successfully!`,
);
loadPages();
// Clear cache to reflect changes on frontend immediately
try {
await fetch("/api/admin/cache/invalidate", {
method: "POST",
headers: { "Content-Type": "application/json" },
credentials: "include",
body: JSON.stringify({ patterns: ["pages", "page:", slug] }),
});
} catch (e) {
console.log("Cache invalidation skipped");
}
} catch (error) {
console.error("Error saving page:", error);
showSaving(false);
await showResultModal(
"error",
"Save Failed",
error.message || "Failed to save page. Please try again.",
);
}
}
// Save team members
async function saveTeamMembers() {
for (let i = 0; i < teamMembers.length; i++) {
const member = teamMembers[i];
member.display_order = i;
const url = member.id
? `/api/admin/team-members/${member.id}`
: "/api/admin/team-members";
const method = member.id ? "PUT" : "POST";
try {
const response = await fetch(url, {
method,
headers: { "Content-Type": "application/json" },
credentials: "include",
body: JSON.stringify(member),
});
if (response.ok) {
const data = await response.json();
teamMembers[i] = data.teamMember || data;
}
} catch (err) {
console.error("Error saving team member:", err);
}
}
}
// Build contact page data
function buildContactPageData() {
const hours = [];
const hourRows = document.querySelectorAll(".hours-row");
hourRows.forEach((row) => {
const day = row.querySelector(".hour-day").value.trim();
const time = row.querySelector(".hour-time").value.trim();
if (day || time) {
hours.push({ day, hours: time });
}
});
return {
header: {
title: document.getElementById("contactHeaderTitle").value,
subtitle: document.getElementById("contactHeaderSubtitle").value,
},
phone: document.getElementById("contactPhone").value,
email: document.getElementById("contactEmail").value,
address: document.getElementById("contactAddress").value,
businessHours: hours,
};
}
// Build FAQ page data
function buildFaqPageData() {
return {
header: {
title:
document.getElementById("faqHeaderTitle").value ||
"Frequently Asked Questions",
subtitle:
document.getElementById("faqHeaderSubtitle").value ||
"Quick answers to common questions",
},
items: faqItems.filter(
(item) => item.question.trim() || item.answer.trim(),
),
};
}
// Build Returns page data
function buildReturnsPageData() {
return {
header: {
title:
document.getElementById("returnsHeaderTitle").value ||
"Returns & Refunds",
subtitle:
document.getElementById("returnsHeaderSubtitle").value ||
"Our hassle-free return policy",
},
highlight: document.getElementById("returnsHighlight").value,
sections: returnsSections.filter((s) => s.title.trim() || s.content.trim()),
};
}
// Build Shipping page data
function buildShippingPageData() {
return {
header: {
title:
document.getElementById("shippingHeaderTitle").value ||
"Shipping Information",
subtitle:
document.getElementById("shippingHeaderSubtitle").value ||
"Fast, reliable delivery to your door",
},
sections: shippingSections.filter(
(s) => s.title.trim() || s.content.trim(),
),
};
}
// Build Privacy page data
function buildPrivacyPageData() {
const data = {
header: {
title:
document.getElementById("privacyHeaderTitle").value || "Privacy Policy",
subtitle:
document.getElementById("privacyHeaderSubtitle").value ||
"How we protect and use your information",
},
lastUpdated: document.getElementById("privacyLastUpdated").value || "",
mainContent: privacyMainEditor ? privacyMainEditor.root.innerHTML : "",
contactBox: {
title:
document.getElementById("privacyContactTitle").value ||
"Privacy Questions?",
message:
document.getElementById("privacyContactMessage").value ||
"If you have any questions about this Privacy Policy, please contact us:",
email:
document.getElementById("privacyContactEmail").value ||
"privacy@skyartshop.com",
phone:
document.getElementById("privacyContactPhone").value ||
"(555) 123-4567",
address:
document.getElementById("privacyContactAddress").value ||
"123 Creative Lane, City, ST 12345",
},
};
console.log("Privacy page data being saved:", data);
console.log("Main content length:", data.mainContent.length);
return data;
}
// Show/hide saving overlay
function showSaving(show) {
const overlay = document.getElementById("savingOverlay");
if (show) {
overlay.classList.add("active");
document.getElementById("savePageBtn").disabled = true;
} else {
overlay.classList.remove("active");
document.getElementById("savePageBtn").disabled = false;
}
}
// Show toast notification
function showToast(message, type = "info") {
const container = document.getElementById("toastContainer");
const bgClass =
{
success: "bg-success",
error: "bg-danger",
warning: "bg-warning",
info: "bg-info",
}[type] || "bg-info";
const icon =
{
success: "bi-check-circle",
error: "bi-x-circle",
warning: "bi-exclamation-triangle",
info: "bi-info-circle",
}[type] || "bi-info-circle";
const toast = document.createElement("div");
toast.className = `toast show ${bgClass} text-white`;
toast.setAttribute("role", "alert");
toast.innerHTML = `
`;
container.appendChild(toast);
setTimeout(() => {
toast.remove();
}, 5000);
}
// Escape HTML
function escapeHtml(text) {
if (!text) return "";
const div = document.createElement("div");
div.textContent = text;
return div.innerHTML;
}
// Logout function
function logout() {
window.location.href = "/admin/logout";
}