// Dialog component for Billit // Replaces browser confirm/alert dialogs with custom styled modals (function() { 'use strict'; // Dialog state let currentResolve = null; let currentElement = null; // Create dialog HTML structure function createDialogElement() { const dialog = document.createElement('div'); dialog.id = 'dialog'; dialog.className = 'dialog-overlay'; dialog.innerHTML = `

`; document.body.appendChild(dialog); // Event listeners dialog.querySelector('.dialog-cancel').addEventListener('click', () => closeDialog(false)); dialog.querySelector('.dialog-confirm').addEventListener('click', () => closeDialog(true)); dialog.addEventListener('click', (e) => { if (e.target === dialog) closeDialog(false); }); // Escape key closes dialog document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && dialog.classList.contains('dialog-open')) { closeDialog(false); } }); return dialog; } // Get or create dialog element function getDialog() { return document.getElementById('dialog') || createDialogElement(); } // Open dialog with options function openDialog(options) { const dialog = getDialog(); const title = options.title || 'Confirm'; const message = options.message || 'Are you sure?'; const confirmText = options.confirmText || 'Confirm'; const cancelText = options.cancelText || 'Cancel'; const confirmClass = options.confirmClass || 'btn-danger'; const html = options.html || null; const wide = options.wide || false; const allowClose = options.allowClose !== false; dialog.querySelector('.dialog-title').textContent = title; // Support HTML content if (html) { dialog.querySelector('.dialog-body').innerHTML = html; } else { dialog.querySelector('.dialog-body').innerHTML = '

' + escapeHtml(message) + '

'; } dialog.querySelector('.dialog-confirm').textContent = confirmText; dialog.querySelector('.dialog-confirm').className = 'btn ' + confirmClass + ' dialog-confirm'; dialog.querySelector('.dialog-cancel').textContent = cancelText; // Show/hide cancel button for alert-style dialogs dialog.querySelector('.dialog-cancel').style.display = options.showCancel !== false ? '' : 'none'; // Wide mode for larger content dialog.querySelector('.dialog-box').style.maxWidth = wide ? '600px' : '400px'; // Store allowClose setting dialog.dataset.allowClose = allowClose; dialog.classList.add('dialog-open'); dialog.querySelector('.dialog-confirm').focus(); return new Promise((resolve) => { currentResolve = resolve; }); } // Close dialog function closeDialog(result) { const dialog = getDialog(); // Check if closing is allowed (for disclaimer) if (!result && dialog.dataset.allowClose === 'false') { return; } dialog.classList.remove('dialog-open'); if (currentResolve) { currentResolve(result); currentResolve = null; } // If there's a pending HTMX request, trigger it if (result && currentElement) { htmx.trigger(currentElement, 'confirmed'); } currentElement = null; } // Escape HTML for safe rendering function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // Public API window.Dialog = { confirm: function(options) { if (typeof options === 'string') { options = { message: options }; } return openDialog({ ...options, showCancel: true }); }, alert: function(options) { if (typeof options === 'string') { options = { message: options }; } return openDialog({ ...options, showCancel: false, confirmText: options.confirmText || 'OK', confirmClass: options.confirmClass || 'btn-primary' }); }, // Custom dialog with HTML content custom: function(options) { return openDialog(options); } }; // HTMX integration: intercept hx-confirm and use custom dialog // Elements can customize the dialog with data attributes: // data-dialog-title="Custom Title" // data-dialog-confirm="Button Text" // data-dialog-class="btn-danger" (or btn-primary, etc.) // If no data-dialog-* attributes are present, uses browser default confirm document.addEventListener('htmx:confirm', function(e) { const element = e.detail.elt; // Check if element wants custom dialog (has any data-dialog-* attribute) const hasCustomDialog = element.dataset.dialogTitle || element.dataset.dialogConfirm || element.dataset.dialogClass; if (!hasCustomDialog) { return; // Let default browser confirm handle it } // Prevent default browser confirm e.preventDefault(); const message = e.detail.question; const title = element.dataset.dialogTitle || 'Confirm'; const confirmText = element.dataset.dialogConfirm || 'Confirm'; const confirmClass = element.dataset.dialogClass || 'btn-primary'; // Store element for later currentElement = element; Dialog.confirm({ title: title, message: message, confirmText: confirmText, confirmClass: confirmClass }).then(function(confirmed) { if (confirmed) { // Issue the request e.detail.issueRequest(true); } currentElement = null; }); }); // Disclaimer dialog - show on first visit function showDisclaimer() { const DISCLAIMER_KEY = 'billit_disclaimer_accepted'; // Check if already accepted if (localStorage.getItem(DISCLAIMER_KEY)) { return; } const disclaimerHTML = `

Please read these terms carefully before using this software. By proceeding, you agree to the conditions below:

Consult a qualified legal or financial advisor before relying on any data generated by this tool.

`; Dialog.custom({ title: '⚠️ GENERAL USE & NO LIABILITY DISCLAIMER', html: disclaimerHTML, confirmText: 'I Understand & Accept', confirmClass: 'btn-primary', showCancel: false, wide: true, allowClose: false }).then(function(accepted) { if (accepted) { localStorage.setItem(DISCLAIMER_KEY, Date.now().toString()); } }); } // Show disclaimer when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', showDisclaimer); } else { showDisclaimer(); } })();