192 lines
8.7 KiB
TypeScript
192 lines
8.7 KiB
TypeScript
import { useState } from 'react';
|
|
import {
|
|
Eye,
|
|
EyeOff,
|
|
LockKeyhole,
|
|
Shield,
|
|
User,
|
|
} from 'lucide-react';
|
|
|
|
import { Button } from '@/components/ui/Button';
|
|
import { loginApi } from '@/services/loginApi';
|
|
import logoIcon from '@/assets/logo-icon.png';
|
|
|
|
type LoginScreenProps = {
|
|
onLogin: (keepLoggedIn: boolean) => void;
|
|
};
|
|
|
|
export function LoginScreen({
|
|
onLogin,
|
|
}: LoginScreenProps) {
|
|
const [username, setUsername] = useState('');
|
|
const [password, setPassword] = useState('');
|
|
const [error, setError] = useState('');
|
|
const [keepLoggedIn, setKeepLoggedIn] = useState(false);
|
|
const [showPassword, setShowPassword] = useState(false);
|
|
const [submitting, setSubmitting] = useState(false);
|
|
|
|
async function handleSubmit(event: React.FormEvent) {
|
|
event.preventDefault();
|
|
|
|
if (submitting) {
|
|
return;
|
|
}
|
|
|
|
setSubmitting(true);
|
|
setError('');
|
|
|
|
try {
|
|
console.log('LOGIN TRY', {
|
|
username: username.trim(),
|
|
passwordLength: password.length,
|
|
});
|
|
|
|
const response = await loginApi.login(
|
|
username.trim(),
|
|
password,
|
|
);
|
|
|
|
if (response.authenticated) {
|
|
onLogin(keepLoggedIn);
|
|
return;
|
|
}
|
|
|
|
setError('Credenciais inválidas.');
|
|
} catch (error) {
|
|
setError(`Falha no login: ${String(error)}`);
|
|
} finally {
|
|
setSubmitting(false);
|
|
}
|
|
}
|
|
return (
|
|
<div className="relative flex min-h-screen items-center justify-center overflow-hidden bg-ink-950 px-6 text-white">
|
|
<div className="pointer-events-none absolute -left-32 top-10 h-96 w-96 rounded-full bg-blue-500/20 blur-3xl" />
|
|
<div className="pointer-events-none absolute -right-32 bottom-10 h-96 w-96 rounded-full bg-cyan-400/10 blur-3xl" />
|
|
<div className="pointer-events-none absolute inset-0 bg-[radial-gradient(circle_at_top,rgba(59,130,246,0.16),transparent_42%)]" />
|
|
|
|
<div className="relative w-full max-w-md">
|
|
<div className="absolute -inset-px rounded-[2rem] bg-gradient-to-br from-blue-500/40 via-cyan-400/10 to-white/5 opacity-80 blur-sm" />
|
|
|
|
<div className="relative rounded-[2rem] border border-white/10 bg-slate-950/80 p-8 shadow-2xl shadow-blue-500/10 backdrop-blur-xl">
|
|
<div className="mb-10 flex items-center gap-4">
|
|
<div className="relative shrink-0">
|
|
<div className="absolute inset-0 rounded-full bg-blue-500/20 blur-2xl" />
|
|
|
|
<img
|
|
src={logoIcon}
|
|
alt="Litoral Regas"
|
|
className="relative h-16 w-16 object-contain drop-shadow-[0_0_18px_rgba(59,130,246,0.45)]"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<h1 className="text-3xl font-bold tracking-tight text-white">
|
|
Litoral Regas
|
|
</h1>
|
|
|
|
<p className="mt-1 text-sm font-medium tracking-[0.18em] text-blue-200/75 uppercase">
|
|
VPN Orchestrator
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<form onSubmit={handleSubmit}>
|
|
<div className="space-y-5">
|
|
<div>
|
|
<label className="mb-2 block text-xs font-semibold uppercase tracking-[0.18em] text-slate-500">
|
|
Utilizador
|
|
</label>
|
|
|
|
<div className="group relative">
|
|
<User
|
|
size={17}
|
|
className="absolute left-4 top-1/2 -translate-y-1/2 text-blue-300/60 transition group-focus-within:text-blue-300"
|
|
/>
|
|
|
|
<input
|
|
value={username}
|
|
onChange={(event) =>
|
|
setUsername(event.target.value)
|
|
}
|
|
autoComplete="username"
|
|
className="w-full rounded-2xl border border-white/10 bg-black/25 py-4 pl-11 pr-4 text-sm text-white outline-none transition placeholder:text-slate-600 focus:border-blue-400/60 focus:bg-black/35 focus:shadow-[0_0_0_4px_rgba(59,130,246,0.12)]"
|
|
placeholder="Introduza o utilizador"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="mb-2 block text-xs font-semibold uppercase tracking-[0.18em] text-slate-500">
|
|
Palavra-passe
|
|
</label>
|
|
|
|
<div className="group relative">
|
|
<LockKeyhole
|
|
size={17}
|
|
className="absolute left-4 top-1/2 -translate-y-1/2 text-blue-300/60 transition group-focus-within:text-blue-300"
|
|
/>
|
|
|
|
<input
|
|
type={showPassword ? 'text' : 'password'}
|
|
value={password}
|
|
onChange={(event) =>
|
|
setPassword(event.target.value)
|
|
}
|
|
autoComplete="current-password"
|
|
className="w-full rounded-2xl border border-white/10 bg-black/25 py-4 pl-11 pr-12 text-sm text-white outline-none transition placeholder:text-slate-600 focus:border-blue-400/60 focus:bg-black/35 focus:shadow-[0_0_0_4px_rgba(59,130,246,0.12)]"
|
|
placeholder="Introduza a palavra-passe"
|
|
/>
|
|
|
|
<button
|
|
type="button"
|
|
onClick={() =>
|
|
setShowPassword((current) => !current)
|
|
}
|
|
className="absolute right-4 top-1/2 -translate-y-1/2 text-slate-500 transition hover:text-blue-200"
|
|
>
|
|
{showPassword ? (
|
|
<EyeOff size={17} />
|
|
) : (
|
|
<Eye size={17} />
|
|
)}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{error && (
|
|
<div className="mt-5 rounded-2xl border border-red-500/20 bg-red-500/10 p-3 text-sm text-red-200">
|
|
{error}
|
|
</div>
|
|
)}
|
|
|
|
<label className="mt-5 flex cursor-pointer items-center gap-3 text-sm text-slate-400 transition hover:text-slate-200">
|
|
<input
|
|
type="checkbox"
|
|
checked={keepLoggedIn}
|
|
onChange={(event) =>
|
|
setKeepLoggedIn(event.target.checked)
|
|
}
|
|
className="h-4 w-4 accent-blue-500"
|
|
/>
|
|
|
|
Manter sessão iniciada
|
|
</label>
|
|
|
|
<Button
|
|
type="submit"
|
|
disabled={submitting}
|
|
className="mt-7 w-full justify-center rounded-2xl py-4 text-sm font-bold shadow-lg shadow-blue-500/20 transition hover:-translate-y-[1px]"
|
|
>
|
|
{submitting ? 'A entrar...' : 'Entrar'}
|
|
</Button>
|
|
</form>
|
|
|
|
<div className="mt-8 border-t border-white/10 pt-5 text-center text-xs text-slate-600">
|
|
Acesso restrito a técnicos autorizados
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
} |