Skip to content

Commit 02b8c3b

Browse files
Implemented Frontend Login with Local Token Storage
Se agregó funcionalidad para el inicio de sesión de los usuarios. El proceso de inicio de sesión ahora almacena de forma segura los tokens de autenticación localmente para la gestión de sesiones y una mejor experiencia de usuario.
1 parent f725ab3 commit 02b8c3b

21 files changed

+320
-153
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ Durante el desarrollo del proyecto, se utilizaron las siguientes librerías:
5555
npm install react-router-dom
5656
npm install axios
5757
npm install d3
58+
npm install jwt-decode
5859
```
5960

6061
## MANTENIMIENTO Y CONSTRUCCIÓN

package-lock.json

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"@testing-library/user-event": "^13.5.0",
1010
"axios": "^1.8.4",
1111
"d3": "^7.9.0",
12+
"jwt-decode": "^4.0.0",
1213
"react": "^19.0.0",
1314
"react-dom": "^19.0.0",
1415
"react-native": "^0.78.1",

src/App.css

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,16 +92,18 @@ ul {
9292

9393
.content .panel .header {
9494
display: flex;
95-
flex-direction: column;
96-
justify-content: center;
97-
width: 100%;
95+
flex-direction: row;
96+
justify-content: flex-start;
97+
align-items: center;
9898
height: 20%;
99+
margin-right: 5%;
99100
}
100101

101102
.content .panel .header .info {
102103
display: flex;
103104
flex-direction: column;
104105
gap: 5px;
106+
width: 100%;
105107
}
106108

107109
.content .panel .header .info .title {
@@ -112,6 +114,19 @@ ul {
112114
font-size: 21px;
113115
}
114116

117+
.content .panel .header .user-info {
118+
display: flex;
119+
flex-direction: row;
120+
gap: 20px;
121+
width: 100%;
122+
align-items: center;
123+
justify-content: flex-end;
124+
}
125+
126+
.content .panel .header .user-info span {
127+
font-size: 18px;
128+
}
129+
115130
.content .panel .container {
116131
flex: 1;
117132
display: flex;

src/App.js

Lines changed: 117 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@ import {
1212
import { ReactComponent as House } from "./assets/icons/house-user_11269953 1.svg";
1313
import { ReactComponent as Room } from "./assets/icons/workshop_14672030 1.svg";
1414
import { ReactComponent as UserIcon } from "./assets/icons/User.svg";
15-
import Home from "./pages/Home/Home.js";
16-
import LoginPage from "./pages/Login/LoginPage.jsx";
17-
import AdministratorHome from "./pages/Administrator/AdministratorHome.jsx";
15+
import { consultarUsuarioPorCorreo } from "./api/usuario";
16+
import Home from "./pages/Home/Home";
17+
import LoginPage from "./pages/Login/LoginPage";
18+
import AdministratorHome from "./pages/Administrator/AdministratorHome";
1819
import styled from "styled-components";
20+
import { jwtDecode } from "jwt-decode";
1921
import "./App.css";
2022

2123
/**
@@ -32,9 +34,11 @@ const routesConfig = {
3234

3335
const Menu = ({ user }) => {
3436
if (!user) return null;
37+
const userRoutes = routesConfig[user.isAdmin ? "admin" : "profe"] || [];
38+
3539
return (
3640
<ul className="menu">
37-
{routesConfig[(user.isAdmin ? "admin" : "profe")]?.map((item, index) => (
41+
{userRoutes.map((item, index) => (
3842
<li className="item-menu" key={index}>
3943
<Link className="navBarBTN" to={item.path}>
4044
{item.icon}
@@ -54,16 +58,14 @@ const Header = ({ user, onLogout }) => {
5458
const navigate = useNavigate();
5559
let title = "Elysium";
5660

57-
if (user) {
58-
const currentPage = routesConfig[(user.isAdmin ? "admin" : "profe")]?.find(
59-
(route) => route.path === location.pathname
60-
);
61-
title = currentPage ? currentPage.name : "Elysium";
62-
}
63-
6461
useEffect(() => {
65-
document.title = title;
66-
}, [title]);
62+
if (user) {
63+
const currentPage = routesConfig[user.isAdmin ? "admin" : "profe"]?.find(
64+
(route) => route.path === location.pathname
65+
);
66+
document.title = currentPage ? currentPage.name : "Elysium";
67+
}
68+
}, [location.pathname, user]);
6769

6870
if (!user) return null;
6971

@@ -83,12 +85,13 @@ const Header = ({ user, onLogout }) => {
8385
src="https://img.freepik.com/vector-gratis/establecimiento-circulos-usuarios_78370-4704.jpg?ga=GA1.1.204243624.1732496744&semt=ais_hybrid"
8486
alt="Avatar de usuario"
8587
/>
88+
<span>{user.nombre} {user.apellido}</span>
8689
<LogoutIcon
8790
src="https://cdn.builder.io/api/v1/image/assets/TEMP/1954f6c7c642021490080ffd4c81bc9798bf0beb?placeholderIfAbsent=true"
8891
alt="Logout"
8992
onClick={() => {
9093
onLogout();
91-
navigate("/"); // Regresa al LoginPage
94+
navigate("/");
9295
}}
9396
/>
9497
</div>
@@ -107,68 +110,122 @@ const LogoutIcon = styled.img`
107110
width: 32px;
108111
height: 32px;
109112
cursor: pointer;
113+
fill: var(--variable-collection-current-color);
110114
`;
111115

116+
const obtenerCorreoDesdeToken = (token) => {
117+
try {
118+
const decoded = jwtDecode(token);
119+
return decoded.sub;
120+
} catch (error) {
121+
return null;
122+
}
123+
};
124+
125+
126+
112127
//
113-
// Componente principal de la aplicación
128+
// Componente principal de rutas
114129
//
115-
function App() {
116-
// El usuario se establecerá a través del LoginPage, por lo que no simulamos nada aquí.
117-
const [user, setUser] = useState(null);
130+
function AppRoutes({ user, setUser }) {
118131
const [loading, setLoading] = useState(true);
132+
const navigate = useNavigate();
119133

120-
// Simulación de carga inicial; en producción, no se simula usuario.
121134
useEffect(() => {
122-
setLoading(false);
135+
const fetchUser = async () => {
136+
const token = localStorage.getItem("token");
137+
if (token) {
138+
try {
139+
const correoGuardado = obtenerCorreoDesdeToken(token);
140+
if (correoGuardado) {
141+
const usuario = await consultarUsuarioPorCorreo(correoGuardado);
142+
setUser(usuario);
143+
144+
if (usuario.isAdmin) {
145+
document.documentElement.style.setProperty("--variable-collection-current-color", "var(--variable-collection-user-admin)");
146+
navigate("/administrador");
147+
} else {
148+
document.documentElement.style.setProperty("--variable-collection-current-color", "var(--variable-collection-user-estandar)");
149+
navigate("/home");
150+
}
151+
} else {
152+
localStorage.removeItem("token");
153+
}
154+
} catch (error) {
155+
console.error("Error obteniendo usuario:", error);
156+
localStorage.removeItem("token");
157+
}
158+
}
159+
setLoading(false);
160+
};
161+
162+
fetchUser();
123163
}, []);
124164

165+
const handleLogout = () => {
166+
localStorage.removeItem("token");
167+
setUser(null);
168+
};
169+
125170
if (loading) return <div>Cargando...</div>;
126171

127172
return (
128-
<Router>
129-
<Routes>
130-
{/* Si no hay usuario autenticado, se muestra LoginPage */}
131-
{!user ? (
132-
<>
133-
<Route path="/" element={<LoginPage onLogin={setUser} />} />
134-
<Route path="*" element={<Navigate to="/" replace />} />
135-
</>
136-
) : (
137-
// Una vez autenticado, se muestra la aplicación completa (Header, Menu, contenido)
138-
<Route
139-
path="/*"
140-
element={
141-
<div className="content">
142-
<div className="navBar">
143-
<Menu user={user} />
144-
</div>
145-
<div className="panel">
146-
<Header user={user} onLogout={() => setUser(null)} />
147-
<div className="container">
148-
<Routes>
149-
{user.isAdmin ? (
150-
<>
151-
<Route path="/administrador" element={<AdministratorHome />} />
152-
<Route path="/administrador/salones" element={<div>Gestión de Salones</div>} />
153-
<Route path="/administrador/usuarios" element={<div>Gestión de Usuarios</div>} />
154-
<Route path="*" element={<Navigate to="/administrador" />} />
155-
</>
156-
) : (
157-
<>
158-
<Route path="/home" element={<Home />} />
159-
<Route path="*" element={<Navigate to="/home" />} />
160-
</>
161-
)}
162-
</Routes>
163-
</div>
173+
<Routes>
174+
{/* Si no hay usuario autenticado, se muestra LoginPage */}
175+
{!user ? (
176+
<>
177+
<Route path="/" element={<LoginPage onLogin={setUser} />} />
178+
<Route path="*" element={<Navigate to="/" replace />} />
179+
</>
180+
) : (
181+
// Una vez autenticado, se muestra la aplicación completa (Header, Menu, contenido)
182+
<Route
183+
path="/*"
184+
element={
185+
<div className="content">
186+
<div className="navBar">
187+
<Menu user={user} />
188+
</div>
189+
<div className="panel">
190+
<Header user={user} onLogout={handleLogout} />
191+
<div className="container">
192+
<Routes>
193+
{user.isAdmin ? (
194+
<>
195+
<Route path="/administrador" element={<AdministratorHome token={localStorage.getItem("token")} />} />
196+
<Route path="/administrador/salones" element={<div>Gestión de Salones</div>} />
197+
<Route path="/administrador/usuarios" element={<div>Gestión de Usuarios</div>} />
198+
<Route path="*" element={<Navigate to="/administrador" />} />
199+
</>
200+
) : (
201+
<>
202+
<Route path="/home" element={<Home />} />
203+
<Route path="*" element={<Navigate to="/home" />} />
204+
</>
205+
)}
206+
</Routes>
164207
</div>
165208
</div>
166-
}
167-
/>
168-
)}
169-
</Routes>
209+
</div>
210+
}
211+
/>
212+
)}
213+
</Routes>
214+
);
215+
}
216+
217+
//
218+
// Componente principal de la aplicación con <Router> en el nivel más alto
219+
//
220+
function App() {
221+
const [user, setUser] = useState(null);
222+
223+
return (
224+
<Router>
225+
<AppRoutes user={user} setUser={setUser} />
170226
</Router>
171227
);
172228
}
173229

230+
174231
export default App;

src/api/auth.jsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import axios from "axios";
2+
import { BASE_URL } from "../config/config.js";
3+
4+
export async function login(correo="", contraseña="") {
5+
if (correo === "" || contraseña === "") {
6+
throw new Error("El correo y la contraseña son obligatorios.");
7+
}
8+
try {
9+
const data = {
10+
correoInstitucional: correo,
11+
password: contraseña
12+
}
13+
const response = await axios.post(`${BASE_URL}/login`, data);
14+
return response.data;
15+
} catch (error) {
16+
throw new Error(error.response ? error.response.data.message : error.message);
17+
}
18+
}
19+
20+
export async function register(usuario = {}) {
21+
try {
22+
const response = await axios.post(`${BASE_URL}/register`, usuario );
23+
return response.data;
24+
} catch (error) {
25+
throw new Error(error.response ? error.response.data.message : error.message);
26+
}
27+
}

0 commit comments

Comments
 (0)