Auth: nedokoncena registracia sa da dokoncit novym QR kodom

Ucet bez potvrdeneho kodu (auth_token is NULL a totp_last_step == 0)
uz neblokuje meno: opakovany register_account vyda novy secret (stary
QR prestane platit) a login vyhodi RegistrationIncomplete, na ktoru
server odpovie novym QR -- klient sa prepne na registracny tab.
Admin statistiky vykazuju nedokoncene registracie osobitne, Hraci
celkom pocita len potvrdene ucty. Novy event register v analytike.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
tim
2026-07-04 17:34:03 +02:00
co-authored by Claude Fable 5
parent 53274607b1
commit 5b9d6342ad
7 changed files with 124 additions and 20 deletions
+11 -1
View File
@@ -1,4 +1,4 @@
import { useState } from 'react';
import { useEffect, useState } from 'react';
import { QRCodeSVG } from 'qrcode.react';
import { useGameStore } from '../store/gameStore';
import { emit } from '../lib/socket';
@@ -20,6 +20,15 @@ export default function Auth() {
const remember = (name: string) => localStorage.setItem('bridzik_name', name.trim());
// Server can push a registration payload even from the login tab (unfinished
// registration → re-issued QR) — always land on the tab that shows it.
useEffect(() => {
if (registration) {
setMode('register');
setCode('');
}
}, [registration]);
const handleLogin = (e: React.FormEvent) => {
e.preventDefault();
if (!username.trim() || code.trim().length < 6) return;
@@ -31,6 +40,7 @@ export default function Auth() {
e.preventDefault();
if (!username.trim()) return;
remember(username);
trackEvent('register');
emit.registerAccount(username.trim());
};
+2
View File
@@ -20,6 +20,7 @@ interface DailyStats {
completion_rate: number | null;
avg_game_duration_minutes: number | null;
total_players: number;
unconfirmed_players: number;
peak_hours: Record<string, number>;
rounds_per_day: Record<string, number>;
pageviews_per_day: Record<string, number>;
@@ -155,6 +156,7 @@ export default function AdminStats() {
<div className="flex flex-col gap-4">
<div className={`flex gap-3 ${desktop ? '' : 'flex-wrap'}`}>
<SummaryCard label="Hráči celkom" value={String(data.total_players)} />
<SummaryCard label="Nedokončené registrácie" value={String(data.unconfirmed_players)} />
<SummaryCard
label="Dokončené hry"
value={data.completion_rate != null ? `${Math.round(data.completion_rate * 100)}%` : '—'}