Skip to content

Commit 68c6f1d

Browse files
committed
Merge branch 'grupo-3-main' of https://github.com/codeableorg/fullstock-frontend into grupo-3-main
2 parents 1bf4cd9 + ef1925a commit 68c6f1d

File tree

10 files changed

+234
-89
lines changed

10 files changed

+234
-89
lines changed

prisma/initial_data.ts

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -395,36 +395,36 @@ export const variantAttributeValues = [
395395

396396
// --- STICKERS (dimensiones: 3x3, 6x6, 9x9) ---
397397
{ attributeId: 2, productId: 10, value: "3x3", price: 2.99 },
398-
{ attributeId: 2, productId: 10, value: "6x6", price: 3.99 },
399-
{ attributeId: 2, productId: 10, value: "9x9", price: 4.99 },
398+
{ attributeId: 2, productId: 10, value: "5x5", price: 3.99 },
399+
{ attributeId: 2, productId: 10, value: "10x10", price: 4.99 },
400400

401401
{ attributeId: 2, productId: 11, value: "3x3", price: 2.49 },
402-
{ attributeId: 2, productId: 11, value: "6x6", price: 3.49 },
403-
{ attributeId: 2, productId: 11, value: "9x9", price: 4.49 },
402+
{ attributeId: 2, productId: 11, value: "5x5", price: 3.49 },
403+
{ attributeId: 2, productId: 11, value: "10x10", price: 4.49 },
404404

405405
{ attributeId: 2, productId: 12, value: "3x3", price: 3.99 },
406-
{ attributeId: 2, productId: 12, value: "6x6", price: 4.99 },
407-
{ attributeId: 2, productId: 12, value: "9x9", price: 5.99 },
406+
{ attributeId: 2, productId: 12, value: "5x5", price: 4.99 },
407+
{ attributeId: 2, productId: 12, value: "10x10", price: 5.99 },
408408

409409
{ attributeId: 2, productId: 13, value: "3x3", price: 2.99 },
410-
{ attributeId: 2, productId: 13, value: "6x6", price: 3.99 },
411-
{ attributeId: 2, productId: 13, value: "9x9", price: 4.99 },
410+
{ attributeId: 2, productId: 13, value: "5x5", price: 3.99 },
411+
{ attributeId: 2, productId: 13, value: "10x10", price: 4.99 },
412412

413413
{ attributeId: 2, productId: 14, value: "3x3", price: 2.49 },
414-
{ attributeId: 2, productId: 14, value: "6x6", price: 3.49 },
415-
{ attributeId: 2, productId: 14, value: "9x9", price: 4.49 },
414+
{ attributeId: 2, productId: 14, value: "5x5", price: 3.49 },
415+
{ attributeId: 2, productId: 14, value: "10x10", price: 4.49 },
416416

417417
{ attributeId: 2, productId: 15, value: "3x3", price: 2.49 },
418-
{ attributeId: 2, productId: 15, value: "6x6", price: 3.49 },
419-
{ attributeId: 2, productId: 15, value: "9x9", price: 4.49 },
418+
{ attributeId: 2, productId: 15, value: "5x5", price: 3.49 },
419+
{ attributeId: 2, productId: 15, value: "10x10", price: 4.49 },
420420

421421
{ attributeId: 2, productId: 16, value: "3x3", price: 2.99 },
422-
{ attributeId: 2, productId: 16, value: "6x6", price: 3.99 },
423-
{ attributeId: 2, productId: 16, value: "9x9", price: 4.99 },
422+
{ attributeId: 2, productId: 16, value: "5x5", price: 3.99 },
423+
{ attributeId: 2, productId: 16, value: "10x10", price: 4.99 },
424424

425425
{ attributeId: 2, productId: 17, value: "3x3", price: 2.99 },
426-
{ attributeId: 2, productId: 17, value: "6x6", price: 3.99 },
427-
{ attributeId: 2, productId: 17, value: "9x9", price: .99 },
426+
{ attributeId: 2, productId: 17, value: "5x5", price: 3.99 },
427+
{ attributeId: 2, productId: 17, value: "10x10", price: .99 },
428428

429429
// --- TAZAS (no aplica: Único) ---
430430
{ attributeId: 3, productId: 18, value: "Único", price: 14.99 },

src/models/product.model.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
1+
import type { VariantAttributeValue } from "./variant-attribute.model";
12
import type { Product as PrismaProduct } from "@/../generated/prisma/client";
23

3-
export type Product = Omit<PrismaProduct, "price"> & {
4-
price: number;
4+
export type Product = PrismaProduct & {
5+
price?: number | null;
6+
minPrice?: number | null;
7+
maxPrice?: number | null;
58
};
9+
10+
export type ProductVariantValue = PrismaProduct & {
11+
variantAttributeValues: VariantAttributeValue[];
12+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import type { VariantAttributeValue as PrismaVariantAttributeValue } from "@/../generated/prisma/client";
2+
export type VariantAttributeValue= PrismaVariantAttributeValue

src/routes/category/components/product-card/index.tsx

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
import { Link } from "react-router";
2-
32
import type { Product } from "@/models/product.model";
43

54
interface ProductCardProps {
65
product: Product;
76
}
87

8+
const stickerCategoryId = 3;
9+
910
export function ProductCard({ product }: ProductCardProps) {
11+
12+
const isSticker = stickerCategoryId;
13+
1014
return (
15+
<>
1116
<Link
1217
to={`/products/${product.id}`}
1318
className="block"
@@ -25,7 +30,18 @@ export function ProductCard({ product }: ProductCardProps) {
2530
<div className="flex grow flex-col gap-2 p-4">
2631
<h2 className="text-sm font-medium">{product.title}</h2>
2732
<p className="text-sm text-muted-foreground">{product.description}</p>
28-
<p className="mt-auto text-base font-medium">S/{product.price}</p>
33+
{isSticker ? (
34+
<div className="text-xs text-muted-foreground">
35+
<p className="text-base font-semibold text-accent-foreground">
36+
Desde
37+
</p>
38+
<p className="font-medium text-foreground text-base">
39+
S/{product.minPrice} - S/{product.maxPrice}
40+
</p>
41+
</div>
42+
) : (
43+
<p className="mt-auto text-base font-medium">S/{product.price}</p>
44+
)}
2945
</div>
3046
{product.isOnSale && (
3147
<span className="absolute top-0 right-0 rounded-bl-xl bg-primary px-2 py-1 text-sm font-medium text-primary-foreground">
@@ -34,5 +50,6 @@ export function ProductCard({ product }: ProductCardProps) {
3450
)}
3551
</div>
3652
</Link>
53+
</>
3754
);
3855
}

src/routes/category/index.tsx

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,24 @@ export async function loader({ params, request }: Route.LoaderArgs) {
3636
const min = minPrice ? parseFloat(minPrice) : 0;
3737
const max = maxPrice ? parseFloat(maxPrice) : Infinity;
3838
return products.filter(
39-
(product) => product.price >= min && product.price <= max
40-
);
39+
(product) => {
40+
const minProductPrice = product.minPrice||0
41+
const maxProductPrice = product.maxPrice ||0
42+
const productPrice = product.price || 0
43+
44+
if (min && max) {
45+
return ((productPrice||minProductPrice) >= min) && ((productPrice||maxProductPrice) <= max)
46+
}
47+
48+
if (min) {
49+
return (productPrice||minProductPrice) >= min
50+
}
51+
if (max) {
52+
return (productPrice||maxProductPrice) <= max
53+
54+
}
55+
return true
56+
});
4157
};
4258

4359
const filteredProducts = filterProductsByPrice(

src/routes/checkout/index.tsx

Lines changed: 73 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -301,56 +301,79 @@ export default function Checkout({
301301
Información de envío
302302
</legend>
303303
<div className="flex flex-col gap-6">
304-
<InputField
305-
label="Nombre"
306-
autoComplete="given-name"
307-
error={errors.firstName?.message}
308-
{...register("firstName")}
309-
/>
310-
<InputField
311-
label="Apellido"
312-
autoComplete="family-name"
313-
error={errors.lastName?.message}
314-
{...register("lastName")}
315-
/>
316-
<InputField
317-
label="Compañia"
318-
autoComplete="organization"
319-
error={errors.company?.message}
320-
{...register("company")}
321-
/>
322-
{errors.company?.message && <p>{errors.company?.message}</p>}
323-
<InputField
324-
label="Dirección"
325-
autoComplete="street-address"
326-
error={errors.address?.message}
327-
{...register("address")}
328-
/>
329-
<InputField
330-
label="Ciudad"
331-
autoComplete="address-level2"
332-
error={errors.city?.message}
333-
{...register("city")}
334-
/>
335-
<SelectField
336-
label="País"
337-
options={countryOptions}
338-
placeholder="Seleccionar país"
339-
error={errors.country?.message}
340-
{...register("country")}
341-
/>
342-
<InputField
343-
label="Provincia/Estado"
344-
autoComplete="address-level1"
345-
error={errors.region?.message}
346-
{...register("region")}
347-
/>
348-
<InputField
349-
label="Código Postal"
350-
autoComplete="postal-code"
351-
error={errors.zip?.message}
352-
{...register("zip")}
353-
/>
304+
<div className="flex flex-col sm:flex-row gap-4">
305+
<div className="flex-1">
306+
<InputField
307+
label="Nombre"
308+
autoComplete="given-name"
309+
error={errors.firstName?.message}
310+
{...register("firstName")}
311+
/>
312+
</div>
313+
<div className="flex-1">
314+
<InputField
315+
label="Apellido"
316+
autoComplete="family-name"
317+
error={errors.lastName?.message}
318+
{...register("lastName")}
319+
/>
320+
</div>
321+
</div>
322+
323+
<InputField
324+
label="Compañia"
325+
autoComplete="organization"
326+
error={errors.company?.message}
327+
{...register("company")}
328+
/>
329+
{errors.company?.message && <p>{errors.company?.message}</p>}
330+
331+
<InputField
332+
label="Dirección"
333+
autoComplete="street-address"
334+
error={errors.address?.message}
335+
{...register("address")}
336+
/>
337+
338+
<div className="flex flex-col sm:flex-row gap-4">
339+
<div className="flex-1">
340+
<InputField
341+
label="Ciudad"
342+
autoComplete="address-level2"
343+
error={errors.city?.message}
344+
{...register("city")}
345+
/>
346+
</div>
347+
<div className="flex-1">
348+
<SelectField
349+
label="País"
350+
options={countryOptions}
351+
placeholder="Seleccionar país"
352+
error={errors.country?.message}
353+
{...register("country")}
354+
/>
355+
</div>
356+
</div>
357+
358+
<div className="flex flex-col sm:flex-row gap-4">
359+
<div className="flex-1">
360+
<InputField
361+
label="Provincia/Estado"
362+
autoComplete="address-level1"
363+
error={errors.region?.message}
364+
{...register("region")}
365+
/>
366+
</div>
367+
<div className="flex-1">
368+
<InputField
369+
label="Código Postal"
370+
autoComplete="postal-code"
371+
error={errors.zip?.message}
372+
{...register("zip")}
373+
/>
374+
</div>
375+
</div>
376+
354377
<InputField
355378
label="Teléfono"
356379
autoComplete="tel"

src/routes/product/index.tsx

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
import { Form, useNavigation } from "react-router";
2-
2+
import { useState } from "react";
33
import { Button, Container, Separator } from "@/components/ui";
44
import { type Product } from "@/models/product.model";
55
import { getProductById } from "@/services/product.service";
6-
76
import NotFound from "../not-found";
8-
97
import type { Route } from "./+types";
108

119
export async function loader({ params }: Route.LoaderArgs) {
@@ -21,11 +19,38 @@ export default function Product({ loaderData }: Route.ComponentProps) {
2119
const { product } = loaderData;
2220
const navigation = useNavigation();
2321
const cartLoading = navigation.state === "submitting";
22+
const [selectedSize, setSelectedSize] = useState<string>("Medium");
2423

2524
if (!product) {
2625
return <NotFound />;
2726
}
2827

28+
const showSizeSelector = product.categoryId === 1 || product.categoryId === 3;
29+
30+
const getSizeOptions = () => {
31+
if (product.categoryId === 3) {
32+
return {
33+
label: "Dimensiones",
34+
options: [
35+
{ value: "Small", label: "3x3 cm" },
36+
{ value: "Medium", label: "5x5 cm" },
37+
{ value: "Large", label: "10x10 cm" }
38+
]
39+
};
40+
} else {
41+
return {
42+
label: "Talla",
43+
options: [
44+
{ value: "Small", label: "Small" },
45+
{ value: "Medium", label: "Medium" },
46+
{ value: "Large", label: "Large" }
47+
]
48+
};
49+
}
50+
};
51+
52+
const sizeOptions = getSizeOptions();
53+
2954
return (
3055
<>
3156
<section className="py-12">
@@ -40,11 +65,35 @@ export default function Product({ loaderData }: Route.ComponentProps) {
4065
<div className="flex-grow flex-basis-0">
4166
<h1 className="text-3xl leading-9 font-bold mb-3">
4267
{product.title}
68+
{showSizeSelector && (
69+
<span className="text-muted-foreground">
70+
{" "}({sizeOptions.options.find(option => option.value === selectedSize)?.label})
71+
</span>
72+
)}
4373
</h1>
4474
<p className="text-3xl leading-9 mb-6">S/{product.price}</p>
4575
<p className="text-sm leading-5 text-muted-foreground mb-10">
4676
{product.description}
4777
</p>
78+
79+
{showSizeSelector && (
80+
<div className="mb-9">
81+
<p className="text-sm font-semibold text-accent-foreground mb-2">{sizeOptions.label}</p>
82+
<div className="flex gap-2">
83+
{sizeOptions.options.map((option) => (
84+
<Button
85+
key={option.value}
86+
variant={selectedSize === option.value ? "default" : "secondary"}
87+
size="lg"
88+
onClick={() => setSelectedSize(option.value)}
89+
>
90+
{option.label}
91+
</Button>
92+
))}
93+
</div>
94+
</div>
95+
)}
96+
4897
<Form method="post" action="/cart/add-item">
4998
<input
5099
type="hidden"
@@ -62,7 +111,9 @@ export default function Product({ loaderData }: Route.ComponentProps) {
62111
{cartLoading ? "Agregando..." : "Agregar al Carrito"}
63112
</Button>
64113
</Form>
114+
65115
<Separator className="my-6" />
116+
66117
<div>
67118
<h2 className="text-sm font-semibold text-accent-foreground mb-6">
68119
Características
@@ -78,4 +129,4 @@ export default function Product({ loaderData }: Route.ComponentProps) {
78129
</section>
79130
</>
80131
);
81-
}
132+
}

src/routes/root/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ export async function loader({ request }: Route.LoaderArgs) {
6868
}
6969

7070
const totalItems =
71-
cart?.items.reduce((total, item) => total + item.quantity, 0) || 0;
71+
cart?.items?.reduce((total, item) => total + item.quantity, 0) || 0;
7272

7373
// Preparar datos de respuesta según estado de autenticación
7474
const responseData = user ? { user, totalItems } : { totalItems };

0 commit comments

Comments
 (0)