feat(ui): Live-Kamera QR-Code Scanner für Admin Setup integriert
All checks were successful
Build and Deploy / build (push) Successful in 59s
All checks were successful
Build and Deploy / build (push) Successful in 59s
This commit is contained in:
@@ -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 = {
|
||||
|
||||
Reference in New Issue
Block a user