// 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 `

${escapeHtml(page.title)}

/${page.slug}
${isPublished ? "Published" : "Draft"}
`; }) .join(""); } // Filter pages based on search function filterPages() { renderPages(); } // Refresh pages function refreshPages() { document.getElementById("pagesContainer").innerHTML = `

Loading pages...

`; 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) => `
${ member.image_url ? `${escapeHtml(member.name || ` : '' }
`, ) .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) => `
${index + 1}
`, ) .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) => `
${index + 1}
${(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) => `
${index + 1}
${(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 = `
${escapeHtml(message)}
`; 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"; }