'use client'; import { useState, useMemo } from 'react'; import { CdnImage as Image } from '@/ui/shared/cdn-image'; import type { OrderBump, RecommendationVariant } from 'brainerce'; import { formatPrice, getVariantOptions } from 'brainerce'; import { useCurrency } from '@/core/lib/use-currency'; import { useTranslations } from '@/core/lib/translations'; import { cn } from '@/core/lib/utils'; interface OrderBumpCardProps { bump: OrderBump; isAdded: boolean; onToggle: (bumpId: string, add: boolean, variantId?: string) => void; loading: boolean; className?: string; } export function OrderBumpCard({ bump, isAdded, onToggle, loading, className }: OrderBumpCardProps) { const t = useTranslations('checkout'); const currency = useCurrency(); const [selectedVariantId, setSelectedVariantId] = useState(null); const product = bump.bumpProduct; const variants = product.variants; const requiresSelection = bump.requiresVariantSelection && variants && variants.length > 0; // Build attribute groups from variants for pill selector const attributeGroups = useMemo(() => { if (!requiresSelection || !variants) return []; const groups = new Map>(); for (const v of variants) { const opts = getVariantOptions(v as any); for (const opt of opts) { if (!groups.has(opt.name)) groups.set(opt.name, new Set()); groups.get(opt.name)!.add(opt.value); } } return Array.from(groups.entries()).map(([name, values]) => ({ name, values: Array.from(values), })); }, [requiresSelection, variants]); // Track selected attributes const [selectedAttrs, setSelectedAttrs] = useState>({}); // Find matching variant based on selected attributes const selectedVariant = useMemo(() => { if (!requiresSelection || !variants) return null; return ( variants.find((v) => { const opts = getVariantOptions(v as any); return attributeGroups.every((group) => { const opt = opts.find((o) => o.name === group.name); return opt && selectedAttrs[group.name] === opt.value; }); }) ?? null ); }, [requiresSelection, variants, selectedAttrs, attributeGroups]); // Update selectedVariantId when variant match changes const effectiveVariantId = selectedVariant?.id ?? selectedVariantId; function handleAttrSelect(attrName: string, value: string) { const next = { ...selectedAttrs, [attrName]: value }; setSelectedAttrs(next); // Find matching variant with new selection if (variants) { const match = variants.find((v) => { const opts = getVariantOptions(v as any); return attributeGroups.every((group) => { const opt = opts.find((o) => o.name === group.name); return opt && next[group.name] === opt.value; }); }); setSelectedVariantId(match?.id ?? null); } } // Compute display price const { displayOriginal, displayDiscounted } = useMemo(() => { let effectivePrice: number; if (selectedVariant) { const vSale = selectedVariant.salePrice ? parseFloat(selectedVariant.salePrice) : null; const vPrice = selectedVariant.price ? parseFloat(selectedVariant.price) : null; effectivePrice = vSale ?? vPrice ?? parseFloat(bump.originalPrice); } else { effectivePrice = parseFloat(bump.originalPrice); } let discounted: number | null = null; if (bump.discountType && bump.discountValue) { const dv = parseFloat(bump.discountValue); if (bump.discountType === 'PERCENTAGE') { discounted = effectivePrice * (1 - dv / 100); } else { discounted = Math.max(0, effectivePrice - dv); } } return { displayOriginal: effectivePrice, displayDiscounted: discounted }; }, [selectedVariant, bump.originalPrice, bump.discountType, bump.discountValue]); // Check if selected variant is out of stock const isOos = selectedVariant?.inventory?.trackingMode !== 'NOT_TRACKED' && selectedVariant?.inventory?.available != null && selectedVariant.inventory.available <= 0; // Locked variant label const lockedLabel = bump.lockedVariant?.name ?? (bump.lockedVariant?.attributes ? Object.values(bump.lockedVariant.attributes).join(' / ') : null); const firstImage = product.images?.[0]; const imageUrl = firstImage ? typeof firstImage === 'string' ? firstImage : firstImage.url : null; const canToggle = !requiresSelection || !!effectiveVariantId; return (
{/* Compact variant selector */} {requiresSelection && !isAdded && (
{attributeGroups.map((group) => (
{group.name}: {group.values.map((value) => { const isSelected = selectedAttrs[group.name] === value; // Check if this value leads to any available variant const variantForValue = variants?.find((v) => { const opts = getVariantOptions(v as any); const matchesValue = opts.some((o) => o.name === group.name && o.value === value); if (!matchesValue) return false; // Check other selected attrs return Object.entries(selectedAttrs).every(([k, sv]) => { if (k === group.name) return true; return opts.some((o) => o.name === k && o.value === sv); }); }); const isVariantOos = variantForValue?.inventory?.trackingMode !== 'NOT_TRACKED' && variantForValue?.inventory?.available != null && variantForValue.inventory.available <= 0; return ( ); })}
))} {isOos && effectiveVariantId && (

{t('outOfStock') || 'Out of stock'}

)}
)}
); }