import { useState, useCallback } from 'react'; import { useNavigate, useParams } from 'react-router'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useTranslation } from 'react-i18next'; import { createNumberInputHandler } from '../utils/inputHelpers'; import { promocodesApi, PromoCodeDetail, PromoCodeType, PromoCodeCreateRequest, PromoCodeUpdateRequest, PromoGroup, } from '../api/promocodes'; import { tariffsApi } from '../api/tariffs'; import { usePlatform } from '../platform/hooks/usePlatform'; import { BackIcon, RefreshIcon } from '@/components/icons'; export default function AdminPromocodeCreate() { const { t } = useTranslation(); const navigate = useNavigate(); const { id } = useParams<{ id: string }>(); const queryClient = useQueryClient(); const { capabilities } = usePlatform(); const isEdit = !!id; // Form state const [code, setCode] = useState(''); const [type, setType] = useState('balance'); const [balanceBonusRubles, setBalanceBonusRubles] = useState(0); const [subscriptionDays, setSubscriptionDays] = useState(0); const [maxUses, setMaxUses] = useState(1); const [isActive, setIsActive] = useState(true); const [firstPurchaseOnly, setFirstPurchaseOnly] = useState(false); const [validUntil, setValidUntil] = useState(''); const [promoGroupId, setPromoGroupId] = useState(null); const [tariffId, setTariffId] = useState(null); // Fetch promo groups (for promo_group type) const { data: promoGroupsData } = useQuery({ queryKey: ['admin-promo-groups'], queryFn: () => promocodesApi.getPromoGroups({ limit: 100 }), }); const promoGroups: PromoGroup[] = promoGroupsData?.items || []; // Fetch tariffs to show trial tariff info const { data: tariffsData } = useQuery({ queryKey: ['admin-tariffs-for-promo'], queryFn: () => tariffsApi.getTariffs(true), enabled: type === 'trial_subscription', }); const trialTariff = tariffsData?.tariffs?.find((t) => t.is_trial_available) || null; // Fetch promocode for editing const { isLoading: isLoadingPromocode } = useQuery({ queryKey: ['admin-promocode', id], queryFn: () => promocodesApi.getPromocode(Number(id)), enabled: isEdit, staleTime: 0, gcTime: 0, refetchOnMount: true, refetchOnWindowFocus: false, select: useCallback((data: PromoCodeDetail) => { setCode(data.code); setType(data.type); // For discount type, balance_bonus_kopeks is percentage directly // For balance type, balance_bonus_kopeks needs to be converted to rubles if (data.type === 'discount') { setBalanceBonusRubles(data.balance_bonus_kopeks); } else { setBalanceBonusRubles(data.balance_bonus_rubles || 0); } setSubscriptionDays(data.subscription_days || 0); setMaxUses(data.max_uses || 1); setIsActive(data.is_active ?? true); setFirstPurchaseOnly(data.first_purchase_only || false); setValidUntil(data.valid_until ? data.valid_until.split('T')[0] : ''); setPromoGroupId(data.promo_group_id || null); setTariffId(data.tariff_id || null); return data; }, []), }); // Create mutation const createMutation = useMutation({ mutationFn: promocodesApi.createPromocode, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['admin-promocodes'] }); navigate('/admin/promocodes'); }, }); // Update mutation const updateMutation = useMutation({ mutationFn: ({ id, data }: { id: number; data: PromoCodeUpdateRequest }) => promocodesApi.updatePromocode(id, data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['admin-promocodes'] }); navigate('/admin/promocodes'); }, }); const handleSubmit = () => { // For discount: balance_bonus_kopeks = percent (integer), subscription_days = hours // For balance: balance_bonus_kopeks = rubles * 100 const balanceValue = balanceBonusRubles === '' ? 0 : balanceBonusRubles; const daysValue = subscriptionDays === '' ? 0 : subscriptionDays; const maxUsesValue = maxUses === '' ? 0 : maxUses; const data: PromoCodeCreateRequest | PromoCodeUpdateRequest = { code: code.trim().toUpperCase(), type, balance_bonus_kopeks: type === 'discount' ? Math.round(balanceValue) // percent as integer : Math.round(balanceValue * 100), // rubles to kopeks subscription_days: daysValue, max_uses: maxUsesValue, is_active: isActive, first_purchase_only: firstPurchaseOnly, valid_until: validUntil ? new Date(validUntil).toISOString() : null, promo_group_id: type === 'promo_group' ? promoGroupId : null, ...(type === 'trial_subscription' && tariffId ? { tariff_id: tariffId } : {}), }; if (isEdit) { updateMutation.mutate({ id: Number(id), data }); } else { createMutation.mutate(data as PromoCodeCreateRequest); } }; const isLoading = createMutation.isPending || updateMutation.isPending; // Validation const isCodeValid = code.trim().length > 0; const balanceValue = balanceBonusRubles === '' ? 0 : balanceBonusRubles; const daysValue = subscriptionDays === '' ? 0 : subscriptionDays; const isValid = (): boolean => { if (!isCodeValid) return false; if (type === 'balance' && balanceValue <= 0) return false; if ((type === 'subscription_days' || type === 'trial_subscription') && daysValue <= 0) return false; if (type === 'promo_group' && !promoGroupId) return false; if (type === 'discount' && (balanceValue <= 0 || balanceValue > 100 || daysValue <= 0)) return false; return true; }; // Collect validation errors for display const validationErrors: string[] = []; if (!isCodeValid) { validationErrors.push('codeRequired'); } if (type === 'balance' && balanceValue <= 0) { validationErrors.push('balanceRequired'); } if ((type === 'subscription_days' || type === 'trial_subscription') && daysValue <= 0) { validationErrors.push('daysRequired'); } if (type === 'promo_group' && !promoGroupId) { validationErrors.push('groupRequired'); } if (type === 'discount') { if (balanceValue <= 0 || balanceValue > 100) { validationErrors.push('discountPercentInvalid'); } if (daysValue <= 0) { validationErrors.push('discountHoursRequired'); } } // Loading state if (isEdit && isLoadingPromocode) { return (
); } return (
{/* Header */}
{/* Show back button only on web, not in Telegram Mini App */} {!capabilities.hasBackButton && ( )}

{isEdit ? t('admin.promocodes.modal.editPromocode') : t('admin.promocodes.modal.newPromocode')}

{t('admin.promocodes.subtitle')}

{/* Form */}
{/* Code */}
setCode(e.target.value.toUpperCase())} className={`input uppercase ${!isCodeValid && code.length > 0 ? 'border-error-500/50' : ''}`} placeholder="SUMMER2025" maxLength={50} />
{/* Type */}
{/* Type-specific fields */} {type === 'balance' && (
{t('admin.promocodes.form.rub')}
)} {(type === 'subscription_days' || type === 'trial_subscription') && (
{t('admin.promocodes.form.days')}
)} {type === 'trial_subscription' && (
{!tariffId && !trialTariff && (
{t( 'admin.promocodes.form.noTrialTariffHint', 'Выберите тариф или отметьте тариф как «доступен для триала» в настройках.', )}
)}
)} {type === 'promo_group' && (
)} {type === 'discount' && ( <>
{ const val = e.target.value; if (val === '') { setBalanceBonusRubles(''); } else { setBalanceBonusRubles(Math.min(100, Math.max(0, parseFloat(val) || 0))); } }} className="input w-32" min={1} max={100} placeholder="0" /> %

{t('admin.promocodes.form.discountHint')}

{ const val = e.target.value; if (val === '') { setSubscriptionDays(''); } else { setSubscriptionDays(Math.max(0, parseInt(val) || 0)); } }} className="input w-32" min={1} placeholder="0" /> {t('admin.promocodes.form.hours')}

{t('admin.promocodes.form.validityHint')}

)} {/* Max Uses */}
{t('admin.promocodes.form.unlimitedHint')}
{/* Valid Until */}
setValidUntil(e.target.value)} className="input" />

{t('admin.promocodes.form.validUntilHint')}

{/* Options */}
{/* Footer */}
{validationErrors.length > 0 && (

{t('admin.tariffs.cannotSave')}

    {validationErrors.map((error) => (
  • {t(`admin.promocodes.validation.${error}`)}
  • ))}
)}
); }