250 lines
8.3 KiB
JavaScript
250 lines
8.3 KiB
JavaScript
// 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 = `
|
|
<div class="dialog-box">
|
|
<div class="dialog-header">
|
|
<h3 class="dialog-title"></h3>
|
|
</div>
|
|
<div class="dialog-body">
|
|
<p class="dialog-message"></p>
|
|
</div>
|
|
<div class="dialog-footer">
|
|
<button type="button" class="btn btn-outline dialog-cancel">Cancel</button>
|
|
<button type="button" class="btn btn-danger dialog-confirm">Confirm</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
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 = '<p class="dialog-message">' + escapeHtml(message) + '</p>';
|
|
}
|
|
|
|
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 = `
|
|
<div class="disclaimer-content">
|
|
<p style="font-weight: bold; margin-bottom: 15px;">
|
|
Please read these terms carefully before using this software. By proceeding, you agree to the conditions below:
|
|
</p>
|
|
<ul style="padding-left: 20px; line-height: 1.8; margin: 0;">
|
|
<li>
|
|
<strong>1. FREE OF CHARGE & CPA EXEMPTION:</strong> This software is provided strictly <strong>"Free of Charge"</strong> and without any monetary consideration. It therefore does not constitute a "Service" under the Indian Consumer Protection Act, 2019.
|
|
</li>
|
|
<li style="margin-top: 10px;">
|
|
<strong>2. "AS IS" & NO WARRANTY:</strong> The software is provided <strong>"AS IS"</strong>. The developer provides <strong>NO WARRANTY</strong>, express or implied, regarding its performance, accuracy, security, or suitability for any purpose.
|
|
</li>
|
|
<li style="margin-top: 10px;">
|
|
<strong>3. USER ASSUMPTION OF RISK:</strong> The developer is not liable for any financial losses, data corruption, calculation errors, or legal issues resulting from the use or misuse of this application. Users assume all associated risks and agree to indemnify and hold harmless the developer.
|
|
</li>
|
|
</ul>
|
|
<p style="font-size: 0.9em; font-style: italic; color: #666; margin-top: 15px; margin-bottom: 0;">
|
|
<small>Consult a qualified legal or financial advisor before relying on any data generated by this tool.</small>
|
|
</p>
|
|
</div>
|
|
`;
|
|
|
|
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();
|
|
}
|
|
|
|
})();
|