Compare commits

..

No commits in common. "fab315eecea8c6785e7811e8e5892ef4bb0bc33a" and "69deb9c18dcaf2f3d46dca2bf7947c0af4cf3b91" have entirely different histories.

8 changed files with 70 additions and 317 deletions

View file

@ -1,3 +0,0 @@
{
"ignorePatterns": []
}

View file

@ -1,100 +0,0 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": [],
"categories": {
"correctness": "off"
},
"env": {
"builtin": true
},
"ignorePatterns": [
"dist"
],
"overrides": [
{
"files": [
"**/*.{js,jsx}"
],
"rules": {
"constructor-super": "error",
"for-direction": "error",
"getter-return": "error",
"no-async-promise-executor": "error",
"no-case-declarations": "error",
"no-class-assign": "error",
"no-compare-neg-zero": "error",
"no-cond-assign": "error",
"no-const-assign": "error",
"no-constant-binary-expression": "error",
"no-constant-condition": "error",
"no-control-regex": "error",
"no-debugger": "error",
"no-delete-var": "error",
"no-dupe-class-members": "error",
"no-dupe-else-if": "error",
"no-dupe-keys": "error",
"no-duplicate-case": "error",
"no-empty": "error",
"no-empty-character-class": "error",
"no-empty-pattern": "error",
"no-empty-static-block": "error",
"no-ex-assign": "error",
"no-extra-boolean-cast": "error",
"no-fallthrough": "error",
"no-func-assign": "error",
"no-global-assign": "error",
"no-import-assign": "error",
"no-invalid-regexp": "error",
"no-irregular-whitespace": "error",
"no-loss-of-precision": "error",
"no-misleading-character-class": "error",
"no-new-native-nonconstructor": "error",
"no-nonoctal-decimal-escape": "error",
"no-obj-calls": "error",
"no-prototype-builtins": "error",
"no-redeclare": "error",
"no-regex-spaces": "error",
"no-self-assign": "error",
"no-setter-return": "error",
"no-shadow-restricted-names": "error",
"no-sparse-arrays": "error",
"no-this-before-super": "error",
"no-unexpected-multiline": "error",
"no-unreachable": "error",
"no-unsafe-finally": "error",
"no-unsafe-negation": "error",
"no-unsafe-optional-chaining": "error",
"no-unused-labels": "error",
"no-unused-private-class-members": "error",
"no-unused-vars": [
"error",
{
"varsIgnorePattern": "^[A-Z_]"
}
],
"no-useless-backreference": "error",
"no-useless-catch": "error",
"no-useless-escape": "error",
"no-with": "error",
"require-yield": "error",
"use-isnan": "error",
"valid-typeof": "error",
"react/rules-of-hooks": "error",
"react/exhaustive-deps": "warn",
"react/only-export-components": [
"error",
{
"allowConstantExport": true
}
]
},
"plugins": [
"react"
],
"env": {
"es2020": true,
"browser": true
}
}
]
}

View file

@ -1,11 +1,9 @@
[tools] [tools]
aube = "latest" aube = "latest"
mprocs = "latest" biome = "latest"
oxlint = "latest"
pitchfork = "latest" pitchfork = "latest"
pnpm = "latest" pnpm = "latest"
prek = "latest" prek = "latest"
oxfmt = "latest"
[tasks.dev] [tasks.dev]
description = "Arranca el servidor dev" description = "Arranca el servidor dev"
@ -13,4 +11,4 @@ run = "aube dev"
[tasks.lint] [tasks.lint]
description = "Lintea los archivos" description = "Lintea los archivos"
run = "oxlint" run = "biome lint ."

View file

@ -1,5 +0,0 @@
procs:
frontend:
shell: just start-frontend
backend:
shell: just start-backend

View file

@ -23,11 +23,11 @@ function App() {
<Route path="/" element={<Home />} /> <Route path="/" element={<Home />} />
<Route <Route
path="/login" path="/login"
element={token ? <Navigate to="/" /> : <Login />} element={!token ? <Navigate to="/" /> : <Login />}
/> />
<Route <Route
path="/register" path="/register"
element={token ? <Navigate to="/" /> : <Register />} element={!token ? <Navigate to="/" /> : <Register />}
/> />
<Route path="/pizza/:id" element={<Pizza />} /> <Route path="/pizza/:id" element={<Pizza />} />
<Route path="/cart" element={<Cart />} /> <Route path="/cart" element={<Cart />} />

View file

@ -3,47 +3,12 @@ import { createContext, useState } from "react";
export const UserContext = createContext(); export const UserContext = createContext();
const UserProvider = ({ children }) => { const UserProvider = ({ children }) => {
const [token, setToken] = useState(localStorage.getItem("loginToken")); const [token, setToken] = useState(true);
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 = () => { const logout = () => {
localStorage.removeItem("loginToken"); setToken(false);
setToken(null);
setEmail(null);
}; };
return ( return (
<UserContext.Provider <UserContext.Provider value={{ token, setToken, logout }}>
value={{ token, setToken, email, setEmail, login, register, logout }}
>
{children} {children}
</UserContext.Provider> </UserContext.Provider>
); );

View file

@ -1,90 +1,39 @@
// Librería recomendada en tutoría import { useState } from "react";
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 Login = () => {
const { login } = useContext(UserContext); const [user, setUser] = useState("");
const form = useForm({ const [pass, setPass] = useState("");
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<HTMLFormElement>}
*/
return ( return (
<form <div>
onSubmit={(ev) => { <p>Usuario</p>
ev.preventDefault(); <input
form.handleSubmit(); type="text"
}} className="bg-gray-200"
> onChange={(ev) => setUser(ev.target.value)}
<form.Field
name="user"
children={(field) => {
return (
<>
<p>Usuario</p>
<input
type="text"
className="bg-gray-200"
value={field.value}
onChange={(ev) => field.handleChange(ev.target.value)}
/>
{!field.state.meta.isValid && (
<p className="text-red-300">
{field.state.meta.errors.map((e) => e.message).join(", ")}
</p>
)}
</>
);
}}
/> />
<p>Contraseña</p> <p>Contraseña</p>
<form.Field <input
name="password" type="password"
children={(field) => { className="bg-gray-200"
return ( onChange={(ev) => setPass(ev.target.value)}
<>
<input
type="password"
className="bg-gray-200"
value={field.value}
onChange={(ev) => field.handleChange(ev.target.value)}
/>
{!field.state.meta.isValid && (
<p className="text-red-300">
{field.state.meta.errors.map((e) => e.message).join(", ")}
</p>
)}
</>
);
}}
/> />
<button className="p-2 rounded-md bg-teal-400" type="submit"> <p>Confirmar contraseña</p>
<button
className="p-2 rounded-md bg-teal-400"
onClick={() => {
if (pass.length < 6) {
alert("La contraseña debe tener por lo menos 6 caracteres");
return;
}
if (user.length === 0) {
alert("Se requiere un nombre de usuario");
}
alert("Autenticacion exitosa");
}}
>
Iniciar sesión Iniciar sesión
</button> </button>
</form> </div>
); );
}; };

View file

@ -1,101 +1,50 @@
import { useForm } from "@tanstack/react-form"; import { useState } from "react";
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 Register = () => {
const { register } = useContext(UserContext); const [user, setUser] = useState("");
const form = useForm({ const [pass, setPass] = useState("");
defaultValues: { user: "", password: "", confirmPassword: "" }, const [confirmPass, setConfirmPass] = useState("");
validators: { onChange: registerSchema },
onSubmit: ({ value: { user, password, confirmPassword } }) => {
try {
register(user, password);
} catch (error) {
alert("Error de registro");
}
},
});
return ( return (
<form <div>
onSubmit={(ev) => {
ev.preventDefault();
form.handleSubmit();
}}
>
<p>Usuario</p> <p>Usuario</p>
<form.Field <input
name="user" type="text"
children={(field) => ( className="bg-gray-200"
<> onChange={(ev) => setUser(ev.target.value)}
<input
type="text"
className="bg-gray-200"
value={field.value}
onChange={(ev) => field.handleChange(ev.target.value)}
/>
{!field.state.meta.isValid && (
<p className="text-red-300">
{field.state.meta.errors.map((e) => e.message).join(", ")}
</p>
)}
</>
)}
/> />
<p>Contraseña</p> <p>Contraseña</p>
<form.Field <input
name="password" type="password"
children={(field) => ( className="bg-gray-200"
<> onChange={(ev) => setPass(ev.target.value)}
<input
type="password"
className="bg-gray-200"
onChange={(ev) => field.handleChange(ev.target.value)}
/>
{!field.state.meta.isValid && (
<p className="text-red-300">
{field.state.meta.errors.map((e) => e.message).join(", ")}
</p>
)}
</>
)}
/> />
<p>Confirmar contraseña</p> <p>Confirmar contraseña</p>
<form.Field <input
name="confirmPassword" type="password"
children={(field) => ( className="bg-gray-200"
<> onChange={(ev) => setConfirmPass(ev.target.value)}
<input
type="password"
className="bg-gray-200"
onChange={(ev) => field.handleChange(ev.target.value)}
/>
{!field.state.meta.isValid && (
<p className="text-red-300">
{field.state.meta.errors.map((e) => e.message).join(", ")}
</p>
)}
</>
)}
/> />
<br /> <br />
<button className="p-2 rounded-md bg-teal-400" type="submit"> <button
className="p-2 rounded-md bg-teal-400"
onClick={() => {
if (pass.length < 6) {
alert("La contraseña debe tener por lo menos 6 caracteres");
return;
}
if (pass !== confirmPass) {
alert("Las contraseñas no coinciden");
return;
}
if (user.length === 0) {
alert("Se requiere un nombre de usuario");
}
alert("Autenticacion exitosa");
}}
>
Registrarse Registrarse
</button> </button>
</form> </div>
); );
}; };