feat: add fullscreen toggle button and improve UI consistency across components
This commit is contained in:
@@ -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
|
|
||||||
|
|||||||
@@ -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 "$@"
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -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="text-right">
|
<div className="flex items-center gap-3">
|
||||||
<p className="text-lg font-semibold text-gray-800">{roomInfo.room_name}</p>
|
<div className="text-right">
|
||||||
<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/>
|
||||||
</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>
|
||||||
|
|||||||
Reference in New Issue
Block a user