Unifi-Voucher-Tool/assets/global.js
Claude a1021a0f84
feat: comprehensive UI/UX and feature improvements
- Dark mode: CSS custom properties (global.css) + toggle button, persisted in localStorage
- i18n: German/English language switcher (lang/de.php, lang/en.php, includes/I18n.php)
- Mobile-responsive admin layout: hamburger menu, sidebar overlay (global.js + global.css)
- Shared admin navigation include (includes/admin_nav.php) used across all admin pages
- Toast notifications system globally available via global.js
- Voucher templates/profiles: CRUD UI at admin/templates.php with voucher_templates DB table
- Bulk voucher creation: create 1-20 vouchers at once with multi-print layout on index.php
- Configurable voucher defaults: expire time, device limit, max limit in admin settings
- Template quick-select on voucher form: auto-fills max_uses and expire_minutes
- Password reset flow: forgot_password.php + reset_password.php with token-based reset
- Audit log UI: admin/audit_log.php with filter, pagination, audit_log DB table
- Audit logging on login, user create/edit/delete, site create/edit/delete
- Admin pages updated: index, vouchers, users, sites all use admin_nav.php + dark mode + i18n
- Voucher admin: live search input added alongside existing status filter + pagination
- Users admin: password-reset-link button per user row (when SMTP enabled)
- Login page: i18n, dark mode, language switcher, forgot password link

https://claude.ai/code/session_01YN6Bcm1VSi8mpDeyKpyrdJ
2026-05-08 17:59:17 +00:00

117 lines
3.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* === DARK MODE === */
(function() {
const saved = localStorage.getItem('theme') || 'light';
document.documentElement.setAttribute('data-theme', saved);
})();
function toggleDarkMode() {
const html = document.documentElement;
const current = html.getAttribute('data-theme') || 'light';
const next = current === 'dark' ? 'light' : 'dark';
html.setAttribute('data-theme', next);
localStorage.setItem('theme', next);
updateDarkModeBtn();
}
function updateDarkModeBtn() {
const btn = document.getElementById('darkModeBtn');
if (!btn) return;
const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
btn.textContent = isDark ? '☀️' : '🌙';
btn.title = isDark ? 'Light Mode' : 'Dark Mode';
}
document.addEventListener('DOMContentLoaded', updateDarkModeBtn);
/* === TOAST NOTIFICATIONS === */
(function() {
let container = null;
function getContainer() {
if (!container) {
container = document.getElementById('toast-container');
if (!container) {
container = document.createElement('div');
container.id = 'toast-container';
document.body.appendChild(container);
}
}
return container;
}
const icons = {
success: '✓',
error: '✕',
info: '',
warning: '⚠'
};
window.showToast = function(type, title, message, duration) {
duration = duration || 4000;
const c = getContainer();
const el = document.createElement('div');
el.className = 'toast ' + type;
el.innerHTML = `
<span class="toast-icon">${icons[type] || ''}</span>
<div class="toast-body">
<div class="toast-title">${title}</div>
${message ? `<div class="toast-msg">${message}</div>` : ''}
</div>
<button class="toast-close" onclick="this.parentElement.remove()">×</button>
`;
c.appendChild(el);
requestAnimationFrame(() => {
requestAnimationFrame(() => el.classList.add('show'));
});
setTimeout(() => {
el.classList.remove('show');
setTimeout(() => el.remove(), 350);
}, duration);
return el;
};
})();
/* === MOBILE SIDEBAR === */
function toggleMobileSidebar() {
const sidebar = document.querySelector('.sidebar');
const overlay = document.querySelector('.sidebar-overlay');
if (!sidebar) return;
sidebar.classList.toggle('mobile-open');
if (overlay) overlay.classList.toggle('active');
}
function closeMobileSidebar() {
const sidebar = document.querySelector('.sidebar');
const overlay = document.querySelector('.sidebar-overlay');
if (sidebar) sidebar.classList.remove('mobile-open');
if (overlay) overlay.classList.remove('active');
}
document.addEventListener('DOMContentLoaded', function() {
const overlay = document.querySelector('.sidebar-overlay');
if (overlay) overlay.addEventListener('click', closeMobileSidebar);
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') closeMobileSidebar();
});
});
/* === LANGUAGE SWITCHER === */
function switchLanguage(lang) {
fetch('?set_lang=' + lang, { method: 'GET' }).then(() => location.reload());
}
/* === CLIPBOARD === */
function copyToClipboard(text, successMsg) {
navigator.clipboard.writeText(text).then(() => {
showToast('success', successMsg || 'Kopiert!', '');
}).catch(() => {
const ta = document.createElement('textarea');
ta.value = text;
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
document.body.removeChild(ta);
showToast('success', successMsg || 'Kopiert!', '');
});
}