|
|
|
|
@@ -6,7 +6,6 @@ import {
|
|
|
|
|
adminBroadcastsApi,
|
|
|
|
|
BroadcastFilter,
|
|
|
|
|
TariffFilter,
|
|
|
|
|
BroadcastChannel,
|
|
|
|
|
CombinedBroadcastCreateRequest,
|
|
|
|
|
} from '../api/adminBroadcasts';
|
|
|
|
|
import { AdminBackButton } from '../components/admin';
|
|
|
|
|
@@ -118,12 +117,15 @@ export default function AdminBroadcastCreate() {
|
|
|
|
|
const queryClient = useQueryClient();
|
|
|
|
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
|
|
|
|
|
|
|
|
// Channel selection
|
|
|
|
|
const [channel, setChannel] = useState<BroadcastChannel>('telegram');
|
|
|
|
|
// Channel toggles (both can be enabled)
|
|
|
|
|
const [telegramEnabled, setTelegramEnabled] = useState(true);
|
|
|
|
|
const [emailEnabled, setEmailEnabled] = useState(false);
|
|
|
|
|
|
|
|
|
|
// Common state
|
|
|
|
|
const [target, setTarget] = useState('');
|
|
|
|
|
const [showFilters, setShowFilters] = useState(false);
|
|
|
|
|
// Separate targets per channel
|
|
|
|
|
const [telegramTarget, setTelegramTarget] = useState('');
|
|
|
|
|
const [emailTarget, setEmailTarget] = useState('');
|
|
|
|
|
const [showTelegramFilters, setShowTelegramFilters] = useState(false);
|
|
|
|
|
const [showEmailFilters, setShowEmailFilters] = useState(false);
|
|
|
|
|
|
|
|
|
|
// Telegram-specific state
|
|
|
|
|
const [messageText, setMessageText] = useState('');
|
|
|
|
|
@@ -138,29 +140,32 @@ export default function AdminBroadcastCreate() {
|
|
|
|
|
const [emailSubject, setEmailSubject] = useState('');
|
|
|
|
|
const [emailContent, setEmailContent] = useState('');
|
|
|
|
|
|
|
|
|
|
// Submitting state for dual send
|
|
|
|
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
|
|
|
|
|
|
|
|
// Fetch Telegram filters
|
|
|
|
|
const { data: filtersData, isLoading: filtersLoading } = useQuery({
|
|
|
|
|
queryKey: ['admin', 'broadcasts', 'filters'],
|
|
|
|
|
queryFn: adminBroadcastsApi.getFilters,
|
|
|
|
|
enabled: channel === 'telegram',
|
|
|
|
|
enabled: telegramEnabled,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Fetch Email filters
|
|
|
|
|
const { data: emailFiltersData, isLoading: emailFiltersLoading } = useQuery({
|
|
|
|
|
queryKey: ['admin', 'broadcasts', 'email-filters'],
|
|
|
|
|
queryFn: adminBroadcastsApi.getEmailFilters,
|
|
|
|
|
enabled: channel === 'email',
|
|
|
|
|
enabled: emailEnabled,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Fetch buttons
|
|
|
|
|
const { data: buttonsData } = useQuery({
|
|
|
|
|
queryKey: ['admin', 'broadcasts', 'buttons'],
|
|
|
|
|
queryFn: adminBroadcastsApi.getButtons,
|
|
|
|
|
enabled: channel === 'telegram',
|
|
|
|
|
enabled: telegramEnabled,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Preview mutations
|
|
|
|
|
const previewMutation = useMutation({
|
|
|
|
|
// Preview mutations — separate for each channel
|
|
|
|
|
const telegramPreviewMutation = useMutation({
|
|
|
|
|
mutationFn: adminBroadcastsApi.preview,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
@@ -168,7 +173,7 @@ export default function AdminBroadcastCreate() {
|
|
|
|
|
mutationFn: adminBroadcastsApi.previewEmail,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Create mutation
|
|
|
|
|
// Create mutation (used for single-channel sends)
|
|
|
|
|
const createMutation = useMutation({
|
|
|
|
|
mutationFn: adminBroadcastsApi.createCombined,
|
|
|
|
|
onSuccess: (data) => {
|
|
|
|
|
@@ -178,7 +183,7 @@ export default function AdminBroadcastCreate() {
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Group Telegram filters
|
|
|
|
|
const groupedFilters = useMemo(() => {
|
|
|
|
|
const groupedTelegramFilters = useMemo(() => {
|
|
|
|
|
if (!filtersData) return {};
|
|
|
|
|
const groups: Record<string, (BroadcastFilter | TariffFilter)[]> = {};
|
|
|
|
|
|
|
|
|
|
@@ -215,48 +220,46 @@ export default function AdminBroadcastCreate() {
|
|
|
|
|
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) return null;
|
|
|
|
|
|
|
|
|
|
if (channel === 'telegram' && filtersData) {
|
|
|
|
|
// Selected filter info for each channel
|
|
|
|
|
const selectedTelegramFilter = useMemo(() => {
|
|
|
|
|
if (!telegramTarget || !filtersData) return null;
|
|
|
|
|
const all = [
|
|
|
|
|
...filtersData.filters,
|
|
|
|
|
...filtersData.tariff_filters,
|
|
|
|
|
...filtersData.custom_filters,
|
|
|
|
|
];
|
|
|
|
|
return all.find((f) => f.key === target);
|
|
|
|
|
}
|
|
|
|
|
return all.find((f) => f.key === telegramTarget) ?? null;
|
|
|
|
|
}, [telegramTarget, filtersData]);
|
|
|
|
|
|
|
|
|
|
if (channel === 'email' && emailFiltersData) {
|
|
|
|
|
return emailFiltersData.filters.find((f) => f.key === target);
|
|
|
|
|
}
|
|
|
|
|
const selectedEmailFilter = useMemo(() => {
|
|
|
|
|
if (!emailTarget || !emailFiltersData) return null;
|
|
|
|
|
return emailFiltersData.filters.find((f) => f.key === emailTarget) ?? null;
|
|
|
|
|
}, [emailTarget, emailFiltersData]);
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}, [target, channel, filtersData, emailFiltersData]);
|
|
|
|
|
// Handle toggling channels
|
|
|
|
|
const handleToggleTelegram = () => {
|
|
|
|
|
setTelegramEnabled((prev) => !prev);
|
|
|
|
|
setTelegramTarget('');
|
|
|
|
|
telegramPreviewMutation.reset();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Handle channel change
|
|
|
|
|
const handleChannelChange = (newChannel: BroadcastChannel) => {
|
|
|
|
|
setChannel(newChannel);
|
|
|
|
|
setTarget('');
|
|
|
|
|
previewMutation.reset();
|
|
|
|
|
const handleToggleEmail = () => {
|
|
|
|
|
setEmailEnabled((prev) => !prev);
|
|
|
|
|
setEmailTarget('');
|
|
|
|
|
emailPreviewMutation.reset();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Handle filter selection
|
|
|
|
|
const handleFilterSelect = (filterKey: string) => {
|
|
|
|
|
setTarget(filterKey);
|
|
|
|
|
setShowFilters(false);
|
|
|
|
|
// Handle filter selection per channel
|
|
|
|
|
const handleTelegramFilterSelect = (filterKey: string) => {
|
|
|
|
|
setTelegramTarget(filterKey);
|
|
|
|
|
setShowTelegramFilters(false);
|
|
|
|
|
telegramPreviewMutation.mutate(filterKey);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (channel === 'telegram') {
|
|
|
|
|
previewMutation.mutate(filterKey);
|
|
|
|
|
} else {
|
|
|
|
|
const handleEmailFilterSelect = (filterKey: string) => {
|
|
|
|
|
setEmailTarget(filterKey);
|
|
|
|
|
setShowEmailFilters(false);
|
|
|
|
|
emailPreviewMutation.mutate(filterKey);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Handle file selection
|
|
|
|
|
@@ -308,114 +311,105 @@ export default function AdminBroadcastCreate() {
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Validate form
|
|
|
|
|
const isTelegramValid = telegramEnabled && telegramTarget && messageText.trim().length > 0;
|
|
|
|
|
const isEmailValid =
|
|
|
|
|
emailEnabled && emailTarget && emailSubject.trim().length > 0 && emailContent.trim().length > 0;
|
|
|
|
|
|
|
|
|
|
const isValid = useMemo(() => {
|
|
|
|
|
if (!target) return false;
|
|
|
|
|
if (!telegramEnabled && !emailEnabled) return false;
|
|
|
|
|
if (telegramEnabled && !isTelegramValid) return false;
|
|
|
|
|
if (emailEnabled && !isEmailValid) return false;
|
|
|
|
|
return true;
|
|
|
|
|
}, [telegramEnabled, emailEnabled, isTelegramValid, isEmailValid]);
|
|
|
|
|
|
|
|
|
|
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]);
|
|
|
|
|
const bothChannels = telegramEnabled && emailEnabled;
|
|
|
|
|
|
|
|
|
|
// Submit
|
|
|
|
|
const handleSubmit = () => {
|
|
|
|
|
const handleSubmit = async () => {
|
|
|
|
|
if (!isValid) return;
|
|
|
|
|
|
|
|
|
|
// Single channel — use existing createMutation with navigation to detail
|
|
|
|
|
if (telegramEnabled && !emailEnabled) {
|
|
|
|
|
const data: CombinedBroadcastCreateRequest = {
|
|
|
|
|
channel,
|
|
|
|
|
target,
|
|
|
|
|
channel: 'telegram',
|
|
|
|
|
target: telegramTarget,
|
|
|
|
|
message_text: messageText,
|
|
|
|
|
selected_buttons: selectedButtons,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (channel === 'telegram') {
|
|
|
|
|
data.message_text = messageText;
|
|
|
|
|
data.selected_buttons = selectedButtons;
|
|
|
|
|
|
|
|
|
|
if (uploadedFileId) {
|
|
|
|
|
data.media = {
|
|
|
|
|
type: mediaType,
|
|
|
|
|
file_id: uploadedFileId,
|
|
|
|
|
};
|
|
|
|
|
data.media = { type: mediaType, file_id: uploadedFileId };
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (channel === 'email') {
|
|
|
|
|
data.email_subject = emailSubject;
|
|
|
|
|
data.email_html_content = emailContent;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
createMutation.mutate(data);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (emailEnabled && !telegramEnabled) {
|
|
|
|
|
const data: CombinedBroadcastCreateRequest = {
|
|
|
|
|
channel: 'email',
|
|
|
|
|
target: emailTarget,
|
|
|
|
|
email_subject: emailSubject,
|
|
|
|
|
email_html_content: emailContent,
|
|
|
|
|
};
|
|
|
|
|
createMutation.mutate(data);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Both channels — two sequential requests, navigate to list
|
|
|
|
|
setIsSubmitting(true);
|
|
|
|
|
try {
|
|
|
|
|
const telegramData: CombinedBroadcastCreateRequest = {
|
|
|
|
|
channel: 'telegram',
|
|
|
|
|
target: telegramTarget,
|
|
|
|
|
message_text: messageText,
|
|
|
|
|
selected_buttons: selectedButtons,
|
|
|
|
|
};
|
|
|
|
|
if (uploadedFileId) {
|
|
|
|
|
telegramData.media = { type: mediaType, file_id: uploadedFileId };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const emailData: CombinedBroadcastCreateRequest = {
|
|
|
|
|
channel: 'email',
|
|
|
|
|
target: emailTarget,
|
|
|
|
|
email_subject: emailSubject,
|
|
|
|
|
email_html_content: emailContent,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const recipientsCount =
|
|
|
|
|
channel === 'telegram'
|
|
|
|
|
? (previewMutation.data?.count ?? selectedFilter?.count ?? null)
|
|
|
|
|
: (emailPreviewMutation.data?.count ?? selectedFilter?.count ?? null);
|
|
|
|
|
await adminBroadcastsApi.createCombined(telegramData);
|
|
|
|
|
await adminBroadcastsApi.createCombined(emailData);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="space-y-6">
|
|
|
|
|
{/* Header */}
|
|
|
|
|
<div className="flex items-center gap-3">
|
|
|
|
|
<AdminBackButton />
|
|
|
|
|
<div className="flex items-center gap-3">
|
|
|
|
|
<div className="rounded-lg bg-accent-500/20 p-2 text-accent-400">
|
|
|
|
|
<BroadcastIcon />
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<h1 className="text-xl font-bold text-dark-100">{t('admin.broadcasts.create')}</h1>
|
|
|
|
|
<p className="text-sm text-dark-400">{t('admin.broadcasts.subtitle')}</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
queryClient.invalidateQueries({ queryKey: ['admin', 'broadcasts'] });
|
|
|
|
|
navigate('/admin/broadcasts');
|
|
|
|
|
} finally {
|
|
|
|
|
setIsSubmitting(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
{/* Channel selection */}
|
|
|
|
|
<div className="card">
|
|
|
|
|
<label className="mb-3 block text-sm font-medium text-dark-300">
|
|
|
|
|
{t('admin.broadcasts.selectChannel')}
|
|
|
|
|
</label>
|
|
|
|
|
<div className="flex gap-3">
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => handleChannelChange('telegram')}
|
|
|
|
|
className={`flex flex-1 items-center justify-center gap-2 rounded-lg border p-4 transition-all ${
|
|
|
|
|
channel === 'telegram'
|
|
|
|
|
? 'border-accent-500 bg-accent-500/10 text-accent-400'
|
|
|
|
|
: 'border-dark-700 bg-dark-800 text-dark-300 hover:border-dark-600'
|
|
|
|
|
}`}
|
|
|
|
|
>
|
|
|
|
|
<TelegramIcon />
|
|
|
|
|
<span className="font-medium">{t('admin.broadcasts.channel.telegram')}</span>
|
|
|
|
|
</button>
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => handleChannelChange('email')}
|
|
|
|
|
className={`flex flex-1 items-center justify-center gap-2 rounded-lg border p-4 transition-all ${
|
|
|
|
|
channel === 'email'
|
|
|
|
|
? 'border-accent-500 bg-accent-500/10 text-accent-400'
|
|
|
|
|
: 'border-dark-700 bg-dark-800 text-dark-300 hover:border-dark-600'
|
|
|
|
|
}`}
|
|
|
|
|
>
|
|
|
|
|
<EmailIcon />
|
|
|
|
|
<span className="font-medium">{t('admin.broadcasts.channel.email')}</span>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
// Recipients counts per channel
|
|
|
|
|
const telegramRecipientsCount = telegramEnabled
|
|
|
|
|
? (telegramPreviewMutation.data?.count ?? selectedTelegramFilter?.count ?? null)
|
|
|
|
|
: null;
|
|
|
|
|
|
|
|
|
|
{/* Content */}
|
|
|
|
|
<div className="card space-y-6">
|
|
|
|
|
{/* Section title */}
|
|
|
|
|
<h2 className="text-lg font-semibold text-dark-100">
|
|
|
|
|
{channel === 'telegram'
|
|
|
|
|
? t('admin.broadcasts.telegramSection')
|
|
|
|
|
: t('admin.broadcasts.emailSection')}
|
|
|
|
|
</h2>
|
|
|
|
|
const emailRecipientsCount = emailEnabled
|
|
|
|
|
? (emailPreviewMutation.data?.count ?? selectedEmailFilter?.count ?? null)
|
|
|
|
|
: null;
|
|
|
|
|
|
|
|
|
|
{/* Filter selection */}
|
|
|
|
|
const isPending = createMutation.isPending || isSubmitting;
|
|
|
|
|
|
|
|
|
|
// Render filter dropdown
|
|
|
|
|
const renderFilterDropdown = (
|
|
|
|
|
channelType: 'telegram' | 'email',
|
|
|
|
|
target: string,
|
|
|
|
|
selectedFilter: BroadcastFilter | TariffFilter | null,
|
|
|
|
|
recipientsCount: number | null,
|
|
|
|
|
showFilters: boolean,
|
|
|
|
|
setShowFilters: (v: boolean) => void,
|
|
|
|
|
handleFilterSelect: (key: string) => void,
|
|
|
|
|
groupedFilters: Record<string, (BroadcastFilter | TariffFilter)[]>,
|
|
|
|
|
isLoading: boolean,
|
|
|
|
|
) => (
|
|
|
|
|
<div>
|
|
|
|
|
<label className="mb-2 block text-sm font-medium text-dark-300">
|
|
|
|
|
{channel === 'telegram'
|
|
|
|
|
{channelType === 'telegram'
|
|
|
|
|
? t('admin.broadcasts.selectFilter')
|
|
|
|
|
: t('admin.broadcasts.selectEmailFilter')}
|
|
|
|
|
</label>
|
|
|
|
|
@@ -429,7 +423,7 @@ export default function AdminBroadcastCreate() {
|
|
|
|
|
<span className={selectedFilter ? 'text-dark-100' : 'text-dark-400'}>
|
|
|
|
|
{selectedFilter
|
|
|
|
|
? selectedFilter.label
|
|
|
|
|
: channel === 'telegram'
|
|
|
|
|
: channelType === 'telegram'
|
|
|
|
|
? t('admin.broadcasts.selectFilterPlaceholder')
|
|
|
|
|
: t('admin.broadcasts.selectEmailFilterPlaceholder')}
|
|
|
|
|
</span>
|
|
|
|
|
@@ -444,10 +438,10 @@ export default function AdminBroadcastCreate() {
|
|
|
|
|
|
|
|
|
|
{showFilters && (
|
|
|
|
|
<div className="absolute left-0 right-0 top-full z-10 mt-1 max-h-64 overflow-y-auto rounded-lg border border-dark-700 bg-dark-800 shadow-xl">
|
|
|
|
|
{isFiltersLoading ? (
|
|
|
|
|
{isLoading ? (
|
|
|
|
|
<div className="p-4 text-center text-dark-400">{t('common.loading')}</div>
|
|
|
|
|
) : (
|
|
|
|
|
Object.entries(currentFilters).map(([group, filters]) => (
|
|
|
|
|
Object.entries(groupedFilters).map(([group, filters]) => (
|
|
|
|
|
<div key={group}>
|
|
|
|
|
<div className="sticky top-0 bg-dark-900 px-3 py-2 text-xs font-medium text-dark-400">
|
|
|
|
|
{FILTER_GROUP_LABEL_KEYS[group] ? t(FILTER_GROUP_LABEL_KEYS[group]) : group}
|
|
|
|
|
@@ -473,10 +467,81 @@ export default function AdminBroadcastCreate() {
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="space-y-6">
|
|
|
|
|
{/* Header */}
|
|
|
|
|
<div className="flex items-center gap-3">
|
|
|
|
|
<AdminBackButton />
|
|
|
|
|
<div className="flex items-center gap-3">
|
|
|
|
|
<div className="rounded-lg bg-accent-500/20 p-2 text-accent-400">
|
|
|
|
|
<BroadcastIcon />
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<h1 className="text-xl font-bold text-dark-100">{t('admin.broadcasts.create')}</h1>
|
|
|
|
|
<p className="text-sm text-dark-400">{t('admin.broadcasts.subtitle')}</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Channel toggles */}
|
|
|
|
|
<div className="card">
|
|
|
|
|
<label className="mb-3 block text-sm font-medium text-dark-300">
|
|
|
|
|
{t('admin.broadcasts.selectChannel')}
|
|
|
|
|
</label>
|
|
|
|
|
<div className="flex gap-3">
|
|
|
|
|
<button
|
|
|
|
|
onClick={handleToggleTelegram}
|
|
|
|
|
className={`flex flex-1 items-center justify-center gap-2 rounded-lg border p-4 transition-all ${
|
|
|
|
|
telegramEnabled
|
|
|
|
|
? 'border-accent-500 bg-accent-500/10 text-accent-400'
|
|
|
|
|
: 'border-dark-700 bg-dark-800 text-dark-300 hover:border-dark-600'
|
|
|
|
|
}`}
|
|
|
|
|
>
|
|
|
|
|
<TelegramIcon />
|
|
|
|
|
<span className="font-medium">{t('admin.broadcasts.enableTelegram')}</span>
|
|
|
|
|
</button>
|
|
|
|
|
<button
|
|
|
|
|
onClick={handleToggleEmail}
|
|
|
|
|
className={`flex flex-1 items-center justify-center gap-2 rounded-lg border p-4 transition-all ${
|
|
|
|
|
emailEnabled
|
|
|
|
|
? 'border-accent-500 bg-accent-500/10 text-accent-400'
|
|
|
|
|
: 'border-dark-700 bg-dark-800 text-dark-300 hover:border-dark-600'
|
|
|
|
|
}`}
|
|
|
|
|
>
|
|
|
|
|
<EmailIcon />
|
|
|
|
|
<span className="font-medium">{t('admin.broadcasts.enableEmail')}</span>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
{!telegramEnabled && !emailEnabled && (
|
|
|
|
|
<p className="mt-2 text-sm text-error-400">{t('admin.broadcasts.atLeastOneChannel')}</p>
|
|
|
|
|
)}
|
|
|
|
|
{bothChannels && (
|
|
|
|
|
<p className="mt-2 text-sm text-accent-400">{t('admin.broadcasts.sendingBoth')}</p>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Telegram section */}
|
|
|
|
|
{telegramEnabled && (
|
|
|
|
|
<div className="card space-y-6">
|
|
|
|
|
<h2 className="text-lg font-semibold text-dark-100">
|
|
|
|
|
{t('admin.broadcasts.telegramSection')}
|
|
|
|
|
</h2>
|
|
|
|
|
|
|
|
|
|
{/* Telegram filter selection */}
|
|
|
|
|
{renderFilterDropdown(
|
|
|
|
|
'telegram',
|
|
|
|
|
telegramTarget,
|
|
|
|
|
selectedTelegramFilter,
|
|
|
|
|
telegramRecipientsCount,
|
|
|
|
|
showTelegramFilters,
|
|
|
|
|
setShowTelegramFilters,
|
|
|
|
|
handleTelegramFilterSelect,
|
|
|
|
|
groupedTelegramFilters,
|
|
|
|
|
filtersLoading,
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* Telegram-specific fields */}
|
|
|
|
|
{channel === 'telegram' && (
|
|
|
|
|
<>
|
|
|
|
|
{/* Message text */}
|
|
|
|
|
<div>
|
|
|
|
|
<label className="mb-2 block text-sm font-medium text-dark-300">
|
|
|
|
|
@@ -575,12 +640,29 @@ export default function AdminBroadcastCreate() {
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* Email section */}
|
|
|
|
|
{emailEnabled && (
|
|
|
|
|
<div className="card space-y-6">
|
|
|
|
|
<h2 className="text-lg font-semibold text-dark-100">
|
|
|
|
|
{t('admin.broadcasts.emailSection')}
|
|
|
|
|
</h2>
|
|
|
|
|
|
|
|
|
|
{/* Email filter selection */}
|
|
|
|
|
{renderFilterDropdown(
|
|
|
|
|
'email',
|
|
|
|
|
emailTarget,
|
|
|
|
|
selectedEmailFilter,
|
|
|
|
|
emailRecipientsCount,
|
|
|
|
|
showEmailFilters,
|
|
|
|
|
setShowEmailFilters,
|
|
|
|
|
handleEmailFilterSelect,
|
|
|
|
|
groupedEmailFilters,
|
|
|
|
|
emailFiltersLoading,
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* Email-specific fields */}
|
|
|
|
|
{channel === 'email' && (
|
|
|
|
|
<>
|
|
|
|
|
{/* Email subject */}
|
|
|
|
|
<div>
|
|
|
|
|
<label className="mb-2 block text-sm font-medium text-dark-300">
|
|
|
|
|
@@ -627,18 +709,26 @@ export default function AdminBroadcastCreate() {
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* Footer */}
|
|
|
|
|
<div className="card flex items-center justify-between">
|
|
|
|
|
<div className="text-sm text-dark-400">
|
|
|
|
|
{recipientsCount !== null && (
|
|
|
|
|
{(telegramRecipientsCount !== null || emailRecipientsCount !== null) && (
|
|
|
|
|
<span>
|
|
|
|
|
{t('admin.broadcasts.willBeSent')}:{' '}
|
|
|
|
|
<strong className="text-accent-400">{recipientsCount}</strong>{' '}
|
|
|
|
|
{t('admin.broadcasts.users')}
|
|
|
|
|
{telegramRecipientsCount !== null && (
|
|
|
|
|
<>
|
|
|
|
|
<strong className="text-accent-400">{telegramRecipientsCount}</strong> (TG)
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
{telegramRecipientsCount !== null && emailRecipientsCount !== null && ' + '}
|
|
|
|
|
{emailRecipientsCount !== null && (
|
|
|
|
|
<>
|
|
|
|
|
<strong className="text-accent-400">{emailRecipientsCount}</strong> (Email)
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
@@ -648,10 +738,10 @@ export default function AdminBroadcastCreate() {
|
|
|
|
|
</button>
|
|
|
|
|
<button
|
|
|
|
|
onClick={handleSubmit}
|
|
|
|
|
disabled={!isValid || createMutation.isPending || isUploading}
|
|
|
|
|
disabled={!isValid || isPending || isUploading}
|
|
|
|
|
className="btn-primary flex items-center gap-2"
|
|
|
|
|
>
|
|
|
|
|
{createMutation.isPending ? <RefreshIcon /> : <BroadcastIcon />}
|
|
|
|
|
{isPending ? <RefreshIcon /> : <BroadcastIcon />}
|
|
|
|
|
{t('admin.broadcasts.send')}
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
|