mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 09:33:46 +00:00
Merge PR #484: поле icon_custom_emoji_id у кастомных кнопок рассылки
feat(broadcasts): поле icon_custom_emoji_id у кастомных кнопок рассылки
This commit is contained in:
@@ -52,6 +52,8 @@ export interface CustomBroadcastButton {
|
|||||||
label: string;
|
label: string;
|
||||||
action_type: 'callback' | 'url';
|
action_type: 'callback' | 'url';
|
||||||
action_value: string;
|
action_value: string;
|
||||||
|
/** Telegram custom emoji перед текстом кнопки (числовая строка custom_emoji_id) */
|
||||||
|
icon_custom_emoji_id?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BroadcastMedia {
|
export interface BroadcastMedia {
|
||||||
|
|||||||
@@ -1947,6 +1947,7 @@
|
|||||||
"customButtonTypeUrl": "URL",
|
"customButtonTypeUrl": "URL",
|
||||||
"customButtonCallbackPlaceholder": "callback_data (e.g. back_to_menu)",
|
"customButtonCallbackPlaceholder": "callback_data (e.g. back_to_menu)",
|
||||||
"customButtonUrlPlaceholder": "https://example.com",
|
"customButtonUrlPlaceholder": "https://example.com",
|
||||||
|
"customButtonEmojiIdPlaceholder": "Custom emoji ID (optional)",
|
||||||
"preview": "Preview",
|
"preview": "Preview",
|
||||||
"previewEmpty": "— empty —",
|
"previewEmpty": "— empty —",
|
||||||
"emailSubjectLabel": "Subject",
|
"emailSubjectLabel": "Subject",
|
||||||
|
|||||||
@@ -1580,6 +1580,7 @@
|
|||||||
"customButtonTypeUrl": "لینک",
|
"customButtonTypeUrl": "لینک",
|
||||||
"customButtonCallbackPlaceholder": "callback_data (مثلاً back_to_menu)",
|
"customButtonCallbackPlaceholder": "callback_data (مثلاً back_to_menu)",
|
||||||
"customButtonUrlPlaceholder": "https://example.com",
|
"customButtonUrlPlaceholder": "https://example.com",
|
||||||
|
"customButtonEmojiIdPlaceholder": "شناسه ایموجی سفارشی (اختیاری)",
|
||||||
"category": "دستهبندی",
|
"category": "دستهبندی",
|
||||||
"categoryNews": "اخبار",
|
"categoryNews": "اخبار",
|
||||||
"categoryPromo": "تبلیغاتی",
|
"categoryPromo": "تبلیغاتی",
|
||||||
|
|||||||
@@ -1974,6 +1974,7 @@
|
|||||||
"customButtonTypeUrl": "Ссылка",
|
"customButtonTypeUrl": "Ссылка",
|
||||||
"customButtonCallbackPlaceholder": "callback_data (например, back_to_menu)",
|
"customButtonCallbackPlaceholder": "callback_data (например, back_to_menu)",
|
||||||
"customButtonUrlPlaceholder": "https://example.com",
|
"customButtonUrlPlaceholder": "https://example.com",
|
||||||
|
"customButtonEmojiIdPlaceholder": "Custom emoji ID (необязательно)",
|
||||||
"preview": "Предпросмотр",
|
"preview": "Предпросмотр",
|
||||||
"previewEmpty": "— пусто —",
|
"previewEmpty": "— пусто —",
|
||||||
"emailSubjectLabel": "Тема",
|
"emailSubjectLabel": "Тема",
|
||||||
|
|||||||
@@ -1681,6 +1681,7 @@
|
|||||||
"customButtonTypeUrl": "链接",
|
"customButtonTypeUrl": "链接",
|
||||||
"customButtonCallbackPlaceholder": "callback_data(例如 back_to_menu)",
|
"customButtonCallbackPlaceholder": "callback_data(例如 back_to_menu)",
|
||||||
"customButtonUrlPlaceholder": "https://example.com",
|
"customButtonUrlPlaceholder": "https://example.com",
|
||||||
|
"customButtonEmojiIdPlaceholder": "自定义 Emoji ID(可选)",
|
||||||
"category": "类别",
|
"category": "类别",
|
||||||
"categoryNews": "新闻",
|
"categoryNews": "新闻",
|
||||||
"categoryPromo": "促销",
|
"categoryPromo": "促销",
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
adminBroadcastsApi,
|
adminBroadcastsApi,
|
||||||
BroadcastFilter,
|
type BroadcastFilter,
|
||||||
TariffFilter,
|
type TariffFilter,
|
||||||
CombinedBroadcastCreateRequest,
|
type CombinedBroadcastCreateRequest,
|
||||||
CustomBroadcastButton,
|
type CustomBroadcastButton,
|
||||||
} from '../api/adminBroadcasts';
|
} from '../api/adminBroadcasts';
|
||||||
import { AdminBackButton } from '../components/admin';
|
import { AdminBackButton } from '../components/admin';
|
||||||
import { TelegramPreview, EmailPreview } from '../components/broadcasts/BroadcastPreview';
|
import { TelegramPreview, EmailPreview } from '../components/broadcasts/BroadcastPreview';
|
||||||
@@ -63,6 +63,7 @@ export default function AdminBroadcastCreate() {
|
|||||||
const [newButtonLabel, setNewButtonLabel] = useState('');
|
const [newButtonLabel, setNewButtonLabel] = useState('');
|
||||||
const [newButtonActionType, setNewButtonActionType] = useState<'callback' | 'url'>('callback');
|
const [newButtonActionType, setNewButtonActionType] = useState<'callback' | 'url'>('callback');
|
||||||
const [newButtonActionValue, setNewButtonActionValue] = useState('');
|
const [newButtonActionValue, setNewButtonActionValue] = useState('');
|
||||||
|
const [newButtonEmojiId, setNewButtonEmojiId] = useState('');
|
||||||
const [mediaFile, setMediaFile] = useState<File | null>(null);
|
const [mediaFile, setMediaFile] = useState<File | null>(null);
|
||||||
const [mediaType, setMediaType] = useState<'photo' | 'video' | 'document'>('photo');
|
const [mediaType, setMediaType] = useState<'photo' | 'video' | 'document'>('photo');
|
||||||
const [mediaPreview, setMediaPreview] = useState<string | null>(null);
|
const [mediaPreview, setMediaPreview] = useState<string | null>(null);
|
||||||
@@ -305,6 +306,8 @@ export default function AdminBroadcastCreate() {
|
|||||||
// Custom button validation
|
// Custom button validation
|
||||||
const isNewButtonValid = useMemo(() => {
|
const isNewButtonValid = useMemo(() => {
|
||||||
if (!newButtonLabel.trim() || !newButtonActionValue.trim()) return false;
|
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') {
|
if (newButtonActionType === 'url') {
|
||||||
return /^https:\/\/|^tg:\/\//.test(newButtonActionValue.trim());
|
return /^https:\/\/|^tg:\/\//.test(newButtonActionValue.trim());
|
||||||
}
|
}
|
||||||
@@ -312,22 +315,25 @@ export default function AdminBroadcastCreate() {
|
|||||||
return new TextEncoder().encode(newButtonActionValue.trim()).length <= 64;
|
return new TextEncoder().encode(newButtonActionValue.trim()).length <= 64;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}, [newButtonLabel, newButtonActionType, newButtonActionValue]);
|
}, [newButtonLabel, newButtonActionType, newButtonActionValue, newButtonEmojiId]);
|
||||||
|
|
||||||
// Custom button handlers
|
// Custom button handlers
|
||||||
const addCustomButton = () => {
|
const addCustomButton = () => {
|
||||||
if (!isNewButtonValid) return;
|
if (!isNewButtonValid) return;
|
||||||
|
const emojiId = newButtonEmojiId.trim();
|
||||||
setCustomButtons((prev) => [
|
setCustomButtons((prev) => [
|
||||||
...prev,
|
...prev,
|
||||||
{
|
{
|
||||||
label: newButtonLabel.trim(),
|
label: newButtonLabel.trim(),
|
||||||
action_type: newButtonActionType,
|
action_type: newButtonActionType,
|
||||||
action_value: newButtonActionValue.trim(),
|
action_value: newButtonActionValue.trim(),
|
||||||
|
...(emojiId ? { icon_custom_emoji_id: emojiId } : {}),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setNewButtonLabel('');
|
setNewButtonLabel('');
|
||||||
setNewButtonActionValue('');
|
setNewButtonActionValue('');
|
||||||
setNewButtonActionType('callback');
|
setNewButtonActionType('callback');
|
||||||
|
setNewButtonEmojiId('');
|
||||||
setIsAddingCustomButton(false);
|
setIsAddingCustomButton(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -800,6 +806,15 @@ export default function AdminBroadcastCreate() {
|
|||||||
maxLength={newButtonActionType === 'callback' ? 64 : 256}
|
maxLength={newButtonActionType === 'callback' ? 64 : 256}
|
||||||
className="input"
|
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">
|
<div className="flex gap-2">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -807,6 +822,7 @@ export default function AdminBroadcastCreate() {
|
|||||||
setIsAddingCustomButton(false);
|
setIsAddingCustomButton(false);
|
||||||
setNewButtonLabel('');
|
setNewButtonLabel('');
|
||||||
setNewButtonActionValue('');
|
setNewButtonActionValue('');
|
||||||
|
setNewButtonEmojiId('');
|
||||||
}}
|
}}
|
||||||
className="btn-secondary flex-1"
|
className="btn-secondary flex-1"
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user