frontend finished

This commit is contained in:
2025-11-10 20:13:02 +01:00
parent d2f3cad487
commit ceac24ffe7
9 changed files with 630 additions and 3 deletions

View File

@@ -0,0 +1,16 @@
const AlertMessage = ({ type = 'info', message, onClose }) => {
if (!message) {
return null
}
return (
<div className={`alert alert-${type} d-flex justify-content-between align-items-center`} role="alert">
<span>{message}</span>
{onClose && (
<button type="button" className="btn-close" onClick={onClose} aria-label="Cerrar" />
)}
</div>
)
}
export default AlertMessage

View File

@@ -0,0 +1,42 @@
const Enable2FA = ({ secret, otpauthUrl, qrBase64, onBack, onRefresh, isLoading }) => {
return (
<div className="d-flex flex-column gap-3">
<p>Escanea el codigo o introduce la clave manualmente en tu app TOTP.</p>
{qrBase64 ? (
<img
src={`data:image/png;base64,${qrBase64}`}
alt="Codigo QR 2FA"
className="align-self-center border rounded"
style={{ width: '220px', height: '220px' }}
/>
) : (
<div className="alert alert-warning" role="alert">
No se pudo generar la imagen QR. Usa la clave manual.
</div>
)}
<div className="bg-dark-subtle rounded p-3">
<strong>Clave TOTP:</strong>
<div className="mt-2 text-break">{secret}</div>
</div>
<div className="bg-dark-subtle rounded p-3">
<strong>URL OTPAuth:</strong>
<div className="mt-2 text-break">{otpauthUrl}</div>
</div>
<div className="d-flex gap-2">
<button type="button" className="btn btn-outline-secondary" onClick={onBack}>
Volver a verificacion
</button>
<button
type="button"
className="btn btn-outline-primary ms-auto"
onClick={onRefresh}
disabled={isLoading}
>
{isLoading ? 'Generando...' : 'Generar de nuevo'}
</button>
</div>
</div>
)
}
export default Enable2FA

View File

@@ -0,0 +1,54 @@
import { useState } from 'react'
const LoginForm = ({ onSubmit, isLoading }) => {
const [formState, setFormState] = useState({
user_name: '',
password: ''
})
const handleChange = (event) => {
const { name, value } = event.target
setFormState((prev) => ({ ...prev, [name]: value }))
}
const handleSubmit = (event) => {
event.preventDefault()
onSubmit(formState)
}
return (
<form onSubmit={handleSubmit} className="d-flex flex-column gap-3">
<div>
<label htmlFor="user_name" className="form-label">Usuario</label>
<input
id="user_name"
name="user_name"
type="text"
className="form-control"
autoComplete="username"
value={formState.user_name}
onChange={handleChange}
required
/>
</div>
<div>
<label htmlFor="password" className="form-label">Clave</label>
<input
id="password"
name="password"
type="password"
className="form-control"
autoComplete="current-password"
value={formState.password}
onChange={handleChange}
required
/>
</div>
<button type="submit" className="btn btn-primary" disabled={isLoading}>
{isLoading ? 'Iniciando...' : 'Iniciar sesion'}
</button>
</form>
)
}
export default LoginForm

View File

@@ -0,0 +1,72 @@
import { useState } from 'react'
const RegisterForm = ({ onSubmit, isLoading }) => {
const [formState, setFormState] = useState({
user_name: '',
password: '',
confirmPassword: ''
})
const [localError, setLocalError] = useState('')
const handleChange = (event) => {
const { name, value } = event.target
setFormState((prev) => ({ ...prev, [name]: value }))
setLocalError('')
}
const handleSubmit = (event) => {
event.preventDefault()
if (formState.password !== formState.confirmPassword) {
setLocalError('Las claves no coinciden')
return
}
onSubmit({ user_name: formState.user_name, password: formState.password })
}
return (
<form onSubmit={handleSubmit} className="d-flex flex-column gap-3">
<div>
<label htmlFor="register-user" className="form-label">Usuario</label>
<input
id="register-user"
name="user_name"
type="text"
className="form-control"
value={formState.user_name}
onChange={handleChange}
required
/>
</div>
<div>
<label htmlFor="register-password" className="form-label">Clave</label>
<input
id="register-password"
name="password"
type="password"
className="form-control"
value={formState.password}
onChange={handleChange}
required
/>
</div>
<div>
<label htmlFor="register-confirm" className="form-label">Repetir clave</label>
<input
id="register-confirm"
name="confirmPassword"
type="password"
className="form-control"
value={formState.confirmPassword}
onChange={handleChange}
required
/>
{localError && <div className="form-text text-danger">{localError}</div>}
</div>
<button type="submit" className="btn btn-primary" disabled={isLoading}>
{isLoading ? 'Registrando...' : 'Crear cuenta'}
</button>
</form>
)
}
export default RegisterForm

View File

@@ -0,0 +1,48 @@
import { useState } from 'react'
const TotpForm = ({ onSubmit, isLoading, onBack }) => {
const [totpCode, setTotpCode] = useState('')
const handleSubmit = (event) => {
event.preventDefault()
onSubmit(totpCode.replace(/\s+/g, ''))
}
return (
<form onSubmit={handleSubmit} className="d-flex flex-column gap-3">
<div>
<label htmlFor="totpCode" className="form-label">Codigo TOTP</label>
<input
id="totpCode"
name="totpCode"
type="text"
pattern="[0-9]{6}"
inputMode="numeric"
className="form-control"
value={totpCode}
onChange={(event) => setTotpCode(event.target.value)}
placeholder="123456"
required
/>
<div className="form-text">Introduce el codigo de tu app de autenticacion</div>
</div>
<div className="d-flex gap-2">
{onBack && (
<button
type="button"
className="btn btn-outline-secondary"
onClick={onBack}
disabled={isLoading}
>
Configurar 2FA
</button>
)}
<button type="submit" className="btn btn-success ms-auto" disabled={isLoading}>
{isLoading ? 'Validando...' : 'Validar codigo'}
</button>
</div>
</form>
)
}
export default TotpForm