diff --git a/src/api/branding.ts b/src/api/branding.ts index 72d6ffc..9969d65 100644 --- a/src/api/branding.ts +++ b/src/api/branding.ts @@ -149,6 +149,11 @@ export const initLogoPreload = () => { } }; +export interface BotStartVideoInfo { + has_video: boolean; + file_id?: string | null; +} + export const brandingApi = { // Get current branding (public, no auth required) getBranding: async (): Promise => { @@ -176,6 +181,29 @@ export const brandingApi = { return response.data; }, + // ── Видео стартового меню бота ──────────────────────────────────────── + // Хранится как Telegram file_id: файл на нашей стороне не сохраняется. + + getBotStartVideo: async (): Promise => { + const response = await apiClient.get('/cabinet/branding/bot-start-video'); + return response.data; + }, + + uploadBotStartVideo: async (file: File): Promise => { + const formData = new FormData(); + formData.append('file', file); + const response = await apiClient.post( + '/cabinet/branding/bot-start-video', + formData, + ); + return response.data; + }, + + deleteBotStartVideo: async (): Promise => { + const response = await apiClient.delete('/cabinet/branding/bot-start-video'); + return response.data; + }, + // Delete custom logo (admin only) deleteLogo: async (): Promise => { const response = await apiClient.delete('/cabinet/branding/logo'); diff --git a/src/components/admin/BrandingTab.tsx b/src/components/admin/BrandingTab.tsx index aec1362..4814b1f 100644 --- a/src/components/admin/BrandingTab.tsx +++ b/src/components/admin/BrandingTab.tsx @@ -15,6 +15,7 @@ export function BrandingTab({ accentColor = '#3b82f6' }: BrandingTabProps) { const { t } = useTranslation(); const queryClient = useQueryClient(); const fileInputRef = useRef(null); + const startVideoInputRef = useRef(null); const [editingName, setEditingName] = useState(false); const [newName, setNewName] = useState(''); @@ -25,6 +26,12 @@ export function BrandingTab({ accentColor = '#3b82f6' }: BrandingTabProps) { queryFn: brandingApi.getBranding, }); + // Видео стартового меню бота: хранится как Telegram file_id + const { data: startVideo } = useQuery({ + queryKey: ['bot-start-video'], + queryFn: brandingApi.getBotStartVideo, + }); + const { data: fullscreenSettings } = useQuery({ queryKey: ['fullscreen-enabled'], queryFn: brandingApi.getFullscreenEnabled, @@ -100,6 +107,29 @@ export function BrandingTab({ accentColor = '#3b82f6' }: BrandingTabProps) { }, }); + const uploadStartVideoMutation = useMutation({ + mutationFn: brandingApi.uploadBotStartVideo, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['bot-start-video'] }); + }, + }); + + const deleteStartVideoMutation = useMutation({ + mutationFn: brandingApi.deleteBotStartVideo, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['bot-start-video'] }); + }, + }); + + const handleStartVideoUpload = (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (file) { + uploadStartVideoMutation.mutate(file); + } + // Сбрасываем input, чтобы повторный выбор того же файла снова сработал + e.target.value = ''; + }; + const handleLogoUpload = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (file) { @@ -211,6 +241,57 @@ export function BrandingTab({ accentColor = '#3b82f6' }: BrandingTabProps) { + {/* Видео стартового меню бота */} +
+

+ {t('admin.settings.botStartVideo')} +

+

{t('admin.settings.botStartVideoDesc')}

+ +
+ + + + {startVideo?.has_video && ( + + )} + + + {startVideo?.has_video + ? t('admin.settings.botStartVideoActive') + : t('admin.settings.botStartVideoNone')} + +
+ + {uploadStartVideoMutation.isError && ( +
+ {t('admin.settings.botStartVideoError')} +
+ )} +
+ {/* Animated Background Editor */}
diff --git a/src/locales/en.json b/src/locales/en.json index 4f244b5..f6247a3 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -2320,7 +2320,15 @@ "offlineConv": "Offline Conversions", "apiKey": "API Key", "legalFooter": "Legal Footer", - "legalFooterDesc": "Links to the offer, privacy policy and recurring payments at the bottom of the login page" + "legalFooterDesc": "Links to the offer, privacy policy and recurring payments at the bottom of the login page", + "botStartVideo": "Bot start-menu video", + "botStartVideoDesc": "The bot attaches this video to the /start message instead of the logo image. The file is uploaded to Telegram once — we only keep its identifier.", + "botStartVideoUpload": "Upload video", + "botStartVideoReplace": "Replace video", + "botStartVideoRemove": "Remove", + "botStartVideoActive": "Video is set", + "botStartVideoNone": "No video — the menu uses the logo image", + "botStartVideoError": "Could not upload the video" }, "bulkActions": { "title": "Bulk Actions", diff --git a/src/locales/fa.json b/src/locales/fa.json index 9d9f8e4..247abae 100644 --- a/src/locales/fa.json +++ b/src/locales/fa.json @@ -1983,7 +1983,15 @@ "offlineConv": "تبدیل‌های آفلاین", "apiKey": "کلید API", "legalFooter": "پاورقی حقوقی", - "legalFooterDesc": "پیوند به پیشنهاد، سیاست حریم خصوصی و پرداخت‌های مکرر در پایین صفحه ورود" + "legalFooterDesc": "پیوند به پیشنهاد، سیاست حریم خصوصی و پرداخت‌های مکرر در پایین صفحه ورود", + "botStartVideo": "ویدیوی منوی شروع ربات", + "botStartVideoDesc": "ربات این ویدیو را به‌جای تصویر لوگو به پیام /start پیوست می‌کند. فایل یک‌بار در تلگرام بارگذاری می‌شود و ما فقط شناسه آن را نگه می‌داریم.", + "botStartVideoUpload": "بارگذاری ویدیو", + "botStartVideoReplace": "جایگزینی ویدیو", + "botStartVideoRemove": "حذف", + "botStartVideoActive": "ویدیو تنظیم شده است", + "botStartVideoNone": "ویدیویی تنظیم نشده — منو از تصویر لوگو استفاده می‌کند", + "botStartVideoError": "بارگذاری ویدیو ناموفق بود" }, "buttons": { "color": "رنگ دکمه", diff --git a/src/locales/ru.json b/src/locales/ru.json index 07d9a0b..77050b9 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -2823,7 +2823,15 @@ "offlineConv": "Офлайн-конверсии", "apiKey": "API-ключ", "legalFooter": "Юридический футер", - "legalFooterDesc": "Ссылки на оферту, политику и рекуррентные платежи внизу страницы входа" + "legalFooterDesc": "Ссылки на оферту, политику и рекуррентные платежи внизу страницы входа", + "botStartVideo": "Видео стартового меню бота", + "botStartVideoDesc": "Бот прикрепит это видео к сообщению /start вместо картинки-логотипа. Файл разово отправляется в Telegram — храним только его идентификатор.", + "botStartVideoUpload": "Загрузить видео", + "botStartVideoReplace": "Заменить видео", + "botStartVideoRemove": "Удалить", + "botStartVideoActive": "Видео подключено", + "botStartVideoNone": "Видео не задано — меню с картинкой-логотипом", + "botStartVideoError": "Не удалось загрузить видео" }, "buttons": { "color": "Цвет кнопки", diff --git a/src/locales/zh.json b/src/locales/zh.json index 89b38e9..08ca1eb 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -2050,7 +2050,15 @@ "offlineConv": "离线转化", "apiKey": "API 密钥", "legalFooter": "法律页脚", - "legalFooterDesc": "登录页面底部的要约、隐私政策和定期付款链接" + "legalFooterDesc": "登录页面底部的要约、隐私政策和定期付款链接", + "botStartVideo": "机器人开始菜单视频", + "botStartVideoDesc": "机器人会在 /start 消息中附带该视频,替代 logo 图片。文件仅上传到 Telegram 一次,我们只保存其标识符。", + "botStartVideoUpload": "上传视频", + "botStartVideoReplace": "替换视频", + "botStartVideoRemove": "删除", + "botStartVideoActive": "已设置视频", + "botStartVideoNone": "未设置视频 — 菜单使用 logo 图片", + "botStartVideoError": "视频上传失败" }, "buttons": { "color": "按钮颜色",