feat: add fullscreen toggle button and improve UI consistency across components

This commit is contained in:
EmanueleAlfano
2026-02-28 09:52:53 +01:00
parent f8283a0e89
commit c91c4016ec
9 changed files with 166 additions and 102 deletions
-7
View File
@@ -149,10 +149,3 @@ VotoFocolari/
6. **Success Modal Interrompibile**: Se durante il carosello di benvenuto si passa un nuovo badge, la modal si chiude e 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. 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
+13
View File
@@ -346,6 +346,15 @@ cmd_test_e2e_ui() {
npm run 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 # Help
cmd_help() { cmd_help() {
echo "============================================" echo "============================================"
@@ -359,6 +368,7 @@ cmd_help() {
echo " dev Avvia frontend (dev) + backend (api-only) in parallelo" echo " dev Avvia frontend (dev) + backend (api-only) in parallelo"
echo " build Builda il frontend per produzione" echo " build Builda il frontend per produzione"
echo " build:debug Builda il frontend in modalità DEBUG (no minify, sourcemap)" 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 Builda frontend (se cambiato) e avvia server completo"
echo " server:debug Builda DEBUG + avvia server (per debug con Chrome DevTools)" echo " server:debug Builda DEBUG + avvia server (per debug con Chrome DevTools)"
echo " backend Avvia solo il backend (api-only)" echo " backend Avvia solo il backend (api-only)"
@@ -401,6 +411,9 @@ case "${1:-help}" in
"build:debug") "build:debug")
cmd_build_debug cmd_build_debug
;; ;;
check)
cmd_check
;;
server) server)
shift shift
cmd_server "$@" cmd_server "$@"
+1
View File
@@ -6,6 +6,7 @@
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "tsc -b && vite build", "build": "tsc -b && vite build",
"check": "tsc --noEmit",
"lint": "eslint .", "lint": "eslint .",
"preview": "vite preview", "preview": "vite preview",
"test": "vitest", "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="p-2 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" 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" 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 ( 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 */} {/* 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 <img
src={user.url_foto} src={user.url_foto}
alt={`${user.nome} ${user.cognome}`} 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) => { onError={(e) => {
(e.target as HTMLImageElement).src = (e.target as HTMLImageElement).src =
'https://via.placeholder.com/200?text=' + user.nome.charAt(0); '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"> <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} {user.nome} {user.cognome}
</h2> </h2>
<div className="mt-3 flex flex-wrap gap-2"> <div className="mt-2 flex flex-wrap gap-2">
<span className={`px-4 py-2 text-lg font-semibold rounded-full ${roleColors[user.ruolo]}`}> <span className={`px-3 py-1.5 text-base font-semibold rounded-full ${roleColors[user.ruolo]}`}>
{user.ruolo} {user.ruolo}
</span> </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 user.ammesso
? 'bg-success text-white' ? 'bg-success text-white'
: 'bg-error text-white animate-blink' : 'bg-error text-white animate-blink'
@@ -79,7 +79,7 @@ export function UserCard({user, size = 'full'}: UserCardProps) {
</span> </span>
</div> </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> Badge: <span className="font-mono font-semibold">{user.badge_code}</span>
</p> </p>
</div> </div>
@@ -87,8 +87,8 @@ export function UserCard({user, size = 'full'}: UserCardProps) {
{/* Warning Box */} {/* Warning Box */}
{user.warning && ( {user.warning && (
<div className="mt-4 rounded-xl bg-error/20 border-2 border-error p-4"> <div className="mt-3 rounded-xl bg-error/20 border-2 border-error p-3">
<p className="text-lg font-bold text-error text-center"> <p className="text-base font-bold text-error text-center">
{user.warning} {user.warning}
</p> </p>
</div> </div>
+1
View File
@@ -8,3 +8,4 @@ export {Button} from './Button';
export {Input} from './Input'; export {Input} from './Input';
export {WelcomeCarousel} from './WelcomeCarousel'; export {WelcomeCarousel} from './WelcomeCarousel';
export {NumLockBanner} from './NumLockBanner'; export {NumLockBanner} from './NumLockBanner';
export {FullscreenButton} from './FullscreenButton';
+49 -48
View File
@@ -3,7 +3,7 @@
* Schermata principale del varco attivo * 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'; import type {RFIDScannerState, RoomInfo, User} from '../types';
// Timeout per badge non trovato (30 secondi) // Timeout per badge non trovato (30 secondi)
@@ -39,16 +39,17 @@ export function ActiveGateScreen({
showValidatorBadgeNotice = false, showValidatorBadgeNotice = false,
}: ActiveGateScreenProps) { }: ActiveGateScreenProps) {
return ( 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 */}
<header <header
className="p-4 md:p-6 flex items-center justify-between border-b bg-white/80 backdrop-blur shadow-sm"> className="p-3 flex items-center justify-between border-b bg-white/80 backdrop-blur shadow-sm">
<Logo size="md"/> <Logo size="sm"/>
<div className="flex items-center gap-4 md:gap-8"> <div className="flex items-center gap-3">
<div className="text-right hidden sm:block"> <div className="text-right hidden sm:block">
<p className="text-lg font-semibold text-gray-800">{roomInfo.room_name}</p> <p className="text-base font-semibold text-gray-800">{roomInfo.room_name}</p>
<p className="text-sm text-gray-500">ID: {roomInfo.meeting_id}</p> <p className="text-xs text-gray-500">ID: {roomInfo.meeting_id}</p>
</div> </div>
<FullscreenButton/>
<Button <Button
variant="secondary" variant="secondary"
size="sm" size="sm"
@@ -61,23 +62,23 @@ export function ActiveGateScreen({
{/* Notifica Badge Validatore Ignorato */} {/* Notifica Badge Validatore Ignorato */}
{showValidatorBadgeNotice && ( {showValidatorBadgeNotice && (
<div className="absolute top-20 left-1/2 -translate-x-1/2 z-50 animate-fade-in"> <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-6 py-3 rounded-xl shadow-lg"> <div className="bg-amber-100 border border-amber-400 text-amber-800 px-4 py-2 rounded-xl shadow-lg">
<p className="font-semibold">Badge validatore rilevato</p> <p className="font-semibold text-sm">Badge validatore rilevato</p>
<p className="text-sm">Se il validatore è cambiato, esci e rilogga con il nuovo badge.</p> <p className="text-xs">Se il validatore è cambiato, esci e rilogga con il nuovo badge.</p>
</div> </div>
</div> </div>
)} )}
{/* Main Content */} {/* 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 ? (
// Loading state // 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 <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 <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" viewBox="0 0 24 24"
> >
<circle <circle
@@ -96,13 +97,13 @@ export function ActiveGateScreen({
/> />
</svg> </svg>
</div> </div>
<p className="text-xl text-gray-600">Caricamento dati...</p> <p className="text-lg text-gray-600">Caricamento dati...</p>
</div> </div>
) : notFoundBadge ? ( ) : notFoundBadge ? (
// Badge non trovato // 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 */} {/* Timer bar in alto come per l'utente trovato */}
<div className="mb-8"> <div className="mb-4">
<CountdownTimer <CountdownTimer
key={`not-found-${notFoundBadge}`} key={`not-found-${notFoundBadge}`}
seconds={NOT_FOUND_TIMEOUT_SECONDS} seconds={NOT_FOUND_TIMEOUT_SECONDS}
@@ -113,9 +114,9 @@ export function ActiveGateScreen({
</div> </div>
<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 <svg
className="h-16 w-16 text-error" className="h-10 w-10 text-error"
fill="none" fill="none"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke="currentColor" stroke="currentColor"
@@ -128,9 +129,9 @@ export function ActiveGateScreen({
/> />
</svg> </svg>
</div> </div>
<p className="text-2xl text-gray-700 mb-2">Utente con badge:</p> <p className="text-xl text-gray-700 mb-1">Utente con badge:</p>
<p className="text-3xl font-bold text-error font-mono mb-4">{notFoundBadge}</p> <p className="text-2xl font-bold text-error font-mono mb-2">{notFoundBadge}</p>
<p className="text-2xl text-gray-700 mb-8">non trovato nel sistema</p> <p className="text-xl text-gray-700 mb-4">non trovato nel sistema</p>
{/* Cancel Button */} {/* Cancel Button */}
<Button <Button
@@ -143,11 +144,11 @@ export function ActiveGateScreen({
</div> </div>
) : error ? ( ) : error ? (
// Error state // 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 <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 <svg
className="h-10 w-10 text-error" className="h-8 w-8 text-error"
fill="none" fill="none"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke="currentColor" stroke="currentColor"
@@ -160,7 +161,7 @@ export function ActiveGateScreen({
/> />
</svg> </svg>
</div> </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}> <Button variant="secondary" onClick={onCancelUser}>
Chiudi Chiudi
</Button> </Button>
@@ -168,9 +169,9 @@ export function ActiveGateScreen({
) : currentUser ? ( ) : currentUser ? (
// User found - Decision screen // User found - Decision screen
<div className="w-full max-w-5xl animate-slide-up"> <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 */} {/* Timer bar */}
<div className="mb-6"> <div className="mb-3">
<CountdownTimer <CountdownTimer
seconds={userTimeoutSeconds} seconds={userTimeoutSeconds}
onExpire={onUserTimeout} onExpire={onUserTimeout}
@@ -183,24 +184,24 @@ export function ActiveGateScreen({
<UserCard user={currentUser} size="full"/> <UserCard user={currentUser} size="full"/>
{/* Action Hint */} {/* Action Hint */}
<div className="mt-8 text-center"> <div className="mt-4 text-center">
{currentUser.ammesso ? ( {currentUser.ammesso ? (
<div className="py-6 px-8 bg-success/10 rounded-2xl border-2 border-success/30"> <div className="py-3 px-6 bg-success/10 rounded-2xl border-2 border-success/30">
<p className="text-xl text-success font-semibold mb-2"> <p className="text-lg text-success font-semibold mb-1">
Utente ammesso all'ingresso Utente ammesso all'ingresso
</p> </p>
<p className="text-lg text-gray-600"> <p className="text-base text-gray-600">
Passa il <span Passa il <span
className="font-bold text-focolare-blue">badge VALIDATORE</span> per className="font-bold text-focolare-blue">badge VALIDATORE</span> per
confermare l'accesso confermare l'accesso
</p> </p>
</div> </div>
) : ( ) : (
<div className="py-6 px-8 bg-error/10 rounded-2xl border-2 border-error/30"> <div className="py-3 px-6 bg-error/10 rounded-2xl border-2 border-error/30">
<p className="text-xl text-error font-bold mb-2 animate-blink"> <p className="text-lg text-error font-bold mb-1 animate-blink">
ACCESSO NON CONSENTITO ACCESSO NON CONSENTITO
</p> </p>
<p className="text-lg text-gray-600"> <p className="text-base text-gray-600">
Questo utente non è autorizzato ad entrare Questo utente non è autorizzato ad entrare
</p> </p>
</div> </div>
@@ -208,7 +209,7 @@ export function ActiveGateScreen({
</div> </div>
{/* Cancel Button */} {/* Cancel Button */}
<div className="mt-6 flex justify-center"> <div className="mt-3 flex justify-center">
<Button <Button
variant="secondary" variant="secondary"
size="lg" size="lg"
@@ -222,11 +223,11 @@ export function ActiveGateScreen({
) : ( ) : (
// Idle - Waiting for participant // Idle - Waiting for participant
<div <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 <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 <svg
className="w-20 h-20 text-focolare-blue" className="w-12 h-12 text-focolare-blue"
fill="none" fill="none"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke="currentColor" stroke="currentColor"
@@ -240,39 +241,39 @@ export function ActiveGateScreen({
</svg> </svg>
</div> </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 Varco Attivo
</h1> </h1>
<p className="text-3xl text-gray-600 mb-10"> <p className="text-2xl text-gray-600 mb-4">
In attesa del partecipante... In attesa del partecipante...
</p> </p>
<div <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"> <div className="flex items-center justify-center gap-4">
<svg <svg
className="w-12 h-12 text-focolare-orange animate-pulse" className="w-10 h-10 text-focolare-orange animate-pulse"
fill="currentColor" fill="currentColor"
viewBox="0 0 24 24" viewBox="0 0 24 24"
> >
<path <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"/> 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> </svg>
<span className="text-3xl text-focolare-orange font-medium"> <span className="text-2xl text-focolare-orange font-medium">
Passa il badge Passa il badge
</span> </span>
</div> </div>
</div> </div>
{/* Banner NumLock per desktop */} {/* Banner NumLock per desktop */}
<NumLockBanner className="mt-8"/> <NumLockBanner className="mt-4"/>
</div> </div>
)} )}
</main> </main>
{/* Footer with RFID Status */} {/* 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"> <div className="flex items-center gap-4">
<RFIDStatus state={rfidState} buffer={rfidBuffer}/> <RFIDStatus state={rfidState} buffer={rfidBuffer}/>
<a <a
@@ -283,7 +284,7 @@ export function ActiveGateScreen({
Debug Debug
</a> </a>
</div> </div>
<span className="text-sm text-gray-400"> <span className="text-xs text-gray-400">
Varco attivo {new Date().toLocaleTimeString('it-IT')} Varco attivo {new Date().toLocaleTimeString('it-IT')}
</span> </span>
</footer> </footer>
+11 -11
View File
@@ -61,21 +61,21 @@ export function LoadingScreen({
return ( return (
<div <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"> 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-3xl p-12 shadow-xl animate-fade-in max-w-lg w-full text-center"> <div className="glass rounded-2xl p-8 shadow-xl animate-fade-in max-w-lg w-full text-center">
<Logo size="lg" showText={false}/> <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 Focolari Voting System
</h1> </h1>
{!error ? ( {!error ? (
<> <>
<div className="mt-8"> <div className="mt-4">
<div <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 <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" viewBox="0 0 24 24"
> >
<circle <circle
@@ -99,10 +99,10 @@ export function LoadingScreen({
</> </>
) : ( ) : (
<> <>
<div className="mt-8"> <div className="mt-4">
<div className="inline-flex items-center justify-center w-16 h-16 rounded-full bg-error/10"> <div className="inline-flex items-center justify-center w-14 h-14 rounded-full bg-error/10">
<svg <svg
className="h-8 w-8 text-error" className="h-7 w-7 text-error"
fill="none" fill="none"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke="currentColor" stroke="currentColor"
@@ -137,7 +137,7 @@ export function LoadingScreen({
<button <button
onClick={handleRetry} onClick={handleRetry}
disabled={isRetrying} 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 font-semibold hover:bg-focolare-blue/90 transition-colors
disabled:opacity-50 disabled:cursor-not-allowed" disabled:opacity-50 disabled:cursor-not-allowed"
> >
+28 -25
View File
@@ -3,7 +3,7 @@
*/ */
import {useEffect, useRef, useState} from 'react'; 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'; import type {RFIDScannerState, RoomInfo} from '../types';
interface ValidatorLoginScreenProps { interface ValidatorLoginScreenProps {
@@ -45,27 +45,30 @@ export function ValidatorLoginScreen({
}; };
return ( 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 */}
<header className="p-6 flex items-center justify-between border-b bg-white/80 backdrop-blur shadow-sm"> <header className="p-3 flex items-center justify-between border-b bg-white/80 backdrop-blur shadow-sm">
<Logo size="md"/> <Logo size="sm"/>
<div className="flex items-center gap-3">
<div className="text-right"> <div className="text-right">
<p className="text-lg font-semibold text-gray-800">{roomInfo.room_name}</p> <p className="text-base font-semibold text-gray-800">{roomInfo.room_name}</p>
<p className="text-sm text-gray-500">ID: {roomInfo.meeting_id}</p> <p className="text-xs text-gray-500">ID: {roomInfo.meeting_id}</p>
</div>
<FullscreenButton/>
</div> </div>
</header> </header>
{/* Main Content */} {/* Main Content */}
<main className="flex-1 flex items-center justify-center p-8"> <main className="flex-1 flex items-center justify-center p-4">
<div className="glass rounded-3xl p-10 shadow-xl max-w-xl w-full animate-slide-up"> <div className="glass rounded-2xl p-6 shadow-xl max-w-xl w-full animate-slide-up">
{!validatorBadge ? ( {!validatorBadge ? (
// Attesa badge validatore // Attesa badge validatore
<> <>
<div className="text-center"> <div className="text-center">
<div <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 <svg
className="w-12 h-12 text-focolare-blue" className="w-8 h-8 text-focolare-blue"
fill="none" fill="none"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke="currentColor" stroke="currentColor"
@@ -79,38 +82,38 @@ export function ValidatorLoginScreen({
</svg> </svg>
</div> </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 Accesso Varco
</h1> </h1>
<p className="text-xl text-gray-600 mb-8"> <p className="text-lg text-gray-600 mb-4">
Passa il badge del <span Passa il badge del <span
className="font-semibold text-focolare-blue">Validatore</span> per iniziare className="font-semibold text-focolare-blue">Validatore</span> per iniziare
</p> </p>
<div <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"> <div className="flex items-center justify-center gap-3">
<svg <svg
className="w-8 h-8 text-focolare-blue animate-pulse" className="w-7 h-7 text-focolare-blue animate-pulse"
fill="currentColor" fill="currentColor"
viewBox="0 0 24 24" viewBox="0 0 24 24"
> >
<path <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"/> 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> </svg>
<span className="text-xl text-focolare-blue font-medium"> <span className="text-lg text-focolare-blue font-medium">
In attesa del badge... In attesa del badge...
</span> </span>
</div> </div>
</div> </div>
{/* Banner NumLock per desktop */} {/* Banner NumLock per desktop */}
<NumLockBanner className="mt-6"/> <NumLockBanner className="mt-3"/>
{/* Messaggio errore */} {/* Messaggio errore */}
{error && ( {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> <p className="text-red-700">{error}</p>
</div> </div>
)} )}
@@ -119,11 +122,11 @@ export function ValidatorLoginScreen({
) : ( ) : (
// Form password // Form password
<> <>
<div className="text-center mb-8"> <div className="text-center mb-4">
<div <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 <svg
className="w-10 h-10 text-success" className="w-8 h-8 text-success"
fill="none" fill="none"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke="currentColor" stroke="currentColor"
@@ -137,10 +140,10 @@ export function ValidatorLoginScreen({
</svg> </svg>
</div> </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 Badge Riconosciuto
</h2> </h2>
<p className="text-gray-500"> <p className="text-gray-500 text-sm">
Badge: <span className="font-mono font-semibold">{validatorBadge}</span> Badge: <span className="font-mono font-semibold">{validatorBadge}</span>
</p> </p>
</div> </div>
@@ -158,7 +161,7 @@ export function ValidatorLoginScreen({
disabled={loading} disabled={loading}
/> />
<div className="mt-6 flex gap-4"> <div className="mt-4 flex gap-4">
<Button <Button
type="button" type="button"
variant="secondary" variant="secondary"
@@ -184,7 +187,7 @@ export function ValidatorLoginScreen({
</main> </main>
{/* Footer with RFID Status */} {/* 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"> <div className="flex items-center gap-4">
<RFIDStatus state={rfidState} buffer={rfidBuffer}/> <RFIDStatus state={rfidState} buffer={rfidBuffer}/>
<a <a
@@ -195,7 +198,7 @@ export function ValidatorLoginScreen({
Debug Debug
</a> </a>
</div> </div>
<span className="text-sm text-gray-400"> <span className="text-xs text-gray-400">
{new Date().toLocaleTimeString('it-IT')} {new Date().toLocaleTimeString('it-IT')}
</span> </span>
</footer> </footer>