From b4b4bca2cb290c1be4ae4c076593200c686b72c5 Mon Sep 17 00:00:00 2001 From: kewldan <40865857+kewldan@users.noreply.github.com> Date: Mon, 13 Jul 2026 02:32:11 +0300 Subject: [PATCH] =?UTF-8?q?feat(broadcasts):=20=D0=BF=D0=BE=D0=BB=D0=B5=20?= =?UTF-8?q?icon=5Fcustom=5Femoji=5Fid=20=D0=B4=D0=BB=D1=8F=20=D0=BA=D0=B0?= =?UTF-8?q?=D1=81=D1=82=D0=BE=D0=BC=D0=BD=D1=8B=D1=85=20=D0=BA=D0=BD=D0=BE?= =?UTF-8?q?=D0=BF=D0=BE=D0=BA=20=D1=80=D0=B0=D1=81=D1=81=D1=8B=D0=BB=D0=BA?= =?UTF-8?q?=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/adminBroadcasts.ts | 2 ++ src/locales/en.json | 1 + src/locales/fa.json | 1 + src/locales/ru.json | 1 + src/locales/zh.json | 1 + src/pages/AdminBroadcastCreate.tsx | 26 +++++++++++++++++++++----- 6 files changed, 27 insertions(+), 5 deletions(-) diff --git a/src/api/adminBroadcasts.ts b/src/api/adminBroadcasts.ts index dfc77ff..30c2dbe 100644 --- a/src/api/adminBroadcasts.ts +++ b/src/api/adminBroadcasts.ts @@ -52,6 +52,8 @@ export interface CustomBroadcastButton { label: string; action_type: 'callback' | 'url'; action_value: string; + /** Telegram custom emoji перед текстом кнопки (числовая строка custom_emoji_id) */ + icon_custom_emoji_id?: string; } export interface BroadcastMedia { diff --git a/src/locales/en.json b/src/locales/en.json index e0541f3..ab9bae8 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1947,6 +1947,7 @@ "customButtonTypeUrl": "URL", "customButtonCallbackPlaceholder": "callback_data (e.g. back_to_menu)", "customButtonUrlPlaceholder": "https://example.com", + "customButtonEmojiIdPlaceholder": "Custom emoji ID (optional)", "preview": "Preview", "previewEmpty": "— empty —", "emailSubjectLabel": "Subject", diff --git a/src/locales/fa.json b/src/locales/fa.json index 64b6dee..78e9e40 100644 --- a/src/locales/fa.json +++ b/src/locales/fa.json @@ -1580,6 +1580,7 @@ "customButtonTypeUrl": "لینک", "customButtonCallbackPlaceholder": "callback_data (مثلاً back_to_menu)", "customButtonUrlPlaceholder": "https://example.com", + "customButtonEmojiIdPlaceholder": "شناسه ایموجی سفارشی (اختیاری)", "category": "دسته‌بندی", "categoryNews": "اخبار", "categoryPromo": "تبلیغاتی", diff --git a/src/locales/ru.json b/src/locales/ru.json index 3415f79..5183e30 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -1974,6 +1974,7 @@ "customButtonTypeUrl": "Ссылка", "customButtonCallbackPlaceholder": "callback_data (например, back_to_menu)", "customButtonUrlPlaceholder": "https://example.com", + "customButtonEmojiIdPlaceholder": "Custom emoji ID (необязательно)", "preview": "Предпросмотр", "previewEmpty": "— пусто —", "emailSubjectLabel": "Тема", diff --git a/src/locales/zh.json b/src/locales/zh.json index 08eb37c..02ae25f 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -1681,6 +1681,7 @@ "customButtonTypeUrl": "链接", "customButtonCallbackPlaceholder": "callback_data(例如 back_to_menu)", "customButtonUrlPlaceholder": "https://example.com", + "customButtonEmojiIdPlaceholder": "自定义 Emoji ID(可选)", "category": "类别", "categoryNews": "新闻", "categoryPromo": "促销", diff --git a/src/pages/AdminBroadcastCreate.tsx b/src/pages/AdminBroadcastCreate.tsx index 529eb5e..8220b17 100644 --- a/src/pages/AdminBroadcastCreate.tsx +++ b/src/pages/AdminBroadcastCreate.tsx @@ -4,10 +4,10 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useTranslation } from 'react-i18next'; import { adminBroadcastsApi, - BroadcastFilter, - TariffFilter, - CombinedBroadcastCreateRequest, - CustomBroadcastButton, + type BroadcastFilter, + type TariffFilter, + type CombinedBroadcastCreateRequest, + type CustomBroadcastButton, } from '../api/adminBroadcasts'; import { AdminBackButton } from '../components/admin'; import { TelegramPreview, EmailPreview } from '../components/broadcasts/BroadcastPreview'; @@ -63,6 +63,7 @@ export default function AdminBroadcastCreate() { const [newButtonLabel, setNewButtonLabel] = useState(''); const [newButtonActionType, setNewButtonActionType] = useState<'callback' | 'url'>('callback'); const [newButtonActionValue, setNewButtonActionValue] = useState(''); + const [newButtonEmojiId, setNewButtonEmojiId] = useState(''); const [mediaFile, setMediaFile] = useState(null); const [mediaType, setMediaType] = useState<'photo' | 'video' | 'document'>('photo'); const [mediaPreview, setMediaPreview] = useState(null); @@ -305,6 +306,8 @@ export default function AdminBroadcastCreate() { // Custom button validation const isNewButtonValid = useMemo(() => { if (!newButtonLabel.trim() || !newButtonActionValue.trim()) return false; + // custom_emoji_id — необязательное поле, но если задано — числовая строка (Bot API) + if (newButtonEmojiId.trim() && !/^\d{1,64}$/.test(newButtonEmojiId.trim())) return false; if (newButtonActionType === 'url') { return /^https:\/\/|^tg:\/\//.test(newButtonActionValue.trim()); } @@ -312,22 +315,25 @@ export default function AdminBroadcastCreate() { return new TextEncoder().encode(newButtonActionValue.trim()).length <= 64; } return true; - }, [newButtonLabel, newButtonActionType, newButtonActionValue]); + }, [newButtonLabel, newButtonActionType, newButtonActionValue, newButtonEmojiId]); // Custom button handlers const addCustomButton = () => { if (!isNewButtonValid) return; + const emojiId = newButtonEmojiId.trim(); setCustomButtons((prev) => [ ...prev, { label: newButtonLabel.trim(), action_type: newButtonActionType, action_value: newButtonActionValue.trim(), + ...(emojiId ? { icon_custom_emoji_id: emojiId } : {}), }, ]); setNewButtonLabel(''); setNewButtonActionValue(''); setNewButtonActionType('callback'); + setNewButtonEmojiId(''); setIsAddingCustomButton(false); }; @@ -800,6 +806,15 @@ export default function AdminBroadcastCreate() { maxLength={newButtonActionType === 'callback' ? 64 : 256} className="input" /> + setNewButtonEmojiId(e.target.value)} + placeholder={t('admin.broadcasts.customButtonEmojiIdPlaceholder')} + maxLength={64} + className="input" + />