import { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Star, ThumbsUp, AlertTriangle, Zap, Send, X } from 'lucide-react'; import api from '@/services/api'; import { RATING_PROMPT_EVENT } from '@/utils/ratingPrompt'; interface ToolRatingProps { /** Tool slug e.g. "compress-pdf" */ toolSlug: string; } const TAGS = [ { key: 'fast', icon: Zap }, { key: 'accurate', icon: ThumbsUp }, { key: 'issue', icon: AlertTriangle }, ] as const; export default function ToolRating({ toolSlug }: ToolRatingProps) { const { t } = useTranslation(); const [isOpen, setIsOpen] = useState(false); const [rating, setRating] = useState(0); const [hoveredStar, setHoveredStar] = useState(0); const [selectedTag, setSelectedTag] = useState(''); const [feedback, setFeedback] = useState(''); const [submitted, setSubmitted] = useState(false); const [submitting, setSubmitting] = useState(false); const [error, setError] = useState(''); const submittedStorageKey = useMemo(() => `tool-rating:submitted:${toolSlug}`, [toolSlug]); const dismissedStorageKey = useMemo(() => `tool-rating:dismissed:${toolSlug}`, [toolSlug]); const readStorage = useCallback((storage: 'localStorage' | 'sessionStorage', key: string) => { if (typeof window === 'undefined') return null; try { return window[storage].getItem(key); } catch { return null; } }, []); const writeStorage = useCallback( (storage: 'localStorage' | 'sessionStorage', key: string, value: string) => { if (typeof window === 'undefined') return; try { window[storage].setItem(key, value); } catch { // Ignore storage failures and keep the modal functional. } }, [] ); const resetForm = useCallback(() => { setRating(0); setHoveredStar(0); setSelectedTag(''); setFeedback(''); setSubmitted(false); setSubmitting(false); setError(''); }, []); const closeModal = useCallback(() => { setIsOpen(false); if (!submitted) { writeStorage('sessionStorage', dismissedStorageKey, '1'); } }, [dismissedStorageKey, submitted, writeStorage]); useEffect(() => { function handleRatingPrompt(event: Event) { const detail = (event as CustomEvent<{ toolSlug?: string; forceOpen?: boolean }>).detail; if (!detail?.toolSlug || detail.toolSlug !== toolSlug) return; if (readStorage('localStorage', submittedStorageKey)) return; if (!detail.forceOpen && readStorage('sessionStorage', dismissedStorageKey)) return; resetForm(); setIsOpen(true); } window.addEventListener(RATING_PROMPT_EVENT, handleRatingPrompt as EventListener); return () => { window.removeEventListener(RATING_PROMPT_EVENT, handleRatingPrompt as EventListener); }; }, [dismissedStorageKey, readStorage, resetForm, submittedStorageKey, toolSlug]); useEffect(() => { if (!isOpen) return; const previousOverflow = document.body.style.overflow; function handleKeyDown(event: KeyboardEvent) { if (event.key === 'Escape') { closeModal(); } } document.body.style.overflow = 'hidden'; document.addEventListener('keydown', handleKeyDown); return () => { document.body.style.overflow = previousOverflow; document.removeEventListener('keydown', handleKeyDown); }; }, [closeModal, isOpen]); useEffect(() => { if (!submitted || !isOpen) return; const timer = window.setTimeout(() => { setIsOpen(false); resetForm(); }, 1800); return () => window.clearTimeout(timer); }, [isOpen, resetForm, submitted]); async function handleSubmit() { if (rating === 0) return; setSubmitting(true); setError(''); try { await api.post('/ratings/submit', { tool: toolSlug, rating, feedback: feedback.trim(), tag: selectedTag, }); writeStorage('localStorage', submittedStorageKey, '1'); writeStorage('sessionStorage', dismissedStorageKey, '1'); setSubmitted(true); } catch { setError( t('pages.rating.error', 'Failed to submit rating. Please try again.') ); } finally { setSubmitting(false); } } if (!isOpen) { return null; } if (submitted) { return (
{t('pages.rating.successTitle', 'Thank you for your feedback!')}
{t( 'pages.rating.successBody', 'Your rating helps us improve the tools and catch issues faster.' )}
{t( 'pages.rating.promptBody', 'A quick rating after download helps us improve this tool and catch issues sooner.' )}
{error}
)}