import { useState, useEffect, useRef } from 'react'; import { useNavigate } from 'react-router'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useTranslation } from 'react-i18next'; import { promoOffersApi, PromoOfferBroadcastRequest, TARGET_SEGMENTS, TargetSegment, OFFER_TYPE_CONFIG, OfferType, } from '../api/promoOffers'; import { adminUsersApi, UserListItem } from '../api/adminUsers'; import { AdminBackButton } from '../components/admin'; // Icons const SendIcon = () => ( ); const CheckIcon = () => ( ); const UsersIcon = () => ( ); const UserIcon = () => ( ); const SearchIcon = () => ( ); const CloseIcon = () => ( ); const getOfferTypeIcon = (offerType: string): string => { return OFFER_TYPE_CONFIG[offerType as OfferType]?.icon || '🎁'; }; export default function AdminPromoOfferSend() { const { t } = useTranslation(); const navigate = useNavigate(); const queryClient = useQueryClient(); const [selectedTemplateId, setSelectedTemplateId] = useState(null); const [sendMode, setSendMode] = useState<'segment' | 'user'>('segment'); const [selectedTarget, setSelectedTarget] = useState('active'); const [userId, setUserId] = useState(''); const [searchQuery, setSearchQuery] = useState(''); const [selectedUser, setSelectedUser] = useState(null); const [showDropdown, setShowDropdown] = useState(false); const searchRef = useRef(null); const [result, setResult] = useState<{ title: string; message: string; isSuccess: boolean; } | null>(null); // Query templates const { data: templatesData, isLoading } = useQuery({ queryKey: ['admin-promo-templates'], queryFn: promoOffersApi.getTemplates, }); const templates = templatesData?.items || []; const activeTemplates = templates.filter((t) => t.is_active); const selectedTemplate = templates.find((t) => t.id === selectedTemplateId); // Set default template when loaded if (!selectedTemplateId && activeTemplates.length > 0) { setSelectedTemplateId(activeTemplates[0].id); } // User search query with debounce const { data: searchResults, isFetching: isSearching } = useQuery({ queryKey: ['admin-users-search', searchQuery], queryFn: () => adminUsersApi.getUsers({ search: searchQuery, limit: 10 }), enabled: searchQuery.length >= 2 && sendMode === 'user', staleTime: 30000, }); // Filter users with telegram_id only const filteredUsers = (searchResults?.users || []).filter((u) => u.telegram_id); // Close dropdown on click outside useEffect(() => { const handleClickOutside = (e: MouseEvent) => { if (searchRef.current && !searchRef.current.contains(e.target as Node)) { setShowDropdown(false); } }; document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); }, []); // Handle user selection const handleSelectUser = (user: UserListItem) => { setSelectedUser(user); setUserId(user.telegram_id.toString()); setSearchQuery(''); setShowDropdown(false); }; // Clear selected user const handleClearUser = () => { setSelectedUser(null); setUserId(''); setSearchQuery(''); }; // Broadcast mutation const broadcastMutation = useMutation({ mutationFn: promoOffersApi.broadcastOffer, onSuccess: (data) => { queryClient.invalidateQueries({ queryKey: ['admin-promo-logs'] }); let message = t('admin.promoOffers.result.offersCreated', { count: data.created_offers }); if (data.notifications_sent > 0 || data.notifications_failed > 0) { message += '\n' + t('admin.promoOffers.result.notificationsSent', { count: data.notifications_sent }); if (data.notifications_failed > 0) { message += ' ' + t('admin.promoOffers.result.notificationsFailed', { count: data.notifications_failed, }); } } setResult({ title: t('admin.promoOffers.result.sentTitle'), message, isSuccess: true, }); }, onError: (error: unknown) => { const axiosErr = error as { response?: { data?: { detail?: string } } }; setResult({ title: t('common.error'), message: axiosErr.response?.data?.detail || t('admin.promoOffers.result.sendError'), isSuccess: false, }); }, }); const handleSubmit = () => { if (!selectedTemplateId || !selectedTemplate) return; const data: PromoOfferBroadcastRequest = { notification_type: selectedTemplate.offer_type, valid_hours: selectedTemplate.valid_hours, discount_percent: selectedTemplate.discount_percent, effect_type: selectedTemplate.offer_type === 'test_access' ? 'test_access' : 'percent_discount', extra_data: { template_id: selectedTemplate.id, active_discount_hours: selectedTemplate.active_discount_hours, test_duration_hours: selectedTemplate.test_duration_hours, test_squad_uuids: selectedTemplate.test_squad_uuids, }, send_notification: true, message_text: selectedTemplate.message_text, button_text: selectedTemplate.button_text, }; if (sendMode === 'user') { const id = parseInt(userId); if (!id) return; data.telegram_id = id; } else { data.target = selectedTarget; } broadcastMutation.mutate(data); }; const isValid = () => { if (!selectedTemplateId) return false; if (sendMode === 'user' && !userId.trim()) return false; return true; }; if (isLoading) { return ( ); } // Result screen if (result) { return ( {result.isSuccess ? ( ) : ( )} {result.title} {result.message} navigate('/admin/promo-offers')} className="rounded-lg bg-accent-500 px-6 py-2 text-white transition-colors hover:bg-accent-600" > {t('admin.promoOffers.backToList')} {result.isSuccess && ( setResult(null)} className="rounded-lg border border-dark-600 px-6 py-2 text-dark-300 transition-colors hover:text-dark-100" > {t('admin.promoOffers.sendAnother')} )} ); } return ( {/* Header */} {t('admin.promoOffers.send.title')} {activeTemplates.length === 0 ? ( {t('admin.promoOffers.noActiveTemplates')} ) : ( {/* Template Selection */} {t('admin.promoOffers.send.offerTemplate')} * {activeTemplates.map((template) => ( setSelectedTemplateId(template.id)} className={`w-full rounded-lg border p-4 text-left transition-colors ${ selectedTemplateId === template.id ? 'border-accent-500 bg-accent-500/10' : 'border-dark-600 bg-dark-700 hover:border-dark-500' }`} > {getOfferTypeIcon(template.offer_type)} {template.name} {template.discount_percent > 0 && t('admin.promoOffers.send.discountLabel', { percent: template.discount_percent, })} {template.offer_type === 'test_access' && t('admin.promoOffers.offerType.testAccess')} • {t('admin.promoOffers.send.hoursToActivate', { hours: template.valid_hours, })} {selectedTemplateId === template.id && ( )} ))} {/* Send Mode */} {t('admin.promoOffers.send.sendTo')} * setSendMode('segment')} className={`flex flex-1 items-center justify-center gap-2 rounded-lg border py-2.5 text-sm font-medium transition-colors ${ sendMode === 'segment' ? 'border-accent-500 bg-accent-500/10 text-accent-400' : 'border-dark-600 text-dark-400 hover:text-dark-200' }`} > {t('admin.promoOffers.send.segment')} setSendMode('user')} className={`flex flex-1 items-center justify-center gap-2 rounded-lg border py-2.5 text-sm font-medium transition-colors ${ sendMode === 'user' ? 'border-accent-500 bg-accent-500/10 text-accent-400' : 'border-dark-600 text-dark-400 hover:text-dark-200' }`} > {t('admin.promoOffers.send.user')} {sendMode === 'segment' ? ( setSelectedTarget(e.target.value as TargetSegment)} className="input" > {Object.entries(TARGET_SEGMENTS).map(([key, labelKey]) => ( {t(labelKey)} ))} ) : ( {selectedUser ? ( // Selected user display {selectedUser.full_name || selectedUser.username || `ID: ${selectedUser.telegram_id}`} {selectedUser.username && `@${selectedUser.username} · `} Telegram: {selectedUser.telegram_id} ) : ( // Search input <> { setSearchQuery(e.target.value); setShowDropdown(true); }} onFocus={() => setShowDropdown(true)} placeholder={t('admin.promoOffers.send.searchUserPlaceholder')} className="input pl-10" /> {isSearching && ( )} {/* Dropdown results */} {showDropdown && searchQuery.length >= 2 && ( {filteredUsers.length > 0 ? ( filteredUsers.map((user) => ( handleSelectUser(user)} className="flex w-full items-center gap-3 px-3 py-2.5 text-left transition-colors hover:bg-dark-700" > {user.full_name || user.username || `User #${user.id}`} {user.username && `@${user.username} · `} Telegram: {user.telegram_id} {user.has_subscription && ( {t('admin.promoOffers.send.hasSubscription')} )} )) ) : !isSearching ? ( {t('admin.promoOffers.send.noUsersFound')} ) : null} )} > )} )} {/* Preview */} {selectedTemplate && ( {t('admin.promoOffers.send.preview')} {selectedTemplate.message_text} {selectedTemplate.button_text} )} {/* Actions */} navigate('/admin/promo-offers')} className="btn-secondary"> {t('common.cancel')} {broadcastMutation.isPending ? t('admin.promoOffers.send.sending') : t('admin.promoOffers.send.sendButton')} )} ); }
{result.message}
{t('admin.promoOffers.noActiveTemplates')}