{ document.getElementById("modalBox").scrollIntoView({ behavior: "smooth", block: "center" }); }, 50); } document.getElementById("btnCloseModal").addEventListener("click", () => { document.getElementById("signUpModal").style.display = "none"; }); document.getElementById("btnSignUpNow").addEventListener("click", () => { window.parent.postMessage({ type: "navigate", url: "/sign-up" }, "*"); }); const defaultAppConfig = { businessName: "Your Business Name", logoDataStr: "", address: "123 Business Road\nCity Suite\nAB12 3CD", email: "billing@yourdomain.com", web: "www.yourdomain.com", companyRegNumber: "", vatRegNumber: "", invoicePrefix: "INV", vatPercentage: "0", currency: "GBP", paymentDays: "30", bankName: "GLOBAL BUSINESS BANKING", sortCode: "00-00-00", accNumber: "12345678", services: "Standard Consulting\nPremium Service Package\nCustom Development Rate", altPayments: "" }; let activeConfig = {}; let runtimeUploadedLogoData = ""; let isSettingsOpen = false; let savedClients = []; const currencyMap = { GBP: "£", USD: "$", EUR: "€" }; function loadSavedClients() { const clientsStr = localStorage.getItem("saas_invoice_clients"); if (clientsStr) savedClients = JSON.parse(clientsStr); renderClientDropdown(); } function renderClientDropdown() { const select = document.getElementById("savedClientsSelect"); select.innerHTML = `Select Saved Client...';`; savedClients.forEach((client, index) => { const opt = document.createElement("option"); opt.value = index; opt.innerText = client.name || `Client ${index + 1}`; select.appendChild(opt); }); } document.getElementById("btnSaveClient").addEventListener("click", () => { const name = document.getElementById("clientName").value.trim(); const email = document.getElementById("clientEmail").value.trim(); const address = document.getElementById("clientAddress").value.trim(); if (!name) return alert("Please enter a Client Name to save this profile."); const existingIndex = savedClients.findIndex(c => c.name.toLowerCase() === name.toLowerCase()); if (existingIndex >= 0) savedClients[existingIndex] = { name, email, address }; else savedClients.push({ name, email, address }); localStorage.setItem("saas_invoice_clients", JSON.stringify(savedClients)); renderClientDropdown(); document.getElementById("savedClientsSelect").value = existingIndex >= 0 ? existingIndex : savedClients.length - 1; const btn = document.getElementById("btnSaveClient"); const originalText = btn.innerText; btn.innerText = "✓ SAVED!"; btn.classList.add("bg-emerald-100", "text-emerald-700", "border-emerald-300"); setTimeout(() => { btn.innerText = originalText; btn.classList.remove("bg-emerald-100", "text-emerald-700", "border-emerald-300"); }, 2000); }); document.getElementById("savedClientsSelect").addEventListener("change", (e) => { const idx = e.target.value; if (idx !== "") { const client = savedClients[idx]; document.getElementById("clientName").value = client.name || ""; document.getElementById("clientEmail").value = client.email || ""; document.getElementById("clientAddress").value = client.address || ""; } else { document.getElementById("clientName").value = ""; document.getElementById("clientEmail").value = ""; document.getElementById("clientAddress").value = ""; } }); function getServiceOptionsHTML() { const lines = activeConfig.services.split("\n"); let html = ""; lines.forEach(line => { if(line.trim() !== "") html += `${line.trim()}`; }); html += `Other (Type Custom Description)...`; return html; } function createItemRow() { const container = document.getElementById("itemsContainer"); const isDiscEnabled = document.getElementById("toggleDiscounts").checked; const discStyle = isDiscEnabled ? "block" : "none"; const div = document.createElement("div"); div.className = "item-block relative bg-slate-50 border border-slate-200 p-5 sm:p-6 rounded-xl shadow-sm group w-full overflow-visible"; div.innerHTML = ` Line Item Remove Item / Service \${getServiceOptionsHTML()} Quantity Unit Price £ Discount % Description (Optional) `; container.appendChild(div); updateCurrencySymbols(); updateRemoveButtons(); } function updateRemoveButtons() { const blocks = document.querySelectorAll(".item-block"); const btns = document.querySelectorAll(".btn-remove-item"); if (blocks.length > 1) { btns.forEach(btn => btn.style.display = "flex"); } else { btns.forEach(btn => btn.style.display = "none"); } } function populateAllSelects() { const optionsHTML = getServiceOptionsHTML(); document.querySelectorAll(".item-desc-select").forEach(select => { const currentVal = select.value; select.innerHTML = optionsHTML; if (currentVal && Array.from(select.options).some(o => o.value === currentVal)) { select.value = currentVal; } }); } document.getElementById("btnToggleSettings").addEventListener("click", function() { if (isSettingsOpen) saveSettings(); else toggleSettings(); }); document.getElementById("toggleDiscounts").addEventListener("change", function(e) { const isEnabled = e.target.checked; const globalWrapper = document.getElementById("globalDiscountWrapper"); const metaGrid = document.getElementById("metaGrid"); if (isEnabled) { globalWrapper.style.display = "block"; metaGrid.classList.replace("lg:grid-cols-3", "lg:grid-cols-4"); document.querySelectorAll(".item-disc-wrapper").forEach(el => el.style.display = "block"); } else { globalWrapper.style.display = "none"; metaGrid.classList.replace("lg:grid-cols-4", "lg:grid-cols-3"); document.getElementById("globalDiscount").value = ""; document.querySelectorAll(".item-disc-wrapper").forEach(el => { el.style.display = "none"; const input = el.querySelector(".item-disc"); if (input) input.value = ""; }); } }); document.getElementById("itemsContainer").addEventListener("change", function(e) { if (e.target.classList.contains("item-desc-select")) { const customInput = e.target.nextElementSibling; if (e.target.value === "__OTHER__") customInput.style.display = "block"; else customInput.style.display = "none"; } }); document.getElementById("itemsContainer").addEventListener("click", function(e) { const btn = e.target.closest(".btn-remove-item"); if (btn) { btn.closest(".item-block").remove(); updateRemoveButtons(); } }); document.getElementById("btnAddItem").addEventListener("click", createItemRow); function updateCurrencySymbols() { const symbol = currencyMap[activeConfig.currency] || "£"; const elements = document.querySelectorAll(".currencySymbol"); elements.forEach(el => el.innerText = symbol); } function processLogoUpload(inputElement) { if (inputElement.files && inputElement.files[0]) { const reader = new FileReader(); reader.onload = function(e) { runtimeUploadedLogoData = e.target.result; document.getElementById("cfg_logoPreviewContainer").style.display = "block"; let imgEl = document.getElementById("prev_logo"); if (!imgEl) { imgEl = document.createElement("img"); imgEl.id = "prev_logo"; imgEl.style.display = "none"; document.body.appendChild(imgEl); } imgEl.src = runtimeUploadedLogoData; } reader.readAsDataURL(inputElement.files[0]); } } function loadAppState() { const storedConfig = localStorage.getItem("saas_invoice_config"); if (storedConfig) activeConfig = JSON.parse(storedConfig); else activeConfig = { ...defaultAppConfig }; document.getElementById("cfg_businessName").value = activeConfig.businessName; document.getElementById("cfg_address").value = activeConfig.address; document.getElementById("cfg_email").value = activeConfig.email; document.getElementById("cfg_web").value = activeConfig.web; document.getElementById("cfg_regNumber").value = activeConfig.companyRegNumber || ""; document.getElementById("cfg_vatRegNumber").value = activeConfig.vatRegNumber || ""; document.getElementById("cfg_prefix").value = activeConfig.invoicePrefix; document.getElementById("cfg_vat").value = activeConfig.vatPercentage || "0"; document.getElementById("cfg_currency").value = activeConfig.currency || "GBP"; document.getElementById("cfg_paymentDays").value = activeConfig.paymentDays || "30"; document.getElementById("cfg_bankName").value = activeConfig.bankName; document.getElementById("cfg_sortCode").value = activeConfig.sortCode; document.getElementById("cfg_accNumber").value = activeConfig.accNumber; document.getElementById("cfg_services").value = activeConfig.services; document.getElementById("cfg_altPayments").value = activeConfig.altPayments || ""; runtimeUploadedLogoData = activeConfig.logoDataStr || ""; if(runtimeUploadedLogoData) { document.getElementById("cfg_logoPreviewContainer").style.display = "block"; let imgEl = document.getElementById("prev_logo"); if (!imgEl) { imgEl = document.createElement("img"); imgEl.id = "prev_logo"; imgEl.style.display = "none"; document.body.appendChild(imgEl); } imgEl.src = runtimeUploadedLogoData; } else { document.getElementById("cfg_logoPreviewContainer").style.display = "none"; } const termsDaysValue = activeConfig.paymentDays || "30"; document.getElementById("display_paymentTermsLabel").innerText = termsDaysValue; document.getElementById("display_paymentTermsLabelSub").innerText = termsDaysValue; const randNum = Math.floor(10000 + Math.random() * 90000); document.getElementById("invoiceNumber").innerText = activeConfig.invoicePrefix + randNum; populateAllSelects(); if (document.querySelectorAll(".item-block").length === 0) createItemRow(); updateCurrencySymbols(); updateDueDate(); loadSavedClients(); } function toggleSettings() { const panel = document.getElementById("settingsPanel"); const mainForm = document.getElementById("mainInvoiceForm"); const btnToggle = document.getElementById("btnToggleSettings"); isSettingsOpen = !isSettingsOpen; if (isSettingsOpen) { panel.style.display = "block"; mainForm.style.display = "none"; btnToggle.innerHTML = ` SAVE & RETURN`; } else { panel.style.display = "none"; mainForm.style.display = "block"; btnToggle.innerHTML = ` CONFIGURE PROFILE`; } } function saveSettings() { const newConfig = { businessName: document.getElementById("cfg_businessName").value || defaultAppConfig.businessName, logoDataStr: runtimeUploadedLogoData, address: document.getElementById("cfg_address").value || defaultAppConfig.address, email: document.getElementById("cfg_email").value || defaultAppConfig.email, web: document.getElementById("cfg_web").value || defaultAppConfig.web, companyRegNumber: document.getElementById("cfg_regNumber").value || "", vatRegNumber: document.getElementById("cfg_vatRegNumber").value || "", invoicePrefix: document.getElementById("cfg_prefix").value || defaultAppConfig.invoicePrefix, vatPercentage: document.getElementById("cfg_vat").value || "0", currency: document.getElementById("cfg_currency").value || "GBP", paymentDays: document.getElementById("cfg_paymentDays").value || "30", bankName: document.getElementById("cfg_bankName").value || defaultAppConfig.bankName, sortCode: document.getElementById("cfg_sortCode").value || defaultAppConfig.sortCode, accNumber: document.getElementById("cfg_accNumber").value || defaultAppConfig.accNumber, services: document.getElementById("cfg_services").value || defaultAppConfig.services, altPayments: document.getElementById("cfg_altPayments").value || "" }; localStorage.setItem("saas_invoice_config", JSON.stringify(newConfig)); loadAppState(); populateAllSelects(); if (isSettingsOpen) toggleSettings(); } function formatDateForInput(date) { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, "0"); const day = String(date.getDate()).padStart(2, "0"); return `${year}-${month}-${day}`; } function updateDueDate() { const invoiceDateEl = document.getElementById("invoiceDate"); const dueDateEl = document.getElementById("dueDate"); if (invoiceDateEl && invoiceDateEl.value) { const parts = invoiceDateEl.value.split("-"); const year = parseInt(parts[0], 10); const month = parseInt(parts[1], 10) - 1; const day = parseInt(parts[2], 10); const calculatedDate = new Date(year, month, day); const termOffsetDays = parseInt(activeConfig.paymentDays) || 30; calculatedDate.setDate(calculatedDate.getDate() + termOffsetDays); if (dueDateEl) dueDateEl.value = formatDateForInput(calculatedDate); } } function setupApp() { loadAppState(); document.getElementById("invoiceDate").value = formatDateForInput(new Date()); updateDueDate(); document.getElementById("btnSaveSettings").addEventListener("click", saveSettings); document.getElementById("btnDownloadPdf").addEventListener("click", function(e) { e.preventDefault(); showSignUpModal("Sign up to download your PDF invoice."); }); document.getElementById("invoiceDate").addEventListener("change", updateDueDate); document.getElementById("invoiceDate").addEventListener("input", updateDueDate); document.getElementById("cfg_logoFile").addEventListener("change", function(e) { processLogoUpload(e.target); }); // Height Tracker fixed to monitor document baseline const observer = new ResizeObserver(() => { window.parent.postMessage({ type: "resize", height: document.body.scrollHeight }, "*"); }); observer.observe(document.body); setTimeout(() => { window.parent.postMessage({ type: "resize", height: document.body.scrollHeight }, "*"); }, 500); } if (document.readyState === "loading") { window.addEventListener("DOMContentLoaded", setupApp); } else { setupApp(); }