From 2c65ca8a7ff372725bcbaa002e96bd043022bad1 Mon Sep 17 00:00:00 2001 From: Fringg Date: Sat, 7 Mar 2026 01:48:18 +0300 Subject: [PATCH] feat: migrate Telegram Login Widget to v23 with admin-configurable settings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Upgrade telegram-widget.js from v22 to v23 (TelegramLoginButton + ConnectedAccounts) - TelegramLoginButton fetches widget config from API instead of hardcoded values - Add TelegramWidgetConfig type and API method with fallback to env defaults - Remove botUsername prop — now sourced from backend API - Add useEffect cleanup to prevent orphaned iframes on unmount - Add TELEGRAM_WIDGET category to admin interface settings menu - Add i18n translations for widget settings (ru, en, zh, fa) --- src/api/branding.ts | 26 +++++++++++++ src/components/TelegramLoginButton.tsx | 52 ++++++++++++++++---------- src/components/admin/constants.ts | 1 + src/locales/en.json | 11 +++++- src/locales/fa.json | 11 +++++- src/locales/ru.json | 5 +++ src/locales/zh.json | 11 +++++- src/pages/ConnectedAccounts.tsx | 2 +- src/pages/Login.tsx | 5 +-- 9 files changed, 97 insertions(+), 27 deletions(-) diff --git a/src/api/branding.ts b/src/api/branding.ts index 1d1e013..f146f58 100644 --- a/src/api/branding.ts +++ b/src/api/branding.ts @@ -23,6 +23,14 @@ export interface EmailAuthEnabled { enabled: boolean; } +export interface TelegramWidgetConfig { + bot_username: string; + size: 'large' | 'medium' | 'small'; + radius: number; + userpic: boolean; + request_access: boolean; +} + export interface AnalyticsCounters { yandex_metrika_id: string; google_ads_id: string; @@ -251,6 +259,24 @@ export const brandingApi = { } }, + // Get Telegram widget config (public, no auth required) + getTelegramWidgetConfig: async (): Promise => { + try { + const response = await apiClient.get( + '/cabinet/branding/telegram-widget', + ); + return response.data; + } catch { + return { + bot_username: import.meta.env.VITE_TELEGRAM_BOT_USERNAME || '', + size: 'large', + radius: 8, + userpic: true, + request_access: true, + }; + } + }, + // Update analytics counters (admin only) updateAnalyticsCounters: async (data: Partial): Promise => { const response = await apiClient.patch('/cabinet/branding/analytics', data); diff --git a/src/components/TelegramLoginButton.tsx b/src/components/TelegramLoginButton.tsx index ebbd986..ad09aed 100644 --- a/src/components/TelegramLoginButton.tsx +++ b/src/components/TelegramLoginButton.tsx @@ -1,42 +1,58 @@ import { useEffect, useRef } from 'react'; import { useTranslation } from 'react-i18next'; +import { useQuery } from '@tanstack/react-query'; +import { brandingApi, type TelegramWidgetConfig } from '../api/branding'; interface TelegramLoginButtonProps { - botUsername: string; referralCode?: string; } -export default function TelegramLoginButton({ - botUsername, - referralCode, -}: TelegramLoginButtonProps) { +export default function TelegramLoginButton({ referralCode }: TelegramLoginButtonProps) { const { t } = useTranslation(); const containerRef = useRef(null); + const { data: widgetConfig } = useQuery({ + queryKey: ['telegram-widget-config'], + queryFn: brandingApi.getTelegramWidgetConfig, + staleTime: 60000, + }); + + const botUsername = + widgetConfig?.bot_username || import.meta.env.VITE_TELEGRAM_BOT_USERNAME || ''; + // Load widget script useEffect(() => { - if (!containerRef.current || !botUsername) return; + if (!containerRef.current || !botUsername || !widgetConfig) return; - // Clear previous widget using safe DOM API - while (containerRef.current.firstChild) { - containerRef.current.removeChild(containerRef.current.firstChild); + const container = containerRef.current; + + // Clear previous widget + while (container.firstChild) { + container.removeChild(container.firstChild); } - // Get current URL for redirect const redirectUrl = `${window.location.origin}/auth/telegram/callback`; - // Create script element for Telegram Login Widget const script = document.createElement('script'); - script.src = 'https://telegram.org/js/telegram-widget.js?22'; + script.src = 'https://telegram.org/js/telegram-widget.js?23'; script.setAttribute('data-telegram-login', botUsername); - script.setAttribute('data-size', 'large'); - script.setAttribute('data-radius', '8'); + script.setAttribute('data-size', widgetConfig.size); + script.setAttribute('data-radius', String(widgetConfig.radius)); + script.setAttribute('data-userpic', String(widgetConfig.userpic)); script.setAttribute('data-auth-url', redirectUrl); - script.setAttribute('data-request-access', 'write'); + if (widgetConfig.request_access) { + script.setAttribute('data-request-access', 'write'); + } script.async = true; - containerRef.current.appendChild(script); - }, [botUsername]); + container.appendChild(script); + + return () => { + while (container.firstChild) { + container.removeChild(container.firstChild); + } + }; + }, [botUsername, widgetConfig]); if (!botUsername || botUsername === 'your_bot') { return ( @@ -48,10 +64,8 @@ export default function TelegramLoginButton({ return (
- {/* Telegram Widget will be inserted here */}
- {/* Fallback link for mobile */}

{t('auth.orOpenInApp')}

) : ( - + )}