import React from 'react'; import { Button } from '@/components/ui/button'; import { Loader } from '@/app/components/ui/loader'; import { RiSubtractLine, RiAddLine } from '@remixicon/react'; import ShopPayButton from '@/components/shopify/shop-pay-button'; import type { ProductOption, ProductOptionValue } from '@/hooks/use-shopify-products'; import { isSwatchOptionName, swatchColorForName } from '@/config/swatches'; interface ProductPrice { amount: string; currencyCode: string; } interface ProductVariant { id: string; title: string; price: ProductPrice; availableForSale: boolean; selectedOptions: Array<{ name: string; value: string; }>; } interface Product { id: string; title: string; description?: string; descriptionHtml?: string; handle: string; priceRange: { minVariantPrice: ProductPrice; }; compareAtPriceRange?: { minVariantPrice: ProductPrice; }; options: ProductOption[]; } export interface ProductFeature { icon: string; label: string; } interface ProductDetailInfoProps { product: Product; selectedVariant: ProductVariant | null; selectedOptions: Record; quantity: number; setQuantity: (quantity: number) => void; handleAddToCart: () => void; handleBuyNow?: () => void; onOptionChange: (optionName: string, value: string) => void; loading?: boolean; buyingNow?: boolean; addToCartLabel?: string; } // A swatch comes from the option value's own swatch (colour or image) or the // colour its name implies (see config/swatches) — never a variant photo. const swatchStyle = ( value: ProductOptionValue ): { background?: string; image?: string } => { if (value.swatch?.color) return { background: value.swatch.color }; const swatchImage = value.swatch?.image?.previewImage?.url; if (swatchImage) return { image: swatchImage }; const namedColor = swatchColorForName(value.name); if (namedColor) return { background: namedColor }; return {}; }; const ProductDetailInfo: React.FC = ({ product, selectedVariant, selectedOptions, quantity, setQuantity, handleAddToCart, handleBuyNow, onOptionChange, loading = false, buyingNow = false, addToCartLabel = 'Add to Cart', }) => { const formatPrice = (amount: string) => { return `$${parseFloat(amount).toFixed(2)}`; }; const price = selectedVariant?.price || product.priceRange.minVariantPrice; const compareAtPrice = product.compareAtPriceRange?.minVariantPrice; const hasDiscount = compareAtPrice && parseFloat(compareAtPrice.amount) > parseFloat(price.amount); const isAvailable = selectedVariant?.availableForSale ?? false; const isSwatchOption = (option: ProductOption) => isSwatchOptionName(option.name); // Some products return Size before Color; show the swatches first either way. const orderedOptions = [...(product.options ?? [])].sort( (a, b) => Number(isSwatchOption(b)) - Number(isSwatchOption(a)) ); // `optionValues` carries the swatch data; fall back to plain `values`. const optionValuesFor = (option: ProductOption): ProductOptionValue[] => option.optionValues?.length ? option.optionValues : option.values.map((value) => ({ id: value, name: value })); return (

{product.title}

{formatPrice(price.amount)} {hasDiscount && compareAtPrice && ( {formatPrice(compareAtPrice.amount)} )}
{/* Product Options — colour swatches lead, whatever order the API returns */} {orderedOptions.map((option) => { const isSwatch = isSwatchOption(option); const selected = selectedOptions[option.name]; return (
{option.name} {isSwatch && selected && ( : {selected} )}
{optionValuesFor(option).map((value) => { const isSelected = selected === value.name; if (isSwatch) { const { background, image } = swatchStyle(value); return ( ); } return ( ); })}
); })} {/* Quantity + Add to Cart */}
{quantity}
{/* Cart permalink into Shop Pay; falls back to the cart checkout URL. */} {/* Description */} {(product.descriptionHtml || product.description) && (
{product.descriptionHtml ? (
) : (

{product.description}

)}
)}
); }; export default ProductDetailInfo;