mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 17:43:47 +00:00
feat(broadcasts): поле icon_custom_emoji_id для кастомных кнопок рассылки
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -1580,6 +1580,7 @@
|
||||
"customButtonTypeUrl": "لینک",
|
||||
"customButtonCallbackPlaceholder": "callback_data (مثلاً back_to_menu)",
|
||||
"customButtonUrlPlaceholder": "https://example.com",
|
||||
"customButtonEmojiIdPlaceholder": "شناسه ایموجی سفارشی (اختیاری)",
|
||||
"category": "دستهبندی",
|
||||
"categoryNews": "اخبار",
|
||||
"categoryPromo": "تبلیغاتی",
|
||||
|
||||
@@ -1974,6 +1974,7 @@
|
||||
"customButtonTypeUrl": "Ссылка",
|
||||
"customButtonCallbackPlaceholder": "callback_data (например, back_to_menu)",
|
||||
"customButtonUrlPlaceholder": "https://example.com",
|
||||
"customButtonEmojiIdPlaceholder": "Custom emoji ID (необязательно)",
|
||||
"preview": "Предпросмотр",
|
||||
"previewEmpty": "— пусто —",
|
||||
"emailSubjectLabel": "Тема",
|
||||
|
||||
@@ -1681,6 +1681,7 @@
|
||||
"customButtonTypeUrl": "链接",
|
||||
"customButtonCallbackPlaceholder": "callback_data(例如 back_to_menu)",
|
||||
"customButtonUrlPlaceholder": "https://example.com",
|
||||
"customButtonEmojiIdPlaceholder": "自定义 Emoji ID(可选)",
|
||||
"category": "类别",
|
||||
"categoryNews": "新闻",
|
||||
"categoryPromo": "促销",
|
||||
|
||||
@@ -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<File | null>(null);
|
||||
const [mediaType, setMediaType] = useState<'photo' | 'video' | 'document'>('photo');
|
||||
const [mediaPreview, setMediaPreview] = useState<string | null>(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"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
inputMode="numeric"
|
||||
value={newButtonEmojiId}
|
||||
onChange={(e) => setNewButtonEmojiId(e.target.value)}
|
||||
placeholder={t('admin.broadcasts.customButtonEmojiIdPlaceholder')}
|
||||
maxLength={64}
|
||||
className="input"
|
||||
/>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
@@ -807,6 +822,7 @@ export default function AdminBroadcastCreate() {
|
||||
setIsAddingCustomButton(false);
|
||||
setNewButtonLabel('');
|
||||
setNewButtonActionValue('');
|
||||
setNewButtonEmojiId('');
|
||||
}}
|
||||
className="btn-secondary flex-1"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user