changes: Added chat button on product details
This commit is contained in:
133
components/chat-prompt-bubble.tsx
Normal file
133
components/chat-prompt-bubble.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user