feat(ui): Live-Kamera QR-Code Scanner für Admin Setup integriert
All checks were successful
Build and Deploy / build (push) Successful in 59s

This commit is contained in:
DanielS
2026-09-22 22:12:45 +02:00
parent 21cb5c929a
commit 204218c098

View File

@@ -7,6 +7,8 @@
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&family=Space+Grotesk:wght@600;700&display=swap" rel="stylesheet">
<!-- HTML5-QRCode Scanner Library -->
<script src="https://unpkg.com/html5-qrcode" type="text/javascript"></script>
<style>
:root {
--bg: #090b10;
@@ -159,6 +161,16 @@
}
.notification.success { display: block; background: rgba(16, 185, 129, 0.2); border: 1px solid var(--success); color: #a7f3d0; }
.notification.error { display: block; background: rgba(239, 68, 68, 0.2); border: 1px solid var(--danger); color: #fecaca; }
#reader {
width: 100%;
max-width: 450px;
margin: 16px auto;
border-radius: 12px;
overflow: hidden;
display: none;
border: 2px solid #8b5cf6;
}
</style>
</head>
<body>
@@ -189,7 +201,7 @@
<div class="header-bar">
<div>
<h2>POS Cloud Administration</h2>
<p style="font-size: 13px; color: var(--text-muted);">Schnittstelle, QR-Setup & Speisekarten-Synchronisation</p>
<p style="font-size: 13px; color: var(--text-muted);">Schnittstelle, Live QR-Scanner & Speisekarten-Synchronisation</p>
</div>
<div style="display: flex; gap: 10px;">
<a href="/" target="_blank" class="btn btn-secondary">🍽️ Zur Karte</a>
@@ -201,21 +213,32 @@
<!-- QR-Code Scanner / Setup -->
<div class="card" style="border-left: 4px solid #8b5cf6;">
<h3 style="margin-bottom: 8px; color: #a78bfa;">📷 Automatisches QR-Code Setup (ww-setup)</h3>
<p style="font-size: 13px; color: var(--text-muted); margin-bottom: 16px;">
Füge hier den gescannten QR-Code Inhalt (JSON) aus der POS Cloud ein, um den automatischen Handshake (@LNK) und die Aktivierung (CASPOS.PUT) auszuführen.
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
<h3 style="color: #a78bfa;">📷 Live QR-Code Scanner (Kamera & JSON)</h3>
<button type="button" id="btn-toggle-camera" class="btn btn-qr" style="padding: 8px 14px; font-size: 13px;">
🎥 Kamera öffnen
</button>
</div>
<p style="font-size: 13px; color: var(--text-muted); margin-bottom: 12px;">
Halte den POS Cloud Einrichtungs-QR-Code vor die Kamera oder füge das JSON unten manuell ein.
</p>
<form id="qr-form">
<!-- Kamera Viewport -->
<div id="reader"></div>
<form id="qr-form" style="margin-top: 12px;">
<div class="form-group">
<textarea id="qr-json" rows="3" placeholder='{"$type": "ww-setup", "url": "https://...", "servicepasslink": "...", "mdcDeviceId": "1"}' required></textarea>
</div>
<button type="submit" class="btn btn-qr">🚀 QR-Code Handshake ausführen</button>
<div style="display: flex; gap: 10px;">
<button type="submit" class="btn btn-qr">🚀 QR-Handshake ausführen</button>
</div>
</form>
</div>
<!-- POS Cloud Konfiguration -->
<div class="card">
<h3 style="margin-bottom: 16px; color: var(--primary);">⚙️ Manuelle POS Cloud Verbindungsparameter</h3>
<h3 style="margin-bottom: 16px; color: var(--primary);">⚙️ Aktive POS Cloud Verbindungsparameter</h3>
<form id="config-form">
<div class="grid-2">
<div class="form-group">
@@ -239,7 +262,7 @@
</div>
</div>
<div class="form-group" style="margin-top: 8px; border-top: 1px solid rgba(255,255,255,0.06); padding-top: 16px;">
<div class="form-group" style="margin-top: 8px; border-top: 1px solid rgba(255,255,200,0.06); padding-top: 16px;">
<label>Admin-Passwort ändern (Optional)</label>
<input type="password" id="cfg-admin-pass" placeholder="Neues Passwort vergeben..." />
</div>
@@ -300,6 +323,9 @@
</div>
<script>
let html5QrCode = null;
let cameraActive = false;
function showAlert(id, msg, isError = false) {
const el = document.getElementById(id);
el.textContent = msg;
@@ -323,6 +349,7 @@
function showLogin() {
document.getElementById('login-section').style.display = 'block';
document.getElementById('dashboard-section').style.display = 'none';
stopCamera();
}
function showDashboard() {
@@ -357,6 +384,7 @@
// Logout
document.getElementById('btn-logout').addEventListener('click', async () => {
stopCamera();
await fetch('/api/admin/logout', { method: 'POST' });
showLogin();
});
@@ -373,10 +401,61 @@
}
}
// QR Handshake Form
document.getElementById('qr-form').addEventListener('submit', async (e) => {
e.preventDefault();
const qrContent = document.getElementById('qr-json').value;
// Kamera Scanner Toggle
document.getElementById('btn-toggle-camera').addEventListener('click', async () => {
if (cameraActive) {
stopCamera();
} else {
startCamera();
}
});
async function startCamera() {
const readerEl = document.getElementById('reader');
const btn = document.getElementById('btn-toggle-camera');
readerEl.style.display = 'block';
btn.textContent = '⏹️ Kamera stoppen';
btn.style.background = '#ef4444';
html5QrCode = new Html5Qrcode("reader");
cameraActive = true;
try {
await html5QrCode.start(
{ facingMode: "environment" },
{ fps: 10, qrbox: { width: 250, height: 250 } },
(decodedText) => {
// QR Code erkannt
document.getElementById('qr-json').value = decodedText;
stopCamera();
submitQrPayload(decodedText);
},
(errorMessage) => {}
);
} catch (err) {
showAlert('dash-alert', 'Kamera konnte nicht gestartet werden: ' + err, true);
stopCamera();
}
}
async function stopCamera() {
if (html5QrCode && cameraActive) {
try {
await html5QrCode.stop();
html5QrCode.clear();
} catch(e) {}
}
cameraActive = false;
const readerEl = document.getElementById('reader');
if (readerEl) readerEl.style.display = 'none';
const btn = document.getElementById('btn-toggle-camera');
if (btn) {
btn.textContent = '🎥 Kamera öffnen';
btn.style.background = '#8b5cf6';
}
}
async function submitQrPayload(qrContent) {
showAlert('dash-alert', 'QR-Code Handshake läuft...');
try {
const res = await fetch('/api/admin/qr-setup', {
@@ -386,8 +465,7 @@
});
const data = await res.json();
if (res.ok) {
showAlert('dash-alert', 'QR-Code Handshake erfolgreich! Parameter übernommen & aktiviert.');
document.getElementById('qr-json').value = '';
showAlert('dash-alert', 'QR-Code Handshake erfolgreich! Kasse & ServicePass aktiviert.');
loadConfig();
loadData();
} else {
@@ -396,8 +474,16 @@
} catch (err) {
showAlert('dash-alert', 'Netzwerkfehler beim QR-Handshake', true);
}
}
// QR Handshake Form Submit
document.getElementById('qr-form').addEventListener('submit', (e) => {
e.preventDefault();
const qrContent = document.getElementById('qr-json').value;
submitQrPayload(qrContent);
});
// Manuelle Config
document.getElementById('config-form').addEventListener('submit', async (e) => {
e.preventDefault();
const body = {