import { useState } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useTranslation } from 'react-i18next'; import { promoApi, PromoOffer } from '../api/promo'; // Icons const GiftIcon = () => ( ); const ClockIcon = () => ( ); const SparklesIcon = () => ( ); const CheckIcon = () => ( ); const ServerIcon = () => ( ); // Helper functions const formatTimeLeft = ( expiresAt: string, t: (key: string, options?: Record) => 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 t('promo.offers.expired'); 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')}`; }; const getOfferIcon = (effectType: string) => { if (effectType === 'test_access') return ; return ; }; const getOfferTitle = ( offer: PromoOffer, t: (key: string, options?: Record) => string, ): string => { if (offer.effect_type === 'test_access') { return t('promo.offers.testAccess'); } if (offer.discount_percent) { return t('promo.offers.discountPercent', { percent: offer.discount_percent }); } return t('promo.offers.specialOffer'); }; const getOfferDescription = ( offer: PromoOffer, t: (key: string, options?: Record) => string, ): string => { if (offer.effect_type === 'test_access') { const squads = offer.extra_data?.test_squad_uuids?.length || 0; return squads > 0 ? t('promo.offers.serverAccess', { count: squads }) : t('promo.offers.additionalServers'); } return t('promo.offers.activateDiscountHint'); }; interface PromoOffersSectionProps { className?: string; } export default function PromoOffersSection({ className = '' }: PromoOffersSectionProps) { const { t } = useTranslation(); const queryClient = useQueryClient(); const [claimingId, setClaimingId] = useState(null); const [successMessage, setSuccessMessage] = useState(null); const [errorMessage, setErrorMessage] = useState(null); // Fetch available offers const { data: offers = [], isLoading: offersLoading } = useQuery({ queryKey: ['promo-offers'], queryFn: promoApi.getOffers, staleTime: 30000, }); // Fetch active discount const { data: activeDiscount } = useQuery({ queryKey: ['active-discount'], queryFn: promoApi.getActiveDiscount, staleTime: 30000, }); // Claim offer mutation const claimMutation = useMutation({ mutationFn: promoApi.claimOffer, onSuccess: (result) => { queryClient.invalidateQueries({ queryKey: ['promo-offers'] }); queryClient.invalidateQueries({ queryKey: ['active-discount'] }); queryClient.invalidateQueries({ queryKey: ['subscription'] }); setSuccessMessage(result.message); setClaimingId(null); setTimeout(() => setSuccessMessage(null), 5000); }, onError: (error: unknown) => { const axiosErr = error as { response?: { data?: { detail?: string } } }; setErrorMessage(axiosErr.response?.data?.detail || t('promo.offers.activationFailed')); setClaimingId(null); setTimeout(() => setErrorMessage(null), 5000); }, }); const handleClaim = (offerId: number) => { setClaimingId(offerId); setErrorMessage(null); setSuccessMessage(null); claimMutation.mutate(offerId); }; // Filter unclaimed and active offers const availableOffers = offers.filter((o) => o.is_active && !o.is_claimed); // Don't render if no offers and no active discount if ( !offersLoading && availableOffers.length === 0 && (!activeDiscount || !activeDiscount.is_active) ) { return null; } return ( {/* Active Discount Banner */} {activeDiscount && activeDiscount.is_active && activeDiscount.discount_percent > 0 && ( {t('promo.offers.discountActiveTitle', { percent: activeDiscount.discount_percent, })} {t('promo.offers.statusActive')} {activeDiscount.expires_at && ( {t('promo.offers.expires', { time: formatTimeLeft(activeDiscount.expires_at, t), })} )} )} {/* Success/Error Messages */} {successMessage && ( {successMessage} )} {errorMessage && ( {errorMessage} )} {/* Available Offers */} {availableOffers.length > 0 && ( {availableOffers.map((offer) => ( {getOfferIcon(offer.effect_type)} {getOfferTitle(offer, t)} {offer.effect_type === 'test_access' && ( {t('promo.offers.test')} )} {getOfferDescription(offer, t)} {t('promo.offers.remaining', { time: formatTimeLeft(offer.expires_at, t) })} handleClaim(offer.id)} disabled={claimingId === offer.id} className="flex items-center gap-2 rounded-lg bg-orange-500 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-orange-600 disabled:cursor-not-allowed disabled:opacity-50" > {claimingId === offer.id ? ( <> {t('promo.offers.activating')} > ) : ( <> {t('promo.offers.activate')} > )} ))} )} {/* Loading State */} {offersLoading && ( )} ); }
{getOfferDescription(offer, t)}