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 */}
>
);
}