changes: Added chat button on product details

This commit is contained in:
2025-10-13 07:02:42 -06:00
parent 25bbfb0489
commit aff6909cc4
2 changed files with 241 additions and 17 deletions

View File

@@ -0,0 +1,133 @@
'use client';
import { useEffect, useState } from 'react';
import { MessageCircle, Sparkles, X } from 'lucide-react';
interface ChatPromptBubbleProps {
message: string;
isVisible: boolean;
onClose: () => void;
onChatClick: () => void;
autoCloseDuration?: number; // milliseconds
}
export default function ChatPromptBubble({
message,
isVisible,
onClose,
onChatClick,
autoCloseDuration = 6000
}: ChatPromptBubbleProps) {
const [isAnimatingOut, setIsAnimatingOut] = useState(false);
useEffect(() => {
if (isVisible) {
// Auto-close after duration
const timer = setTimeout(() => {
handleClose();
}, autoCloseDuration);
return () => clearTimeout(timer);
}
}, [isVisible, autoCloseDuration]);
const handleClose = () => {
setIsAnimatingOut(true);
// Wait for animation to complete before actually closing
setTimeout(() => {
setIsAnimatingOut(false);
onClose();
}, 300);
};
const handleChatClick = () => {
handleClose();
onChatClick();
};
if (!isVisible && !isAnimatingOut) return null;
return (
<div
className={`fixed bottom-24 right-4 sm:right-6 z-[200] max-w-[320px] transition-all duration-300 ${
isAnimatingOut
? 'animate-out slide-out-to-right-4 fade-out'
: 'animate-in slide-in-from-right-4 fade-in'
}`}
style={{
bottom: 'max(6rem, calc(6rem + env(safe-area-inset-bottom)))'
}}
>
{/* Chat Bubble Container */}
<div className="relative group">
{/* Glow Effect */}
<div className="absolute -inset-1 bg-gradient-to-r from-blue-500 to-purple-500 rounded-2xl blur opacity-30 group-hover:opacity-50 animate-pulse"></div>
{/* Main Bubble */}
<div className="relative bg-gradient-to-br from-blue-600 to-purple-600 rounded-2xl shadow-2xl overflow-hidden border border-white/20">
{/* 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>
{/* Close Button */}
<button
onClick={handleClose}
className="absolute top-2 right-2 p-1 rounded-full bg-white/10 hover:bg-white/20 transition-colors"
aria-label="Cerrar"
>
<X size={14} className="text-white" />
</button>
{/* Content */}
<div className="p-4 pr-8">
{/* AI Icon with Animation */}
<div className="flex items-start gap-3 mb-3">
<div className="relative">
{/* Pulsing Ring */}
<div className="absolute inset-0 w-10 h-10 bg-white/20 rounded-full animate-ping"></div>
<div className="relative w-10 h-10 bg-white/20 rounded-full flex items-center justify-center backdrop-blur-sm">
<Sparkles size={20} className="text-white animate-pulse" />
</div>
</div>
{/* Message */}
<div className="flex-1 pt-1">
<p className="text-white text-sm font-medium leading-relaxed">
{message}
</p>
</div>
</div>
{/* CTA Button */}
<button
onClick={handleChatClick}
className="w-full mt-2 flex items-center justify-center gap-2 bg-white/20 hover:bg-white/30 backdrop-blur-sm text-white font-semibold py-2.5 px-4 rounded-lg transition-all duration-200 hover:scale-[1.02] active:scale-[0.98] border border-white/30"
>
<MessageCircle size={18} />
<span>Chatear ahora</span>
<span className="text-lg"></span>
</button>
</div>
{/* Badge "IA En Línea" */}
<div className="absolute -top-2 -left-2">
<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>
</div>
{/* Tail/Pointer */}
<div className="absolute -bottom-2 right-6 w-4 h-4 bg-gradient-to-br from-blue-600 to-purple-600 rotate-45 border-r border-b border-white/20"></div>
</div>
{/* Shimmer Keyframe */}
<style jsx>{`
@keyframes shimmer {
0% { transform: translateX(-100%); }
100% { transform: translateX(100%); }
}
`}</style>
</div>
);
}

View File

@@ -9,8 +9,9 @@ import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/
import { Badge } from "@/components/ui/badge";
import { Button } from '@/components/ui/button';
import { ExternalLink, Package, Truck, Shield, Star, ChevronRight, Store, Tag } from 'lucide-react';
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, type Product } from '@/lib/product-api';
import ChatPromptBubble from '@/components/chat-prompt-bubble';
interface ShoeResultsPopupProps {
isOpen: boolean;
@@ -24,6 +25,11 @@ export default function ShoeResultsPopup({ isOpen, onOpenChange, detectedSKU }:
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);
// Fetch product data when popup opens
useEffect(() => {
if (isOpen && !product) {
@@ -49,7 +55,55 @@ export default function ShoeResultsPopup({ isOpen, onOpenChange, detectedSKU }:
}
}, [isOpen, product]);
// Show chat prompt when popup opens (contextual message)
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
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]);
const handleOpenChat = () => {
if (typeof window !== 'undefined' && window.$chatwoot) {
window.$chatwoot.toggle();
}
};
const handleViewDetails = () => {
if (product?.linkText && selectedVariant) {
@@ -389,28 +443,65 @@ export default function ShoeResultsPopup({ isOpen, onOpenChange, detectedSKU }:
{/* 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)))' }}>
<div className="flex gap-3">
{/* 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={handleViewDetails}
disabled={!selectedVariant}
className="flex-1 min-h-[48px] h-12 sm:h-14 text-base sm:text-lg bg-gradient-to-r from-blue-600 to-purple-600 active:from-blue-700 active:to-purple-700 border-0 shadow-lg shadow-blue-500/25 active:scale-[0.98] transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
aria-label="Ver detalles completos del producto"
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"
>
<ExternalLink className="w-4 h-4 sm:w-5 sm:h-5 mr-2" />
<span className="truncate">Ver Detalles</span>
</Button>
<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>
{/* 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 */}
<ChatPromptBubble
message={promptMessage}
isVisible={showChatPrompt}
onClose={() => setShowChatPrompt(false)}
onChatClick={handleOpenChat}
/>
</Drawer.Portal>
</Drawer.Root>
);