Files
temp_SSA_SCAN/components/shoe-results-popup.tsx

598 lines
27 KiB
TypeScript

'use client';
import Image from 'next/image';
import { useState, useEffect } from 'react';
import { Drawer } from 'vaul';
import { Carousel, CarouselContent, CarouselItem } from "@/components/ui/carousel";
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion";
import { Badge } from "@/components/ui/badge";
import { Button } from '@/components/ui/button';
import { ExternalLink, Package, Truck, Shield, Star, ChevronRight, Store, Tag, MessageCircle, Sparkles } from 'lucide-react';
import { fetchProduct, getProductImages, getProductPricing, getProductVariants, getProductCategories, getProductClusters, getStockStatus, getProductGender, getProductSeason, getProductOccasion, getProductColors, getProductHighlight, fetchTestProduct, type Product } from '@/lib/product-api';
import ChatPromptBubble from '@/components/chat-prompt-bubble';
interface ShoeResultsPopupProps {
isOpen: boolean;
onOpenChange: (isOpen: boolean) => void;
detectedSKU?: string | null;
}
export default function ShoeResultsPopup({ isOpen, onOpenChange, detectedSKU }: ShoeResultsPopupProps) {
const [product, setProduct] = useState<Product | null>(null);
const [loading, setLoading] = useState(false);
const [selectedVariant, setSelectedVariant] = useState<string>('');
const [selectedSize, setSelectedSize] = useState<string>('');
// Chat tooltip states
const [showChatPrompt, setShowChatPrompt] = useState(false);
const [promptMessage, setPromptMessage] = useState('');
const [lastPromptTime, setLastPromptTime] = useState(0);
const [wasPopupOpenBeforeChat, setWasPopupOpenBeforeChat] = useState(false);
// Reset product when detectedSKU changes
useEffect(() => {
if (detectedSKU) {
console.log('🔄 SKU cambió, reseteando producto...');
setProduct(null);
setSelectedVariant('');
setSelectedSize('');
}
}, [detectedSKU]);
// Fetch product data when popup opens
useEffect(() => {
if (isOpen && !product) {
setLoading(true);
console.log('🔍 ShoeResultsPopup: Buscando producto con SKU:', detectedSKU);
// 🧪 FLUJO ESPECIAL: Si es test_product, usar producto genérico
if (detectedSKU === 'test_product') {
console.log('🧪 Producto de prueba detectado en popup');
fetchTestProduct().then((testProduct) => {
if (testProduct) {
setProduct(testProduct);
const images = getProductImages(testProduct);
if (images.length > 0) {
// setActiveImageUrl(images[0]);
}
// Set first available variant
const variants = getProductVariants(testProduct);
if (variants.length > 0) {
setSelectedVariant(variants[0].itemId);
if (variants[0].sizes.length > 0) {
setSelectedSize(variants[0].sizes[0]);
}
}
}
setLoading(false);
}).catch((error) => {
console.error('Error loading test product:', error);
setLoading(false);
});
} else {
// FLUJO NORMAL: Extract productId from detectedSKU (first 6 characters)
// Example: "18047409" → "180474"
const productId = detectedSKU?.substring(0, 6);
console.log('📦 ProductId extraído:', productId);
fetchProduct(productId).then((data) => {
if (data) {
setProduct(data);
const images = getProductImages(data);
if (images.length > 0) {
// setActiveImageUrl(images[0]);
}
// Set first available variant
const variants = getProductVariants(data);
if (variants.length > 0) {
setSelectedVariant(variants[0].itemId);
if (variants[0].sizes.length > 0) {
setSelectedSize(variants[0].sizes[0]);
}
}
}
setLoading(false);
});
}
}
}, [isOpen, product, detectedSKU]);
// Show chat prompt when popup opens (contextual message) - DISABLED
// useEffect(() => {
// if (isOpen && product) {
// // Wait 2 seconds after popup opens to show prompt
// const timer = setTimeout(() => {
// const now = Date.now();
// // Only show if we haven't shown a prompt in the last 10 seconds
// if (now - lastPromptTime > 10000) {
// setPromptMessage('🤖 ¿Dudas sobre este modelo? Nuestra IA está lista para ayudarte');
// setShowChatPrompt(true);
// setLastPromptTime(now);
// }
// }, 2000);
// return () => clearTimeout(timer);
// }
// }, [isOpen, product, lastPromptTime]);
// Timer de 15 segundos para mostrar prompts periódicos - DISABLED
// useEffect(() => {
// if (!isOpen) return;
// const interval = setInterval(() => {
// const now = Date.now();
// // Check if Chatwoot is not already open and enough time has passed
// if (typeof window !== 'undefined' && window.$chatwoot) {
// // Only show if chat is not open and enough time since last prompt
// if (now - lastPromptTime > 15000) {
// const messages = [
// '👋 ¿Necesitas ayuda para encontrar tu talla perfecta?',
// '🎯 ¿Buscas algo en específico? Chatea con nuestra IA',
// '💬 Pregúntame sobre tallas, disponibilidad o características',
// ];
// const randomMessage = messages[Math.floor(Math.random() * messages.length)];
// setPromptMessage(randomMessage);
// setShowChatPrompt(true);
// setLastPromptTime(now);
// }
// }
// }, 15000); // Every 15 seconds
// return () => clearInterval(interval);
// }, [isOpen, lastPromptTime]);
// Listen to Chatwoot events to manage popup visibility
useEffect(() => {
if (typeof window === 'undefined') return;
const handleChatwootClose = () => {
console.log('Chatwoot closed - reopening popup if needed');
// If popup was open before opening chat, reopen it
if (wasPopupOpenBeforeChat) {
setTimeout(() => {
onOpenChange(true);
setWasPopupOpenBeforeChat(false);
}, 300); // Small delay for smooth transition
}
};
// Listen to Chatwoot events - try multiple event names
window.addEventListener('chatwoot:close', handleChatwootClose);
window.addEventListener('chatwoot:ready', handleChatwootClose);
// Also use Chatwoot SDK callback if available
if (window.$chatwoot) {
window.$chatwoot.on = window.$chatwoot.on || function() {};
try {
window.$chatwoot.on('widget-closed', handleChatwootClose);
} catch (e) {
console.log('Chatwoot callback not available');
}
}
return () => {
window.removeEventListener('chatwoot:close', handleChatwootClose);
window.removeEventListener('chatwoot:ready', handleChatwootClose);
};
}, [wasPopupOpenBeforeChat, onOpenChange]);
const handleOpenChat = () => {
if (typeof window !== 'undefined' && window.$chatwoot) {
// Save popup state before opening chat and close popup
if (isOpen) {
setWasPopupOpenBeforeChat(true);
onOpenChange(false); // Close the popup immediately
}
// Open Chatwoot
window.$chatwoot.toggle();
}
};
const handleViewDetails = () => {
if (product?.linkText && selectedVariant) {
window.open(`https://www.impuls.com.mx/${product.linkText}/p?skuId=${selectedVariant}`, '_blank');
}
};
if (loading) {
return (
<Drawer.Root open={isOpen} onOpenChange={onOpenChange}>
<Drawer.Portal>
<Drawer.Overlay className="fixed inset-0 bg-black/60 backdrop-blur-sm z-40 pointer-events-none" />
<Drawer.Content className="fixed bottom-0 left-0 right-0 mt-24 flex h-[90%] flex-col rounded-t-3xl bg-black/90 backdrop-blur-xl border-t-2 border-white/20 z-50 shadow-2xl pointer-events-auto"
style={{ paddingBottom: 'env(safe-area-inset-bottom)' }}>
{/* Loading shimmer badge */}
<div className="absolute top-4 right-4 z-20">
<div className="bg-gradient-to-r from-blue-400 to-purple-500 px-3 py-1.5 rounded-full text-sm font-bold text-white animate-pulse">
<span className="flex items-center gap-1">
<span></span>
<span>...</span>
</span>
</div>
</div>
<Drawer.Title className="sr-only">Cargando producto</Drawer.Title>
<Drawer.Description className="sr-only">Obteniendo información del producto</Drawer.Description>
<div className="flex-1 flex items-center justify-center">
<div className="flex flex-col items-center gap-4">
<div className="w-12 h-12 border-4 border-blue-400 border-t-transparent rounded-full animate-spin"></div>
<p className="text-white/80 text-lg">Cargando producto...</p>
</div>
</div>
</Drawer.Content>
</Drawer.Portal>
</Drawer.Root>
);
}
if (!product) return null;
const images = getProductImages(product);
const pricing = getProductPricing(product);
const variants = getProductVariants(product);
const categories = getProductCategories(product);
const clusters = getProductClusters(product);
const genderInfo = getProductGender(product);
const seasonInfo = getProductSeason(product);
const occasionInfo = getProductOccasion(product);
const colorsInfo = getProductColors(product);
const highlightInfo = getProductHighlight(product);
const selectedVariantData = variants.find(v => v.itemId === selectedVariant);
return (
<Drawer.Root open={isOpen} onOpenChange={onOpenChange}>
<Drawer.Portal>
<Drawer.Overlay className="fixed inset-0 bg-black/60 backdrop-blur-sm z-40 pointer-events-none" />
<Drawer.Content className="fixed bottom-0 left-0 right-0 mt-24 flex h-[90%] flex-col rounded-t-3xl bg-black/90 backdrop-blur-xl border-t-2 border-white/20 z-50 shadow-2xl pointer-events-auto"
style={{ paddingBottom: 'env(safe-area-inset-bottom)' }}>
{/* Corner Highlight Badge */}
{highlightInfo && (
<div className="absolute top-4 right-4 z-20">
<div className={`
${highlightInfo.style}
px-3 py-1.5 rounded-full text-sm font-bold
transform rotate-3 hover:rotate-0
transition-all duration-300 cursor-pointer
animate-pulse hover:animate-none
border border-white/20
`}>
<span className="flex items-center gap-1">
<span>{highlightInfo.icon}</span>
<span>{highlightInfo.label}</span>
</span>
</div>
</div>
)}
<div className="flex-1 overflow-y-auto rounded-t-[24px]">
<div className="sticky top-0 z-10 w-full bg-black/80 backdrop-blur-xl rounded-t-[24px] py-6 border-b border-white/10">
<div className="mx-auto h-1.5 w-16 flex-shrink-0 rounded-full bg-white/40 shadow-lg" />
</div>
<Drawer.Title className="sr-only">{product.productName}</Drawer.Title>
<Drawer.Description className="sr-only">{product.description}</Drawer.Description>
<div className="p-6 space-y-6">
{/* Brand Badge and Tags */}
<div className="flex items-center gap-2 overflow-x-auto pb-1">
<Badge variant="secondary" className="bg-white/10 text-white border-white/20 whitespace-nowrap">
<Store size={12} className="mr-1" />
{product.brand}
</Badge>
{pricing.discount > 0 && (
<Badge className="bg-red-500/20 text-red-300 border-red-500/30 whitespace-nowrap">
-{pricing.discount}%
</Badge>
)}
{genderInfo && (
<Badge variant="outline" className={`${genderInfo.color} whitespace-nowrap`}>
<span className="mr-1">{genderInfo.icon}</span>
{genderInfo.label}
</Badge>
)}
{seasonInfo && (
<Badge variant="outline" className={`${seasonInfo.color} whitespace-nowrap`}>
<span className="mr-1">{seasonInfo.icon}</span>
{seasonInfo.season}
</Badge>
)}
{occasionInfo && (
<Badge variant="outline" className={`${occasionInfo.color} whitespace-nowrap`}>
<span className="mr-1">{occasionInfo.icon}</span>
{occasionInfo.label}
</Badge>
)}
</div>
{/* Product Name & Price */}
<div className="space-y-2">
<h1 className="text-2xl font-bold tracking-tight text-white md:text-3xl leading-tight">
{product.productName}
</h1>
<div className="flex items-baseline gap-4">
<p className="text-3xl font-bold bg-gradient-to-r from-green-400 to-emerald-400 bg-clip-text text-transparent">
${pricing.price}
</p>
{pricing.listPrice > pricing.price && (
<p className="text-xl text-gray-500 line-through">
${pricing.listPrice}
</p>
)}
</div>
</div>
{/* Image Carousel */}
<div className="bg-white/5 backdrop-blur-sm rounded-2xl p-4 border border-white/10">
{images.length > 0 ? (
<Carousel className="w-full">
<CarouselContent>
{images.map((url, index) => (
<CarouselItem key={index}>
<div className="relative h-72 w-full overflow-hidden rounded-xl bg-white/10 backdrop-blur-sm border border-white/20 md:h-80">
<Image
src={url}
alt={`${product.productName} imagen ${index + 1}`}
layout="fill"
objectFit="contain"
className="p-4"
onError={(e) => {
const target = e.target as HTMLImageElement;
target.style.display = 'none';
}}
/>
</div>
</CarouselItem>
))}
</CarouselContent>
</Carousel>
) : (
<div className="relative h-72 w-full overflow-hidden rounded-xl bg-white/10 backdrop-blur-sm border border-white/20 md:h-80 flex items-center justify-center">
<div className="flex flex-col items-center gap-4 text-white/60">
<Package size={48} />
<p className="text-lg font-medium">Imagen no disponible</p>
</div>
</div>
)}
</div>
{/* Colors Section */}
{colorsInfo.length > 0 && (
<div className="bg-white/5 backdrop-blur-sm rounded-2xl p-6 border border-white/10">
<h3 className="text-xl font-bold text-white mb-4 flex items-center gap-3">
<div className="w-2 h-2 bg-blue-400 rounded-full"></div>
{colorsInfo.length === 1 ? 'Color Disponible' : 'Colores Disponibles'}
</h3>
<div className="flex items-center flex-wrap gap-3">
{colorsInfo.map((colorInfo, index) => (
<div key={index} className="flex items-center gap-2">
{/* Color dot indicator */}
<div
className="w-6 h-6 rounded-full border-2 border-white/20 shadow-lg"
style={{ backgroundColor: colorInfo.hex }}
/>
<Badge variant="outline" className={colorInfo.badge}>
{colorInfo.label}
</Badge>
</div>
))}
</div>
{/* Additional color info if available */}
{colorsInfo.length === 1 && (
<div className="mt-4 text-sm text-white/60">
Este producto está disponible únicamente en color {colorsInfo[0].label.toLowerCase()}
</div>
)}
</div>
)}
{/* Size Selection with Stock Info */}
{variants.length > 0 && (
<div className="bg-white/5 backdrop-blur-sm rounded-2xl p-6 border border-white/10">
<h3 className="text-xl font-bold text-white mb-4 flex items-center gap-3">
<div className="w-2 h-2 bg-blue-400 rounded-full"></div>
Tallas y Disponibilidad
</h3>
<div className="space-y-3">
{variants.map((variant) => {
const stockInfo = getStockStatus(variant.availableQuantity);
const isOutOfStock = stockInfo.status === 'out_of_stock';
return variant.sizes.map((size) => {
const isSelected = selectedSize === size && selectedVariant === variant.itemId;
return (
<div
key={`${variant.itemId}-${size}`}
className="flex items-center justify-between p-3 bg-white/5 rounded-lg border border-white/10"
>
<button
onClick={() => {
if (!isOutOfStock) {
setSelectedSize(size);
setSelectedVariant(variant.itemId);
}
}}
disabled={isOutOfStock}
className={`flex-1 text-left font-medium transition-colors ${
isSelected
? 'text-blue-400'
: isOutOfStock
? 'text-gray-500 cursor-not-allowed'
: 'text-white hover:text-blue-400'
}`}
>
Talla {size}
</button>
<div className="flex items-center gap-2">
{variant.price > 0 && (
<span className="text-sm text-white/60">
${variant.price}
</span>
)}
<Badge
variant={isOutOfStock ? "secondary" : "default"}
className={`text-xs ${
stockInfo.status === 'out_of_stock'
? 'bg-red-500/20 text-red-300 border-red-500/30'
: stockInfo.status === 'low_stock'
? 'bg-yellow-500/20 text-yellow-300 border-yellow-500/30'
: stockInfo.status === 'limited'
? 'bg-orange-500/20 text-orange-300 border-orange-500/30'
: 'bg-green-500/20 text-green-300 border-green-500/30'
}`}
>
{stockInfo.label}
</Badge>
{isSelected && (
<Button
size="sm"
onClick={() => {
if (product?.linkText && variant.itemId) {
window.open(`https://www.impuls.com.mx/${product.linkText}/p?skuId=${variant.itemId}`, '_blank');
}
}}
className="bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 border-0 shadow-md text-xs px-3 py-1 h-auto animate-in fade-in slide-in-from-right-5 duration-500 ease-out"
>
<ExternalLink className="w-3 h-3 mr-1" />
Ver Detalle
</Button>
)}
</div>
{isSelected && (
<div className="ml-2 w-2 h-2 bg-blue-400 rounded-full animate-in fade-in zoom-in duration-300"></div>
)}
</div>
);
});
})}
</div>
</div>
)}
{/* Product Details Accordion */}
<div className="bg-white/5 backdrop-blur-sm rounded-2xl border border-white/10 overflow-hidden">
<Accordion type="single" collapsible className="w-full">
<AccordionItem value="description" className="border-white/10">
<AccordionTrigger className="px-6 py-4 text-white hover:no-underline hover:bg-white/5">
<div className="flex items-center gap-3">
<Star className="w-4 h-4" />
<span className="font-semibold">Descripción</span>
</div>
</AccordionTrigger>
<AccordionContent className="px-6 pb-4 text-white/80 leading-relaxed">
{product.description || 'Descripción no disponible.'}
</AccordionContent>
</AccordionItem>
<AccordionItem value="shipping" className="border-white/10">
<AccordionTrigger className="px-6 py-4 text-white hover:no-underline hover:bg-white/5">
<div className="flex items-center gap-3">
<Truck className="w-4 h-4" />
<span className="font-semibold">Envío y Devoluciones</span>
</div>
</AccordionTrigger>
<AccordionContent className="px-6 pb-4 text-white/80">
<div className="space-y-2">
<p> Envío gratuito en compras mayores a $999</p>
<p> Entrega en 3-5 días hábiles</p>
<p> Devoluciones gratuitas dentro de 30 días</p>
</div>
</AccordionContent>
</AccordionItem>
<AccordionItem value="warranty" className="border-white/10 border-b-0">
<AccordionTrigger className="px-6 py-4 text-white hover:no-underline hover:bg-white/5">
<div className="flex items-center gap-3">
<Shield className="w-4 h-4" />
<span className="font-semibold">Garantía</span>
</div>
</AccordionTrigger>
<AccordionContent className="px-6 pb-4 text-white/80">
<div className="space-y-2">
<p> Garantía del fabricante de 6 meses</p>
<p> Protección contra defectos de manufactura</p>
<p> Soporte técnico especializado</p>
</div>
</AccordionContent>
</AccordionItem>
</Accordion>
</div>
</div>
</div>
{/* Action Button - Touch optimized with safe area */}
<div className="sticky bottom-0 w-full border-t border-white/10 bg-black/80 backdrop-blur-xl p-4 sm:p-6"
style={{ paddingBottom: 'max(1.5rem, calc(1.5rem + env(safe-area-inset-bottom)))' }}>
{/* Chat con IA Button with Visual Effects */}
<div className="relative w-full group">
{/* Glow effect */}
<div className="absolute -inset-1 bg-gradient-to-r from-blue-500 to-purple-500 rounded-xl blur opacity-40 group-hover:opacity-60 group-active:opacity-70 animate-pulse transition-opacity"></div>
{/* IA En Línea Badge */}
<div className="absolute -top-3 -right-2 z-10">
<div className="bg-green-500 text-white text-xs font-bold px-2 py-1 rounded-full shadow-lg flex items-center gap-1 border border-white/30">
<div className="w-1.5 h-1.5 bg-white rounded-full animate-pulse"></div>
<span>IA En Línea</span>
</div>
</div>
{/* Main Button */}
<Button
size="lg"
onClick={handleOpenChat}
className="relative w-full min-h-[48px] h-12 sm:h-14 text-base sm:text-lg bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 active:from-blue-800 active:to-purple-800 border-0 shadow-lg shadow-blue-500/25 active:scale-[0.98] transition-all duration-200 overflow-hidden"
aria-label="Chatear con asistente de IA"
>
{/* Shimmer effect */}
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/10 to-transparent -translate-x-full animate-[shimmer_2s_infinite]"></div>
{/* Button content */}
<div className="relative flex items-center justify-center gap-2">
<Sparkles className="w-4 h-4 sm:w-5 sm:h-5 animate-pulse" />
<span className="font-semibold truncate">Hablar con IA Asistente</span>
<MessageCircle className="w-4 h-4 sm:w-5 sm:h-5" />
</div>
</Button>
</div>
{/* Favorite Button - Hidden for now */}
{/* <Button
size="lg"
variant="outline"
className="min-w-[48px] min-h-[48px] h-12 sm:h-14 px-4 sm:px-6 bg-white/5 border-white/20 text-white active:bg-white/10 active:border-white/30"
aria-label="Agregar a favoritos"
>
<span className="text-lg sm:text-xl">❤️</span>
</Button> */}
</div>
{/* Shimmer Keyframe */}
<style jsx>{`
@keyframes shimmer {
0% { transform: translateX(-100%); }
100% { transform: translateX(100%); }
}
`}</style>
</Drawer.Content>
{/* Chat Prompt Bubble - DISABLED */}
{/* <ChatPromptBubble
message={promptMessage}
isVisible={showChatPrompt}
onClose={() => setShowChatPrompt(false)}
onChatClick={handleOpenChat}
/> */}
</Drawer.Portal>
</Drawer.Root>
);
}