Appearance
Archivos de la Carpeta src/features
La carpeta src/features está diseñada para contener la lógica y los componentes de UI que forman una funcionalidad específica y autocontenida de la aplicación. A menudo, un "feature" agrupa componentes, hooks y utilidades que trabajan juntos para lograr un objetivo funcional, como la autenticación o la gestión de usuarios.
login.tsx
Este archivo define el componente principal de la funcionalidad de inicio de sesión. Es el punto de orquestación donde se gestiona el estado del formulario de login, la comunicación con la API de autenticación y la redirección del usuario.
- Descripción:
Logines un componente de React que actúa como el "cerebro" del proceso de inicio de sesión. Muestra un formulario de login (LoginForm), captura las credenciales del usuario, las envía a la API a través deauthenticateUser, y luego utiliza el contexto de autenticación (useAuth) para gestionar el estado de la sesión. - Propósito: Proporcionar una interfaz de usuario funcional para que los usuarios puedan autenticarse en la aplicación, manejando la lógica de interacción con el backend y la gestión del estado de la sesión.
- Funcionalidad clave:
- Estados Internos:
isLoading: Un estadouseState<boolean>que indica si una petición de login está en curso, lo que permite deshabilitar el formulario o mostrar un spinner.
useAuth(): Accede al contexto de autenticación (../components/auth/auth.context.tsx) para verificar si el usuario ya está autenticado (isAuthenticated) y para obtener la funciónlogindel contexto (que gestiona el almacenamiento del token).useNavigate(): Hook dereact-router-domutilizado para la navegación programática, específicamente para redirigir al usuario al/dashboarddespués de un inicio de sesión exitoso.- Redirección si Autenticado (
useEffect): UnuseEffectmonitorea el estadoauth.isAuthenticated. Si el usuario ya está autenticado, lo redirige inmediatamente al/dashboard, evitando que vea la página de login innecesariamente. handleLogin(email: string, password: string)(useCallback):- Es la función principal que se pasa al
LoginFormpara manejar el envío de credenciales. - Envuelve la lógica en
useCallbackpara optimizar su rendimiento. - Establece
isLoadingatrueal inicio. - Petición de Autenticación: Llama a la función utilitaria
authenticateUser(importada de../../utils/auth.utils.ts) para enviar elemailypasswordal backend y obtener untoken. - Gestión del Token: Si la autenticación es exitosa, llama a
auth.login(token)(la función proporcionada porAuthProvideryuseLoginhook) para almacenar el token en el cliente. - Feedback al Usuario: Utiliza el componente funcional
ToastMessage(importado de../../components/common/toast-message/toast.message.tsx) para mostrar notificaciones toast (éxito o error) al usuario. - Redirección Exitosa: Después de un login exitoso y el almacenamiento del token, redirige al usuario al
/dashboard. - Manejo de Errores Mejorado: Captura cualquier
errordurante la autenticación o el procesamiento del token. El mensaje de error se extrae de la instancia delErrorsi es posible, proporcionando feedback más preciso al usuario. - Finalización: El bloque
finallyasegura quesetIsLoading(false)siempre se ejecute, independientemente del éxito o fracaso.
- Es la función principal que se pasa al
GridyLoginForm: Utiliza un componenteGridpara el layout y renderiza el componente presentacionalLoginForm(importado de./login.form.tsx), pasándole la funciónhandleLoginy el estadoisLoading.
- Estados Internos:
- Rol en la aplicación:
Logines el componente de funcionalidad de autenticación clave. Orquesta el flujo de inicio de sesión de principio a fin, desde la captura de datos hasta la interacción con la API y la gestión del estado de la sesión global.
tsx
// src/features/login/login.tsx
import { useState, useEffect, useCallback } from "react"; // Hooks de React
import { useAuth } from "../../components/auth/auth.context"; // Hook para acceder al contexto de autenticación
import { useNavigate } from "react-router-dom"; // Hook para navegación de React Router
import { Grid } from "../../components/common/grid/grid"; // Componente de layout Grid
import { LoginForm } from "./login.form"; // Componente presentacional del formulario de login
import { authenticateUser } from "../../utils/auth.utils"; // Función utilitaria para autenticar con la API
import css from "../../assets/styles/layout/login.form.module.scss"; // Estilos SCSS
import "react-toastify/dist/ReactToastify.css"; // Estilos CSS para react-toastify
import { ToastMessage } from "../../components/common/toast-message/toast.message"; // Función para mostrar notificaciones toast
export default function Login() {
// Estado para controlar el indicador de carga durante el proceso de login
const [isLoading, setIsLoading] = useState(false);
// Accede al contexto de autenticación para obtener el estado de autenticación y la función de login
const auth = useAuth();
// Hook para la navegación programática
const goTo = useNavigate();
// Efecto para redirigir al usuario al dashboard si ya está autenticado
useEffect(() => {
if (auth.isAuthenticated) {
goTo("/dashboard"); // Redirige al dashboard
}
}, [auth.isAuthenticated, goTo]); // Dependencias: se re-ejecuta si el estado de autenticación o la función de navegación cambian
/**
* Manejador de la lógica de inicio de sesión.
* Envía las credenciales al backend, gestiona el token y la redirección.
* @param email El correo electrónico del usuario.
* @param password La contraseña del usuario.
*/
const handleLogin = useCallback(
async (email: string, password: string) => {
setIsLoading(true); // Activa el estado de carga
try {
// Llama a la función utilitaria para autenticar al usuario con el backend
// Esta función podría lanzar un error si las credenciales son incorrectas.
const token = await authenticateUser(email, password);
// Llama a la función de login del contexto de autenticación para almacenar el token.
// El hook useLogin (dentro de auth.context) es responsable de validar y almacenar el token
// y podría tener su propia lógica de notificación para errores relacionados con el token corrupto/inválido.
await auth.login(token);
// Si todo es exitoso (autenticación y login del token), muestra un mensaje de éxito
ToastMessage({ type: "success", message: "Inicio de sesión exitoso!" });
goTo("/dashboard"); // Redirige al usuario al dashboard
} catch (error: any) {
// En caso de cualquier error (ej. credenciales incorrectas desde authenticateUser,
// o un error de token corrupto que auth.login propaga), muestra un mensaje de error.
// Aseguramos que el mensaje sea legible, extrayéndolo si es una instancia de Error.
ToastMessage({
type: "error",
message: `Error al iniciar sesión: ${(error instanceof Error) ? error.message : "Ocurrió un error inesperado."}`,
});
} finally {
setIsLoading(false); // Desactiva el estado de carga al finalizar la operación
}
},
// Dependencias de useCallback: se re-crea si auth.login o goTo cambian
[auth.login, goTo]
);
// Renderiza el componente LoginForm dentro de un Grid
return (
<Grid className={css.loginContainer}>
<LoginForm onSubmit={handleLogin} isLoading={isLoading} />
</Grid>
);
}
login.form.tsx
Este archivo define el componente LoginForm, que es el formulario de inicio de sesión puramente presentacional. Su rol es capturar las credenciales (email y contraseña) del usuario y comunicarlas al componente padre (login.tsx) para su procesamiento.
- Descripción:
LoginFormes un componente de React que renderiza la interfaz de usuario para el inicio de sesión. Mantiene el estado local de los campos de email y contraseña, y utiliza un componenteFormInputreutilizable. No contiene lógica de autenticación directa, sino que delega el envío de las credenciales y el manejo del estado de carga a las propiedades que recibe. - Propósito: Proporcionar una interfaz de usuario limpia y controlada para la entrada de credenciales de usuario, desacoplada de la lógica de autenticación subyacente.
- Funcionalidad clave:
- Estados Internos (
useState):email: Almacena el valor actual del campo de correo electrónico.password: Almacena el valor actual del campo de contraseña.
LoginFormProps: La interfaz (import LoginFormProps from "../../interface/login/login.form.props";) define las propiedades exactas que este componente espera recibir (onSubmityisLoading).handleSubmit(e: React.FormEvent<HTMLFormElement>):- Este manejador se ejecuta cuando el formulario es enviado.
- Llama a
e.preventDefault()para evitar el comportamiento por defecto de recarga de la página. - Invoca la prop
onSubmit, pasándole los valores actuales deemailypasswordpara que el componente padre los procese (ej. enviándolos a la API de autenticación).
FormInput: Utiliza el componenteFormInput(importado de../../components/common/input-field/Input.field) para los campos de entrada de email y contraseña. Este componente reutilizable se encarga de la estilización y la funcionalidad básica de un input.- Tipado Explícito en
onChange: Los parámetroseen los manejadoresonChange(onChange={(e: React.ChangeEvent<HTMLInputElement>) => ...}) se tipan explícitamente para asegurar que TypeScript infiera correctamente el tipo del evento, evitando advertenciasTS7006sobreanyimplícitos. - Propiedad
name: Se aseguran de pasar la propiedadname(name="email",name="password") a cadaFormInput, lo cual es esencial para el correcto funcionamiento de los formularios HTML y su manejo de estado en el padre.
- Tipado Explícito en
- Botón de Envío (
<button type="submit">):- El botón tiene
type="submit"para activar elhandleSubmitdel formulario. - Está deshabilitado (
disabled={isLoading}) si la propisLoadingestrue, lo que proporciona un feedback visual al usuario durante el proceso de login. - El texto del botón cambia a "Cargando..." si
isLoadingestrue.
- El botón tiene
- Estilos y Logo: Aplica estilos locales (
css.loginForm,css.inputTitle, etc.) y muestra un logo (logo) para la identidad de la aplicación.
- Estados Internos (
- Rol en la aplicación:
LoginFormes un componente presentacional dentro del feature de login. Es la interfaz directa con el usuario para capturar las credenciales, manteniendo la lógica de estado mínima y delegando las acciones complejas a su componente padre,login.tsx.
tsx
// src/features/login/login.form.tsx
import { useState } from "react"; // Hook para gestionar el estado local
import FormInput from "../../components/common/input-field/Input.field"; // Importa el componente FormInput (antes InputField)
import css from "../../assets/styles/layout/login.form.module.scss"; // Estilos CSS específicos para el formulario de login
import LoginFormProps from "../../interface/login/login.form.props"; // Interfaz para las propiedades del formulario de login
import logo from "../../assets/images/LOGO-escuelas.png"; // Logo de la aplicación
/**
* Componente presentacional del formulario de inicio de sesión.
* Gestiona los estados locales de email y contraseña y envía los datos al padre.
* @param {LoginFormProps} props Las propiedades del formulario: `onSubmit` y `isLoading`.
* @param props.onSubmit Función callback que se ejecuta al enviar el formulario con email y contraseña.
* @param props.isLoading Booleano que indica si la petición de login está en curso.
*/
export const LoginForm: React.FC<LoginFormProps> = ({
onSubmit, // Función para manejar el envío de credenciales (pasada desde el padre)
isLoading, // Estado de carga (pasado desde el padre para deshabilitar el botón)
}) => {
// Estados locales para el email y la contraseña del formulario
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
/**
* Manejador del envío del formulario.
* Previene el comportamiento por defecto y llama a la función onSubmit del padre.
* @param e Evento de formulario.
*/
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault(); // Evita que la página se recargue
onSubmit(email, password); // Llama a la función de envío del padre con las credenciales
};
return (
<form className={css.loginForm} onSubmit={handleSubmit}>
<div className={css.inputTitle}>
<img src={logo} alt="logo" className={css.logo} /> {/* Logo visual */}
<h2>Inicio de sesión</h2> {/* Título del formulario */}
</div>
{/* Campo de entrada para el email */}
<FormInput
id="email" // ID único para el input
label="Email" // Etiqueta visible
name="email" // Nombre del campo para el formulario (requerido por FormInputProps)
type="email" // Tipo de input HTML
value={email} // Valor controlado por el estado local
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setEmail(e.target.value)} // Actualiza el estado al cambiar, con tipado explícito
/>
{/* Campo de entrada para la contraseña */}
<FormInput
id="password" // ID único para el input
label="Contraseña" // Etiqueta visible
name="password" // Nombre del campo para el formulario (requerido por FormInputProps)
type="password" // Tipo de input HTML (oculta caracteres)
value={password} // Valor controlado por el estado local
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setPassword(e.target.value)} // Actualiza el estado al cambiar, con tipado explícito
/>
{/* Botón de envío del formulario */}
<button className={css.loginButton} disabled={isLoading}>
{isLoading ? "Cargando..." : "Entrar"}{" "}
{/* Texto dinámico según el estado de carga */}
</button>
</form>
);
};