Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f1ae77a36 | ||
|
|
c91c4016ec |
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 "$@"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -8,3 +8,4 @@ export {Button} from './Button';
|
||||
export {Input} from './Input';
|
||||
export {WelcomeCarousel} from './WelcomeCarousel';
|
||||
export {NumLockBanner} from './NumLockBanner';
|
||||
export {FullscreenButton} from './FullscreenButton';
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user