mirror of
https://github.com/idrainformatica/PecFlow.git
synced 2026-06-17 05:05:44 +02:00
197 lines
7.7 KiB
TypeScript
197 lines
7.7 KiB
TypeScript
import { useState } from 'react'
|
||
import { useNavigate, Navigate } from 'react-router-dom'
|
||
import { useForm } from 'react-hook-form'
|
||
import { Mail, Lock, Eye, EyeOff, Shield } from 'lucide-react'
|
||
import { QRCodeSVG } from 'qrcode.react'
|
||
import toast from 'react-hot-toast'
|
||
import { Button } from '@/components/ui/Button'
|
||
import { Input } from '@/components/ui/Input'
|
||
import { Label } from '@/components/ui/Label'
|
||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/Card'
|
||
import { useAuthStore } from '@/store/auth.store'
|
||
import { getErrorMessage } from '@/api/client'
|
||
|
||
interface LoginFormValues {
|
||
email: string
|
||
password: string
|
||
totp_code?: string
|
||
}
|
||
|
||
type LoginStep = 'credentials' | 'totp'
|
||
|
||
export function LoginPage() {
|
||
const navigate = useNavigate()
|
||
const { isAuthenticated, login, isLoading } = useAuthStore()
|
||
const [step, setStep] = useState<LoginStep>('credentials')
|
||
const [showPassword, setShowPassword] = useState(false)
|
||
|
||
const {
|
||
register,
|
||
handleSubmit,
|
||
getValues,
|
||
formState: { errors },
|
||
} = useForm<LoginFormValues>()
|
||
|
||
// Se già autenticato, vai all'inbox
|
||
if (isAuthenticated) {
|
||
return <Navigate to="/inbox" replace />
|
||
}
|
||
|
||
const onSubmit = async (data: LoginFormValues) => {
|
||
try {
|
||
await login({
|
||
email: data.email,
|
||
password: data.password,
|
||
totp_code: data.totp_code,
|
||
})
|
||
toast.success('Accesso effettuato con successo')
|
||
navigate('/inbox', { replace: true })
|
||
} catch (error) {
|
||
const msg = getErrorMessage(error)
|
||
|
||
// Se l'errore indica che serve il TOTP, passa al secondo step
|
||
if (msg.toLowerCase().includes('totp') || msg.toLowerCase().includes('otp') || msg.toLowerCase().includes('2fa')) {
|
||
setStep('totp')
|
||
return
|
||
}
|
||
toast.error(msg)
|
||
}
|
||
}
|
||
|
||
return (
|
||
<div className="min-h-screen flex items-center justify-center bg-gray-50 px-4">
|
||
<div className="w-full max-w-md space-y-6">
|
||
{/* Logo */}
|
||
<div className="text-center">
|
||
<div className="mx-auto h-16 w-16 rounded-2xl bg-blue-600 flex items-center justify-center shadow-lg">
|
||
<span className="text-white font-bold text-2xl">PF</span>
|
||
</div>
|
||
<h1 className="mt-4 text-3xl font-bold text-gray-900">PecFlow</h1>
|
||
<p className="mt-1 text-sm text-gray-500">Gestore PEC SaaS</p>
|
||
</div>
|
||
|
||
<Card className="shadow-sm">
|
||
{step === 'credentials' ? (
|
||
<>
|
||
<CardHeader>
|
||
<CardTitle className="text-xl">Accedi</CardTitle>
|
||
<CardDescription>Inserisci le tue credenziali per continuare</CardDescription>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
|
||
<div className="space-y-2">
|
||
<Label htmlFor="email">Indirizzo email</Label>
|
||
<div className="relative">
|
||
<Mail className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||
<Input
|
||
id="email"
|
||
type="email"
|
||
placeholder="nome@azienda.it"
|
||
className="pl-10"
|
||
autoComplete="email"
|
||
{...register('email', {
|
||
required: 'Email obbligatoria',
|
||
pattern: {
|
||
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
|
||
message: 'Formato email non valido',
|
||
},
|
||
})}
|
||
/>
|
||
</div>
|
||
{errors.email && (
|
||
<p className="text-xs text-destructive">{errors.email.message}</p>
|
||
)}
|
||
</div>
|
||
|
||
<div className="space-y-2">
|
||
<Label htmlFor="password">Password</Label>
|
||
<div className="relative">
|
||
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||
<Input
|
||
id="password"
|
||
type={showPassword ? 'text' : 'password'}
|
||
placeholder="••••••••"
|
||
className="pl-10 pr-10"
|
||
autoComplete="current-password"
|
||
{...register('password', { required: 'Password obbligatoria' })}
|
||
/>
|
||
<button
|
||
type="button"
|
||
onClick={() => setShowPassword(!showPassword)}
|
||
className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
|
||
>
|
||
{showPassword ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
|
||
</button>
|
||
</div>
|
||
{errors.password && (
|
||
<p className="text-xs text-destructive">{errors.password.message}</p>
|
||
)}
|
||
</div>
|
||
|
||
<Button type="submit" className="w-full" isLoading={isLoading}>
|
||
Accedi
|
||
</Button>
|
||
</form>
|
||
</CardContent>
|
||
</>
|
||
) : (
|
||
<>
|
||
<CardHeader>
|
||
<div className="flex items-center gap-2">
|
||
<Shield className="h-5 w-5 text-blue-600" />
|
||
<CardTitle className="text-xl">Verifica 2FA</CardTitle>
|
||
</div>
|
||
<CardDescription>
|
||
Inserisci il codice a 6 cifre dall'app autenticatore per{' '}
|
||
<strong>{getValues('email')}</strong>
|
||
</CardDescription>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
|
||
<div className="space-y-2">
|
||
<Label htmlFor="totp_code">Codice OTP</Label>
|
||
<Input
|
||
id="totp_code"
|
||
type="text"
|
||
placeholder="000000"
|
||
maxLength={6}
|
||
className="text-center text-2xl tracking-widest font-mono"
|
||
autoComplete="one-time-code"
|
||
autoFocus
|
||
{...register('totp_code', {
|
||
required: 'Codice OTP obbligatorio',
|
||
minLength: { value: 6, message: 'Il codice deve avere 6 cifre' },
|
||
maxLength: { value: 6, message: 'Il codice deve avere 6 cifre' },
|
||
pattern: { value: /^\d{6}$/, message: 'Solo cifre (6 caratteri)' },
|
||
})}
|
||
/>
|
||
{errors.totp_code && (
|
||
<p className="text-xs text-destructive">{errors.totp_code.message}</p>
|
||
)}
|
||
</div>
|
||
|
||
<Button type="submit" className="w-full" isLoading={isLoading}>
|
||
Verifica
|
||
</Button>
|
||
|
||
<button
|
||
type="button"
|
||
onClick={() => setStep('credentials')}
|
||
className="w-full text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||
>
|
||
← Torna alle credenziali
|
||
</button>
|
||
</form>
|
||
</CardContent>
|
||
</>
|
||
)}
|
||
</Card>
|
||
|
||
<p className="text-center text-xs text-gray-400">
|
||
PecFlow v1.0 – Piattaforma gestione PEC certificata
|
||
</p>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|