Skip to content

Commit d8699c5

Browse files
committed
feat: add product variants and update cart functionality to support size selection
1 parent dc11176 commit d8699c5

File tree

13 files changed

+269
-81
lines changed

13 files changed

+269
-81
lines changed

.react-router/types/+register.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,5 @@ type Params = {
2929
"/account/orders": {};
3030
"/not-found": {};
3131
"/verify-email": {};
32+
"/chat": {};
3233
};

prisma/initial_data.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,19 @@ export const categories = [
2929
},
3030
];
3131

32+
export const productVariant = [
33+
// Ejemplo para un producto Polo
34+
{ productTitle: "Polo React", sizes: ["small", "medium", "large"] },
35+
{ productTitle: "Polo JavaScript", sizes: ["small", "medium", "large"] },
36+
{ productTitle: "Polo Node.js", sizes: ["small", "medium", "large"] },
37+
{ productTitle: "Polo TypeScript", sizes: ["small", "medium", "large"] },
38+
{ productTitle: "Polo Backend Developer", sizes: ["small", "medium", "large"] },
39+
{ productTitle: "Polo Frontend Developer", sizes: ["small", "medium", "large"] },
40+
{ productTitle: "Polo Full-Stack Developer", sizes: ["small", "medium", "large"] },
41+
{ productTitle: "Polo It's A Feature", sizes: ["small", "medium", "large"] },
42+
{ productTitle: "Polo It Works On My Machine", sizes: ["small", "medium", "large"] },
43+
];
44+
3245
export const products = [
3346
{
3447
title: "Polo React",
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
-- CreateTable
2+
CREATE TABLE "ProductVariants" (
3+
"id" SERIAL NOT NULL,
4+
"productId" INTEGER NOT NULL,
5+
"size" TEXT NOT NULL,
6+
7+
CONSTRAINT "ProductVariant_pkey" PRIMARY KEY ("id")
8+
);
9+
10+
-- AddForeignKey
11+
ALTER TABLE "ProductVariants" ADD CONSTRAINT "ProductVariant_productId_fkey" FOREIGN KEY ("productId") REFERENCES "products"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
Warnings:
3+
4+
- You are about to drop the column `cart_id` on the `cart_items` table. All the data in the column will be lost.
5+
- You are about to drop the column `product_id` on the `cart_items` table. All the data in the column will be lost.
6+
- You are about to drop the `ProductVariants` table. If the table is not empty, all the data it contains will be lost.
7+
- A unique constraint covering the columns `[cartId,productId]` on the table `cart_items` will be added. If there are existing duplicate values, this will fail.
8+
- Added the required column `cartId` to the `cart_items` table without a default value. This is not possible if the table is not empty.
9+
- Added the required column `productId` to the `cart_items` table without a default value. This is not possible if the table is not empty.
10+
11+
*/
12+
-- DropForeignKey
13+
ALTER TABLE "ProductVariants" DROP CONSTRAINT "ProductVariant_productId_fkey";
14+
15+
-- DropForeignKey
16+
ALTER TABLE "cart_items" DROP CONSTRAINT "cart_items_cart_id_fkey";
17+
18+
-- DropForeignKey
19+
ALTER TABLE "cart_items" DROP CONSTRAINT "cart_items_product_id_fkey";
20+
21+
-- DropIndex
22+
DROP INDEX "cart_items_cart_id_product_id_key";
23+
24+
-- AlterTable
25+
ALTER TABLE "cart_items" DROP COLUMN "cart_id",
26+
DROP COLUMN "product_id",
27+
ADD COLUMN "cartId" INTEGER NOT NULL,
28+
ADD COLUMN "productId" INTEGER NOT NULL,
29+
ADD COLUMN "productVariantId" INTEGER;
30+
31+
-- DropTable
32+
DROP TABLE "ProductVariants";
33+
34+
-- CreateTable
35+
CREATE TABLE "ProductVariant" (
36+
"id" SERIAL NOT NULL,
37+
"productId" INTEGER NOT NULL,
38+
"size" TEXT NOT NULL,
39+
40+
CONSTRAINT "ProductVariant_pkey" PRIMARY KEY ("id")
41+
);
42+
43+
-- CreateIndex
44+
CREATE UNIQUE INDEX "cart_items_cartId_productId_key" ON "cart_items"("cartId", "productId");
45+
46+
-- AddForeignKey
47+
ALTER TABLE "ProductVariant" ADD CONSTRAINT "ProductVariant_productId_fkey" FOREIGN KEY ("productId") REFERENCES "products"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
48+
49+
-- AddForeignKey
50+
ALTER TABLE "cart_items" ADD CONSTRAINT "cart_items_cartId_fkey" FOREIGN KEY ("cartId") REFERENCES "carts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
51+
52+
-- AddForeignKey
53+
ALTER TABLE "cart_items" ADD CONSTRAINT "cart_items_productId_fkey" FOREIGN KEY ("productId") REFERENCES "products"("id") ON DELETE CASCADE ON UPDATE CASCADE;
54+
55+
-- AddForeignKey
56+
ALTER TABLE "cart_items" ADD CONSTRAINT "cart_items_productVariantId_fkey" FOREIGN KEY ("productVariantId") REFERENCES "ProductVariant"("id") ON DELETE SET NULL ON UPDATE CASCADE;

prisma/schema.prisma

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -51,17 +51,18 @@ model Category {
5151
}
5252

5353
model Product {
54-
id Int @id @default(autoincrement())
54+
id Int @id @default(autoincrement())
5555
title String
56-
imgSrc String @map("img_src")
56+
imgSrc String @map("img_src")
5757
alt String?
58-
price Decimal @db.Decimal(10, 2)
58+
price Decimal @db.Decimal(10, 2)
5959
description String?
60-
categoryId Int? @map("category_id")
61-
isOnSale Boolean @default(false) @map("is_on_sale")
60+
categoryId Int? @map("category_id")
61+
isOnSale Boolean @default(false) @map("is_on_sale")
6262
features String[]
63-
createdAt DateTime @default(now()) @map("created_at") @db.Timestamp(0)
64-
updatedAt DateTime @default(now()) @map("updated_at") @db.Timestamp(0)
63+
createdAt DateTime @default(now()) @map("created_at") @db.Timestamp(0)
64+
updatedAt DateTime @default(now()) @map("updated_at") @db.Timestamp(0)
65+
variants ProductVariant[]
6566
6667
category Category? @relation(fields: [categoryId], references: [id], onDelete: SetNull)
6768
cartItems CartItem[]
@@ -70,6 +71,15 @@ model Product {
7071
@@map("products")
7172
}
7273

74+
model ProductVariant {
75+
id Int @id @default(autoincrement())
76+
product Product @relation(fields: [productId], references: [id])
77+
productId Int
78+
size String // 'small', 'medium', 'large'
79+
80+
cartItems CartItem[] @relation("CartItemToProductVariant")
81+
}
82+
7383
model Cart {
7484
id Int @id @default(autoincrement())
7585
sessionCartId String @unique @default(dbgenerated("gen_random_uuid()")) @map("session_cart_id") @db.Uuid
@@ -84,15 +94,17 @@ model Cart {
8494
}
8595

8696
model CartItem {
87-
id Int @id @default(autoincrement())
88-
cartId Int @map("cart_id")
89-
productId Int @map("product_id")
90-
quantity Int
91-
createdAt DateTime @default(now()) @map("created_at") @db.Timestamp(0)
92-
updatedAt DateTime @default(now()) @map("updated_at") @db.Timestamp(0)
93-
94-
cart Cart @relation(fields: [cartId], references: [id], onDelete: Cascade)
95-
product Product @relation(fields: [productId], references: [id], onDelete: Cascade)
97+
id Int @id @default(autoincrement())
98+
cartId Int
99+
productId Int
100+
productVariantId Int?
101+
quantity Int
102+
createdAt DateTime @default(now()) @map("created_at") @db.Timestamp(0)
103+
updatedAt DateTime @default(now()) @map("updated_at") @db.Timestamp(0)
104+
105+
cart Cart @relation(fields: [cartId], references: [id], onDelete: Cascade)
106+
product Product @relation(fields: [productId], references: [id], onDelete: Cascade)
107+
productVariant ProductVariant? @relation("CartItemToProductVariant", fields: [productVariantId], references: [id])
96108
97109
@@unique([cartId, productId], name: "unique_cart_item")
98110
@@map("cart_items")

prisma/seed.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,49 @@ import { PrismaClient } from "../generated/prisma/client";
33

44
const prisma = new PrismaClient();
55

6+
// Define las tallas para los productos tipo "Polo"
7+
const poloSizes = ["small", "medium", "large"] as const;
8+
69
async function seedDb() {
10+
// Limpia las tablas para evitar duplicados
11+
await prisma.productVariant.deleteMany();
12+
await prisma.product.deleteMany();
13+
await prisma.category.deleteMany();
14+
15+
// Inserta categorías
716
await prisma.category.createMany({
817
data: categories,
918
});
1019
console.log("1. Categories successfully inserted");
1120

21+
// Inserta productos
1222
await prisma.product.createMany({
1323
data: products,
1424
});
1525
console.log("2. Products successfully inserted");
26+
27+
// Obtiene los productos tipo "Polo" para agregar variantes
28+
const polosCategory = await prisma.category.findUnique({
29+
where: { slug: "polos" },
30+
});
31+
32+
if (polosCategory) {
33+
const polos = await prisma.product.findMany({
34+
where: { categoryId: polosCategory.id },
35+
});
36+
37+
for (const polo of polos) {
38+
for (const size of poloSizes) {
39+
await prisma.productVariant.create({
40+
data: {
41+
productId: polo.id,
42+
size,
43+
},
44+
});
45+
}
46+
}
47+
console.log("3. Polo variants successfully inserted");
48+
}
1649
}
1750

1851
seedDb()
@@ -22,4 +55,4 @@ seedDb()
2255
.finally(async () => {
2356
console.log("--- Database seeded successfully. ---");
2457
await prisma.$disconnect();
25-
});
58+
});

src/lib/cart.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,16 @@ export async function addToCart(
1919
userId: number | undefined,
2020
sessionCartId: string | undefined,
2121
productId: Product["id"],
22-
quantity: number = 1
22+
quantity: number = 1,
23+
productVariantId?: number
2324
) {
2425
try {
2526
const updatedCart = await alterQuantityCartItem(
2627
userId,
2728
sessionCartId,
2829
productId,
29-
quantity
30+
quantity,
31+
productVariantId
3032
);
3133
return updatedCart;
3234
} catch (error) {

src/models/cart.model.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export type CartProductInfo = Pick<
3333
export type CartItemWithProduct = {
3434
product: CartProductInfo;
3535
quantity: number;
36+
productVariantId: number | null;
3637
};
3738

3839
// Tipo para el carrito con items y productos incluidos

src/models/product.model.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,9 @@ import type { Product as PrismaProduct } from "@/../generated/prisma/client";
22

33
export type Product = Omit<PrismaProduct, "price"> & {
44
price: number;
5+
variants?: ProductVariant[];
56
};
7+
export interface ProductVariant {
8+
id: number;
9+
size: "small" | "medium" | "large";
10+
}

src/routes/cart/add-item/index.tsx

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { redirect } from "react-router";
2-
2+
import { prisma } from "@/db/prisma";
33
import { addToCart } from "@/lib/cart";
44
import { getSession } from "@/session.server";
55

@@ -9,12 +9,32 @@ export async function action({ request }: Route.ActionArgs) {
99
const formData = await request.formData();
1010
const productId = Number(formData.get("productId"));
1111
const quantity = Number(formData.get("quantity")) || 1;
12+
const size = formData.get("size") as string | undefined;
1213
const redirectTo = formData.get("redirectTo") as string | null;
1314
const session = await getSession(request.headers.get("Cookie"));
1415
const sessionCartId = session.get("sessionCartId");
1516
const userId = session.get("userId");
1617

17-
await addToCart(userId, sessionCartId, productId, quantity);
18+
let productVariantId: number | undefined = undefined;
19+
20+
// Si hay talla, busca el variant correspondiente
21+
if (size) {
22+
const variant = await prisma.productVariant.findFirst({
23+
where: {
24+
productId,
25+
size,
26+
},
27+
});
28+
if (!variant) {
29+
return new Response(
30+
JSON.stringify({ error: "No se encontró la variante seleccionada." }),
31+
{ status: 400, headers: { "Content-Type": "application/json" } }
32+
);
33+
}
34+
productVariantId = variant.id;
35+
}
36+
37+
await addToCart(userId, sessionCartId, productId, quantity, productVariantId);
1838

1939
return redirect(redirectTo || "/cart");
20-
}
40+
}

0 commit comments

Comments
 (0)