import { useState, useRef, useMemo } from 'react'; import { Link } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; const getAccentColor = (name: string) => { const colors = [ '#4f7eff', '#ff6b6b', '#ffd166', '#06d6a0', '#a855f7', '#f97316', '#06b6d4', '#ec4899', ]; let hash = 0; for (let i = 0; i < name.length; i++) hash = name.charCodeAt(i) + ((hash << 5) - hash); return colors[Math.abs(hash) % colors.length]; }; const getEffectivePrice = (plugin: any): { display: string; isSale: boolean; originalDisplay: string } => { const base = plugin.price_cents ?? 0; const saleActive = plugin.sale_active && plugin.sale_discount_percent > 0; if (saleActive) { const saleCents = Math.round(base * (1 - plugin.sale_discount_percent / 100)); return { display: `€${(saleCents / 100).toFixed(2)}`, isSale: true, originalDisplay: `€${(base / 100).toFixed(2)}`, }; } return { display: `€${(base / 100).toFixed(2)}`, isSale: false, originalDisplay: '', }; }; const PluginCard = ({ plugin }: { plugin: any }) => { const { i18n } = useTranslation(); const [currentIndex, setCurrentIndex] = useState(0); const [isHovering, setIsHovering] = useState(false); const timerRef = useRef(null); const cycleRef = useRef(null); const accent = useMemo(() => getAccentColor(plugin.name), [plugin.name]); const getDescription = (translatedStr: string) => { try { const data = JSON.parse(translatedStr); const currentLang = i18n.language.split('-')[0]; return data[currentLang] || data.en || Object.values(data)[0] || 'No description available.'; } catch { return 'No description available.'; } }; const priceInfo = useMemo(() => getEffectivePrice(plugin), [plugin]); const isSale = plugin.type === 'paid' && priceInfo.isSale; const desc = getDescription(plugin.translated_descriptions); const initial = plugin.name.charAt(0).toUpperCase(); const slideshowImages = useMemo(() => { const images = []; if (plugin.preview_path) images.push(plugin.preview_path); if (plugin.screenshots && plugin.screenshots.length > 0) { images.push(...plugin.screenshots); } return images; }, [plugin.preview_path, plugin.screenshots]); const startHover = () => { setIsHovering(true); if (slideshowImages.length <= 1) return; timerRef.current = setTimeout(() => { cycleRef.current = setInterval(() => { setCurrentIndex(prev => (prev + 1) % slideshowImages.length); }, 2000); }, 200); }; const stopHover = () => { setIsHovering(false); setCurrentIndex(0); if (timerRef.current) clearTimeout(timerRef.current); if (cycleRef.current) clearInterval(cycleRef.current); }; return (
{slideshowImages.length > 0 ? (
{slideshowImages.map((src: string, i: number) => ( {plugin.name} ))} {isHovering && slideshowImages.length > 1 && (
{slideshowImages.map((_: any, i: number) => (
))}
)}
) : (
{initial}
)}
{plugin.category} {plugin.type === 'free' ? ( Free ) : isSale ? ( {priceInfo.originalDisplay} {priceInfo.display} -{plugin.sale_discount_percent}% ) : ( {priceInfo.display} )}

{plugin.name} {plugin.is_early_access && Early Access}

by {plugin.dev_name}

{desc}

{(plugin.downloads ?? 0).toLocaleString()} {plugin.rating != null && ( {Number(plugin.rating).toFixed(1)} )} {plugin.version && ( v{plugin.version} )}
{isSale && (
🔥 Sale — {plugin.sale_discount_percent}% off
)}
); }; export default PluginCard;