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
20 changes: 11 additions & 9 deletions src/routes/category/components/product-card/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { Link } from "react-router";

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

interface ProductCardProps {
product: Product;
}

export function ProductCard({ product }: ProductCardProps) {
const isSticker = product.categoryId === 3;

return (
<>
<Link
Expand All @@ -26,14 +27,15 @@ export function ProductCard({ product }: ProductCardProps) {
<div className="flex grow flex-col gap-2 p-4">
<h2 className="text-sm font-medium">{product.title}</h2>
<p className="text-sm text-muted-foreground">{product.description}</p>
{
product?.price &&
<p className="mt-auto text-base font-medium">S/{product.price}</p>
}
{
product?.minPrice &&
<p className="mt-auto text-base font-medium">Entre S/{product.minPrice} - {product.maxPrice}</p>
}
{isSticker && (
<div className="text-xs text-muted-foreground">
<p className="text-base font-semibold text-accent-foreground">Desde</p>
<p className="font-medium text-foreground text-base">S/{product.minPrice} - S/{product.maxPrice}p>
</div>
)}
{!isSticker && (
<p className="mt-auto text-base font-medium">S/{product.price}</p>
)}
</div>
{product.isOnSale && (
<span className="absolute top-0 right-0 rounded-bl-xl bg-primary px-2 py-1 text-sm font-medium text-primary-foreground">
Expand Down
123 changes: 73 additions & 50 deletions src/routes/checkout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -301,56 +301,79 @@ export default function Checkout({
Información de envío
</legend>
<div className="flex flex-col gap-6">
<InputField
label="Nombre"
autoComplete="given-name"
error={errors.firstName?.message}
{...register("firstName")}
/>
<InputField
label="Apellido"
autoComplete="family-name"
error={errors.lastName?.message}
{...register("lastName")}
/>
<InputField
label="Compañia"
autoComplete="organization"
error={errors.company?.message}
{...register("company")}
/>
{errors.company?.message && <p>{errors.company?.message}</p>}
<InputField
label="Dirección"
autoComplete="street-address"
error={errors.address?.message}
{...register("address")}
/>
<InputField
label="Ciudad"
autoComplete="address-level2"
error={errors.city?.message}
{...register("city")}
/>
<SelectField
label="País"
options={countryOptions}
placeholder="Seleccionar país"
error={errors.country?.message}
{...register("country")}
/>
<InputField
label="Provincia/Estado"
autoComplete="address-level1"
error={errors.region?.message}
{...register("region")}
/>
<InputField
label="Código Postal"
autoComplete="postal-code"
error={errors.zip?.message}
{...register("zip")}
/>
<div className="flex flex-col sm:flex-row gap-4">
<div className="flex-1">
<InputField
label="Nombre"
autoComplete="given-name"
error={errors.firstName?.message}
{...register("firstName")}
/>
</div>
<div className="flex-1">
<InputField
label="Apellido"
autoComplete="family-name"
error={errors.lastName?.message}
{...register("lastName")}
/>
</div>
</div>

<InputField
label="Compañia"
autoComplete="organization"
error={errors.company?.message}
{...register("company")}
/>
{errors.company?.message && <p>{errors.company?.message}</p>}

<InputField
label="Dirección"
autoComplete="street-address"
error={errors.address?.message}
{...register("address")}
/>

<div className="flex flex-col sm:flex-row gap-4">
<div className="flex-1">
<InputField
label="Ciudad"
autoComplete="address-level2"
error={errors.city?.message}
{...register("city")}
/>
</div>
<div className="flex-1">
<SelectField
label="País"
options={countryOptions}
placeholder="Seleccionar país"
error={errors.country?.message}
{...register("country")}
/>
</div>
</div>

<div className="flex flex-col sm:flex-row gap-4">
<div className="flex-1">
<InputField
label="Provincia/Estado"
autoComplete="address-level1"
error={errors.region?.message}
{...register("region")}
/>
</div>
<div className="flex-1">
<InputField
label="Código Postal"
autoComplete="postal-code"
error={errors.zip?.message}
{...register("zip")}
/>
</div>
</div>

<InputField
label="Teléfono"
autoComplete="tel"
Expand Down
59 changes: 55 additions & 4 deletions src/routes/product/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { Form, useNavigation } from "react-router";

import { useState } from "react";
import { Button, Container, Separator } from "@/components/ui";
import { type Product } from "@/models/product.model";
import { getProductById } from "@/services/product.service";

import NotFound from "../not-found";

import type { Route } from "./+types";

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

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

const showSizeSelector = product.categoryId === 1 || product.categoryId === 3;

const getSizeOptions = () => {
if (product.categoryId === 3) {
return {
label: "Dimensiones",
options: [
{ value: "Small", label: "3x3 cm" },
{ value: "Medium", label: "5x5 cm" },
{ value: "Large", label: "10x10 cm" }
]
};
} else {
return {
label: "Talla",
options: [
{ value: "Small", label: "Small" },
{ value: "Medium", label: "Medium" },
{ value: "Large", label: "Large" }
]
};
}
};

const sizeOptions = getSizeOptions();

return (
<>
<section className="py-12">
Expand All @@ -40,11 +65,35 @@ export default function Product({ loaderData }: Route.ComponentProps) {
<div className="flex-grow flex-basis-0">
<h1 className="text-3xl leading-9 font-bold mb-3">
{product.title}
{showSizeSelector && (
<span className="text-muted-foreground">
{" "}({sizeOptions.options.find(option => option.value === selectedSize)?.label})
</span>
)}
</h1>
<p className="text-3xl leading-9 mb-6">S/{product.price}</p>
<p className="text-sm leading-5 text-muted-foreground mb-10">
{product.description}
</p>

{showSizeSelector && (
<div className="mb-9">
<p className="text-sm font-semibold text-accent-foreground mb-2">{sizeOptions.label}</p>
<div className="flex gap-2">
{sizeOptions.options.map((option) => (
<Button
key={option.value}
variant={selectedSize === option.value ? "default" : "secondary"}
size="lg"
onClick={() => setSelectedSize(option.value)}
>
{option.label}
</Button>
))}
</div>
</div>
)}

<Form method="post" action="/cart/add-item">
<input
type="hidden"
Expand All @@ -62,7 +111,9 @@ export default function Product({ loaderData }: Route.ComponentProps) {
{cartLoading ? "Agregando..." : "Agregar al Carrito"}
</Button>
</Form>

<Separator className="my-6" />

<div>
<h2 className="text-sm font-semibold text-accent-foreground mb-6">
Características
Expand All @@ -78,4 +129,4 @@ export default function Product({ loaderData }: Route.ComponentProps) {
</section>
</>
);
}
}