Skip to content

Commit 3524492

Browse files
Merge pull request #250 from codeableorg/feat/update-product-variant-ui
feat: update product variants UI
2 parents f5dbce2 + 73d3ba7 commit 3524492

File tree

3 files changed

+139
-63
lines changed

3 files changed

+139
-63
lines changed

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

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

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

98
export function ProductCard({ product }: ProductCardProps) {
9+
const isSticker = product.categoryId === 3;
10+
1011
return (
1112
<>
1213
<Link
@@ -26,14 +27,15 @@ export function ProductCard({ product }: ProductCardProps) {
2627
<div className="flex grow flex-col gap-2 p-4">
2728
<h2 className="text-sm font-medium">{product.title}</h2>
2829
<p className="text-sm text-muted-foreground">{product.description}</p>
29-
{
30-
product?.price &&
31-
<p className="mt-auto text-base font-medium">S/{product.price}</p>
32-
}
33-
{
34-
product?.minPrice &&
35-
<p className="mt-auto text-base font-medium">Entre S/{product.minPrice} - {product.maxPrice}</p>
36-
}
30+
{isSticker && (
31+
<div className="text-xs text-muted-foreground">
32+
<p className="text-base font-semibold text-accent-foreground">Desde</p>
33+
<p className="font-medium text-foreground text-base">S/{product.minPrice} - S/{product.maxPrice}p>
34+
</div>
35+
)}
36+
{!isSticker && (
37+
<p className="mt-auto text-base font-medium">S/{product.price}</p>
38+
)}
3739
</div>
3840
{product.isOnSale && (
3941
<span className="absolute top-0 right-0 rounded-bl-xl bg-primary px-2 py-1 text-sm font-medium text-primary-foreground">

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+
}

0 commit comments

Comments
 (0)