Files
lr-openwrt-tool/src/components/login/LoginScreen.tsx
T
2026-05-12 14:06:10 +01:00

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>
);
}