- Noch kein Benutzer eingerichtet. Dieser Account wird als Administrator angelegt.
+
+
+
+
+
+
+
+
+
+
+ monitoring
+
+
Anmeldung
+
Provisions-Tracker
-
-
+
diff --git a/public/login.js b/public/login.js
index 277c4b0..06ac7b3 100644
--- a/public/login.js
+++ b/public/login.js
@@ -1,22 +1,29 @@
-// Login page logic. Detects whether initial setup is required and adjusts the form.
+// Login page logic: detects first-run setup mode and swaps the form into
+// admin-creation mode. Talks to /api/auth/setup or /api/auth/login.
(async function init() {
const errorBox = document.getElementById('errorBox');
+ const errorText = document.getElementById('errorText');
const setupNote = document.getElementById('setupNote');
+ const setupTopBar = document.getElementById('setupTopBar');
+ const brandIcon = document.getElementById('brandIcon');
const title = document.getElementById('title');
+ const subtitle = document.getElementById('subtitle');
const submitBtn = document.getElementById('submitBtn');
+ const submitLabel = document.getElementById('submitLabel');
+ const submitIcon = document.getElementById('submitIcon');
const form = document.getElementById('loginForm');
const passwordInput = document.getElementById('password');
let setupMode = false;
function showError(msg) {
- errorBox.textContent = msg;
- errorBox.classList.add('visible');
+ errorText.textContent = msg;
+ errorBox.style.display = 'flex';
}
function clearError() {
- errorBox.textContent = '';
- errorBox.classList.remove('visible');
+ errorText.textContent = '';
+ errorBox.style.display = 'none';
}
try {
@@ -25,14 +32,21 @@
const data = await r.json();
setupMode = !!data.setupRequired;
}
- } catch (e) {
- // Fall through: stay in login mode
+ } catch (_) {
+ // network error -> stay in login mode
}
if (setupMode) {
title.textContent = 'Ersteinrichtung';
- setupNote.style.display = 'block';
- submitBtn.textContent = 'Administrator anlegen';
+ subtitle.textContent = 'Willkommen beim Provisions-Tracker.';
+ setupNote.style.display = 'flex';
+ setupTopBar.style.display = 'block';
+ submitLabel.textContent = 'Administrator anlegen';
+ submitIcon.textContent = 'person_add';
+ // Brand icon switches to admin look in setup mode
+ brandIcon.classList.remove('bg-primary', 'text-on-primary');
+ brandIcon.classList.add('bg-surface-variant', 'text-on-surface');
+ brandIcon.querySelector('span').textContent = 'admin_panel_settings';
passwordInput.setAttribute('autocomplete', 'new-password');
passwordInput.setAttribute('minlength', '8');
}
diff --git a/public/script.js b/public/script.js
index 98cbb1b..8ea7d8c 100644
--- a/public/script.js
+++ b/public/script.js
@@ -54,31 +54,34 @@ document.addEventListener('DOMContentLoaded', async function() {
async function renderWizardAnbieterButtons() {
const container = document.getElementById('wizardAnbieterButtons');
if (!container) return;
- container.innerHTML = '
Lade...
';
+ container.innerHTML = '
Lade...
';
try {
const anbieter = await apiRequest('/api/anbieter');
container.innerHTML = '';
if (!anbieter.length) {
- container.innerHTML = '
Keine Anbieter vorhanden. Bitte zuerst unter Administration einen Anbieter anlegen.
';
+ container.innerHTML = '
Keine Anbieter vorhanden. Bitte zuerst unter Administration einen Anbieter anlegen.
';
return;
}
anbieter.forEach(a => {
const btn = document.createElement('button');
btn.type = 'button';
- btn.className = 'btn-success';
- btn.style.cssText = 'padding:14px 22px; font-size:15px; min-width:140px;';
- btn.textContent = a.name;
+ btn.className = 'group flex flex-col items-center justify-center p-lg border border-outline-variant rounded-lg bg-surface-container-lowest hover:-translate-y-1 hover:shadow-sm hover:border-primary transition-all duration-200 h-32';
+ btn.innerHTML = `
+
cell_tower
+
+ `;
+ btn.querySelector('span.text-title-sm').textContent = a.name;
btn.addEventListener('click', () => wizardSelectAnbieter(a.id, a.name, a.default_vo_id));
container.appendChild(btn);
});
} catch (e) {
- container.innerHTML = '
Fehler beim Laden der Anbieter
';
+ container.innerHTML = '
Fehler beim Laden der Anbieter
';
}
}
async function wizardSelectAnbieter(anbieterId, anbieterName, defaultVoId) {
document.getElementById('anbieter').value = anbieterId;
- document.getElementById('wizardAnbieterLabel').textContent = '· ' + anbieterName;
+ document.getElementById('wizardAnbieterLabel').textContent = ' (' + anbieterName + ')';
document.getElementById('wizardStep1').style.display = 'none';
document.getElementById('wizardStep2').style.display = 'block';
@@ -141,7 +144,7 @@ async function loadTarifoptionenForWizard() {
section.style.display = 'none';
return;
}
- section.style.display = 'block';
+ section.style.display = 'flex';
try {
const options = await apiRequest(`/api/tarife/${tarifId}/tarifoptionen`);
if (!options || options.length === 0) {
@@ -150,18 +153,39 @@ async function loadTarifoptionenForWizard() {
}
options.forEach(opt => {
const wrapper = document.createElement('label');
- wrapper.style.cssText = 'display:flex; align-items:flex-start; gap:8px; padding:8px; border:1px solid #ddd; border-radius:5px; background:#f9f9f9; cursor:pointer;';
+ wrapper.className = 'flex items-start gap-sm p-sm border border-outline-variant rounded-lg bg-surface-container-lowest hover:bg-surface-container-low cursor-pointer';
const cb = document.createElement('input');
cb.type = 'checkbox';
cb.name = 'tarifoptionen';
cb.value = opt.id;
+ cb.className = 'mt-0.5 text-primary rounded-sm border-outline-variant focus:ring-primary/20';
wrapper.appendChild(cb);
- const text = document.createElement('div');
- const price = (opt.provision_euro >= 0 ? '+' : '') + opt.provision_euro + '€';
- text.innerHTML = `
${escapeHtml(opt.name)} (${escapeHtml(price)})
- ${opt.code ? `
Code: ${escapeHtml(opt.code)}
` : ''}
- ${opt.beschreibung ? `
${escapeHtml(opt.beschreibung)}
` : ''}`;
- wrapper.appendChild(text);
+
+ const body = document.createElement('div');
+ body.className = 'flex flex-col';
+ const nameEl = document.createElement('span');
+ nameEl.className = 'text-body-sm text-on-surface font-semibold';
+ nameEl.textContent = opt.name;
+ body.appendChild(nameEl);
+ if (opt.code) {
+ const codeEl = document.createElement('span');
+ codeEl.className = 'text-label-caps text-on-surface-variant';
+ codeEl.textContent = 'Code: ' + opt.code;
+ body.appendChild(codeEl);
+ }
+ if (opt.beschreibung) {
+ const desc = document.createElement('span');
+ desc.className = 'text-body-sm text-on-surface-variant';
+ desc.textContent = opt.beschreibung;
+ body.appendChild(desc);
+ }
+ wrapper.appendChild(body);
+
+ const price = document.createElement('span');
+ price.className = 'ml-auto font-label-numeric text-label-numeric ' + (opt.provision_euro >= 0 ? 'text-primary' : 'text-error');
+ price.textContent = (opt.provision_euro >= 0 ? '+' : '') + opt.provision_euro + ' €';
+ wrapper.appendChild(price);
+
list.appendChild(wrapper);
});
} catch (e) { /* error shown */ }
@@ -169,33 +193,29 @@ async function loadTarifoptionenForWizard() {
function renderUserBadge() {
if (!currentUser) return;
- // Show admin-only navigation elements (e.g. the Benutzer tab) once we know the role.
+ // Show admin-only nav items now that we know the role.
document.querySelectorAll('.admin-only').forEach(el => {
el.style.display = currentUser.isAdmin ? '' : 'none';
});
- let badge = document.getElementById('userBadge');
- if (!badge) {
- badge = document.createElement('div');
- badge.id = 'userBadge';
- badge.style.cssText = 'position:fixed;top:12px;right:16px;z-index:9000;background:rgba(255,255,255,0.95);padding:8px 14px;border-radius:6px;font-size:13px;box-shadow:0 2px 6px rgba(0,0,0,0.15);font-family:Segoe UI,Tahoma,sans-serif;';
- document.body.appendChild(badge);
+
+ // Fill the user name in the new top header.
+ const nameEl = document.getElementById('headerUserName');
+ if (nameEl) nameEl.textContent = currentUser.displayName || currentUser.username;
+
+ // Wire the header logout button (idempotent: only attach once).
+ const logoutBtn = document.getElementById('headerLogoutBtn');
+ if (logoutBtn && !logoutBtn.dataset.wired) {
+ logoutBtn.dataset.wired = '1';
+ logoutBtn.addEventListener('click', async (e) => {
+ e.preventDefault();
+ try { await fetch('/api/auth/logout', { method: 'POST', credentials: 'same-origin' }); } catch (_) {}
+ window.location.href = '/login';
+ });
}
- const name = currentUser.displayName || currentUser.username;
- badge.innerHTML = '';
- const span = document.createElement('span');
- span.textContent = name + ' · ';
- const link = document.createElement('a');
- link.href = '#';
- link.textContent = 'Abmelden';
- link.style.color = '#c0392b';
- link.style.textDecoration = 'none';
- link.addEventListener('click', async (e) => {
- e.preventDefault();
- try { await fetch('/api/auth/logout', { method: 'POST', credentials: 'same-origin' }); } catch (_) {}
- window.location.href = '/login';
- });
- badge.appendChild(span);
- badge.appendChild(link);
+
+ // Remove the legacy floating badge if it was rendered by an older session.
+ const legacy = document.getElementById('userBadge');
+ if (legacy) legacy.remove();
}
// Preload all data in background