import { useState, useRef, useEffect, useCallback } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useNavigate } from 'react-router-dom'; import { createPortal } from 'react-dom'; import { useTranslation } from 'react-i18next'; import { promoApi } from '../api/promo'; const SparklesIcon = () => ( ); const ClockIcon = () => ( ); const XCircleIcon = () => ( ); const WarningIcon = () => ( ); const formatTimeLeft = (expiresAt: string, t: (key: string) => string): string => { const now = new Date(); // Ensure UTC parsing - if no timezone specified, assume UTC let expires: Date; if (expiresAt.includes('Z') || expiresAt.includes('+') || expiresAt.includes('-', 10)) { expires = new Date(expiresAt); } else { // No timezone - treat as UTC expires = new Date(expiresAt + 'Z'); } const diffMs = expires.getTime() - now.getTime(); if (diffMs <= 0) return ''; const hours = Math.floor(diffMs / (1000 * 60 * 60)); const minutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60)); if (hours > 24) { const days = Math.floor(hours / 24); return `${days}${t('promo.time.days')}`; } if (hours > 0) { return `${hours}${t('promo.time.hours')} ${minutes}${t('promo.time.minutes')}`; } return `${minutes}${t('promo.time.minutes')}`; }; // ------- Confirmation Modal ------- interface DeactivateConfirmModalProps { isOpen: boolean; discountPercent: number; isDeactivating: boolean; onConfirm: () => void; onCancel: () => void; } function DeactivateConfirmModal({ isOpen, discountPercent, isDeactivating, onConfirm, onCancel, }: DeactivateConfirmModalProps) { const { t } = useTranslation(); // Escape key to close useEffect(() => { if (!isOpen) return; const handleKeyDown = (e: KeyboardEvent) => { if (e.key === 'Escape' && !isDeactivating) { e.preventDefault(); onCancel(); } }; document.addEventListener('keydown', handleKeyDown); return () => document.removeEventListener('keydown', handleKeyDown); }, [isOpen, isDeactivating, onCancel]); // Scroll lock useEffect(() => { if (!isOpen) return; document.body.style.overflow = 'hidden'; return () => { document.body.style.overflow = ''; }; }, [isOpen]); if (!isOpen) return null; const modalContent = (
{/* Backdrop */}
{/* Modal */}
e.stopPropagation()} > {/* Warning header */}

{t('promo.deactivate.confirmTitle')}

{t('promo.deactivate.confirmDescription', { percent: discountPercent })}

{/* Warning text */}

{t('promo.deactivate.warning')}

{/* Action buttons */}
); if (typeof document !== 'undefined') { return createPortal(modalContent, document.body); } return modalContent; } // ------- Main Component ------- export default function PromoDiscountBadge() { const { t } = useTranslation(); const navigate = useNavigate(); const queryClient = useQueryClient(); const [isOpen, setIsOpen] = useState(false); const [showConfirmModal, setShowConfirmModal] = useState(false); const [deactivateError, setDeactivateError] = useState(null); const dropdownRef = useRef(null); const { data: activeDiscount } = useQuery({ queryKey: ['active-discount'], queryFn: promoApi.getActiveDiscount, staleTime: 30000, refetchInterval: 60000, // Refresh every minute }); const deactivateMutation = useMutation({ mutationFn: () => { const source = activeDiscount?.source; if (source && source.startsWith('promocode:')) { return promoApi.deactivateDiscount(); } return promoApi.clearActiveDiscount(); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['active-discount'] }); queryClient.invalidateQueries({ queryKey: ['promo-offers'] }); queryClient.invalidateQueries({ queryKey: ['subscription'] }); queryClient.invalidateQueries({ queryKey: ['purchase-options'] }); setShowConfirmModal(false); setIsOpen(false); setDeactivateError(null); }, onError: (error: unknown) => { const axiosErr = error as { response?: { data?: { detail?: string } } }; setDeactivateError(axiosErr.response?.data?.detail || t('promo.deactivate.error')); }, }); const handleCloseConfirmModal = useCallback(() => { if (!deactivateMutation.isPending) { setShowConfirmModal(false); setDeactivateError(null); } }, [deactivateMutation.isPending]); const handleConfirmDeactivate = useCallback(() => { setDeactivateError(null); deactivateMutation.mutate(); }, [deactivateMutation]); // Close dropdown on click outside useEffect(() => { function handleClickOutside(event: MouseEvent) { if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { setIsOpen(false); } } document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); }, []); // Don't render if no active discount if (!activeDiscount || !activeDiscount.is_active || !activeDiscount.discount_percent) { return null; } const timeLeft = activeDiscount.expires_at ? formatTimeLeft(activeDiscount.expires_at, t) : null; const handleClick = () => { setIsOpen(!isOpen); }; const handleGoToSubscription = () => { setIsOpen(false); navigate('/subscription'); }; const handleDeactivateClick = () => { setDeactivateError(null); setShowConfirmModal(true); }; return ( <>
{/* Badge button */} {/* Dropdown - mobile: fixed centered, desktop: absolute right */} {isOpen && ( <> {/* Mobile backdrop */}
setIsOpen(false)} />
{/* Header */}
{t('promo.discountActive')}
-{activeDiscount.discount_percent}%
{/* Content */}

{t('promo.discountDescription')}

{/* Time remaining */} {timeLeft && (
{t('promo.expiresIn')}:{' '} {timeLeft}
)} {/* Deactivation error */} {deactivateError && (
{deactivateError}
)} {/* CTA Button */} {/* Deactivate Button */}
)}
{/* Deactivation Confirmation Modal */} ); }