123 lines
3.9 KiB
TypeScript
123 lines
3.9 KiB
TypeScript
"use client";
|
|
|
|
import React, { useState, useEffect } from 'react';
|
|
import Image from 'next/image';
|
|
import { AnimatePresence, motion } from 'framer-motion';
|
|
import { CheckCircle2, X } from 'lucide-react';
|
|
|
|
interface Purchase {
|
|
name: string;
|
|
location: string;
|
|
product: string;
|
|
time: string;
|
|
image: string;
|
|
}
|
|
|
|
const mockPurchases: Purchase[] = [
|
|
{
|
|
name: 'Ben',
|
|
location: 'New York',
|
|
product: 'HP (23.8 Inch) with IPS Panel Technology',
|
|
time: '3 hours ago',
|
|
image: ''
|
|
},
|
|
{
|
|
name: 'Swaraa',
|
|
location: 'California',
|
|
product: 'HP LaserJet P1108 Mono Single Function Laser Printer',
|
|
time: '46 minutes ago',
|
|
image: ''
|
|
},
|
|
{
|
|
name: 'Alex',
|
|
location: 'London',
|
|
product: 'SwapME Braided Nylon Woven Smart Watch',
|
|
time: '12 minutes ago',
|
|
image: ''
|
|
},
|
|
{
|
|
name: 'Hiroshi',
|
|
location: 'Tokyo',
|
|
product: 'Ceptics India to Europe Travel Adapter Plug',
|
|
time: '5 minutes ago',
|
|
image: ''
|
|
}
|
|
];
|
|
|
|
export const LiveToast: React.FC = () => {
|
|
const [currentIndex, setCurrentIndex] = useState(0);
|
|
const [isVisible, setIsVisible] = useState(false);
|
|
|
|
useEffect(() => {
|
|
const initialTimeout = setTimeout(() => {
|
|
setIsVisible(true);
|
|
}, 4000);
|
|
|
|
const interval = setInterval(() => {
|
|
setIsVisible(false);
|
|
setTimeout(() => {
|
|
setCurrentIndex((prev) => (prev + 1) % mockPurchases.length);
|
|
setIsVisible(true);
|
|
}, 1000);
|
|
}, 15000);
|
|
|
|
return () => {
|
|
clearTimeout(initialTimeout);
|
|
clearInterval(interval);
|
|
};
|
|
}, []);
|
|
|
|
if (!mockPurchases.length) return null;
|
|
const currentPurchase = mockPurchases[currentIndex];
|
|
|
|
return (
|
|
<AnimatePresence>
|
|
{isVisible && (
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 50, scale: 0.9 }}
|
|
animate={{ opacity: 1, y: 0, scale: 1 }}
|
|
exit={{ opacity: 0, y: 20, scale: 0.9 }}
|
|
transition={{ type: 'spring', stiffness: 300, damping: 25 }}
|
|
className="fixed bottom-6 left-6 z-50 max-w-[340px] bg-white rounded-theme p-4 shadow-soft hover:shadow-soft-hover border border-gray-100 flex items-start gap-3"
|
|
>
|
|
{currentPurchase.image ? (
|
|
<img
|
|
src={currentPurchase.image}
|
|
alt={currentPurchase.product}
|
|
className="w-12 h-12 object-cover rounded-lg border border-gray-100 flex-shrink-0"
|
|
/>
|
|
) : (
|
|
<div className="w-12 h-12 bg-blue-50 text-primary rounded-lg border border-gray-100 flex items-center justify-center font-bold text-xs flex-shrink-0">
|
|
iF
|
|
</div>
|
|
)}
|
|
<div className="flex-1 min-w-0 pr-4">
|
|
<div className="flex items-center justify-between">
|
|
<p className="text-[11px] text-gray-500 font-medium">
|
|
<span className="font-bold text-gray-800">{currentPurchase.name}</span> from{' '}
|
|
<span className="font-semibold text-primary">{currentPurchase.location}</span>
|
|
</p>
|
|
<button
|
|
onClick={() => setIsVisible(false)}
|
|
className="text-gray-400 hover:text-gray-600 transition-colors"
|
|
>
|
|
<X size={12} />
|
|
</button>
|
|
</div>
|
|
<h4 className="text-[12px] font-bold text-gray-800 mt-0.5 leading-snug truncate">
|
|
{currentPurchase.product}
|
|
</h4>
|
|
<div className="flex items-center gap-1.5 mt-1.5">
|
|
<span className="text-[10px] text-gray-400 font-semibold">{currentPurchase.time}</span>
|
|
<span className="w-1 h-1 bg-gray-300 rounded-full" />
|
|
<div className="flex items-center gap-0.5 text-green-600">
|
|
<CheckCircle2 size={10} fill="currentColor" className="text-white" />
|
|
<span className="text-[9px] font-bold tracking-wider uppercase">Verified</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
);
|
|
};
|