Compare commits
4 commits
69deb9c18d
...
fab315eece
| Author | SHA1 | Date | |
|---|---|---|---|
| fab315eece | |||
| e2cdab8b11 | |||
| e02de65ec2 | |||
| 9b2a8260fe |
8 changed files with 317 additions and 70 deletions
3
.oxfmtrc.json
Normal file
3
.oxfmtrc.json
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"ignorePatterns": []
|
||||||
|
}
|
||||||
100
.oxlintrc.json
Normal file
100
.oxlintrc.json
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
{
|
||||||
|
"$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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
[tools]
|
[tools]
|
||||||
aube = "latest"
|
aube = "latest"
|
||||||
biome = "latest"
|
mprocs = "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"
|
||||||
|
|
@ -11,4 +13,4 @@ run = "aube dev"
|
||||||
|
|
||||||
[tasks.lint]
|
[tasks.lint]
|
||||||
description = "Lintea los archivos"
|
description = "Lintea los archivos"
|
||||||
run = "biome lint ."
|
run = "oxlint"
|
||||||
|
|
|
||||||
5
mprocs.yaml
Normal file
5
mprocs.yaml
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
procs:
|
||||||
|
frontend:
|
||||||
|
shell: just start-frontend
|
||||||
|
backend:
|
||||||
|
shell: just start-backend
|
||||||
|
|
@ -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 />} />
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,47 @@ import { createContext, useState } from "react";
|
||||||
export const UserContext = createContext();
|
export const UserContext = createContext();
|
||||||
|
|
||||||
const UserProvider = ({ children }) => {
|
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 = () => {
|
const logout = () => {
|
||||||
setToken(false);
|
localStorage.removeItem("loginToken");
|
||||||
|
setToken(null);
|
||||||
|
setEmail(null);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<UserContext.Provider value={{ token, setToken, logout }}>
|
<UserContext.Provider
|
||||||
|
value={{ token, setToken, email, setEmail, login, register, logout }}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</UserContext.Provider>
|
</UserContext.Provider>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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 Login = () => {
|
||||||
const [user, setUser] = useState("");
|
const { login } = useContext(UserContext);
|
||||||
const [pass, setPass] = useState("");
|
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<HTMLFormElement>}
|
||||||
|
*/
|
||||||
return (
|
return (
|
||||||
<div>
|
<form
|
||||||
<p>Usuario</p>
|
onSubmit={(ev) => {
|
||||||
<input
|
ev.preventDefault();
|
||||||
type="text"
|
form.handleSubmit();
|
||||||
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>
|
||||||
<input
|
<form.Field
|
||||||
type="password"
|
name="password"
|
||||||
className="bg-gray-200"
|
children={(field) => {
|
||||||
onChange={(ev) => setPass(ev.target.value)}
|
return (
|
||||||
/>
|
<>
|
||||||
<p>Confirmar contraseña</p>
|
<input
|
||||||
<button
|
type="password"
|
||||||
className="p-2 rounded-md bg-teal-400"
|
className="bg-gray-200"
|
||||||
onClick={() => {
|
value={field.value}
|
||||||
if (pass.length < 6) {
|
onChange={(ev) => field.handleChange(ev.target.value)}
|
||||||
alert("La contraseña debe tener por lo menos 6 caracteres");
|
/>
|
||||||
return;
|
{!field.state.meta.isValid && (
|
||||||
}
|
<p className="text-red-300">
|
||||||
if (user.length === 0) {
|
{field.state.meta.errors.map((e) => e.message).join(", ")}
|
||||||
alert("Se requiere un nombre de usuario");
|
</p>
|
||||||
}
|
)}
|
||||||
alert("Autenticacion exitosa");
|
</>
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
|
<button className="p-2 rounded-md bg-teal-400" type="submit">
|
||||||
Iniciar sesión
|
Iniciar sesión
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 Register = () => {
|
||||||
const [user, setUser] = useState("");
|
const { register } = useContext(UserContext);
|
||||||
const [pass, setPass] = useState("");
|
const form = useForm({
|
||||||
const [confirmPass, setConfirmPass] = useState("");
|
defaultValues: { user: "", password: "", confirmPassword: "" },
|
||||||
|
validators: { onChange: registerSchema },
|
||||||
|
onSubmit: ({ value: { user, password, confirmPassword } }) => {
|
||||||
|
try {
|
||||||
|
register(user, password);
|
||||||
|
} catch (error) {
|
||||||
|
alert("Error de registro");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
return (
|
return (
|
||||||
<div>
|
<form
|
||||||
|
onSubmit={(ev) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
form.handleSubmit();
|
||||||
|
}}
|
||||||
|
>
|
||||||
<p>Usuario</p>
|
<p>Usuario</p>
|
||||||
<input
|
<form.Field
|
||||||
type="text"
|
name="user"
|
||||||
className="bg-gray-200"
|
children={(field) => (
|
||||||
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>
|
||||||
<input
|
<form.Field
|
||||||
type="password"
|
name="password"
|
||||||
className="bg-gray-200"
|
children={(field) => (
|
||||||
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>
|
||||||
<input
|
<form.Field
|
||||||
type="password"
|
name="confirmPassword"
|
||||||
className="bg-gray-200"
|
children={(field) => (
|
||||||
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
|
<button className="p-2 rounded-md bg-teal-400" type="submit">
|
||||||
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>
|
||||||
</div>
|
</form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue