import { useState, useMemo, useEffect } from 'react'; import { useNavigate, Link } from 'react-router'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useTranslation } from 'react-i18next'; import { motion, AnimatePresence } from 'framer-motion'; import { giftApi } from '../api/gift'; import type { GiftConfig, GiftTariff, GiftTariffPeriod, GiftPaymentMethod, GiftPurchaseRequest, } from '../api/gift'; import { cn } from '../lib/utils'; import { getApiErrorMessage } from '../utils/api-error'; import { formatPrice } from '../utils/format'; // ============================================================ // Helpers // ============================================================ function detectContactType(value: string): 'email' | 'telegram' { return value.startsWith('@') ? 'telegram' : 'email'; } function isValidContact(value: string): boolean { const trimmed = value.trim(); if (!trimmed) return false; if (trimmed.startsWith('@')) { return /^@[a-zA-Z][a-zA-Z0-9_]{4,31}$/.test(trimmed); } return /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(trimmed); } function formatPeriodLabel( days: number, t: (key: string, options?: Record) => string, ): string { const key = `landing.periodLabels.d${days}`; const result = t(key); if (result !== key) return result; const months = Math.floor(days / 30); const remainder = days % 30; if (months > 0 && remainder === 0) { return t('landing.periodLabels.nMonths', { count: months }); } return t('landing.periodLabels.nDays', { count: days }); } // ============================================================ // Sub-components // ============================================================ function LoadingSkeleton() { return (
); } function ErrorState({ message }: { message: string }) { const { t } = useTranslation(); return (

{t('gift.failedTitle', 'Error')}

{message}

); } function DisabledState() { const { t } = useTranslation(); const navigate = useNavigate(); useEffect(() => { const timer = setTimeout(() => navigate('/'), 3000); return () => clearTimeout(timer); }, [navigate]); return (

{t('gift.featureDisabled', 'Gift subscriptions are currently unavailable')}

{t('gift.redirecting', 'Redirecting...')}

); } function PeriodTabs({ periods, selectedDays, onSelect, }: { periods: GiftTariffPeriod[]; selectedDays: number; onSelect: (days: number) => void; }) { const { t } = useTranslation(); return (
{periods.map((period) => ( ))}
); } function TariffCard({ tariff, isSelected, selectedPeriod, onSelect, }: { tariff: GiftTariff; isSelected: boolean; selectedPeriod: GiftTariffPeriod | undefined; onSelect: () => void; }) { const { t } = useTranslation(); return ( ); } function PaymentModeToggle({ mode, onToggle, balanceLabel, }: { mode: 'balance' | 'gateway'; onToggle: (mode: 'balance' | 'gateway') => void; balanceLabel: string; }) { const { t } = useTranslation(); return (
); } function PaymentMethodCard({ method, isSelected, selectedSubOption, onSelect, onSelectSubOption, }: { method: GiftPaymentMethod; isSelected: boolean; selectedSubOption: string | null; onSelect: () => void; onSelectSubOption: (subOptionId: string) => void; }) { const hasSubOptions = method.sub_options && method.sub_options.length > 1; return (
{/* Sub-options */} {isSelected && hasSubOptions && (
{method.sub_options!.map((opt) => ( ))}
)}
); } function RecipientSection({ value, onChange }: { value: string; onChange: (v: string) => void }) { const { t } = useTranslation(); return (
onChange(e.target.value)} placeholder={t('gift.recipientPlaceholder', 'email@example.com or @telegram')} className="w-full rounded-xl border border-dark-700/50 bg-dark-800/50 px-4 py-3 text-sm text-dark-50 placeholder-dark-500 outline-none transition-colors focus:border-accent-500/50 focus:ring-1 focus:ring-accent-500/25" />

{t( 'gift.recipientHint', 'Enter the email or Telegram username of the person you want to gift', )}

); } function GiftMessageSection({ value, onChange }: { value: string; onChange: (v: string) => void }) { const { t } = useTranslation(); return (