Files
billit/internal/web/assets/js/dialog.js
Arkaprabha Chakraborty 28733e22d3 quite a lot of things
2025-12-06 03:05:44 +05:30

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();
}
})();