208 lines
8.3 KiB
JavaScript
208 lines
8.3 KiB
JavaScript
/* ── Settings page ── */
|
|
|
|
const SettingsPage = (() => {
|
|
async function init(container) {
|
|
let settings, notifPrefs;
|
|
try {
|
|
[settings, notifPrefs] = await Promise.all([
|
|
API.settings(),
|
|
fetch('/api/notifications/me').then(r => r.ok ? r.json() : null),
|
|
]);
|
|
} catch (e) {
|
|
container.innerHTML = `<div class="empty-state"><p>Failed to load settings.</p></div>`;
|
|
return;
|
|
}
|
|
|
|
const notifSection = buildNotifSection(notifPrefs);
|
|
|
|
container.innerHTML = `
|
|
<div class="page-header">
|
|
<h1 class="page-title">Settings</h1>
|
|
</div>
|
|
|
|
<div class="settings-section">
|
|
<h3>General</h3>
|
|
<div class="settings-row">
|
|
<label for="s-currency">Currency</label>
|
|
<select id="s-currency" style="max-width:120px">
|
|
<option value="USD" ${settings.currency === 'USD' ? 'selected' : ''}>USD $</option>
|
|
<option value="EUR" ${settings.currency === 'EUR' ? 'selected' : ''}>EUR €</option>
|
|
<option value="GBP" ${settings.currency === 'GBP' ? 'selected' : ''}>GBP £</option>
|
|
<option value="CAD" ${settings.currency === 'CAD' ? 'selected' : ''}>CAD $</option>
|
|
</select>
|
|
</div>
|
|
<div class="settings-row">
|
|
<label for="s-date-format">Date Format</label>
|
|
<select id="s-date-format" style="max-width:160px">
|
|
<option value="MM/DD/YYYY" ${settings.date_format === 'MM/DD/YYYY' ? 'selected' : ''}>MM/DD/YYYY</option>
|
|
<option value="DD/MM/YYYY" ${settings.date_format === 'DD/MM/YYYY' ? 'selected' : ''}>DD/MM/YYYY</option>
|
|
<option value="YYYY-MM-DD" ${settings.date_format === 'YYYY-MM-DD' ? 'selected' : ''}>YYYY-MM-DD</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="settings-section">
|
|
<h3>Billing Behavior</h3>
|
|
<div class="settings-row">
|
|
<label for="s-grace">Grace Period (days)</label>
|
|
<input type="number" id="s-grace" min="0" max="30" value="${settings.grace_period_days || 5}" style="max-width:80px">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="settings-section">
|
|
<h3>Backup</h3>
|
|
<div class="settings-row">
|
|
<label>Auto Backup</label>
|
|
<label style="cursor:pointer">
|
|
<input type="checkbox" id="s-backup-enabled" ${settings.backup_enabled === 'true' ? 'checked' : ''}>
|
|
Enabled
|
|
</label>
|
|
</div>
|
|
<div class="settings-row">
|
|
<label for="s-backup-freq">Frequency (days)</label>
|
|
<input type="number" id="s-backup-freq" min="1" max="30" value="${settings.backup_frequency_days || 1}" style="max-width:80px">
|
|
</div>
|
|
<div class="settings-row">
|
|
<label for="s-backup-keep">Keep N Backups</label>
|
|
<input type="number" id="s-backup-keep" min="1" max="90" value="${settings.backup_keep_count || 14}" style="max-width:80px">
|
|
</div>
|
|
</div>
|
|
|
|
${notifSection}
|
|
|
|
<div style="display:flex; justify-content:flex-end; gap:8px; margin-top:8px">
|
|
<button class="btn btn-primary" id="save-settings-btn">Save Settings</button>
|
|
</div>
|
|
`;
|
|
|
|
document.getElementById('save-settings-btn').onclick = async () => {
|
|
const data = {
|
|
currency: document.getElementById('s-currency').value,
|
|
date_format: document.getElementById('s-date-format').value,
|
|
grace_period_days: document.getElementById('s-grace').value,
|
|
backup_enabled: document.getElementById('s-backup-enabled').checked ? 'true' : 'false',
|
|
backup_frequency_days: document.getElementById('s-backup-freq').value,
|
|
backup_keep_count: document.getElementById('s-backup-keep').value,
|
|
};
|
|
try {
|
|
await API.saveSettings(data);
|
|
showToast('Settings saved', 'success');
|
|
} catch (err) {
|
|
showToast('Error: ' + err.message, 'error');
|
|
}
|
|
};
|
|
|
|
// Notification save (only wired if section exists)
|
|
const notifForm = document.getElementById('notif-user-form');
|
|
if (notifForm) {
|
|
notifForm.onsubmit = async (e) => {
|
|
e.preventDefault();
|
|
const btn = notifForm.querySelector('[type="submit"]');
|
|
btn.disabled = true;
|
|
try {
|
|
const res = await fetch('/api/notifications/me', {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
notification_email: document.getElementById('n-email').value.trim(),
|
|
notifications_enabled:document.getElementById('n-enabled').checked,
|
|
notify_3d: document.getElementById('n-3d').checked,
|
|
notify_1d: document.getElementById('n-1d').checked,
|
|
notify_due: document.getElementById('n-due').checked,
|
|
notify_overdue: document.getElementById('n-overdue').checked,
|
|
}),
|
|
});
|
|
if (!res.ok) throw new Error((await res.json()).error);
|
|
showToast('Notification preferences saved', 'success');
|
|
} catch (err) {
|
|
showToast('Error: ' + err.message, 'error');
|
|
} finally {
|
|
btn.disabled = false;
|
|
}
|
|
};
|
|
|
|
// Toggle field visibility based on enabled checkbox
|
|
const toggle = () => {
|
|
const on = document.getElementById('n-enabled').checked;
|
|
document.getElementById('n-options').style.opacity = on ? '1' : '.4';
|
|
document.getElementById('n-options').style.pointerEvents = on ? '' : 'none';
|
|
};
|
|
document.getElementById('n-enabled').addEventListener('change', toggle);
|
|
toggle();
|
|
}
|
|
}
|
|
|
|
function buildNotifSection(p) {
|
|
if (!p) return ''; // API call failed, skip silently
|
|
|
|
if (!p.smtp_enabled) {
|
|
return `
|
|
<div class="settings-section">
|
|
<h3>Notifications</h3>
|
|
<p style="color:var(--text-muted);font-size:13px;">
|
|
Email notifications have not been configured by the admin.
|
|
</p>
|
|
</div>`;
|
|
}
|
|
|
|
if (!p.allow_user_config) {
|
|
return `
|
|
<div class="settings-section">
|
|
<h3>Notifications</h3>
|
|
<p style="color:var(--text-muted);font-size:13px;">
|
|
Email notifications are enabled and managed by the admin.
|
|
Your bills will generate reminders automatically.
|
|
</p>
|
|
</div>`;
|
|
}
|
|
|
|
// Full user-configurable notification section
|
|
const chk = (id, label, val) =>
|
|
`<label style="display:flex;align-items:center;gap:8px;cursor:pointer;font-size:13px;font-weight:normal;color:var(--text)">
|
|
<input type="checkbox" id="${id}" ${val ? 'checked' : ''}> ${label}
|
|
</label>`;
|
|
|
|
return `
|
|
<div class="settings-section">
|
|
<h3>Notifications</h3>
|
|
<form id="notif-user-form">
|
|
<div class="settings-row">
|
|
<label>Enable Notifications</label>
|
|
<label style="cursor:pointer;display:flex;align-items:center;gap:6px;font-weight:normal;color:var(--text)">
|
|
<input type="checkbox" id="n-enabled" ${p.notifications_enabled ? 'checked' : ''}>
|
|
Send me email reminders
|
|
</label>
|
|
</div>
|
|
|
|
<div id="n-options">
|
|
<div class="settings-row" style="margin-top:6px">
|
|
<label for="n-email">Notification Email</label>
|
|
<input type="email" id="n-email" value="${escHtml(p.notification_email)}"
|
|
placeholder="your@email.com" style="max-width:280px">
|
|
</div>
|
|
|
|
<div class="settings-row" style="align-items:flex-start">
|
|
<label style="padding-top:2px">Remind me</label>
|
|
<div style="display:flex;flex-direction:column;gap:8px">
|
|
${chk('n-3d', '3 days before due', p.notify_3d)}
|
|
${chk('n-1d', '1 day before due', p.notify_1d)}
|
|
${chk('n-due', 'On the day it\'s due', p.notify_due)}
|
|
${chk('n-overdue', 'Daily while overdue', p.notify_overdue)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div style="display:flex;justify-content:flex-end;margin-top:12px">
|
|
<button type="submit" class="btn btn-ghost btn-sm">Save Notifications</button>
|
|
</div>
|
|
</form>
|
|
</div>`;
|
|
}
|
|
|
|
function escHtml(s) {
|
|
return String(s || '').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
|
}
|
|
|
|
return { init };
|
|
})();
|