feat: redirigir usuarios registrados a Home en login y register
This commit is contained in:
parent
7c2c8e7470
commit
69deb9c18d
27 changed files with 449 additions and 394 deletions
2
.tokeignore
Normal file
2
.tokeignore
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
pnpm-lock.yaml
|
||||||
|
backend/package-lock.json
|
||||||
|
|
@ -1,95 +1,95 @@
|
||||||
import "dotenv/config";
|
import "dotenv/config";
|
||||||
import jwt from "jsonwebtoken";
|
import jwt from "jsonwebtoken";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { authModel } from "../models/auth.model.js";
|
import { authModel } from "../models/auth.model.js";
|
||||||
import { isValidEmail } from "../utils/validators/email.validate.js";
|
import { isValidEmail } from "../utils/validators/email.validate.js";
|
||||||
|
|
||||||
const login = async (req, res) => {
|
const login = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { email = "", password = "" } = req.body;
|
const { email = "", password = "" } = req.body;
|
||||||
|
|
||||||
if (!email.trim() || !password.trim()) {
|
if (!email.trim() || !password.trim()) {
|
||||||
return res.status(400).json({ error: "Email and password are required" });
|
return res.status(400).json({ error: "Email and password are required" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isValidEmail(email)) {
|
if (!isValidEmail(email)) {
|
||||||
return res.status(400).json({ error: "Invalid email" });
|
return res.status(400).json({ error: "Invalid email" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (password.length < 6) {
|
if (password.length < 6) {
|
||||||
return res
|
return res
|
||||||
.status(400)
|
.status(400)
|
||||||
.json({ error: "Password must be at least 6 characters" });
|
.json({ error: "Password must be at least 6 characters" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = await authModel.getUserByEmail(email);
|
const user = await authModel.getUserByEmail(email);
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return res.status(400).json({ error: "User not found" });
|
return res.status(400).json({ error: "User not found" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.password !== password) {
|
if (user.password !== password) {
|
||||||
return res.status(400).json({ error: "Invalid password" });
|
return res.status(400).json({ error: "Invalid password" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const payload = { email, id: user.id };
|
const payload = { email, id: user.id };
|
||||||
const token = jwt.sign(payload, process.env.JWT_SECRET);
|
const token = jwt.sign(payload, process.env.JWT_SECRET);
|
||||||
|
|
||||||
return res.json({ email, token });
|
return res.json({ email, token });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// console.log(error);
|
// console.log(error);
|
||||||
return res.status(500).json({ error: "Server error" });
|
return res.status(500).json({ error: "Server error" });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const register = async (req, res) => {
|
const register = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { email = "", password = "" } = req.body;
|
const { email = "", password = "" } = req.body;
|
||||||
|
|
||||||
if (!email.trim() || !password.trim()) {
|
if (!email.trim() || !password.trim()) {
|
||||||
return res.status(400).json({ error: "Email and password are required" });
|
return res.status(400).json({ error: "Email and password are required" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isValidEmail(email)) {
|
if (!isValidEmail(email)) {
|
||||||
return res.status(400).json({ error: "Invalid email" });
|
return res.status(400).json({ error: "Invalid email" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (password.length < 6) {
|
if (password.length < 6) {
|
||||||
return res
|
return res
|
||||||
.status(400)
|
.status(400)
|
||||||
.json({ error: "Password must be at least 6 characters" });
|
.json({ error: "Password must be at least 6 characters" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = await authModel.getUserByEmail(email);
|
const user = await authModel.getUserByEmail(email);
|
||||||
if (user) {
|
if (user) {
|
||||||
return res.status(400).json({ error: "User already exists" });
|
return res.status(400).json({ error: "User already exists" });
|
||||||
}
|
}
|
||||||
const newUser = { email, password, id: nanoid() };
|
const newUser = { email, password, id: nanoid() };
|
||||||
await authModel.addUser(newUser);
|
await authModel.addUser(newUser);
|
||||||
|
|
||||||
const payload = { email, id: newUser.id };
|
const payload = { email, id: newUser.id };
|
||||||
const token = jwt.sign(payload, process.env.JWT_SECRET);
|
const token = jwt.sign(payload, process.env.JWT_SECRET);
|
||||||
|
|
||||||
return res.json({ email, token });
|
return res.json({ email, token });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// console.log(error);
|
// console.log(error);
|
||||||
return res.status(500).json({ error: "Server error" });
|
return res.status(500).json({ error: "Server error" });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const me = async (req, res) => {
|
const me = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { email } = req.user;
|
const { email } = req.user;
|
||||||
const user = await authModel.getUserByEmail(email);
|
const user = await authModel.getUserByEmail(email);
|
||||||
return res.json({ email, id: user.id });
|
return res.json({ email, id: user.id });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// console.log(error);
|
// console.log(error);
|
||||||
return res.status(500).json({ error: "Server error" });
|
return res.status(500).json({ error: "Server error" });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const authController = {
|
export const authController = {
|
||||||
login,
|
login,
|
||||||
register,
|
register,
|
||||||
me,
|
me,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
const create = async (req, res) => {
|
const create = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
return res.json({
|
return res.json({
|
||||||
message: "Checkout successful",
|
message: "Checkout successful",
|
||||||
cart: req.body,
|
cart: req.body,
|
||||||
user: req.user,
|
user: req.user,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// console.log(error);
|
// console.log(error);
|
||||||
return res.status(500).json({ error: "Server error" });
|
return res.status(500).json({ error: "Server error" });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const checkoutController = {
|
export const checkoutController = {
|
||||||
create,
|
create,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,20 @@
|
||||||
import { pizzaModel } from "../models/pizza.model.js";
|
import { pizzaModel } from "../models/pizza.model.js";
|
||||||
|
|
||||||
const readPizzas = async (req, res) => {
|
const readPizzas = async (req, res) => {
|
||||||
const pizzas = await pizzaModel.getPizzas();
|
const pizzas = await pizzaModel.getPizzas();
|
||||||
res.json(pizzas);
|
res.json(pizzas);
|
||||||
};
|
};
|
||||||
|
|
||||||
const readPizza = async (req, res) => {
|
const readPizza = async (req, res) => {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
const pizza = await pizzaModel.getPizza(id.toLowerCase());
|
const pizza = await pizzaModel.getPizza(id.toLowerCase());
|
||||||
if (!pizza) {
|
if (!pizza) {
|
||||||
return res.status(404).json({ message: "Pizza not found" });
|
return res.status(404).json({ message: "Pizza not found" });
|
||||||
}
|
}
|
||||||
res.json(pizza);
|
res.json(pizza);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const pizzaController = {
|
export const pizzaController = {
|
||||||
readPizzas,
|
readPizzas,
|
||||||
readPizza,
|
readPizza,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,50 +1,50 @@
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"desc": "La pizza napolitana, de masa tierna y delgada pero bordes altos, es la versión propia de la cocina napolitana de la pizza redonda. El término pizza napoletana, por su importancia histórica o regional, se emplea en algunas zonas como sinónimo de pizza tonda.",
|
"desc": "La pizza napolitana, de masa tierna y delgada pero bordes altos, es la versión propia de la cocina napolitana de la pizza redonda. El término pizza napoletana, por su importancia histórica o regional, se emplea en algunas zonas como sinónimo de pizza tonda.",
|
||||||
"id": "p001",
|
"id": "p001",
|
||||||
"img": "https://firebasestorage.googleapis.com/v0/b/apis-varias-mias.appspot.com/o/pizzeria%2Fpizza-1239077_640_cl.jpg?alt=media&token=6a9a33da-5c00-49d4-9080-784dcc87ec2c",
|
"img": "https://firebasestorage.googleapis.com/v0/b/apis-varias-mias.appspot.com/o/pizzeria%2Fpizza-1239077_640_cl.jpg?alt=media&token=6a9a33da-5c00-49d4-9080-784dcc87ec2c",
|
||||||
"ingredients": ["mozzarella", "tomates", "jamón", "orégano"],
|
"ingredients": ["mozzarella", "tomates", "jamón", "orégano"],
|
||||||
"name": "napolitana",
|
"name": "napolitana",
|
||||||
"price": 5950
|
"price": 5950
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"desc": "La pizza es una preparación culinaria que consiste en un pan plano, habitualmente de forma circular, elaborado con harina de trigo, levadura, agua y sal (a veces aceite de oliva) que comúnmente se cubre con salsa de tomate, queso y otros muchos ingredientes, y que se hornea a alta temperatura, tradicionalmente en un horno de leña.",
|
"desc": "La pizza es una preparación culinaria que consiste en un pan plano, habitualmente de forma circular, elaborado con harina de trigo, levadura, agua y sal (a veces aceite de oliva) que comúnmente se cubre con salsa de tomate, queso y otros muchos ingredientes, y que se hornea a alta temperatura, tradicionalmente en un horno de leña.",
|
||||||
"id": "p002",
|
"id": "p002",
|
||||||
"img": "https://firebasestorage.googleapis.com/v0/b/apis-varias-mias.appspot.com/o/pizzeria%2Fcheese-164872_640_com.jpg?alt=media&token=18b2b821-4d0d-43f2-a1c6-8c57bc388fab",
|
"img": "https://firebasestorage.googleapis.com/v0/b/apis-varias-mias.appspot.com/o/pizzeria%2Fcheese-164872_640_com.jpg?alt=media&token=18b2b821-4d0d-43f2-a1c6-8c57bc388fab",
|
||||||
"ingredients": ["mozzarella", "tomates", "jamón", "choricillo"],
|
"ingredients": ["mozzarella", "tomates", "jamón", "choricillo"],
|
||||||
"name": "española",
|
"name": "española",
|
||||||
"price": 7250
|
"price": 7250
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"desc": "La pizza es una preparación culinaria que consiste en un pan plano, habitualmente de forma circular, elaborado con harina de trigo, levadura, agua y sal (a veces aceite de oliva) que comúnmente se cubre con salsa de tomate, queso y otros muchos ingredientes, y que se hornea a alta temperatura, tradicionalmente en un horno de leña.",
|
"desc": "La pizza es una preparación culinaria que consiste en un pan plano, habitualmente de forma circular, elaborado con harina de trigo, levadura, agua y sal (a veces aceite de oliva) que comúnmente se cubre con salsa de tomate, queso y otros muchos ingredientes, y que se hornea a alta temperatura, tradicionalmente en un horno de leña.",
|
||||||
"id": "p003",
|
"id": "p003",
|
||||||
"img": "https://firebasestorage.googleapis.com/v0/b/apis-varias-mias.appspot.com/o/pizzeria%2Fpizza-1239077_640_com.jpg?alt=media&token=e7cde87a-08d5-4040-ac54-90f6c31eb3e3",
|
"img": "https://firebasestorage.googleapis.com/v0/b/apis-varias-mias.appspot.com/o/pizzeria%2Fpizza-1239077_640_com.jpg?alt=media&token=e7cde87a-08d5-4040-ac54-90f6c31eb3e3",
|
||||||
"ingredients": ["mozzarella", "tomates", "salame", "orégano"],
|
"ingredients": ["mozzarella", "tomates", "salame", "orégano"],
|
||||||
"name": "salame",
|
"name": "salame",
|
||||||
"price": 5990
|
"price": 5990
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"desc": "La pizza es una preparación culinaria que consiste en un pan plano, habitualmente de forma circular, elaborado con harina de trigo, levadura, agua y sal (a veces aceite de oliva) que comúnmente se cubre con salsa de tomate, queso y otros muchos ingredientes, y que se hornea a alta temperatura, tradicionalmente en un horno de leña.",
|
"desc": "La pizza es una preparación culinaria que consiste en un pan plano, habitualmente de forma circular, elaborado con harina de trigo, levadura, agua y sal (a veces aceite de oliva) que comúnmente se cubre con salsa de tomate, queso y otros muchos ingredientes, y que se hornea a alta temperatura, tradicionalmente en un horno de leña.",
|
||||||
"id": "p004",
|
"id": "p004",
|
||||||
"img": "https://firebasestorage.googleapis.com/v0/b/apis-varias-mias.appspot.com/o/pizzeria%2Fpizza-2000595_640_c.jpg?alt=media&token=61325b6e-a1e0-441e-b3b5-7335ba13e8be",
|
"img": "https://firebasestorage.googleapis.com/v0/b/apis-varias-mias.appspot.com/o/pizzeria%2Fpizza-2000595_640_c.jpg?alt=media&token=61325b6e-a1e0-441e-b3b5-7335ba13e8be",
|
||||||
"ingredients": ["mozzarella", "salame", "aceitunas", "champiñones"],
|
"ingredients": ["mozzarella", "salame", "aceitunas", "champiñones"],
|
||||||
"name": "cuatro estaciones",
|
"name": "cuatro estaciones",
|
||||||
"price": 9590
|
"price": 9590
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"desc": "La pizza es una preparación culinaria que consiste en un pan plano, habitualmente de forma circular, elaborado con harina de trigo, levadura, agua y sal (a veces aceite de oliva) que comúnmente se cubre con salsa de tomate, queso y otros muchos ingredientes, y que se hornea a alta temperatura, tradicionalmente en un horno de leña.",
|
"desc": "La pizza es una preparación culinaria que consiste en un pan plano, habitualmente de forma circular, elaborado con harina de trigo, levadura, agua y sal (a veces aceite de oliva) que comúnmente se cubre con salsa de tomate, queso y otros muchos ingredientes, y que se hornea a alta temperatura, tradicionalmente en un horno de leña.",
|
||||||
"id": "p005",
|
"id": "p005",
|
||||||
"img": "https://firebasestorage.googleapis.com/v0/b/apis-varias-mias.appspot.com/o/pizzeria%2Fpizza-salame.jpg?alt=media&token=ab3d4bf8-01f2-4810-982b-bd7fb6b517b2",
|
"img": "https://firebasestorage.googleapis.com/v0/b/apis-varias-mias.appspot.com/o/pizzeria%2Fpizza-salame.jpg?alt=media&token=ab3d4bf8-01f2-4810-982b-bd7fb6b517b2",
|
||||||
"ingredients": ["mozzarella", "tomates cherry", "bacon", "orégano"],
|
"ingredients": ["mozzarella", "tomates cherry", "bacon", "orégano"],
|
||||||
"name": "bacon",
|
"name": "bacon",
|
||||||
"price": 6450
|
"price": 6450
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"desc": "La pizza es una preparación culinaria que consiste en un pan plano, habitualmente de forma circular, elaborado con harina de trigo, levadura, agua y sal (a veces aceite de oliva) que comúnmente se cubre con salsa de tomate, queso y otros muchos ingredientes, y que se hornea a alta temperatura, tradicionalmente en un horno de leña.",
|
"desc": "La pizza es una preparación culinaria que consiste en un pan plano, habitualmente de forma circular, elaborado con harina de trigo, levadura, agua y sal (a veces aceite de oliva) que comúnmente se cubre con salsa de tomate, queso y otros muchos ingredientes, y que se hornea a alta temperatura, tradicionalmente en un horno de leña.",
|
||||||
"id": "p006",
|
"id": "p006",
|
||||||
"img": "https://firebasestorage.googleapis.com/v0/b/apis-varias-mias.appspot.com/o/pizzeria%2Fpizza-2000595_640_c.jpg?alt=media&token=61325b6e-a1e0-441e-b3b5-7335ba13e8be",
|
"img": "https://firebasestorage.googleapis.com/v0/b/apis-varias-mias.appspot.com/o/pizzeria%2Fpizza-2000595_640_c.jpg?alt=media&token=61325b6e-a1e0-441e-b3b5-7335ba13e8be",
|
||||||
"ingredients": ["mozzarella", "pimientos", "pollo grillé", "orégano"],
|
"ingredients": ["mozzarella", "pimientos", "pollo grillé", "orégano"],
|
||||||
"name": "pollo picante",
|
"name": "pollo picante",
|
||||||
"price": 8500
|
"price": 8500
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"email": "test@test.com",
|
"email": "test@test.com",
|
||||||
"password": "123123",
|
"password": "123123",
|
||||||
"id": "UYz_2Vy9rNw7uELQ7AZ8D"
|
"id": "UYz_2Vy9rNw7uELQ7AZ8D"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,24 @@
|
||||||
import cors from "cors";
|
import cors from "cors";
|
||||||
import "dotenv/config";
|
import "dotenv/config";
|
||||||
import express from "express";
|
import express from "express";
|
||||||
|
|
||||||
import authRoute from "./routes/auth.route.js";
|
import authRoute from "./routes/auth.route.js";
|
||||||
import checkoutRoute from "./routes/checkout.route.js";
|
import checkoutRoute from "./routes/checkout.route.js";
|
||||||
import pizzaRoute from "./routes/pizza.route.js";
|
import pizzaRoute from "./routes/pizza.route.js";
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
app.use(cors());
|
app.use(cors());
|
||||||
|
|
||||||
app.use("/api/auth", authRoute);
|
app.use("/api/auth", authRoute);
|
||||||
app.use("/api/pizzas", pizzaRoute);
|
app.use("/api/pizzas", pizzaRoute);
|
||||||
app.use("/api/checkouts", checkoutRoute);
|
app.use("/api/checkouts", checkoutRoute);
|
||||||
app.use((_, res) => {
|
app.use((_, res) => {
|
||||||
res.status(404).json({ error: "Not Found" });
|
res.status(404).json({ error: "Not Found" });
|
||||||
});
|
});
|
||||||
|
|
||||||
const PORT = process.env.PORT || 5000;
|
const PORT = process.env.PORT || 5000;
|
||||||
app.listen(PORT, () => {
|
app.listen(PORT, () => {
|
||||||
console.log(`Server is running on port http://localhost:${PORT}`);
|
console.log(`Server is running on port http://localhost:${PORT}`);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
import "dotenv/config";
|
import "dotenv/config";
|
||||||
import jwt from "jsonwebtoken";
|
import jwt from "jsonwebtoken";
|
||||||
|
|
||||||
export const authMiddleware = (req, res, next) => {
|
export const authMiddleware = (req, res, next) => {
|
||||||
const token = req.headers.authorization?.split(" ")[1];
|
const token = req.headers.authorization?.split(" ")[1];
|
||||||
if (!token) {
|
if (!token) {
|
||||||
return res.status(401).json({ error: "No token provided" });
|
return res.status(401).json({ error: "No token provided" });
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const payload = jwt.verify(token, process.env.JWT_SECRET);
|
const payload = jwt.verify(token, process.env.JWT_SECRET);
|
||||||
req.user = payload;
|
req.user = payload;
|
||||||
next();
|
next();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// console.log(error);
|
// console.log(error);
|
||||||
return res.status(401).send({ error: "Invalid token" });
|
return res.status(401).send({ error: "Invalid token" });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,19 @@
|
||||||
import { readFile, writeFile } from "node:fs/promises";
|
import { readFile, writeFile } from "node:fs/promises";
|
||||||
|
|
||||||
const getUserByEmail = async (email) => {
|
const getUserByEmail = async (email) => {
|
||||||
const data = await readFile("db/users.json", "utf-8");
|
const data = await readFile("db/users.json", "utf-8");
|
||||||
const users = JSON.parse(data);
|
const users = JSON.parse(data);
|
||||||
return users.find((user) => user.email === email);
|
return users.find((user) => user.email === email);
|
||||||
};
|
};
|
||||||
|
|
||||||
const addUser = async (newUser) => {
|
const addUser = async (newUser) => {
|
||||||
const data = await readFile("db/users.json", "utf-8");
|
const data = await readFile("db/users.json", "utf-8");
|
||||||
const users = JSON.parse(data);
|
const users = JSON.parse(data);
|
||||||
users.push(newUser);
|
users.push(newUser);
|
||||||
await writeFile("db/users.json", JSON.stringify(users, null, 2));
|
await writeFile("db/users.json", JSON.stringify(users, null, 2));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const authModel = {
|
export const authModel = {
|
||||||
getUserByEmail,
|
getUserByEmail,
|
||||||
addUser,
|
addUser,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
import { readFile } from "node:fs/promises";
|
import { readFile } from "node:fs/promises";
|
||||||
|
|
||||||
const getPizzas = async () => {
|
const getPizzas = async () => {
|
||||||
const data = await readFile("db/pizzas.json", "utf-8");
|
const data = await readFile("db/pizzas.json", "utf-8");
|
||||||
return JSON.parse(data);
|
return JSON.parse(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPizza = async (id) => {
|
const getPizza = async (id) => {
|
||||||
const pizzas = await getPizzas();
|
const pizzas = await getPizzas();
|
||||||
return pizzas.find((pizza) => pizza.id === id);
|
return pizzas.find((pizza) => pizza.id === id);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const pizzaModel = {
|
export const pizzaModel = {
|
||||||
getPizzas,
|
getPizzas,
|
||||||
getPizza,
|
getPizza,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,24 @@
|
||||||
{
|
{
|
||||||
"name": "simple-api-jwt",
|
"name": "simple-api-jwt",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node index.js",
|
"start": "node index.js",
|
||||||
"dev": "nodemon index.js"
|
"dev": "nodemon index.js"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "bluuweb",
|
"author": "bluuweb",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"nanoid": "^5.0.6"
|
"nanoid": "^5.0.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"nodemon": "^3.1.0"
|
"nodemon": "^3.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import { Router } from "express";
|
import { Router } from "express";
|
||||||
import { authController } from "../controllers/auth.controller.js";
|
import { authController } from "../controllers/auth.controller.js";
|
||||||
import { authMiddleware } from "../middlewares/auth.middleware.js";
|
import { authMiddleware } from "../middlewares/auth.middleware.js";
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.post("/login", authController.login);
|
router.post("/login", authController.login);
|
||||||
router.post("/register", authController.register);
|
router.post("/register", authController.register);
|
||||||
router.get("/me", authMiddleware, authController.me);
|
router.get("/me", authMiddleware, authController.me);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import { Router } from "express";
|
import { Router } from "express";
|
||||||
import { checkoutController } from "../controllers/checkout.controller.js";
|
import { checkoutController } from "../controllers/checkout.controller.js";
|
||||||
import { authMiddleware } from "../middlewares/auth.middleware.js";
|
import { authMiddleware } from "../middlewares/auth.middleware.js";
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.use(authMiddleware);
|
router.use(authMiddleware);
|
||||||
router.post("/", checkoutController.create);
|
router.post("/", checkoutController.create);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import { Router } from "express";
|
import { Router } from "express";
|
||||||
import { pizzaController } from "../controllers/pizza.controller.js";
|
import { pizzaController } from "../controllers/pizza.controller.js";
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.get("/", pizzaController.readPizzas);
|
router.get("/", pizzaController.readPizzas);
|
||||||
router.get("/:id", pizzaController.readPizza);
|
router.get("/:id", pizzaController.readPizza);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
export const isValidEmail = (email) => {
|
export const isValidEmail = (email) => {
|
||||||
const regexEmail =
|
const regexEmail =
|
||||||
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||||
return regexEmail.test(email);
|
return regexEmail.test(email);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
62
package.json
62
package.json
|
|
@ -1,33 +1,33 @@
|
||||||
{
|
{
|
||||||
"name": "desafio",
|
"name": "desafio",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"lint": "biome check",
|
"lint": "biome check",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"radashi": "^12.7.2",
|
"radashi": "^12.7.2",
|
||||||
"react": "^19.2.4",
|
"react": "^19.2.4",
|
||||||
"react-dom": "^19.2.4",
|
"react-dom": "^19.2.4",
|
||||||
"react-router-dom": "^7.14.2"
|
"react-router-dom": "^7.14.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "^20.5.0",
|
"@commitlint/cli": "^20.5.0",
|
||||||
"@commitlint/config-conventional": "^20.5.0",
|
"@commitlint/config-conventional": "^20.5.0",
|
||||||
"@eslint/js": "^9.39.4",
|
"@eslint/js": "^9.39.4",
|
||||||
"@types/react": "^19.2.14",
|
"@types/react": "^19.2.14",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@unocss/reset": "^66.6.7",
|
"@unocss/reset": "^66.6.7",
|
||||||
"@vitejs/plugin-react": "^6.0.1",
|
"@vitejs/plugin-react": "^6.0.1",
|
||||||
"eslint": "^9.39.4",
|
"eslint": "^9.39.4",
|
||||||
"eslint-plugin-react-hooks": "^7.0.1",
|
"eslint-plugin-react-hooks": "^7.0.1",
|
||||||
"eslint-plugin-react-refresh": "^0.5.2",
|
"eslint-plugin-react-refresh": "^0.5.2",
|
||||||
"globals": "^17.4.0",
|
"globals": "^17.4.0",
|
||||||
"unocss": "^66.6.7",
|
"unocss": "^66.6.7",
|
||||||
"vite": "^8.0.1"
|
"vite": "^8.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
28
src/App.jsx
28
src/App.jsx
|
|
@ -1,28 +1,40 @@
|
||||||
import { Route, Routes } from "react-router-dom";
|
import { useContext } from "react";
|
||||||
|
import { Navigate, Route, Routes } from "react-router-dom";
|
||||||
|
import "./App.css";
|
||||||
|
import CartProvider from "./context/CartContext.jsx";
|
||||||
|
import { UserContext } from "./context/UserContext.jsx";
|
||||||
import Footer from "./Footer";
|
import Footer from "./Footer";
|
||||||
import Navbar from "./Navbar";
|
import Navbar from "./Navbar";
|
||||||
import Cart from "./pages/Cart";
|
import Cart from "./pages/Cart";
|
||||||
import Home from "./pages/Home";
|
import Home from "./pages/Home";
|
||||||
import Login from "./pages/Login";
|
import Login from "./pages/Login";
|
||||||
import Register from "./pages/Register";
|
|
||||||
import "./App.css";
|
|
||||||
import NotFound from "./pages/NotFound";
|
import NotFound from "./pages/NotFound";
|
||||||
import Pizza from "./pages/Pizza";
|
import Pizza from "./pages/Pizza";
|
||||||
import Profile from "./pages/Profile.jsx";
|
import Profile from "./pages/Profile.jsx";
|
||||||
import CartProvider from "./context/CartContext.jsx";
|
import Register from "./pages/Register";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
const { token } = useContext(UserContext);
|
||||||
return (
|
return (
|
||||||
<CartProvider>
|
<CartProvider>
|
||||||
<Navbar />
|
<Navbar />
|
||||||
<main className="pb-4">
|
<main className="pb-4">
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<Home />} />
|
<Route path="/" element={<Home />} />
|
||||||
<Route path="/login" element={<Login />} />
|
<Route
|
||||||
<Route path="/register" element={<Register />} />
|
path="/login"
|
||||||
<Route path="/pizza/p001" element={<Pizza />} />
|
element={!token ? <Navigate to="/" /> : <Login />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/register"
|
||||||
|
element={!token ? <Navigate to="/" /> : <Register />}
|
||||||
|
/>
|
||||||
|
<Route path="/pizza/:id" element={<Pizza />} />
|
||||||
<Route path="/cart" element={<Cart />} />
|
<Route path="/cart" element={<Cart />} />
|
||||||
<Route path="/profile" element={<Profile />} />
|
<Route
|
||||||
|
path="/profile"
|
||||||
|
element={token ? <Profile /> : <Navigate to="/login" />}
|
||||||
|
/>
|
||||||
<Route path="*" element={<NotFound />} />
|
<Route path="*" element={<NotFound />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</main>
|
</main>
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
import { CartContext } from "./context/CartContext";
|
import { CartContext } from "./context/CartContext";
|
||||||
|
import { UserContext } from "./context/UserContext";
|
||||||
import "./Navbar.css";
|
import "./Navbar.css";
|
||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
const Navbar = () => {
|
const Navbar = () => {
|
||||||
const { cart } = useContext(CartContext);
|
const { getTotal } = useContext(CartContext);
|
||||||
const token = false;
|
const { logout, token } = useContext(UserContext);
|
||||||
const total = cart.reduce((acc, it) => acc + it.price * it.count, 0);
|
const total = getTotal();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className="bg-green-700 text-white flex items-center justify-between gap-4">
|
<nav className="bg-green-700 text-white flex items-center justify-between gap-4">
|
||||||
|
|
@ -17,7 +18,7 @@ const Navbar = () => {
|
||||||
{token ? (
|
{token ? (
|
||||||
<>
|
<>
|
||||||
<Link to="/profile">Profile</Link>
|
<Link to="/profile">Profile</Link>
|
||||||
<button>Logout</button>
|
<button onClick={logout}>Logout</button>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
import "./CardPizza.css";
|
import "./CardPizza.css";
|
||||||
|
import * as R from "radashi";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { CartContext } from "../context/CartContext";
|
import { CartContext } from "../context/CartContext";
|
||||||
import * as R from "radashi";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
|
@ -10,7 +10,7 @@ import * as R from "radashi";
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const CardPizza = (props) => {
|
const CardPizza = (props) => {
|
||||||
const { cart, setCart } = useContext(CartContext);
|
const { addToCart } = useContext(CartContext);
|
||||||
return (
|
return (
|
||||||
<article className="card-pizza">
|
<article className="card-pizza">
|
||||||
<img src={props.img} />
|
<img src={props.img} />
|
||||||
|
|
@ -35,18 +35,7 @@ const CardPizza = (props) => {
|
||||||
Ver más
|
Ver más
|
||||||
</Link>
|
</Link>
|
||||||
<button
|
<button
|
||||||
onClick={() =>
|
onClick={() => addToCart(props)}
|
||||||
setCart((cart) => {
|
|
||||||
const pizza = cart.find((p) => p.id === props.id);
|
|
||||||
if (pizza) {
|
|
||||||
return cart.map((p) =>
|
|
||||||
p.id === props.id ? { ...p, count: p.count + 1 } : p,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return [...cart, { ...R.omit(props, ["key"]), count: 1 }];
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
className="bg-black text-white rounded-md px-4"
|
className="bg-black text-white rounded-md px-4"
|
||||||
>
|
>
|
||||||
Añadir
|
Añadir
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,27 @@
|
||||||
import { createContext, useState } from "react";
|
import { createContext, useState } from "react";
|
||||||
|
import * as R from "radashi";
|
||||||
|
|
||||||
export const CartContext = createContext();
|
export const CartContext = createContext();
|
||||||
|
|
||||||
const CartProvider = ({ children }) => {
|
const CartProvider = ({ children }) => {
|
||||||
const [cart, setCart] = useState([]);
|
const [cart, setCart] = useState([]);
|
||||||
|
const getTotal = () => {
|
||||||
|
return cart.reduce((acc, it) => acc + it.price * it.count, 0);
|
||||||
|
};
|
||||||
|
const addToCart = (pizzaToAdd) => {
|
||||||
|
setCart((cart) => {
|
||||||
|
const pizza = cart.find((p) => p.id === pizzaToAdd.id);
|
||||||
|
if (pizza) {
|
||||||
|
return cart.map((p) =>
|
||||||
|
p.id === pizzaToAdd.id ? { ...p, count: p.count + 1 } : p,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return [...cart, { ...R.omit(pizzaToAdd, ["key"]), count: 1 }];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<CartContext.Provider value={{ cart, setCart }}>
|
<CartContext.Provider value={{ cart, setCart, addToCart, getTotal }}>
|
||||||
{children}
|
{children}
|
||||||
</CartContext.Provider>
|
</CartContext.Provider>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
17
src/context/UserContext.jsx
Normal file
17
src/context/UserContext.jsx
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { createContext, useState } from "react";
|
||||||
|
|
||||||
|
export const UserContext = createContext();
|
||||||
|
|
||||||
|
const UserProvider = ({ children }) => {
|
||||||
|
const [token, setToken] = useState(true);
|
||||||
|
const logout = () => {
|
||||||
|
setToken(false);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<UserContext.Provider value={{ token, setToken, logout }}>
|
||||||
|
{children}
|
||||||
|
</UserContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UserProvider;
|
||||||
|
|
@ -5,11 +5,14 @@ import App from "./App.jsx";
|
||||||
import "@unocss/reset/tailwind.css";
|
import "@unocss/reset/tailwind.css";
|
||||||
import "virtual:uno.css";
|
import "virtual:uno.css";
|
||||||
import { BrowserRouter } from "react-router-dom";
|
import { BrowserRouter } from "react-router-dom";
|
||||||
|
import UserProvider from "./context/UserContext.jsx";
|
||||||
|
|
||||||
createRoot(document.getElementById("root")).render(
|
createRoot(document.getElementById("root")).render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<App />
|
<UserProvider>
|
||||||
|
<App />
|
||||||
|
</UserProvider>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</StrictMode>,
|
</StrictMode>,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
import { useState } from "react";
|
import { useContext, useState } from "react";
|
||||||
import { pizzaCart } from "../pizzas";
|
|
||||||
import { useContext } from "react";
|
|
||||||
import { CartContext } from "../context/CartContext";
|
import { CartContext } from "../context/CartContext";
|
||||||
|
import { pizzaCart } from "../pizzas";
|
||||||
|
import { UserContext } from "../context/UserContext";
|
||||||
|
|
||||||
const Cart = () => {
|
const Cart = () => {
|
||||||
|
const { token } = useContext(UserContext);
|
||||||
const { cart, setCart } = useContext(CartContext);
|
const { cart, setCart } = useContext(CartContext);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
@ -58,7 +59,8 @@ const Cart = () => {
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="bg-black text-white rounded-md p-2 text-lg hover:bg-gray-500"
|
disabled={!token}
|
||||||
|
className="bg-black text-white rounded-md p-2 text-lg hover:bg-gray-500 disabled:bg-gray-200"
|
||||||
>
|
>
|
||||||
Pagar
|
Pagar
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
import { useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import española from "../assets/española.jpg";
|
import española from "../assets/española.jpg";
|
||||||
import napolitana from "../assets/napolitana.jpg";
|
import napolitana from "../assets/napolitana.jpg";
|
||||||
import pepperoni from "../assets/pepperoni.jpg";
|
import pepperoni from "../assets/pepperoni.jpg";
|
||||||
import CardPizza from "../components/CardPizza";
|
import CardPizza from "../components/CardPizza";
|
||||||
import Header from "../Header";
|
import Header from "../Header";
|
||||||
import { useEffect } from "react";
|
|
||||||
|
|
||||||
const Home = () => {
|
const Home = () => {
|
||||||
const [pizzas, setPizzas] = useState([]);
|
const [pizzas, setPizzas] = useState([]);
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,14 @@
|
||||||
import { useEffect, useState } from "react";
|
import { useContext, useEffect, useState } from "react";
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
import { CartContext } from "../context/CartContext";
|
||||||
|
|
||||||
const Pizza = () => {
|
const Pizza = () => {
|
||||||
|
const { id } = useParams();
|
||||||
const [pizza, setPizza] = useState(null);
|
const [pizza, setPizza] = useState(null);
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
|
const { addToCart } = useContext(CartContext);
|
||||||
const fetchPizza = async () => {
|
const fetchPizza = async () => {
|
||||||
const url = "http://localhost:5000/api/pizzas/p001";
|
const url = `http://localhost:5000/api/pizzas/${id}`;
|
||||||
const res = await fetch(url);
|
const res = await fetch(url);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
setPizza(data);
|
setPizza(data);
|
||||||
|
|
@ -26,7 +30,10 @@ const Pizza = () => {
|
||||||
<li key={index}>- {i}</li>
|
<li key={index}>- {i}</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
<button className="text-white bg-black p-2 rounded-md">
|
<button
|
||||||
|
onClick={() => addToCart(pizza)}
|
||||||
|
className="text-white bg-black p-2 rounded-md"
|
||||||
|
>
|
||||||
Añadir al carrito
|
Añadir al carrito
|
||||||
</button>
|
</button>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,19 @@
|
||||||
|
import { useContext } from "react";
|
||||||
|
import { UserContext } from "../context/UserContext";
|
||||||
|
|
||||||
const Profile = () => {
|
const Profile = () => {
|
||||||
const email = "test@example.com";
|
const email = "test@example.com";
|
||||||
|
const { logout } = useContext(UserContext);
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
<strong>Mail: </strong>
|
<strong>Mail: </strong>
|
||||||
{email}
|
{email}
|
||||||
</p>
|
</p>
|
||||||
<button className="bg-green-700 hover:bg-green-300 text-white hover:text-black">
|
<button
|
||||||
|
onClick={logout}
|
||||||
|
className="bg-green-700 hover:bg-green-300 text-white hover:text-black"
|
||||||
|
>
|
||||||
Cerrar sesión
|
Cerrar sesión
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import imgNapolitana from "./assets/napolitana.jpg";
|
|
||||||
import imgEspanola from "./assets/española.jpg";
|
|
||||||
import imgSalame from "./assets/pepperoni.jpg";
|
|
||||||
import imgCuatroEstaciones from "./assets/cuatro-estaciones.jpg";
|
|
||||||
import imgBacon from "./assets/bacon.jpg";
|
import imgBacon from "./assets/bacon.jpg";
|
||||||
|
import imgCuatroEstaciones from "./assets/cuatro-estaciones.jpg";
|
||||||
|
import imgEspanola from "./assets/española.jpg";
|
||||||
|
import imgNapolitana from "./assets/napolitana.jpg";
|
||||||
|
import imgSalame from "./assets/pepperoni.jpg";
|
||||||
import imgPolloPicante from "./assets/pollo-picante.jpg";
|
import imgPolloPicante from "./assets/pollo-picante.jpg";
|
||||||
|
|
||||||
export const pizzas = [
|
export const pizzas = [
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue