2 Commits
10 changed files with 190 additions and 106 deletions
+5 -9
View File
@@ -30,7 +30,7 @@ Applicazione web ottimizzata per **tablet in orizzontale** che gestisce i varchi
```
VotoFocolari/
├── dev.sh # Script di sviluppo (install, dev, server, ...)
├── dev.sh # Script di sviluppo (install, dev, server, check, ...)
├── README.md
├── ai-prompts/ # Documentazione sviluppo
│ ├── 00-welcome-agent.md # Questo file
@@ -51,7 +51,7 @@ VotoFocolari/
└── src/
├── App.tsx # State machine principale
├── hooks/ # useRFIDScanner
├── components/ # UI components
├── components/ # UI components (Logo, UserCard, FullscreenButton, ...)
├── screens/ # Schermate
├── services/ # API client
└── types/ # TypeScript types
@@ -95,6 +95,9 @@ VotoFocolari/
# Frontend: http://localhost:5173
# Backend: http://localhost:8000
# Type-check TypeScript (senza build)
./dev.sh check
# Produzione locale
./dev.sh server
# App completa: http://localhost:8000
@@ -149,10 +152,3 @@ VotoFocolari/
6. **Success Modal Interrompibile**: Se durante il carosello di benvenuto si passa un nuovo badge, la modal si chiude e
viene caricato subito il nuovo utente.
---
## TODO (da concordare con committenti)
- [ ] Verificare se il badge validatore debba essere validato anche lato server
- [ ] Test automatici E2E per regression detection
+19 -2
View File
@@ -69,21 +69,23 @@ Ottimizzata per tablet in orizzontale.
- [x] `Input.tsx` - campo input styled + **toggle password visibility**
- [x] `Modal.tsx` - modale base
- [x] `RFIDStatus.tsx` - indicatore stato scanner
- [x] `UserCard.tsx` - card utente con foto e ruolo
- [x] `UserCard.tsx` - card utente con foto e ruolo (compatta per tablet)
- [x] `CountdownTimer.tsx` - timer con progress bar
- [x] `WelcomeCarousel.tsx` - carosello messaggi multilingua con **animazione smooth sliding**
- [x] `NumLockBanner.tsx` - avviso NumLock per desktop
- [x] `FullscreenButton.tsx` - toggle schermo intero (Fullscreen API)
### 7. Schermate (`screens/`)
- [x] `LoadingScreen.tsx` - caricamento iniziale + ping automatico
- [x] `ValidatorLoginScreen.tsx` - attesa badge + password + NumLockBanner
- [x] `ValidatorLoginScreen.tsx` - attesa badge + password + NumLockBanner + **FullscreenButton**
- [x] `ActiveGateScreen.tsx` - varco attivo:
- [x] Card utente (layout largo per tablet)
- [x] **Schermata "badge non trovato"** con countdown barra visiva (30s)
- [x] **Pulsante "Annulla" nella schermata badge non trovato**
- [x] **Badge diverso durante errore "non trovato" → ricarica nuovo utente/errore**
- [x] **Notifica badge validatore ignorato**
- [x] **Pulsante FullscreenButton nell'header**
- [x] NumLockBanner
- [x] `SuccessModal.tsx` - conferma ingresso con carosello:
- [x] **Carosello fullwidth** (nessun troncamento testo)
@@ -260,6 +262,21 @@ npm run test:e2e:headed # Test con browser visibile
npm run test:e2e:report # Mostra report HTML
```
### 12. Ottimizzazione Tablet (1080x560)
- [x] Layout `h-screen` con `overflow-hidden` (no scroll) su tutte le schermate
- [x] Header compatti: padding `p-3`, Logo `size="sm"`, testo ridotto
- [x] Footer compatti: padding `p-2`, testo `text-xs`
- [x] Card utente ridotta: foto `h-24/w-24``h-28/w-28`, testo `text-2xl/3xl`
- [x] Spaziature e padding ridotti in tutti i box (meno `mb-`, `py-`, `p-`)
- [x] Icone ridotte (da `w-24/h-24` a `w-16/h-16` ecc.)
- [x] **Pulsante Fullscreen** nell'header di tutte le schermate (Fullscreen API)
### 13. Comandi Dev
- [x] `./dev.sh check` — Type-check TypeScript standalone (`tsc --noEmit`)
- [x] `npm run check` — Script npm corrispondente
---
## ✅ FRONTEND COMPLETATO
+13
View File
@@ -346,6 +346,15 @@ cmd_test_e2e_ui() {
npm run test:e2e:ui
}
# Type-check frontend (senza build)
cmd_check() {
check_prereqs
info "Type-check frontend (tsc --noEmit)..."
cd "$FRONTEND_DIR"
npm run check
success "Type-check completato senza errori!"
}
# Help
cmd_help() {
echo "============================================"
@@ -359,6 +368,7 @@ cmd_help() {
echo " dev Avvia frontend (dev) + backend (api-only) in parallelo"
echo " build Builda il frontend per produzione"
echo " build:debug Builda il frontend in modalità DEBUG (no minify, sourcemap)"
echo " check Type-check TypeScript (senza build)"
echo " server Builda frontend (se cambiato) e avvia server completo"
echo " server:debug Builda DEBUG + avvia server (per debug con Chrome DevTools)"
echo " backend Avvia solo il backend (api-only)"
@@ -401,6 +411,9 @@ case "${1:-help}" in
"build:debug")
cmd_build_debug
;;
check)
cmd_check
;;
server)
shift
cmd_server "$@"
+1
View File
@@ -6,6 +6,7 @@
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"check": "tsc --noEmit",
"lint": "eslint .",
"preview": "vite preview",
"test": "vitest",
@@ -0,0 +1,52 @@
/**
* Fullscreen Toggle Button - Focolari Voting System
*/
import {useCallback, useEffect, useState} from 'react';
export function FullscreenButton() {
const [isFullscreen, setIsFullscreen] = useState(false);
useEffect(() => {
const handleChange = () => {
setIsFullscreen(!!document.fullscreenElement);
};
document.addEventListener('fullscreenchange', handleChange);
return () => document.removeEventListener('fullscreenchange', handleChange);
}, []);
const toggleFullscreen = useCallback(() => {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen().catch((err) => {
console.warn('[UI] Fullscreen non disponibile:', err);
});
} else {
document.exitFullscreen();
}
}, []);
return (
<button
onClick={toggleFullscreen}
className="w-9 h-9 flex items-center justify-center rounded-lg text-gray-500 hover:text-focolare-blue hover:bg-focolare-blue/10 transition-colors"
title={isFullscreen ? 'Esci da schermo intero' : 'Schermo intero'}
>
{isFullscreen ? (
// Exit fullscreen icon
<svg className="w-5 h-5 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round"
d="M9 9V4.5M9 9H4.5M9 9L3.75 3.75M9 15v4.5M9 15H4.5M9 15l-5.25 5.25M15 9h4.5M15 9V4.5M15 9l5.25-5.25M15 15h4.5M15 15v4.5m0-4.5l5.25 5.25"/>
</svg>
) : (
// Enter fullscreen icon
<svg className="w-5 h-5 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round"
d="M3.75 3.75v4.5m0-4.5h4.5m-4.5 0L9 9M3.75 20.25v-4.5m0 4.5h4.5m-4.5 0L9 15M20.25 3.75h-4.5m4.5 0v4.5m0-4.5L15 9m5.25 11.25h-4.5m4.5 0v-4.5m0 4.5L15 15"/>
</svg>
)}
</button>
);
}
export default FullscreenButton;
+10 -10
View File
@@ -47,13 +47,13 @@ export function UserCard({user, size = 'full'}: UserCardProps) {
}
return (
<div className={`rounded-2xl border-4 p-6 ${statusClass} animate-slide-up`}>
<div className={`rounded-2xl border-4 p-4 ${statusClass} animate-slide-up`}>
{/* Foto e Dati Principali */}
<div className="flex flex-col items-center gap-4 md:flex-row md:items-start md:gap-6">
<div className="flex flex-col items-center gap-3 md:flex-row md:items-start md:gap-5">
<img
src={user.url_foto}
alt={`${user.nome} ${user.cognome}`}
className="h-32 w-32 rounded-2xl object-cover shadow-lg md:h-40 md:w-40"
className="h-24 w-24 rounded-2xl object-cover shadow-lg md:h-28 md:w-28"
onError={(e) => {
(e.target as HTMLImageElement).src =
'https://via.placeholder.com/200?text=' + user.nome.charAt(0);
@@ -61,16 +61,16 @@ export function UserCard({user, size = 'full'}: UserCardProps) {
/>
<div className="flex flex-col items-center text-center md:items-start md:text-left">
<h2 className="text-3xl font-bold text-gray-800 md:text-4xl">
<h2 className="text-2xl font-bold text-gray-800 md:text-3xl">
{user.nome} {user.cognome}
</h2>
<div className="mt-3 flex flex-wrap gap-2">
<span className={`px-4 py-2 text-lg font-semibold rounded-full ${roleColors[user.ruolo]}`}>
<div className="mt-2 flex flex-wrap gap-2">
<span className={`px-3 py-1.5 text-base font-semibold rounded-full ${roleColors[user.ruolo]}`}>
{user.ruolo}
</span>
<span className={`px-4 py-2 text-lg font-semibold rounded-full ${
<span className={`px-3 py-1.5 text-base font-semibold rounded-full ${
user.ammesso
? 'bg-success text-white'
: 'bg-error text-white animate-blink'
@@ -79,7 +79,7 @@ export function UserCard({user, size = 'full'}: UserCardProps) {
</span>
</div>
<p className="mt-3 text-gray-500">
<p className="mt-2 text-gray-500 text-sm">
Badge: <span className="font-mono font-semibold">{user.badge_code}</span>
</p>
</div>
@@ -87,8 +87,8 @@ export function UserCard({user, size = 'full'}: UserCardProps) {
{/* Warning Box */}
{user.warning && (
<div className="mt-4 rounded-xl bg-error/20 border-2 border-error p-4">
<p className="text-lg font-bold text-error text-center">
<div className="mt-3 rounded-xl bg-error/20 border-2 border-error p-3">
<p className="text-base font-bold text-error text-center">
{user.warning}
</p>
</div>
+1
View File
@@ -8,3 +8,4 @@ export {Button} from './Button';
export {Input} from './Input';
export {WelcomeCarousel} from './WelcomeCarousel';
export {NumLockBanner} from './NumLockBanner';
export {FullscreenButton} from './FullscreenButton';
+49 -48
View File
@@ -3,7 +3,7 @@
* Schermata principale del varco attivo
*/
import {Button, CountdownTimer, Logo, NumLockBanner, RFIDStatus, UserCard} from '../components';
import {Button, CountdownTimer, FullscreenButton, Logo, NumLockBanner, RFIDStatus, UserCard} from '../components';
import type {RFIDScannerState, RoomInfo, User} from '../types';
// Timeout per badge non trovato (30 secondi)
@@ -39,16 +39,17 @@ export function ActiveGateScreen({
showValidatorBadgeNotice = false,
}: ActiveGateScreenProps) {
return (
<div className="min-h-screen flex flex-col bg-gradient-to-br from-slate-50 to-slate-100">
<div className="h-screen flex flex-col bg-gradient-to-br from-slate-50 to-slate-100 overflow-hidden">
{/* Header */}
<header
className="p-4 md:p-6 flex items-center justify-between border-b bg-white/80 backdrop-blur shadow-sm">
<Logo size="md"/>
<div className="flex items-center gap-4 md:gap-8">
className="p-3 flex items-center justify-between border-b bg-white/80 backdrop-blur shadow-sm">
<Logo size="sm"/>
<div className="flex items-center gap-3">
<div className="text-right hidden sm:block">
<p className="text-lg font-semibold text-gray-800">{roomInfo.room_name}</p>
<p className="text-sm text-gray-500">ID: {roomInfo.meeting_id}</p>
<p className="text-base font-semibold text-gray-800">{roomInfo.room_name}</p>
<p className="text-xs text-gray-500">ID: {roomInfo.meeting_id}</p>
</div>
<FullscreenButton/>
<Button
variant="secondary"
size="sm"
@@ -61,23 +62,23 @@ export function ActiveGateScreen({
{/* Notifica Badge Validatore Ignorato */}
{showValidatorBadgeNotice && (
<div className="absolute top-20 left-1/2 -translate-x-1/2 z-50 animate-fade-in">
<div className="bg-amber-100 border border-amber-400 text-amber-800 px-6 py-3 rounded-xl shadow-lg">
<p className="font-semibold">Badge validatore rilevato</p>
<p className="text-sm">Se il validatore è cambiato, esci e rilogga con il nuovo badge.</p>
<div className="absolute top-14 left-1/2 -translate-x-1/2 z-50 animate-fade-in">
<div className="bg-amber-100 border border-amber-400 text-amber-800 px-4 py-2 rounded-xl shadow-lg">
<p className="font-semibold text-sm">Badge validatore rilevato</p>
<p className="text-xs">Se il validatore è cambiato, esci e rilogga con il nuovo badge.</p>
</div>
</div>
)}
{/* Main Content */}
<main className="flex-1 flex items-center justify-center p-4 md:p-8">
<main className="flex-1 flex items-center justify-center p-3">
{loading ? (
// Loading state
<div className="glass rounded-3xl p-12 shadow-xl animate-fade-in text-center">
<div className="glass rounded-2xl p-8 shadow-xl animate-fade-in text-center">
<div
className="inline-flex items-center justify-center w-20 h-20 rounded-full bg-focolare-blue/10 mb-6">
className="inline-flex items-center justify-center w-14 h-14 rounded-full bg-focolare-blue/10 mb-4">
<svg
className="animate-spin h-10 w-10 text-focolare-blue"
className="animate-spin h-8 w-8 text-focolare-blue"
viewBox="0 0 24 24"
>
<circle
@@ -96,13 +97,13 @@ export function ActiveGateScreen({
/>
</svg>
</div>
<p className="text-xl text-gray-600">Caricamento dati...</p>
<p className="text-lg text-gray-600">Caricamento dati...</p>
</div>
) : notFoundBadge ? (
// Badge non trovato
<div className="glass rounded-3xl p-12 md:p-16 shadow-xl text-center max-w-2xl w-full">
<div className="glass rounded-2xl p-6 md:p-8 shadow-xl text-center max-w-2xl w-full">
{/* Timer bar in alto come per l'utente trovato */}
<div className="mb-8">
<div className="mb-4">
<CountdownTimer
key={`not-found-${notFoundBadge}`}
seconds={NOT_FOUND_TIMEOUT_SECONDS}
@@ -113,9 +114,9 @@ export function ActiveGateScreen({
</div>
<div
className="inline-flex items-center justify-center w-32 h-32 rounded-full bg-error/10 mb-8">
className="inline-flex items-center justify-center w-20 h-20 rounded-full bg-error/10 mb-4">
<svg
className="h-16 w-16 text-error"
className="h-10 w-10 text-error"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
@@ -128,9 +129,9 @@ export function ActiveGateScreen({
/>
</svg>
</div>
<p className="text-2xl text-gray-700 mb-2">Utente con badge:</p>
<p className="text-3xl font-bold text-error font-mono mb-4">{notFoundBadge}</p>
<p className="text-2xl text-gray-700 mb-8">non trovato nel sistema</p>
<p className="text-xl text-gray-700 mb-1">Utente con badge:</p>
<p className="text-2xl font-bold text-error font-mono mb-2">{notFoundBadge}</p>
<p className="text-xl text-gray-700 mb-4">non trovato nel sistema</p>
{/* Cancel Button */}
<Button
@@ -143,11 +144,11 @@ export function ActiveGateScreen({
</div>
) : error ? (
// Error state
<div className="glass rounded-3xl p-12 shadow-xl text-center max-w-md">
<div className="glass rounded-2xl p-8 shadow-xl text-center max-w-md">
<div
className="inline-flex items-center justify-center w-20 h-20 rounded-full bg-error/10 mb-6">
className="inline-flex items-center justify-center w-14 h-14 rounded-full bg-error/10 mb-4">
<svg
className="h-10 w-10 text-error"
className="h-8 w-8 text-error"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
@@ -160,7 +161,7 @@ export function ActiveGateScreen({
/>
</svg>
</div>
<p className="text-xl text-error font-semibold mb-4">{error}</p>
<p className="text-lg text-error font-semibold mb-3">{error}</p>
<Button variant="secondary" onClick={onCancelUser}>
Chiudi
</Button>
@@ -168,9 +169,9 @@ export function ActiveGateScreen({
) : currentUser ? (
// User found - Decision screen
<div className="w-full max-w-5xl animate-slide-up">
<div className="glass rounded-3xl p-6 md:p-10 shadow-xl">
<div className="glass rounded-2xl p-4 md:p-6 shadow-xl">
{/* Timer bar */}
<div className="mb-6">
<div className="mb-3">
<CountdownTimer
seconds={userTimeoutSeconds}
onExpire={onUserTimeout}
@@ -183,24 +184,24 @@ export function ActiveGateScreen({
<UserCard user={currentUser} size="full"/>
{/* Action Hint */}
<div className="mt-8 text-center">
<div className="mt-4 text-center">
{currentUser.ammesso ? (
<div className="py-6 px-8 bg-success/10 rounded-2xl border-2 border-success/30">
<p className="text-xl text-success font-semibold mb-2">
<div className="py-3 px-6 bg-success/10 rounded-2xl border-2 border-success/30">
<p className="text-lg text-success font-semibold mb-1">
Utente ammesso all'ingresso
</p>
<p className="text-lg text-gray-600">
<p className="text-base text-gray-600">
Passa il <span
className="font-bold text-focolare-blue">badge VALIDATORE</span> per
confermare l'accesso
</p>
</div>
) : (
<div className="py-6 px-8 bg-error/10 rounded-2xl border-2 border-error/30">
<p className="text-xl text-error font-bold mb-2 animate-blink">
<div className="py-3 px-6 bg-error/10 rounded-2xl border-2 border-error/30">
<p className="text-lg text-error font-bold mb-1 animate-blink">
ACCESSO NON CONSENTITO
</p>
<p className="text-lg text-gray-600">
<p className="text-base text-gray-600">
Questo utente non è autorizzato ad entrare
</p>
</div>
@@ -208,7 +209,7 @@ export function ActiveGateScreen({
</div>
{/* Cancel Button */}
<div className="mt-6 flex justify-center">
<div className="mt-3 flex justify-center">
<Button
variant="secondary"
size="lg"
@@ -222,11 +223,11 @@ export function ActiveGateScreen({
) : (
// Idle - Waiting for participant
<div
className="glass rounded-3xl p-12 md:p-16 shadow-xl animate-fade-in text-center max-w-3xl w-full">
className="glass rounded-2xl p-6 md:p-8 shadow-xl animate-fade-in text-center max-w-3xl w-full">
<div
className="inline-flex items-center justify-center w-40 h-40 rounded-full bg-focolare-blue/10 mb-10">
className="inline-flex items-center justify-center w-24 h-24 rounded-full bg-focolare-blue/10 mb-4">
<svg
className="w-20 h-20 text-focolare-blue"
className="w-12 h-12 text-focolare-blue"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
@@ -240,39 +241,39 @@ export function ActiveGateScreen({
</svg>
</div>
<h1 className="text-5xl font-bold text-focolare-blue mb-6">
<h1 className="text-4xl font-bold text-focolare-blue mb-3">
Varco Attivo
</h1>
<p className="text-3xl text-gray-600 mb-10">
<p className="text-2xl text-gray-600 mb-4">
In attesa del partecipante...
</p>
<div
className="py-10 px-8 bg-focolare-orange/10 rounded-2xl border-2 border-dashed border-focolare-orange/40">
className="py-5 px-6 bg-focolare-orange/10 rounded-2xl border-2 border-dashed border-focolare-orange/40">
<div className="flex items-center justify-center gap-4">
<svg
className="w-12 h-12 text-focolare-orange animate-pulse"
className="w-10 h-10 text-focolare-orange animate-pulse"
fill="currentColor"
viewBox="0 0 24 24"
>
<path
d="M20 4H4c-1.11 0-1.99.89-1.99 2L2 18c0 1.11.89 2 2 2h16c1.11 0 2-.89 2-2V6c0-1.11-.89-2-2-2zm0 14H4v-6h16v6zm0-10H4V6h16v2z"/>
</svg>
<span className="text-3xl text-focolare-orange font-medium">
<span className="text-2xl text-focolare-orange font-medium">
Passa il badge
</span>
</div>
</div>
{/* Banner NumLock per desktop */}
<NumLockBanner className="mt-8"/>
<NumLockBanner className="mt-4"/>
</div>
)}
</main>
{/* Footer with RFID Status */}
<footer className="p-4 border-t bg-white/50 flex items-center justify-between">
<footer className="p-2 border-t bg-white/50 flex items-center justify-between">
<div className="flex items-center gap-4">
<RFIDStatus state={rfidState} buffer={rfidBuffer}/>
<a
@@ -283,7 +284,7 @@ export function ActiveGateScreen({
Debug
</a>
</div>
<span className="text-sm text-gray-400">
<span className="text-xs text-gray-400">
Varco attivo {new Date().toLocaleTimeString('it-IT')}
</span>
</footer>
+11 -11
View File
@@ -61,21 +61,21 @@ export function LoadingScreen({
return (
<div
className="min-h-screen flex flex-col items-center justify-center p-8 bg-gradient-to-br from-focolare-blue/5 to-focolare-blue/20">
<div className="glass rounded-3xl p-12 shadow-xl animate-fade-in max-w-lg w-full text-center">
<Logo size="lg" showText={false}/>
className="h-screen flex flex-col items-center justify-center p-4 bg-gradient-to-br from-focolare-blue/5 to-focolare-blue/20 overflow-hidden">
<div className="glass rounded-2xl p-8 shadow-xl animate-fade-in max-w-lg w-full text-center">
<Logo size="md" showText={false}/>
<h1 className="mt-6 text-3xl font-bold text-focolare-blue">
<h1 className="mt-4 text-2xl font-bold text-focolare-blue">
Focolari Voting System
</h1>
{!error ? (
<>
<div className="mt-8">
<div className="mt-4">
<div
className="inline-flex items-center justify-center w-16 h-16 rounded-full bg-focolare-blue/10">
className="inline-flex items-center justify-center w-14 h-14 rounded-full bg-focolare-blue/10">
<svg
className="animate-spin h-8 w-8 text-focolare-blue"
className="animate-spin h-7 w-7 text-focolare-blue"
viewBox="0 0 24 24"
>
<circle
@@ -99,10 +99,10 @@ export function LoadingScreen({
</>
) : (
<>
<div className="mt-8">
<div className="inline-flex items-center justify-center w-16 h-16 rounded-full bg-error/10">
<div className="mt-4">
<div className="inline-flex items-center justify-center w-14 h-14 rounded-full bg-error/10">
<svg
className="h-8 w-8 text-error"
className="h-7 w-7 text-error"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
@@ -137,7 +137,7 @@ export function LoadingScreen({
<button
onClick={handleRetry}
disabled={isRetrying}
className="mt-6 px-8 py-3 bg-focolare-blue text-white rounded-xl
className="mt-4 px-6 py-2 bg-focolare-blue text-white rounded-xl
font-semibold hover:bg-focolare-blue/90 transition-colors
disabled:opacity-50 disabled:cursor-not-allowed"
>
+29 -26
View File
@@ -3,7 +3,7 @@
*/
import {useEffect, useRef, useState} from 'react';
import {Button, Input, Logo, NumLockBanner, RFIDStatus} from '../components';
import {Button, FullscreenButton, Input, Logo, NumLockBanner, RFIDStatus} from '../components';
import type {RFIDScannerState, RoomInfo} from '../types';
interface ValidatorLoginScreenProps {
@@ -45,27 +45,30 @@ export function ValidatorLoginScreen({
};
return (
<div className="min-h-screen flex flex-col bg-gradient-to-br from-slate-50 to-slate-100">
<div className="h-screen flex flex-col bg-gradient-to-br from-slate-50 to-slate-100 overflow-hidden">
{/* Header */}
<header className="p-6 flex items-center justify-between border-b bg-white/80 backdrop-blur shadow-sm">
<Logo size="md"/>
<div className="text-right">
<p className="text-lg font-semibold text-gray-800">{roomInfo.room_name}</p>
<p className="text-sm text-gray-500">ID: {roomInfo.meeting_id}</p>
<header className="p-3 flex items-center justify-between border-b bg-white/80 backdrop-blur shadow-sm">
<Logo size="sm"/>
<div className="flex items-center gap-3">
<div className="text-right">
<p className="text-base font-semibold text-gray-800">{roomInfo.room_name}</p>
<p className="text-xs text-gray-500">ID: {roomInfo.meeting_id}</p>
</div>
<FullscreenButton/>
</div>
</header>
{/* Main Content */}
<main className="flex-1 flex items-center justify-center p-8">
<div className="glass rounded-3xl p-10 shadow-xl max-w-xl w-full animate-slide-up">
<main className="flex-1 flex items-center justify-center p-4">
<div className="glass rounded-2xl p-6 shadow-xl max-w-xl w-full animate-slide-up">
{!validatorBadge ? (
// Attesa badge validatore
<>
<div className="text-center">
<div
className="inline-flex items-center justify-center w-24 h-24 rounded-full bg-focolare-blue/10 mb-6">
className="inline-flex items-center justify-center w-16 h-16 rounded-full bg-focolare-blue/10 mb-3">
<svg
className="w-12 h-12 text-focolare-blue"
className="w-8 h-8 text-focolare-blue"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
@@ -79,38 +82,38 @@ export function ValidatorLoginScreen({
</svg>
</div>
<h1 className="text-3xl font-bold text-gray-800 mb-4">
<h1 className="text-2xl font-bold text-gray-800 mb-2">
Accesso Varco
</h1>
<p className="text-xl text-gray-600 mb-8">
<p className="text-lg text-gray-600 mb-4">
Passa il badge del <span
className="font-semibold text-focolare-blue">Validatore</span> per iniziare
</p>
<div
className="py-8 px-6 bg-focolare-blue/5 rounded-2xl border-2 border-dashed border-focolare-blue/30">
className="py-4 px-4 bg-focolare-blue/5 rounded-2xl border-2 border-dashed border-focolare-blue/30">
<div className="flex items-center justify-center gap-3">
<svg
className="w-8 h-8 text-focolare-blue animate-pulse"
className="w-7 h-7 text-focolare-blue animate-pulse"
fill="currentColor"
viewBox="0 0 24 24"
>
<path
d="M20 4H4c-1.11 0-1.99.89-1.99 2L2 18c0 1.11.89 2 2 2h16c1.11 0 2-.89 2-2V6c0-1.11-.89-2-2-2zm0 14H4v-6h16v6zm0-10H4V6h16v2z"/>
</svg>
<span className="text-xl text-focolare-blue font-medium">
<span className="text-lg text-focolare-blue font-medium">
In attesa del badge...
</span>
</div>
</div>
{/* Banner NumLock per desktop */}
<NumLockBanner className="mt-6"/>
<NumLockBanner className="mt-3"/>
{/* Messaggio errore */}
{error && (
<div className="mt-6 p-4 bg-red-50 border border-red-200 rounded-lg">
<div className="mt-3 p-3 bg-red-50 border border-red-200 rounded-lg">
<p className="text-red-700">{error}</p>
</div>
)}
@@ -119,11 +122,11 @@ export function ValidatorLoginScreen({
) : (
// Form password
<>
<div className="text-center mb-8">
<div className="text-center mb-4">
<div
className="inline-flex items-center justify-center w-20 h-20 rounded-full bg-success/10 mb-4">
className="inline-flex items-center justify-center w-14 h-14 rounded-full bg-success/10 mb-3">
<svg
className="w-10 h-10 text-success"
className="w-8 h-8 text-success"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
@@ -137,10 +140,10 @@ export function ValidatorLoginScreen({
</svg>
</div>
<h2 className="text-2xl font-bold text-gray-800 mb-2">
<h2 className="text-xl font-bold text-gray-800 mb-1">
Badge Riconosciuto
</h2>
<p className="text-gray-500">
<p className="text-gray-500 text-sm">
Badge: <span className="font-mono font-semibold">{validatorBadge}</span>
</p>
</div>
@@ -158,7 +161,7 @@ export function ValidatorLoginScreen({
disabled={loading}
/>
<div className="mt-6 flex gap-4">
<div className="mt-4 flex gap-4">
<Button
type="button"
variant="secondary"
@@ -184,7 +187,7 @@ export function ValidatorLoginScreen({
</main>
{/* Footer with RFID Status */}
<footer className="p-4 border-t bg-white/50 flex items-center justify-between">
<footer className="p-2 border-t bg-white/50 flex items-center justify-between">
<div className="flex items-center gap-4">
<RFIDStatus state={rfidState} buffer={rfidBuffer}/>
<a
@@ -195,7 +198,7 @@ export function ValidatorLoginScreen({
Debug
</a>
</div>
<span className="text-sm text-gray-400">
<span className="text-xs text-gray-400">
{new Date().toLocaleTimeString('it-IT')}
</span>
</footer>