From d8b459bad560bf93ea7db812a7ffa388e70aa7cf Mon Sep 17 00:00:00 2001 From: Diego Torres Date: Wed, 28 May 2025 19:07:54 -0500 Subject: [PATCH 1/7] feat: create cart if no exists --- src/lib/client.server.ts | 4 +- src/models/cart.model.ts | 14 +- src/repositories/cart.repository.ts | 223 ++++++++++++++++++++++++++++ src/routes/login/index.tsx | 10 +- src/routes/root/index.tsx | 32 ++-- src/routes/signup/index.tsx | 6 +- src/services/cart.service.ts | 65 ++++++-- src/session.server.ts | 2 +- 8 files changed, 313 insertions(+), 43 deletions(-) create mode 100644 src/repositories/cart.repository.ts diff --git a/src/lib/client.server.ts b/src/lib/client.server.ts index 67f7340..193735d 100644 --- a/src/lib/client.server.ts +++ b/src/lib/client.server.ts @@ -12,10 +12,10 @@ export async function serverClient( const cookieHeader = request.headers.get("Cookie"); const session = await getSession(cookieHeader); const token = session.get("token"); - const cartSessionId = session.get("cartSessionId"); + const sessionCartId = session.get("sessionCartId"); const cartSessionHeader: { "x-cart-id": string } | Record = - cartSessionId ? { "x-cart-id": cartSessionId } : {}; + sessionCartId ? { "x-cart-id": sessionCartId } : {}; const config: RequestInit = { method: body ? "POST" : "GET", diff --git a/src/models/cart.model.ts b/src/models/cart.model.ts index 4daf244..569c2f0 100644 --- a/src/models/cart.model.ts +++ b/src/models/cart.model.ts @@ -3,16 +3,22 @@ import { type User } from "./user.model"; export interface CartItem { id: number; - product: Product; + product: Pick< + Product, + "id" | "title" | "imgSrc" | "alt" | "price" | "isOnSale" + >; quantity: number; + createdAt: string; + updatedAt: string; } export interface Cart { id: number; - userId?: User["id"]; - sessionCartId?: string; + userId: User["id"] | null; + sessionCartId: string; items: CartItem[]; - total: number; + createdAt: string; + updatedAt: string; } export interface CartItemInput { diff --git a/src/repositories/cart.repository.ts b/src/repositories/cart.repository.ts new file mode 100644 index 0000000..4850577 --- /dev/null +++ b/src/repositories/cart.repository.ts @@ -0,0 +1,223 @@ +import * as db from "@/db"; +import { type Cart, type CartItem } from "@/models/cart.model"; + +export async function getCart( + userId: number | undefined, + sessionCartId: string | undefined, + id?: number +): Promise { + let whereClause: string; + let paramValue: number | string; + + if (userId) { + whereClause = "WHERE c.user_id = $1"; + paramValue = userId; + } else if (sessionCartId) { + whereClause = "WHERE c.session_cart_id = $1"; + paramValue = sessionCartId; + } else if (id) { + whereClause = "WHERE c.id = $1"; + paramValue = id; + } else { + // Si no se proporciona ningún identificador, devolvemos null + return null; + } + + const query = ` + SELECT + c.*, + COALESCE( + ( + SELECT json_agg( + json_build_object( + 'id', ci.id, + 'quantity', ci.quantity, + 'product', ( + SELECT json_build_object( + 'id', p.id, + 'title', p.title, + 'imgSrc', p.img_src, + 'alt', p.alt, + 'price', p.price, + 'isOnSale', p.is_on_sale + ) + FROM products p + WHERE p.id = ci.product_id + ), + 'createdAt', ci.created_at, + 'updatedAt', ci.updated_at + ) + ORDER BY ci.id ASC + ) + FROM cart_items ci + LEFT JOIN products pr on pr.id = ci.product_id + WHERE ci.cart_id = c.id + )::json, + '[]'::json + ) as items + FROM carts c + ${whereClause} + `; + return await db.queryOne(query, [paramValue]); +} + +export async function createCart(): Promise { + const query = "INSERT INTO carts DEFAULT VALUES RETURNING *"; + return db.queryOne(query); +} + +// export async function createGuestCart(sessionCartId: string): Promise { // new function +// const query = "INSERT INTO carts (session_cart_id) VALUES ($1) RETURNING *"; +// return db.queryOne(query, [sessionCartId]); +// } + +export async function addCartItem( + cartId: number, + productId: number, + quantity: number +): Promise { + const query = ` + INSERT INTO cart_items (cart_id, product_id, quantity) + VALUES ($1, $2, $3) + RETURNING * + `; + + return await db.queryOne(query, [cartId, productId, quantity]); +} + +export async function addCartItems( + cartId: number, + items: { productId: number; quantity: number }[] | [] +): Promise { + // Si no hay elementos para agregar, retornar un array vacío + if (items.length === 0) { + return []; + } + + const valuesClause = items + .map((_, i) => `($1, $${i * 2 + 2}, $${i * 2 + 3})`) + .join(","); + + const query = ` + INSERT INTO cart_items (cart_id, product_id, quantity) + VALUES ${valuesClause} + RETURNING * + `; + + const values = items.reduce( + (acc, item) => { + acc.push(item.productId, item.quantity); + return acc; + }, + [cartId] as (string | number)[] + ); + + return await db.query(query, values); +} + +export async function updateCartItem( + cartId: number, + itemId: number, + quantity: number +): Promise { + const query = + "UPDATE cart_items SET quantity = $1 WHERE id = $2 AND cart_id = $3 RETURNING *"; + + return await db.queryOne(query, [quantity, itemId, cartId]); +} + +export async function removeCartItem( + cartId: number, + itemId: number +): Promise { + const query = "DELETE FROM cart_items WHERE id = $1 AND cart_id = $2"; + await db.query(query, [itemId, cartId]); +} + +export async function clearCart(cartId: number): Promise { + const query = "DELETE FROM carts WHERE id = $1"; + await db.query(query, [cartId]); +} + +export async function updateCartWithUserId( + cartId: number, + userId: number +): Promise { + const query = ` + UPDATE carts + SET user_id = $2 + WHERE id = $1 + RETURNING * + `; + + return await db.queryOne(query, [cartId, userId]); +} + +export async function updateCartBySessionId( + sessionCartId: string, + userId: number +): Promise { + const query = ` + UPDATE carts + SET user_id = $2 + WHERE session_cart_id = $1 + RETURNING * + `; + + return await db.queryOne(query, [sessionCartId, userId]); +} + +export async function mergeGuestCartWithUserCart( + userId: number | null, + sessionCartId: string +): Promise { + // Primero, obtenemos el carrito del usuario y el carrito de invitado + const userCart = await getCart(userId); + const guestCart = await getCart(null, sessionCartId); + + if (!guestCart) { + return userCart; + } + + if (!userCart) { + // Si el usuario no tiene carrito, actualizamos el carrito de invitado con el ID del usuario + const query = ` + UPDATE carts + SET user_id = $1 + WHERE session_cart_id = $2 + RETURNING * + `; + return await db.queryOne(query, [userId, sessionCartId]); + } + + // Eliminamos productos del carrito usuario que también existan en el carrito invitado + await db.query( + ` + DELETE FROM cart_items + WHERE cart_id = $1 + AND product_id IN ( + SELECT product_id FROM cart_items WHERE cart_id = $2 + ) + `, + [userCart.id, guestCart.id] + ); + + // Insertamos los artículos del carrito invitado al carrito usuario + const query = ` + INSERT INTO cart_items (cart_id, product_id, quantity) + SELECT $1, product_id, quantity + FROM cart_items + WHERE cart_id = $2 + RETURNING * + `; + + await db.query(query, [userCart.id, guestCart.id]); + + // Eliminamos el carrito de invitado + await db.query(`DELETE FROM carts WHERE session_cart_id = $1`, [ + sessionCartId, + ]); + + // Devolvemos el carrito actualizado del usuario + return await getCart(userId); +} diff --git a/src/routes/login/index.tsx b/src/routes/login/index.tsx index cce16f6..062b113 100644 --- a/src/routes/login/index.tsx +++ b/src/routes/login/index.tsx @@ -23,7 +23,7 @@ const LoginSchema = z.object({ export async function action({ request }: Route.ActionArgs) { const session = await getSession(request.headers.get("Cookie")); - // const cartSessionId = session.get("cartSessionId"); + // const sessionCartId = session.get("sessionCartId"); const formData = await request.formData(); const email = formData.get("email") as string; @@ -56,9 +56,9 @@ export async function action({ request }: Route.ActionArgs) { // method: "GET", // }); - // if (cartSessionId) { + // if (sessionCartId) { // try { - // // Verificar si el usuario ya tiene un carrito usando getRemoteCart sin cartSessionId + // // Verificar si el usuario ya tiene un carrito usando getRemoteCart sin sessionCartId // const existingUserCart = await getRemoteCart(authenticatedRequest); // if (existingUserCart) { @@ -67,14 +67,14 @@ export async function action({ request }: Route.ActionArgs) { // ); // if (mergedCart) { - // session.unset("cartSessionId"); + // session.unset("sessionCartId"); // } // } else { // // Si el usuario no tiene carrito, vinculamos el carrito de invitado // const linkedCart = await linkCartToUser(authenticatedRequest); // if (linkedCart) { - // session.unset("cartSessionId"); + // session.unset("sessionCartId"); // } // } // } catch (cartError) { diff --git a/src/routes/root/index.tsx b/src/routes/root/index.tsx index c3bd6b6..2636780 100644 --- a/src/routes/root/index.tsx +++ b/src/routes/root/index.tsx @@ -18,7 +18,7 @@ import { } from "@/components/ui"; // import { getCart } from "@/lib/cart"; import { getCurrentUser } from "@/services/auth.service"; -// import { createRemoteItems } from "@/services/cart.service"; +import { createRemoteItems } from "@/services/cart.service"; import { commitSession, getSession } from "@/session.server"; import AuthNav from "./components/auth-nav"; @@ -43,26 +43,26 @@ export async function action({ request }: Route.ActionArgs) { export async function loader({ request }: Route.LoaderArgs) { const session = await getSession(request.headers.get("Cookie")); - // const cartSessionId = session.get("cartSessionId"); + const sessionCartId = session.get("sessionCartId"); const totalItems = 0; // Obtenemos el usuario actual (autenticado o no) const user = await getCurrentUser(request); - // if (!user) { - // // Si no hay cartSessionId, crea un carrito de invitado - // if (!cartSessionId) { - // try { - // const cart = await createRemoteItems(request, []); - // const cartId = cart.sessionCartId; - // if (cartId) { - // session.set("cartSessionId", cartId); - // } - // } catch (error) { - // console.error("Error al crear carrito de invitado:", error); - // } - // } - // } + if (!user) { + // Si no hay sessionCartId, crea un carrito de invitado + if (!sessionCartId) { + try { + const cart = await createRemoteItems(undefined, undefined, []); + const cartId = cart.sessionCartId; + if (cartId) { + session.set("sessionCartId", cartId); + } + } catch (error) { + console.error("Error al crear carrito de invitado:", error); + } + } + } // Obtener el carrito actualizado para contar los items // try { diff --git a/src/routes/signup/index.tsx b/src/routes/signup/index.tsx index 97c6254..0d6ec1f 100644 --- a/src/routes/signup/index.tsx +++ b/src/routes/signup/index.tsx @@ -40,7 +40,7 @@ export async function action({ request }: Route.ActionArgs) { const password = formData.get("password") as string; const session = await getSession(request.headers.get("Cookie")); - // const cartSessionId = session.get("cartSessionId"); + // const sessionCartId = session.get("sessionCartId"); try { // Nuevo flujo: @@ -73,12 +73,12 @@ export async function action({ request }: Route.ActionArgs) { // method: "GET", // }); - // if (cartSessionId) { + // if (sessionCartId) { // try { // const linkedCart = await linkCartToUser(authenticatedRequest); // if (linkedCart) { - // session.unset("cartSessionId"); + // session.unset("sessionCartId"); // } // // } // } catch (cartError) { diff --git a/src/services/cart.service.ts b/src/services/cart.service.ts index f28b7e4..ea3af5f 100644 --- a/src/services/cart.service.ts +++ b/src/services/cart.service.ts @@ -1,24 +1,65 @@ import { serverClient } from "@/lib/client.server"; import { type Cart, type CartItem } from "@/models/cart.model"; +import type { User } from "@/models/user.model"; +import * as cartRepository from "@/repositories/cart.repository"; export async function getRemoteCart(request: Request): Promise { return serverClient("/cart", request); } +export async function getOrCreateCart( + userId: User["id"] | undefined, + sessionCartId: string | undefined +) { + let cart: Cart | null = null; + + cart = await cartRepository.getCart(userId, sessionCartId); + + // Si no se encontró un carrito creamos uno nuevo + if (!cart) { + // Creamos un carrito sin userId ni sessionCartId, dejando que la BD genere el UUID + cart = await cartRepository.createCart(); + // Si se crea el carrito, lo vinculamos a un usuario si se proporciona un userId + if (cart && userId) { + await cartRepository.updateCartWithUserId(cart.id, userId); + } + } + + if (!cart) throw new Error("Failed to create cart"); + + return cart; +} + export async function createRemoteItems( - request: Request, - items: CartItem[] + userId: User["id"] | undefined, + sessionCartId: string | undefined, + items: CartItem[] = [] ): Promise { - const payload = { - items: items.map(({ product, quantity }) => ({ - productId: product.id, - quantity, - })), - }; - - return serverClient("/cart/create-items", request, { - body: payload, - }); + const mappedItems = items.map(({ product, quantity }) => ({ + productId: product.id, + quantity, + })); + + const cart = await getOrCreateCart(userId, sessionCartId); + + if (cart.items.length > 0) { + await cartRepository.clearCart(cart.id); + } + + // Si hay elementos para agregar, agregarlos + if (items.length > 0) { + await cartRepository.addCartItems(cart.id, mappedItems); + } + + const updatedCart = await cartRepository.getCart( + userId, + sessionCartId, + cart.id + ); + + if (!updatedCart) throw new Error("Cart not found after creation"); + + return updatedCart; } export async function alterQuantityCartItem( diff --git a/src/session.server.ts b/src/session.server.ts index 70f6120..89a5cb2 100644 --- a/src/session.server.ts +++ b/src/session.server.ts @@ -2,7 +2,7 @@ import { createCookieSessionStorage } from "react-router"; type SessionData = { token?: string; - cartSessionId?: string; + sessionCartId?: string; userId?: number; }; From ffdae01f4faded1d3910a94c2e94b0eebdd4b4b4 Mon Sep 17 00:00:00 2001 From: Diego Torres Date: Wed, 28 May 2025 19:32:11 -0500 Subject: [PATCH 2/7] fix: update return value of getCart function --- src/repositories/cart.repository.ts | 3 ++- src/services/cart.service.ts | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/repositories/cart.repository.ts b/src/repositories/cart.repository.ts index 4850577..e882cea 100644 --- a/src/repositories/cart.repository.ts +++ b/src/repositories/cart.repository.ts @@ -63,7 +63,8 @@ export async function getCart( export async function createCart(): Promise { const query = "INSERT INTO carts DEFAULT VALUES RETURNING *"; - return db.queryOne(query); + const cart = await db.queryOne(query); + return getCart(undefined, undefined, cart?.id); } // export async function createGuestCart(sessionCartId: string): Promise { // new function diff --git a/src/services/cart.service.ts b/src/services/cart.service.ts index ea3af5f..23137ff 100644 --- a/src/services/cart.service.ts +++ b/src/services/cart.service.ts @@ -41,6 +41,7 @@ export async function createRemoteItems( })); const cart = await getOrCreateCart(userId, sessionCartId); + console.log("Cart", cart); if (cart.items.length > 0) { await cartRepository.clearCart(cart.id); From b09f45515f154feff52bbb9bc0ddc0462c1c5c34 Mon Sep 17 00:00:00 2001 From: Diego Torres Date: Wed, 28 May 2025 19:48:49 -0500 Subject: [PATCH 3/7] refactor: simplify getCart function to use getOrCreateCart and improve cart retrieval logic --- src/lib/cart.ts | 20 +++--------------- src/routes/root/index.tsx | 43 ++++++++++++++++----------------------- 2 files changed, 20 insertions(+), 43 deletions(-) diff --git a/src/lib/cart.ts b/src/lib/cart.ts index 56e10b8..ddfc847 100644 --- a/src/lib/cart.ts +++ b/src/lib/cart.ts @@ -3,27 +3,13 @@ import { type Product } from "@/models/product.model"; import { alterQuantityCartItem, deleteRemoteCartItem, - getRemoteCart, + getOrCreateCart, } from "@/services/cart.service"; import { getProductById } from "@/services/product.service"; -export async function getCart(request: Request) { +export async function getCart(userId?: number, sessionCartId?: string) { try { - const remoteCart = await getRemoteCart(request); - - // Si ya existe un carrito (con ítems o vacío), lo devolvemos - if (remoteCart) { - // Si no existe la propiedad total, calcúlala sumando las cantidades de cada ítem - if (remoteCart.total === undefined) { - remoteCart.total = - remoteCart.items?.reduce((total, item) => total + item.quantity, 0) || - 0; - } - return remoteCart; - } - - // No se encontró carrito - return null; + return getOrCreateCart(userId, sessionCartId); } catch (error) { console.error(error); return null; diff --git a/src/routes/root/index.tsx b/src/routes/root/index.tsx index 2636780..99be149 100644 --- a/src/routes/root/index.tsx +++ b/src/routes/root/index.tsx @@ -16,7 +16,8 @@ import { Section, Separator, } from "@/components/ui"; -// import { getCart } from "@/lib/cart"; +import { getCart } from "@/lib/cart"; +import type { Cart } from "@/models/cart.model"; import { getCurrentUser } from "@/services/auth.service"; import { createRemoteItems } from "@/services/cart.service"; import { commitSession, getSession } from "@/session.server"; @@ -44,39 +45,29 @@ export async function action({ request }: Route.ActionArgs) { export async function loader({ request }: Route.LoaderArgs) { const session = await getSession(request.headers.get("Cookie")); const sessionCartId = session.get("sessionCartId"); - const totalItems = 0; + let cart: Cart | null = null; // Obtenemos el usuario actual (autenticado o no) const user = await getCurrentUser(request); - if (!user) { - // Si no hay sessionCartId, crea un carrito de invitado - if (!sessionCartId) { - try { - const cart = await createRemoteItems(undefined, undefined, []); - const cartId = cart.sessionCartId; - if (cartId) { - session.set("sessionCartId", cartId); - } - } catch (error) { - console.error("Error al crear carrito de invitado:", error); + if (!user && !sessionCartId) { + try { + cart = await createRemoteItems(undefined, undefined, []); + const cartId = cart.sessionCartId; + if (cartId) { + session.set("sessionCartId", cartId); } + } catch (error) { + console.error("Error al crear carrito de invitado:", error); } } - // Obtener el carrito actualizado para contar los items - // try { - // const cart = await getCart(request); - // // Sumar las cantidades de cada ítem - // if (cart?.items && cart.items.length > 0) { - // totalItems = cart.items.reduce( - // (sum, item) => sum + (item.quantity || 0), - // 0 - // ); - // } - // } catch (error) { - // console.error("Error al obtener el carrito:", error); - // } + if (!cart) { + cart = await getCart(user?.id, sessionCartId); + } + + const totalItems = + cart?.items.reduce((total, item) => total + item.quantity, 0) || 0; // Preparar datos de respuesta según estado de autenticación const responseData = user ? { user, totalItems } : { totalItems }; From 4df11ac305c39db2425d04fbf28a629549030340 Mon Sep 17 00:00:00 2001 From: Diego Torres Date: Wed, 28 May 2025 20:06:23 -0500 Subject: [PATCH 4/7] feat: refactor cart management to use userId and sessionCartId in addToCart and alterQuantityCartItem functions --- src/lib/cart.ts | 10 ++++----- src/models/product.model.ts | 7 +++++-- src/repositories/product.repository.ts | 18 ++++++++++++++++ src/routes/cart/add-item/index.tsx | 6 +++++- src/services/cart.service.ts | 29 ++++++++++++++++++++++---- 5 files changed, 58 insertions(+), 12 deletions(-) create mode 100644 src/repositories/product.repository.ts diff --git a/src/lib/cart.ts b/src/lib/cart.ts index ddfc847..3831fa2 100644 --- a/src/lib/cart.ts +++ b/src/lib/cart.ts @@ -5,7 +5,6 @@ import { deleteRemoteCartItem, getOrCreateCart, } from "@/services/cart.service"; -import { getProductById } from "@/services/product.service"; export async function getCart(userId?: number, sessionCartId?: string) { try { @@ -17,15 +16,16 @@ export async function getCart(userId?: number, sessionCartId?: string) { } export async function addToCart( - request: Request, + userId: number | undefined, + sessionCartId: string | undefined, productId: Product["id"], quantity: number = 1 ) { try { - const product = await getProductById(request, productId); const updatedCart = await alterQuantityCartItem( - request, - product.id, + userId, + sessionCartId, + productId, quantity ); return updatedCart; diff --git a/src/models/product.model.ts b/src/models/product.model.ts index 91c0afa..c42f14d 100644 --- a/src/models/product.model.ts +++ b/src/models/product.model.ts @@ -1,4 +1,4 @@ -import { type Category } from "./category.model"; +// import { type Category } from "./category.model"; export interface Product { id: number; @@ -7,7 +7,10 @@ export interface Product { alt: string | null; price: number; description: string | null; - categorySlug: Category["slug"]; + categoryId: number; + // categorySlug: Category["slug"]; isOnSale: boolean; features: string[]; + createdAt: string; + updatedAt: string; } diff --git a/src/repositories/product.repository.ts b/src/repositories/product.repository.ts new file mode 100644 index 0000000..efab174 --- /dev/null +++ b/src/repositories/product.repository.ts @@ -0,0 +1,18 @@ +import * as db from "@/db"; +import { type Product } from "@/models/product.model"; + +export async function getAllProducts(): Promise { + return await db.query("SELECT * FROM products"); +} + +export async function getProductById(id: number): Promise { + const query = "SELECT * FROM products WHERE id = $1"; + return await db.queryOne(query, [id]); +} + +export async function getProductsByCategory( + categoryId: number +): Promise { + const query = "SELECT * FROM products WHERE category_id = $1"; + return await db.query(query, [categoryId]); +} diff --git a/src/routes/cart/add-item/index.tsx b/src/routes/cart/add-item/index.tsx index a793ed1..ac49758 100644 --- a/src/routes/cart/add-item/index.tsx +++ b/src/routes/cart/add-item/index.tsx @@ -1,6 +1,7 @@ import { redirect } from "react-router"; import { addToCart } from "@/lib/cart"; +import { getSession } from "@/session.server"; import type { Route } from "../+types"; @@ -9,8 +10,11 @@ export async function action({ request }: Route.ActionArgs) { const productId = Number(formData.get("productId")); const quantity = Number(formData.get("quantity")) || 1; const redirectTo = formData.get("redirectTo") as string | null; + const session = await getSession(request.headers.get("Cookie")); + const sessionCartId = session.get("sessionCartId"); + const userId = session.get("userId"); - await addToCart(request, productId, quantity); + await addToCart(userId, sessionCartId, productId, quantity); return redirect(redirectTo || "/cart"); } diff --git a/src/services/cart.service.ts b/src/services/cart.service.ts index 23137ff..8f30079 100644 --- a/src/services/cart.service.ts +++ b/src/services/cart.service.ts @@ -64,13 +64,34 @@ export async function createRemoteItems( } export async function alterQuantityCartItem( - request: Request, + userId: User["id"] | undefined, + sessionCartId: string | undefined, productId: number, quantity: number = 1 ): Promise { - return serverClient("/cart/add-item", request, { - body: { productId, quantity }, - }); + const cart = await getOrCreateCart(userId, sessionCartId); + + const existingItem = cart.items.find((item) => item.product.id === productId); + + if (existingItem) { + const newQuantity = existingItem.quantity + quantity; + if (newQuantity <= 0) + throw new Error("Cannot set item quantity to 0 or less"); + + await cartRepository.updateCartItem(cart.id, existingItem.id, newQuantity); + } else { + await cartRepository.addCartItem(cart.id, productId, quantity); + } + + const updatedCart = await cartRepository.getCart( + userId, + cart.sessionCartId, + cart.id + ); + + if (!updatedCart) throw new Error("Cart not found after update"); + + return updatedCart; } export async function deleteRemoteCartItem( From a43d0233b388bce2ceb3b09462742b419aa63848 Mon Sep 17 00:00:00 2001 From: Diego Torres Date: Wed, 28 May 2025 20:18:07 -0500 Subject: [PATCH 5/7] feat: update removeFromCart function to accept userId and sessionCartId, enhancing cart item removal logic --- src/lib/cart.ts | 12 ++++++++++-- src/routes/cart/index.tsx | 19 +++++++++++++++---- src/routes/cart/remove-item/index.tsx | 6 +++++- src/services/cart.service.ts | 18 ++++++++++++++---- 4 files changed, 44 insertions(+), 11 deletions(-) diff --git a/src/lib/cart.ts b/src/lib/cart.ts index 3831fa2..f25e5ce 100644 --- a/src/lib/cart.ts +++ b/src/lib/cart.ts @@ -35,10 +35,18 @@ export async function addToCart( } } -export async function removeFromCart(request: Request, itemId: CartItem["id"]) { +export async function removeFromCart( + userId: number | undefined, + sessionCartId: string | undefined, + itemId: CartItem["id"] +) { try { // El backend determinará si es un usuario autenticado o invitado - const updatedCart = await deleteRemoteCartItem(request, itemId); + const updatedCart = await deleteRemoteCartItem( + userId, + sessionCartId, + itemId + ); return updatedCart; } catch (error) { console.error(error); diff --git a/src/routes/cart/index.tsx b/src/routes/cart/index.tsx index 29fdebd..eb3df11 100644 --- a/src/routes/cart/index.tsx +++ b/src/routes/cart/index.tsx @@ -4,17 +4,28 @@ import { Form, Link } from "react-router"; import { Button, Container, Section } from "@/components/ui"; import { getCart } from "@/lib/cart"; import { type Cart } from "@/models/cart.model"; +import { getSession } from "@/session.server"; import type { Route } from "./+types"; export async function loader({ request }: Route.LoaderArgs) { - const cart = await getCart(request); + const session = await getSession(request.headers.get("Cookie")); + const sessionCartId = session.get("sessionCartId"); + const userId = session.get("userId"); - return { cart }; + const cart = await getCart(userId, sessionCartId); + + const total = + cart?.items.reduce( + (acc, item) => acc + item.product.price * item.quantity, + 0 + ) || 0; + + return { cart, total }; } export default function Cart({ loaderData }: Route.ComponentProps) { - const { cart } = loaderData; + const { cart, total } = loaderData; return (
@@ -83,7 +94,7 @@ export default function Cart({ loaderData }: Route.ComponentProps) { ))}

Total

-

${(cart?.total || 0).toFixed(2)}

+

${total.toFixed(2)}

diff --git a/src/routes/root/index.tsx b/src/routes/root/index.tsx index 99be149..5152dfc 100644 --- a/src/routes/root/index.tsx +++ b/src/routes/root/index.tsx @@ -66,6 +66,8 @@ export async function loader({ request }: Route.LoaderArgs) { cart = await getCart(user?.id, sessionCartId); } + console.log("Cart in loader:", cart); + const totalItems = cart?.items.reduce((total, item) => total + item.quantity, 0) || 0; diff --git a/src/services/cart.service.ts b/src/services/cart.service.ts index 19039a3..e5cc3f0 100644 --- a/src/services/cart.service.ts +++ b/src/services/cart.service.ts @@ -2,6 +2,7 @@ import { serverClient } from "@/lib/client.server"; import { type Cart, type CartItem } from "@/models/cart.model"; import type { User } from "@/models/user.model"; import * as cartRepository from "@/repositories/cart.repository"; +import { getSession } from "@/session.server"; export async function getRemoteCart(request: Request): Promise { return serverClient("/cart", request); @@ -114,9 +115,18 @@ export async function deleteRemoteCartItem( } export async function deleteRemoteCart(request: Request): Promise { - return serverClient("/cart", request, { - method: "DELETE", - }); + const session = await getSession(request.headers.get("Cookie")); + const sessionCartId = session.get("sessionCartId"); + const userId = session.get("userId"); + + let cart: Cart | null = null; + + if (userId || sessionCartId) { + cart = await cartRepository.getCart(userId, sessionCartId); + } + + if (!cart) throw new Error("Cart not found"); + await cartRepository.clearCart(cart.id); } export async function linkCartToUser(request: Request): Promise { From b5633201172da52fa61a4584399fdd0101df499c Mon Sep 17 00:00:00 2001 From: Diego Torres Date: Wed, 28 May 2025 21:03:26 -0500 Subject: [PATCH 7/7] feat: update cart management functions to handle userId and sessionCartId, enhancing cart merging and linking logic --- src/repositories/cart.repository.ts | 8 +-- src/routes/login/index.tsx | 75 ++++++++++++----------------- src/routes/root/index.tsx | 2 - src/routes/signup/index.tsx | 45 ++++++----------- src/services/cart.service.ts | 40 ++++++++++----- 5 files changed, 79 insertions(+), 91 deletions(-) diff --git a/src/repositories/cart.repository.ts b/src/repositories/cart.repository.ts index e882cea..d8c76de 100644 --- a/src/repositories/cart.repository.ts +++ b/src/repositories/cart.repository.ts @@ -169,12 +169,12 @@ export async function updateCartBySessionId( } export async function mergeGuestCartWithUserCart( - userId: number | null, + userId: number | undefined, sessionCartId: string ): Promise { // Primero, obtenemos el carrito del usuario y el carrito de invitado - const userCart = await getCart(userId); - const guestCart = await getCart(null, sessionCartId); + const userCart = await getCart(userId, undefined); + const guestCart = await getCart(undefined, sessionCartId); if (!guestCart) { return userCart; @@ -220,5 +220,5 @@ export async function mergeGuestCartWithUserCart( ]); // Devolvemos el carrito actualizado del usuario - return await getCart(userId); + return await getCart(userId, undefined); } diff --git a/src/routes/login/index.tsx b/src/routes/login/index.tsx index 062b113..dbfee4f 100644 --- a/src/routes/login/index.tsx +++ b/src/routes/login/index.tsx @@ -7,11 +7,11 @@ import { Button, Container, InputField, Section } from "@/components/ui"; import { comparePasswords } from "@/lib/security"; import { getUserByEmail } from "@/repositories/user.repository"; import { redirectIfAuthenticated } from "@/services/auth.service"; -// import { -// getRemoteCart, -// linkCartToUser, -// mergeGuestCartWithUserCart, -// } from "@/services/cart.service"; +import { + getRemoteCart, + linkCartToUser, + mergeGuestCartWithUserCart, +} from "@/services/cart.service"; import { commitSession, getSession } from "@/session.server"; import type { Route } from "./+types"; @@ -23,7 +23,7 @@ const LoginSchema = z.object({ export async function action({ request }: Route.ActionArgs) { const session = await getSession(request.headers.get("Cookie")); - // const sessionCartId = session.get("sessionCartId"); + const sessionCartId = session.get("sessionCartId"); const formData = await request.formData(); const email = formData.get("email") as string; @@ -44,43 +44,32 @@ export async function action({ request }: Route.ActionArgs) { session.set("userId", user.id); - // const { token } = await login(request, email, password); - // session.set("token", token); - - // Crear una solicitud autenticada con el token - // const cookie = await commitSession(session); - // const authenticatedRequest = new Request(request.url, { - // headers: { - // Cookie: cookie, - // }, - // method: "GET", - // }); - - // if (sessionCartId) { - // try { - // // Verificar si el usuario ya tiene un carrito usando getRemoteCart sin sessionCartId - // const existingUserCart = await getRemoteCart(authenticatedRequest); - - // if (existingUserCart) { - // const mergedCart = await mergeGuestCartWithUserCart( - // authenticatedRequest - // ); - - // if (mergedCart) { - // session.unset("sessionCartId"); - // } - // } else { - // // Si el usuario no tiene carrito, vinculamos el carrito de invitado - // const linkedCart = await linkCartToUser(authenticatedRequest); - - // if (linkedCart) { - // session.unset("sessionCartId"); - // } - // } - // } catch (cartError) { - // console.error("Error al gestionar el carrito:", cartError); - // } - // } + if (sessionCartId) { + try { + // Verificar si el usuario ya tiene un carrito usando getRemoteCart sin sessionCartId + const existingUserCart = await getRemoteCart(user.id); + + if (existingUserCart) { + const mergedCart = await mergeGuestCartWithUserCart( + user.id, + sessionCartId + ); + + if (mergedCart) { + session.unset("sessionCartId"); + } + } else { + // Si el usuario no tiene carrito, vinculamos el carrito de invitado + const linkedCart = await linkCartToUser(user.id, sessionCartId); + + if (linkedCart) { + session.unset("sessionCartId"); + } + } + } catch (cartError) { + console.error("Error al gestionar el carrito:", cartError); + } + } return redirect("/", { headers: { "Set-Cookie": await commitSession(session) }, diff --git a/src/routes/root/index.tsx b/src/routes/root/index.tsx index 5152dfc..99be149 100644 --- a/src/routes/root/index.tsx +++ b/src/routes/root/index.tsx @@ -66,8 +66,6 @@ export async function loader({ request }: Route.LoaderArgs) { cart = await getCart(user?.id, sessionCartId); } - console.log("Cart in loader:", cart); - const totalItems = cart?.items.reduce((total, item) => total + item.quantity, 0) || 0; diff --git a/src/routes/signup/index.tsx b/src/routes/signup/index.tsx index 0d6ec1f..05804dc 100644 --- a/src/routes/signup/index.tsx +++ b/src/routes/signup/index.tsx @@ -4,13 +4,12 @@ import { Link, redirect, useNavigation, useSubmit } from "react-router"; import { z } from "zod"; import { Button, Container, InputField, Section } from "@/components/ui"; -// import { generateToken } from "@/lib/jwt"; import { hashPassword } from "@/lib/security"; import { debounceAsync } from "@/lib/utils"; import type { CreateUserDTO } from "@/models/user.model"; import { createUser, getUserByEmail } from "@/repositories/user.repository"; import { redirectIfAuthenticated } from "@/services/auth.service"; -// import { linkCartToUser } from "@/services/cart.service"; +import { linkCartToUser } from "@/services/cart.service"; import { findEmail } from "@/services/user.client-service"; import { commitSession, getSession } from "@/session.server"; @@ -40,10 +39,9 @@ export async function action({ request }: Route.ActionArgs) { const password = formData.get("password") as string; const session = await getSession(request.headers.get("Cookie")); - // const sessionCartId = session.get("sessionCartId"); + const sessionCartId = session.get("sessionCartId"); try { - // Nuevo flujo: const existingUser = await getUserByEmail(email); if (existingUser) { return { error: "El correo electrónico ya existe" }; @@ -61,32 +59,19 @@ export async function action({ request }: Route.ActionArgs) { const user = await createUser(newUser); session.set("userId", user.id); - // TODO: Eliminar al terminar el port del backend - // const token = generateToken(user); - // session.set("token", token); - - // const cookie = await commitSession(session); - // const authenticatedRequest = new Request(request.url, { - // headers: { - // Cookie: cookie, - // }, - // method: "GET", - // }); - - // if (sessionCartId) { - // try { - // const linkedCart = await linkCartToUser(authenticatedRequest); - - // if (linkedCart) { - // session.unset("sessionCartId"); - // } - // // } - // } catch (cartError) { - // console.error("Error al gestionar el carrito en signup:", cartError); - // } - // } else { - // console.log("No hay carrito de invitado para vincular en el registro"); - // } + if (sessionCartId) { + try { + const linkedCart = await linkCartToUser(user.id, sessionCartId); + + if (linkedCart) { + session.unset("sessionCartId"); + } + } catch (cartError) { + console.error("Error al gestionar el carrito en signup:", cartError); + } + } else { + console.error("No hay carrito de invitado para vincular en el registro"); + } return redirect("/", { headers: { diff --git a/src/services/cart.service.ts b/src/services/cart.service.ts index e5cc3f0..c0c8b86 100644 --- a/src/services/cart.service.ts +++ b/src/services/cart.service.ts @@ -1,11 +1,11 @@ -import { serverClient } from "@/lib/client.server"; import { type Cart, type CartItem } from "@/models/cart.model"; import type { User } from "@/models/user.model"; import * as cartRepository from "@/repositories/cart.repository"; import { getSession } from "@/session.server"; -export async function getRemoteCart(request: Request): Promise { - return serverClient("/cart", request); +export async function getRemoteCart(userId: User["id"]): Promise { + const cart = await cartRepository.getCart(userId, undefined); + return cart; } export async function getOrCreateCart( @@ -42,7 +42,6 @@ export async function createRemoteItems( })); const cart = await getOrCreateCart(userId, sessionCartId); - console.log("Cart", cart); if (cart.items.length > 0) { await cartRepository.clearCart(cart.id); @@ -129,16 +128,33 @@ export async function deleteRemoteCart(request: Request): Promise { await cartRepository.clearCart(cart.id); } -export async function linkCartToUser(request: Request): Promise { - return serverClient("/cart/link-to-user", request, { - method: "POST", - }); +export async function linkCartToUser( + userId: User["id"], + sessionCartId: string +): Promise { + if (!sessionCartId) throw new Error("Session cart ID not found"); + if (!userId) throw new Error("User ID not found"); + + const updatedCart = await cartRepository.updateCartBySessionId( + sessionCartId, + userId + ); + + if (!updatedCart) throw new Error("Cart not found after linking"); + + return updatedCart; } export async function mergeGuestCartWithUserCart( - request: Request + userId: User["id"], + sessionCartId: string ): Promise { - return serverClient("/cart/merge-guest-cart", request, { - method: "POST", - }); + const mergedCart = await cartRepository.mergeGuestCartWithUserCart( + userId, + sessionCartId + ); + + if (!mergedCart) throw new Error("Cart not found after merging"); + + return mergedCart; }