From e2cdab8b113c4e3b372adbb292d61af14aa12275 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sof=C3=ADa=20Maturana?= Date: Mon, 18 May 2026 09:44:14 -0400 Subject: [PATCH] feat: agregar login y register funcionales --- src/context/UserContext.jsx | 41 +++++++++++- src/pages/Login.jsx | 107 ++++++++++++++++++++++--------- src/pages/Register.jsx | 121 +++++++++++++++++++++++++----------- 3 files changed, 203 insertions(+), 66 deletions(-) diff --git a/src/context/UserContext.jsx b/src/context/UserContext.jsx index e8e51e3..a00afa3 100644 --- a/src/context/UserContext.jsx +++ b/src/context/UserContext.jsx @@ -3,12 +3,47 @@ import { createContext, useState } from "react"; export const UserContext = createContext(); const UserProvider = ({ children }) => { - const [token, setToken] = useState(true); + const [token, setToken] = useState(localStorage.getItem("loginToken")); + const [email, setEmail] = useState(null); + + const storeData = (email, token) => { + setEmail(email); + setToken(token); + localStorage.setItem("loginToken", data.token); + }; + const login = async (user, password) => { + const res = await fetch("http://localhost:5000/api/auth/login", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ email: user, password }), + }); + if (!res.ok) { + throw new Error("Error al iniciar sesión"); + } + const data = await res.json(); + storeData(email, data.token); + }; + const register = async (user, password) => { + const res = await fetch("http://localhost:5000/api/auth/register", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ email: user, password }), + }); + if (!res.ok) { + throw new Error("Error al registrarse"); + } + const data = await res.json(); + storeData(email, data.token); + }; const logout = () => { - setToken(false); + localStorage.removeItem("loginToken"); + setToken(null); + setEmail(null); }; return ( - + {children} ); diff --git a/src/pages/Login.jsx b/src/pages/Login.jsx index 856d27e..eebbef6 100644 --- a/src/pages/Login.jsx +++ b/src/pages/Login.jsx @@ -1,39 +1,90 @@ -import { useState } from "react"; +// Librería recomendada en tutoría +import { useForm } from "@tanstack/react-form"; +import { useContext } from "react"; +// Librería recomendada en tutoría +import { z } from "zod"; +import { UserContext } from "../context/UserContext"; +import { useNavigate } from "react-router-dom"; + +// Esquema de validación de login +const loginSchema = z.object({ + user: z + .email({ error: "Email inválido" }) + .nonempty({ error: "Se requiere un email" }), + password: z + .string() + .min(6, { error: "La contraseña debe tener al menos 6 caracteres" }), +}); const Login = () => { - const [user, setUser] = useState(""); - const [pass, setPass] = useState(""); + const { login } = useContext(UserContext); + const form = useForm({ + defaultValues: { user: "", password: "" }, + validators: { onChange: loginSchema }, + onSubmit: async ({ value: { user, password } }) => { + try { + await login(user, password); + } catch (error) { + alert("Error de login"); + } + }, + }); + /** + * @type {React.SubmitEventHandler} + */ return ( -
-

Usuario

- setUser(ev.target.value)} +
{ + ev.preventDefault(); + form.handleSubmit(); + }} + > + { + return ( + <> +

Usuario

+ field.handleChange(ev.target.value)} + /> + {!field.state.meta.isValid && ( +

+ {field.state.meta.errors.map((e) => e.message).join(", ")} +

+ )} + + ); + }} />

Contraseña

- setPass(ev.target.value)} - /> -

Confirmar contraseña

- -
+ ); }; diff --git a/src/pages/Register.jsx b/src/pages/Register.jsx index 4329159..196b83c 100644 --- a/src/pages/Register.jsx +++ b/src/pages/Register.jsx @@ -1,50 +1,101 @@ -import { useState } from "react"; +import { useForm } from "@tanstack/react-form"; +import { useContext, useState } from "react"; +import { z } from "zod"; +import { UserContext } from "../context/UserContext"; + +const registerSchema = z + .object({ + user: z.email().nonempty({ error: "Se requiere un email" }), + password: z + .string() + .min(6, { error: "la contraseña debe ser de al menos 6 caracteres" }), + confirmPassword: z.string(), + }) + .refine((data) => data.password === data.confirmPassword, { + error: "las contraseñas no coinciden", + path: ["confirmPassword"], + }); const Register = () => { - const [user, setUser] = useState(""); - const [pass, setPass] = useState(""); - const [confirmPass, setConfirmPass] = useState(""); + const { register } = useContext(UserContext); + const form = useForm({ + defaultValues: { user: "", password: "", confirmPassword: "" }, + validators: { onChange: registerSchema }, + onSubmit: ({ value: { user, password, confirmPassword } }) => { + try { + register(user, password); + } catch (error) { + alert("Error de registro"); + } + }, + }); return ( -
+
{ + ev.preventDefault(); + form.handleSubmit(); + }} + >

Usuario

- setUser(ev.target.value)} + ( + <> + field.handleChange(ev.target.value)} + /> + {!field.state.meta.isValid && ( +

+ {field.state.meta.errors.map((e) => e.message).join(", ")} +

+ )} + + )} />

Contraseña

- setPass(ev.target.value)} + ( + <> + field.handleChange(ev.target.value)} + /> + {!field.state.meta.isValid && ( +

+ {field.state.meta.errors.map((e) => e.message).join(", ")} +

+ )} + + )} />

Confirmar contraseña

- setConfirmPass(ev.target.value)} + ( + <> + field.handleChange(ev.target.value)} + /> + {!field.state.meta.isValid && ( +

+ {field.state.meta.errors.map((e) => e.message).join(", ")} +

+ )} + + )} />
- -
+ ); };