Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 48 additions & 3 deletions src/routes/category/components/product-card/index.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,71 @@
import { Link } from "react-router";
import { Link, useNavigate } from "react-router";

import type { Product } from "@/models/product.model";
import { Button } from "@/components/ui";

interface ProductCardProps {
product: Product;
categorySlug: string;
}

export function ProductCard({ product }: ProductCardProps) {
export function ProductCard({ product, categorySlug }: ProductCardProps) {
const navigate = useNavigate();
let variantTitle: string | null = null;
let variants: string[] = [];
let variantParamName: "size" | "measure" | null = null;

if (categorySlug === "polos") {
variantTitle = "Elige la talla";
variants = ["Small", "Medium", "Large"];
variantParamName = "size";
} else if (categorySlug === "stickers") {
variantTitle = "Elige la medida";
variants = ["3*3", "5*5", "10*10"];
variantParamName = "measure";
}

const handleVariantClick = (
e: React.MouseEvent<HTMLButtonElement>,
variant: string
) => {
e.preventDefault();
e.stopPropagation();
if (variantParamName) {
const paramValue =
variantParamName === "size" ? variant.toLowerCase() : variant;
navigate(`/products/${product.id}?${variantParamName}=${paramValue}`);
}
};

return (
<Link
to={`/products/${product.id}`}
className="block"
data-testid="product-item"
>
<div className="relative flex h-full flex-col overflow-hidden rounded-xl border border-separator group">
<div className="aspect-[3/4] bg-muted">
<div className="relative aspect-[3/4] bg-muted">
<img
src={product.imgSrc}
alt={product.title}
loading="lazy"
className="h-full w-full object-contain transition-transform duration-200 group-hover:scale-105"
/>
{variantTitle && (
<div className="absolute bottom-0 left-0 right-0 flex flex-col items-center bg-black/50 p-4 text-white opacity-0 transition-opacity duration-200 group-hover:opacity-100">
<h2 className="mb-4 text-xl font-bold">{variantTitle}</h2>
<div className="flex gap-2">
{variants.map((variant) => (
<Button
key={variant}
onClick={(e) => handleVariantClick(e, variant)}
>
{variant}
</Button>
))}
</div>
</div>
)}
</div>
<div className="flex grow flex-col gap-2 p-4">
<h2 className="text-sm font-medium">{product.title}</h2>
Expand Down
6 changes: 5 additions & 1 deletion src/routes/category/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,11 @@ export default function Category({ loaderData }: Route.ComponentProps) {
/>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8 flex-grow">
{products.map((product) => (
<ProductCard product={product} key={product.id} />
<ProductCard
product={product}
key={product.id}
categorySlug={category.slug}
/>
))}
</div>
</div>
Expand Down
44 changes: 34 additions & 10 deletions src/routes/product/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useState } from "react";
import { Form, useNavigation } from "react-router";
import { useEffect, useState } from "react";
import { Form, useNavigation, useSearchParams } from "react-router";

import { VariantSelector } from "@/components/product/VariantSelector";
import { Button, Container, Separator } from "@/components/ui";
Expand All @@ -22,17 +22,41 @@ export async function loader({ params }: Route.LoaderArgs) {
export default function Product({ loaderData }: Route.ComponentProps) {
const { product } = loaderData;
const navigation = useNavigation();
const [searchParams] = useSearchParams();
const cartLoading = navigation.state === "submitting";

// Si el producto tiene variantes, selecciona la primera por defecto
const [selectedSize, setSelectedSize] = useState(
product?.variants?.[0]?.size ?? ""
);
const getInitialSize = () => {
const isValidSize = (size: string | null) => {
return size === "small" || size === "medium" || size === "large";
};
const sizeFromUrl = searchParams.get("size");
const availableSizes = product?.variants?.map((v) => v.size) || [];
if (isValidSize(sizeFromUrl) && availableSizes.includes(sizeFromUrl)) {
return sizeFromUrl;
}
return product?.variants?.[0]?.size ?? "";
};

// Si el producto tiene variantes de stickers, selecciona la primera por defecto
const [selectedMeasure, setSelectedMeasure] = useState(
product?.stickersVariants?.[0]?.measure ?? ""
);
const getInitialMeasure = () => {
const isValidMeasure = (measure: string | null) => {
return measure === "3*3" || measure === "5*5" || measure === "10*10";
};
const measureFromUrl = searchParams.get("measure");
const availableMeasures =
product?.stickersVariants?.map((v) => v.measure) || [];
if (isValidMeasure(measureFromUrl) && availableMeasures.includes(measureFromUrl)) {
return measureFromUrl;
}
return product?.stickersVariants?.[0]?.measure ?? "";
};

const [selectedSize, setSelectedSize] = useState(getInitialSize);
const [selectedMeasure, setSelectedMeasure] = useState(getInitialMeasure);

useEffect(() => {
setSelectedSize(getInitialSize);
setSelectedMeasure(getInitialMeasure);
}, [searchParams, product?.id]);

if (!product) {
return <NotFound />;
Expand Down