From 0773afdf6e37f0b8da679a176048764110795919 Mon Sep 17 00:00:00 2001 From: c0mrade Date: Sun, 1 Feb 2026 22:37:53 +0300 Subject: [PATCH] feat: add Telegram/Email channel selection to broadcast create page --- src/pages/AdminBroadcastCreate.tsx | 473 +++++++++++++++++++++-------- 1 file changed, 339 insertions(+), 134 deletions(-) diff --git a/src/pages/AdminBroadcastCreate.tsx b/src/pages/AdminBroadcastCreate.tsx index c2dfc3e..e6988c3 100644 --- a/src/pages/AdminBroadcastCreate.tsx +++ b/src/pages/AdminBroadcastCreate.tsx @@ -6,7 +6,8 @@ import { adminBroadcastsApi, BroadcastFilter, TariffFilter, - BroadcastCreateRequest, + BroadcastChannel, + CombinedBroadcastCreateRequest, } from '../api/adminBroadcasts'; import { AdminBackButton } from '../components/admin'; @@ -83,6 +84,22 @@ const ChevronDownIcon = () => ( ); +const TelegramIcon = () => ( + + + +); + +const EmailIcon = () => ( + + + +); + // Filter labels const FILTER_GROUP_LABEL_KEYS: Record = { basic: 'admin.broadcasts.filterGroups.basic', @@ -92,6 +109,7 @@ const FILTER_GROUP_LABEL_KEYS: Record = { activity: 'admin.broadcasts.filterGroups.activity', source: 'admin.broadcasts.filterGroups.source', tariff: 'admin.broadcasts.filterGroups.tariff', + email: 'admin.broadcasts.filterGroups.email', }; export default function AdminBroadcastCreate() { @@ -100,7 +118,14 @@ export default function AdminBroadcastCreate() { const queryClient = useQueryClient(); const fileInputRef = useRef(null); + // Channel selection + const [channel, setChannel] = useState('telegram'); + + // Common state const [target, setTarget] = useState(''); + const [showFilters, setShowFilters] = useState(false); + + // Telegram-specific state const [messageText, setMessageText] = useState(''); const [selectedButtons, setSelectedButtons] = useState(['home']); const [mediaFile, setMediaFile] = useState(null); @@ -108,52 +133,65 @@ export default function AdminBroadcastCreate() { const [mediaPreview, setMediaPreview] = useState(null); const [uploadedFileId, setUploadedFileId] = useState(null); const [isUploading, setIsUploading] = useState(false); - const [showFilters, setShowFilters] = useState(false); - // Fetch filters + // Email-specific state + const [emailSubject, setEmailSubject] = useState(''); + const [emailContent, setEmailContent] = useState(''); + + // Fetch Telegram filters const { data: filtersData, isLoading: filtersLoading } = useQuery({ queryKey: ['admin', 'broadcasts', 'filters'], queryFn: adminBroadcastsApi.getFilters, + enabled: channel === 'telegram', + }); + + // Fetch Email filters + const { data: emailFiltersData, isLoading: emailFiltersLoading } = useQuery({ + queryKey: ['admin', 'broadcasts', 'email-filters'], + queryFn: adminBroadcastsApi.getEmailFilters, + enabled: channel === 'email', }); // Fetch buttons const { data: buttonsData } = useQuery({ queryKey: ['admin', 'broadcasts', 'buttons'], queryFn: adminBroadcastsApi.getButtons, + enabled: channel === 'telegram', }); - // Preview mutation + // Preview mutations const previewMutation = useMutation({ mutationFn: adminBroadcastsApi.preview, }); + const emailPreviewMutation = useMutation({ + mutationFn: adminBroadcastsApi.previewEmail, + }); + // Create mutation const createMutation = useMutation({ - mutationFn: adminBroadcastsApi.create, + mutationFn: adminBroadcastsApi.createCombined, onSuccess: (data) => { queryClient.invalidateQueries({ queryKey: ['admin', 'broadcasts'] }); navigate(`/admin/broadcasts/${data.id}`); }, }); - // Group filters + // Group Telegram filters const groupedFilters = useMemo(() => { if (!filtersData) return {}; const groups: Record = {}; - // Basic filters filtersData.filters.forEach((f) => { const group = f.group || 'basic'; if (!groups[group]) groups[group] = []; groups[group].push(f); }); - // Tariff filters if (filtersData.tariff_filters.length > 0) { groups['tariff'] = filtersData.tariff_filters; } - // Custom filters filtersData.custom_filters.forEach((f) => { const group = f.group || 'custom'; if (!groups[group]) groups[group] = []; @@ -163,16 +201,63 @@ export default function AdminBroadcastCreate() { return groups; }, [filtersData]); + // Group Email filters + const groupedEmailFilters = useMemo(() => { + if (!emailFiltersData) return {}; + const groups: Record = {}; + + emailFiltersData.filters.forEach((f) => { + const group = f.group || 'email'; + if (!groups[group]) groups[group] = []; + groups[group].push(f); + }); + + return groups; + }, [emailFiltersData]); + + // Current filters based on channel + const currentFilters = channel === 'telegram' ? groupedFilters : groupedEmailFilters; + const isFiltersLoading = channel === 'telegram' ? filtersLoading : emailFiltersLoading; + // Selected filter info const selectedFilter = useMemo(() => { - if (!target || !filtersData) return null; - const all = [ - ...filtersData.filters, - ...filtersData.tariff_filters, - ...filtersData.custom_filters, - ]; - return all.find((f) => f.key === target); - }, [target, filtersData]); + if (!target) return null; + + if (channel === 'telegram' && filtersData) { + const all = [ + ...filtersData.filters, + ...filtersData.tariff_filters, + ...filtersData.custom_filters, + ]; + return all.find((f) => f.key === target); + } + + if (channel === 'email' && emailFiltersData) { + return emailFiltersData.filters.find((f) => f.key === target); + } + + return null; + }, [target, channel, filtersData, emailFiltersData]); + + // Handle channel change + const handleChannelChange = (newChannel: BroadcastChannel) => { + setChannel(newChannel); + setTarget(''); + previewMutation.reset(); + emailPreviewMutation.reset(); + }; + + // Handle filter selection + const handleFilterSelect = (filterKey: string) => { + setTarget(filterKey); + setShowFilters(false); + + if (channel === 'telegram') { + previewMutation.mutate(filterKey); + } else { + emailPreviewMutation.mutate(filterKey); + } + }; // Handle file selection const handleFileSelect = async (e: React.ChangeEvent) => { @@ -181,7 +266,6 @@ export default function AdminBroadcastCreate() { setMediaFile(file); - // Determine media type if (file.type.startsWith('image/')) { setMediaType('photo'); setMediaPreview(URL.createObjectURL(file)); @@ -193,7 +277,6 @@ export default function AdminBroadcastCreate() { setMediaPreview(null); } - // Upload file setIsUploading(true); try { const result = await adminBroadcastsApi.uploadMedia(file, mediaType); @@ -224,27 +307,54 @@ export default function AdminBroadcastCreate() { ); }; + // Validate form + const isValid = useMemo(() => { + if (!target) return false; + + if (channel === 'telegram') { + return messageText.trim().length > 0; + } + + if (channel === 'email') { + return emailSubject.trim().length > 0 && emailContent.trim().length > 0; + } + + return false; + }, [target, channel, messageText, emailSubject, emailContent]); + // Submit const handleSubmit = () => { - if (!target || !messageText.trim()) return; + if (!isValid) return; - const data: BroadcastCreateRequest = { + const data: CombinedBroadcastCreateRequest = { + channel, target, - message_text: messageText, - selected_buttons: selectedButtons, }; - if (uploadedFileId) { - data.media = { - type: mediaType, - file_id: uploadedFileId, - }; + if (channel === 'telegram') { + data.message_text = messageText; + data.selected_buttons = selectedButtons; + + if (uploadedFileId) { + data.media = { + type: mediaType, + file_id: uploadedFileId, + }; + } + } + + if (channel === 'email') { + data.email_subject = emailSubject; + data.email_html_content = emailContent; } createMutation.mutate(data); }; - const recipientsCount = previewMutation.data?.count ?? selectedFilter?.count ?? null; + const recipientsCount = + channel === 'telegram' + ? (previewMutation.data?.count ?? selectedFilter?.count ?? null) + : (emailPreviewMutation.data?.count ?? selectedFilter?.count ?? null); return (
@@ -257,17 +367,57 @@ export default function AdminBroadcastCreate() {

{t('admin.broadcasts.create')}

-

{t('admin.broadcasts.createSubtitle')}

+

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

+ {/* Channel selection */} +
+ +
+ + +
+
+ {/* Content */}
+ {/* Section title */} +

+ {channel === 'telegram' + ? t('admin.broadcasts.telegramSection') + : t('admin.broadcasts.emailSection')} +

+ {/* Filter selection */}
- {/* Message text */} -
- -