diff --git a/backend/community_manager/actions/chat.py b/backend/community_manager/actions/chat.py
index 854d9630..7265687b 100644
--- a/backend/community_manager/actions/chat.py
+++ b/backend/community_manager/actions/chat.py
@@ -13,7 +13,11 @@
RPCError,
)
from telethon.utils import get_peer_id
-from aiogram.utils.markdown import text as fmt_text, bold as fmt_bold
+from aiogram.utils.markdown import (
+ text as fmt_text,
+ bold as fmt_bold,
+ markdown_decoration,
+)
from community_manager.dtos.chat import TargetChatMembersDTO
from community_manager.events import ChatAdminChangeEventBuilder
@@ -344,7 +348,11 @@ async def _refresh(self, chat: TelegramChat) -> TelegramChat:
logger.warning(
f"Chat {chat.id!r} has insufficient permissions set. Disabling it..."
)
- self.telegram_chat_service.set_insufficient_privileges(chat_id=chat.id)
+ if not chat.insufficient_privileges:
+ self.telegram_chat_service.set_insufficient_privileges(chat_id=chat.id)
+ await self._notify_insufficient_privileges(
+ chat_id=chat.id, chat_title=chat.title
+ )
raise
except (
@@ -629,12 +637,58 @@ async def on_bot_chat_member_update(
self.telegram_chat_service.set_insufficient_privileges(
chat_id=chat.id, value=True
)
+ await self._notify_insufficient_privileges(
+ chat_id=chat.id, chat_title=chat.title
+ )
elif chat.insufficient_privileges:
logger.info("Sufficient permissions for the bot in chat %d", chat.id)
self.telegram_chat_service.set_insufficient_privileges(
chat_id=chat.id, value=False
)
+ async def _notify_insufficient_privileges(
+ self, chat_id: int, chat_title: str
+ ) -> None:
+ try:
+ managers = (
+ self.db_session.query(TelegramChatUser)
+ .filter(
+ TelegramChatUser.chat_id == chat_id,
+ TelegramChatUser.is_manager_admin.is_(True),
+ )
+ .all()
+ )
+ telegram_ids = [m.user.telegram_id for m in managers if m.user]
+
+ if not telegram_ids:
+ logger.warning(f"No manager admins found for chat {chat_id} to notify.")
+ return
+
+ text = fmt_text(
+ "⚠️ ",
+ fmt_bold("Insufficient Privileges"),
+ "\n\n",
+ "The bot no longer has sufficient administrative privileges to manage the chat ",
+ fmt_bold(chat_title),
+ " \\(ID: ",
+ markdown_decoration.quote(str(chat_id)),
+ "\\)\\.\n\n",
+ "Please ensure the bot is added as an administrator with the required permissions\\.",
+ sep="",
+ )
+ async with TelegramBotApiService() as bot_service:
+ for tg_id in telegram_ids:
+ try:
+ await bot_service.send_message(chat_id=tg_id, text=text)
+ except Exception as e:
+ logger.warning(
+ f"Failed to notify manager {tg_id} for chat {chat_id}: {e}"
+ )
+ except Exception as e:
+ logger.error(
+ f"Error in _notify_insufficient_privileges for chat {chat_id}: {e}"
+ )
+
async def on_join_request(
self,
telegram_user_id: int,
diff --git a/backend/core/src/core/dtos/chat/__init__.py b/backend/core/src/core/dtos/chat/__init__.py
index 6d0dfb03..f25e753b 100644
--- a/backend/core/src/core/dtos/chat/__init__.py
+++ b/backend/core/src/core/dtos/chat/__init__.py
@@ -34,10 +34,10 @@ class BaseTelegramChatDTO(TelegramChatPreviewDTO):
username: str | None
is_enabled: bool
join_url: str | None = None
+ insufficient_privileges: bool
class TelegramChatDTO(BaseTelegramChatDTO):
- insufficient_privileges: bool
is_full_control: bool
@classmethod
@@ -128,6 +128,7 @@ def from_object(
join_url=join_url,
members_count=members_count,
is_enabled=obj.is_enabled,
+ insufficient_privileges=obj.insufficient_privileges,
)
else:
return cls(
@@ -143,6 +144,7 @@ def from_object(
is_enabled=obj.is_enabled,
is_eligible=False,
join_url=None,
+ insufficient_privileges=obj.insufficient_privileges,
)
diff --git a/backend/core/src/core/migrations/versions/1750345690-ec82e8aa0d67-telegram_chat_rule_group.py b/backend/core/src/core/migrations/versions/1750345690-ec82e8aa0d67-telegram_chat_rule_group.py
index 2501dcd5..8457ec3c 100644
--- a/backend/core/src/core/migrations/versions/1750345690-ec82e8aa0d67-telegram_chat_rule_group.py
+++ b/backend/core/src/core/migrations/versions/1750345690-ec82e8aa0d67-telegram_chat_rule_group.py
@@ -138,6 +138,9 @@ def set_proper_rule_group_id_in_table(
for chat_id, group_id in chat_id_group_id_mapping.items()
]
+ if not params:
+ return
+
# Use executemany for better performance with many records
connection.execute(
sa.text(
diff --git a/backend/core/src/core/migrations/versions/1780501788-105b4511d5ca-add_last_activity_to_user_wallet.py b/backend/core/src/core/migrations/versions/1780501788-105b4511d5ca-add_last_activity_to_user_wallet.py
new file mode 100644
index 00000000..f514dbfb
--- /dev/null
+++ b/backend/core/src/core/migrations/versions/1780501788-105b4511d5ca-add_last_activity_to_user_wallet.py
@@ -0,0 +1,32 @@
+"""add_last_activity_to_user_wallet
+
+Revision ID: 105b4511d5ca
+Revises: 6c6b45ffe090
+Create Date: 2026-06-03 15:49:48.553736
+
+"""
+from typing import Sequence, Union
+
+from alembic import op
+import sqlalchemy as sa
+from sqlalchemy.dialects import mysql
+
+# revision identifiers, used by Alembic.
+revision: str = "105b4511d5ca"
+down_revision: Union[str, None] = "6c6b45ffe090"
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.add_column(
+ "user_wallet", sa.Column("last_activity", mysql.BIGINT(), nullable=True)
+ )
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.drop_column("user_wallet", "last_activity")
+ # ### end Alembic commands ###
diff --git a/backend/core/src/core/models/wallet.py b/backend/core/src/core/models/wallet.py
index 78fec0d0..8c1636e9 100644
--- a/backend/core/src/core/models/wallet.py
+++ b/backend/core/src/core/models/wallet.py
@@ -27,6 +27,11 @@ class UserWallet(Base):
address = mapped_column(BlockchainAddressRawField, primary_key=True)
user_id = mapped_column(ForeignKey("user.id"), nullable=False)
balance = mapped_column(BIGINT, nullable=True, doc="Balance of the wallet in TONs")
+ last_activity = mapped_column(
+ BIGINT,
+ nullable=True,
+ doc="Last activity timestamp of the wallet on the blockchain",
+ )
# DEPRECATED attribute
hide_wallet = mapped_column(Boolean, default=False, nullable=False)
diff --git a/backend/core/src/core/services/wallet.py b/backend/core/src/core/services/wallet.py
index b7ecdeab..eee9d791 100644
--- a/backend/core/src/core/services/wallet.py
+++ b/backend/core/src/core/services/wallet.py
@@ -101,7 +101,9 @@ def turn_visibility_off(self, user_id: int) -> None:
).update({"hide_wallet": True})
self.db_session.flush()
- def set_balance(self, address_raw: str, balance: int) -> None:
+ def set_balance(
+ self, address_raw: str, balance: int, last_activity: int | None = None
+ ) -> None:
"""
Updates the balance for a specific wallet address using the database session.
@@ -111,10 +113,15 @@ def set_balance(self, address_raw: str, balance: int) -> None:
:param address_raw: The wallet address whose balance needs to be updated.
:param balance: The new balance to be set for the given wallet address in nano
+ :param last_activity: Optional last activity timestamp of the wallet
"""
+ updates = {"balance": balance}
+ if last_activity is not None:
+ updates["last_activity"] = last_activity
+
self.db_session.query(UserWallet).filter(
UserWallet.address == address_raw,
- ).update({"balance": balance})
+ ).update(updates)
def count(self) -> int:
return self.db_session.query(UserWallet).count()
diff --git a/backend/indexer_blockchain/tasks.py b/backend/indexer_blockchain/tasks.py
index c935bb72..ed138b39 100644
--- a/backend/indexer_blockchain/tasks.py
+++ b/backend/indexer_blockchain/tasks.py
@@ -22,14 +22,14 @@
async def get_all_nfts_per_user(
- blockchain_service: TonApiService, address: str, nft_collections: list[str]
+ blockchain_service: TonApiService, address: str
) -> NftItems:
nft_items = []
- for collection_address in nft_collections:
- async for batch in blockchain_service.get_all_nft_items_for_user(
- wallet_address=address, collection_address=collection_address
- ):
- nft_items.extend(batch.nft_items)
+ # Query all NFT items in paginated batches
+ async for batch in blockchain_service.get_all_nft_items_for_user(
+ wallet_address=address, collection_address=None
+ ):
+ nft_items.extend(batch.nft_items)
return NftItems(nft_items=nft_items)
@@ -46,6 +46,20 @@ def fetch_wallet_details(address: str) -> None:
blockchain_service = TonApiService()
account_info = asyncio.run(blockchain_service.get_account_info(address))
+ current_last_activity = account_info.last_activity
+ raw_address = account_info.address.to_raw()
+
+ with DBService().db_session() as db_session:
+ wallet_service = WalletService(db_session)
+ wallet = wallet_service.get_user_wallet(raw_address)
+ stored_last_activity = wallet.last_activity if wallet else None
+
+ if stored_last_activity is not None and current_last_activity is not None:
+ if stored_last_activity == current_last_activity:
+ logger.info(
+ f"Skipping wallet {address!r} sync: last_activity has not changed ({current_last_activity})."
+ )
+ return
jettons_balances: JettonsBalances = asyncio.run(
blockchain_service.get_all_jetton_balances(address)
@@ -64,16 +78,25 @@ def fetch_wallet_details(address: str) -> None:
get_all_nfts_per_user(
blockchain_service=blockchain_service,
address=address,
- nft_collections=whitelist_collection_addresses,
)
)
+ # Pre-filter fetched NFT items against whitelisted collections in memory
+ whitelist_set = set(whitelist_collection_addresses)
+ filtered_nfts = [
+ item
+ for item in nft_items.nft_items
+ if item.collection and item.collection.address.to_raw() in whitelist_set
+ ]
+ nft_items = NftItems(nft_items=filtered_nfts)
+
with DBService().db_session() as db_session:
wallet_service = WalletService(db_session)
wallet_service.set_balance(
- account_info.address.to_raw(),
+ raw_address,
# It already contains the balance in nano
int(str(account_info.balance)),
+ last_activity=current_last_activity,
)
jetton_service = JettonService(db_session)
diff --git a/backend/tests/unit/community_manager/actions/test_insufficient_privileges.py b/backend/tests/unit/community_manager/actions/test_insufficient_privileges.py
new file mode 100644
index 00000000..7783ec2f
--- /dev/null
+++ b/backend/tests/unit/community_manager/actions/test_insufficient_privileges.py
@@ -0,0 +1,175 @@
+import pytest
+from unittest.mock import AsyncMock, MagicMock, patch
+
+from community_manager.actions.chat import CommunityManagerChatAction
+from community_manager.events import ChatAdminChangeEventBuilder
+from core.exceptions.chat import TelegramChatNotSufficientPrivileges
+from core.dtos.chat import TelegramChatDTO
+from tests.factories import TelegramChatFactory, TelegramChatUserFactory, UserFactory
+
+
+@pytest.fixture
+def chat_action(db_session, mocker):
+ # Only mock external network/infrastructure dependencies
+ mocker.patch("community_manager.actions.chat.RedisService")
+ mocker.patch("community_manager.actions.chat.CDNService")
+ mocker.patch("community_manager.actions.chat.TelethonService")
+
+ action = CommunityManagerChatAction(db_session)
+ return action
+
+
+@pytest.mark.asyncio
+async def test_on_bot_chat_member_update_transitions_to_insufficient(
+ db_session, chat_action
+):
+ # Create models using existing factories
+ chat = TelegramChatFactory.with_session(db_session).create(
+ id=123,
+ title="Test Chat",
+ insufficient_privileges=False,
+ )
+ user = UserFactory.with_session(db_session).create(
+ telegram_id=999,
+ first_name="Test",
+ )
+ TelegramChatUserFactory.with_session(db_session).create(
+ chat=chat,
+ user=user,
+ is_admin=True,
+ is_manager_admin=True,
+ )
+ db_session.commit()
+
+ chat_dto = TelegramChatDTO.from_object(chat)
+
+ event = MagicMock(spec=ChatAdminChangeEventBuilder.Event)
+ event.sufficient_bot_privileges = False
+ event.new_participant = MagicMock()
+
+ with patch(
+ "community_manager.actions.chat.TelegramBotApiService"
+ ) as MockBotService:
+ mock_bot_service = AsyncMock()
+ MockBotService.return_value.__aenter__.return_value = mock_bot_service
+
+ await chat_action.on_bot_chat_member_update(event, chat_dto)
+
+ # Verify the database state actually updated
+ db_session.refresh(chat)
+ assert chat.insufficient_privileges is True
+
+ # Verify the message was sent to the owner
+ mock_bot_service.send_message.assert_awaited_once()
+ _, kwargs = mock_bot_service.send_message.call_args
+ assert kwargs["chat_id"] == 999
+ assert "Insufficient Privileges" in kwargs["text"]
+
+
+@pytest.mark.asyncio
+async def test_on_bot_chat_member_update_already_insufficient_no_notify(
+ db_session, chat_action
+):
+ chat = TelegramChatFactory.with_session(db_session).create(
+ id=123,
+ title="Test Chat",
+ insufficient_privileges=True,
+ )
+ db_session.commit()
+
+ chat_dto = TelegramChatDTO.from_object(chat)
+
+ event = MagicMock(spec=ChatAdminChangeEventBuilder.Event)
+ event.sufficient_bot_privileges = False
+ event.new_participant = MagicMock()
+
+ with patch(
+ "community_manager.actions.chat.TelegramBotApiService"
+ ) as MockBotService:
+ mock_bot_service = AsyncMock()
+ MockBotService.return_value.__aenter__.return_value = mock_bot_service
+
+ await chat_action.on_bot_chat_member_update(event, chat_dto)
+
+ # Insufficient privileges should remain True
+ db_session.refresh(chat)
+ assert chat.insufficient_privileges is True
+
+ # No new notification should be sent if it was already insufficient
+ mock_bot_service.send_message.assert_not_called()
+
+
+@pytest.mark.asyncio
+async def test_on_bot_chat_member_update_transitions_to_sufficient(
+ db_session, chat_action
+):
+ chat = TelegramChatFactory.with_session(db_session).create(
+ id=123,
+ title="Test Chat",
+ insufficient_privileges=True,
+ )
+ db_session.commit()
+
+ chat_dto = TelegramChatDTO.from_object(chat)
+
+ event = MagicMock(spec=ChatAdminChangeEventBuilder.Event)
+ event.sufficient_bot_privileges = True
+ event.new_participant = MagicMock()
+
+ with patch(
+ "community_manager.actions.chat.TelegramBotApiService"
+ ) as MockBotService:
+ mock_bot_service = AsyncMock()
+ MockBotService.return_value.__aenter__.return_value = mock_bot_service
+
+ await chat_action.on_bot_chat_member_update(event, chat_dto)
+
+ # Insufficient privileges should update to False
+ db_session.refresh(chat)
+ assert chat.insufficient_privileges is False
+
+ # No warning notification sent when restoring sufficient privileges
+ mock_bot_service.send_message.assert_not_called()
+
+
+@pytest.mark.asyncio
+async def test_refresh_transitions_to_insufficient(db_session, chat_action):
+ chat = TelegramChatFactory.with_session(db_session).create(
+ id=123,
+ title="Test Chat",
+ insufficient_privileges=False,
+ )
+ user = UserFactory.with_session(db_session).create(
+ telegram_id=888,
+ first_name="Test",
+ )
+ TelegramChatUserFactory.with_session(db_session).create(
+ chat=chat,
+ user=user,
+ is_admin=True,
+ is_manager_admin=True,
+ )
+ db_session.commit()
+
+ chat_action._get_chat_data = AsyncMock(
+ side_effect=TelegramChatNotSufficientPrivileges("Insufficient privileges")
+ )
+
+ with patch(
+ "community_manager.actions.chat.TelegramBotApiService"
+ ) as MockBotService:
+ mock_bot_service = AsyncMock()
+ MockBotService.return_value.__aenter__.return_value = mock_bot_service
+
+ with pytest.raises(TelegramChatNotSufficientPrivileges):
+ await chat_action._refresh(chat)
+
+ # Verify the database state actually updated
+ db_session.refresh(chat)
+ assert chat.insufficient_privileges is True
+
+ # Verify notification was sent
+ mock_bot_service.send_message.assert_awaited_once()
+ _, kwargs = mock_bot_service.send_message.call_args
+ assert kwargs["chat_id"] == 888
+ assert "Insufficient Privileges" in kwargs["text"]
diff --git a/backend/tests/unit/indexer_blockchain/test_tasks.py b/backend/tests/unit/indexer_blockchain/test_tasks.py
new file mode 100644
index 00000000..55783e86
--- /dev/null
+++ b/backend/tests/unit/indexer_blockchain/test_tasks.py
@@ -0,0 +1,170 @@
+from contextlib import contextmanager
+import pytest
+from unittest.mock import AsyncMock, MagicMock
+from sqlalchemy.orm import Session
+
+from core.models.wallet import UserWallet
+from pytonapi.schema.jettons import JettonsBalances
+from pytonapi.schema.nft import NftItems
+from indexer_blockchain.tasks import fetch_wallet_details
+from tests.factories.wallet import UserWalletFactory
+
+
+@pytest.fixture(autouse=True)
+def mock_redis_service(mocker):
+ return mocker.patch("indexer_blockchain.tasks.RedisService")
+
+
+@pytest.fixture(autouse=True)
+def mock_db_session(db_session, mocker):
+ @contextmanager
+ def _mock_session():
+ yield db_session
+
+ return mocker.patch(
+ "indexer_blockchain.tasks.DBService.db_session", side_effect=_mock_session
+ )
+
+
+@pytest.mark.usefixtures("db_session")
+class TestIndexerTasks:
+ def test_fetch_wallet_details_initial_sync(self, db_session: Session, mocker):
+ # 1. Arrange: Create wallet with last_activity = None
+ raw_address = (
+ "0:1111111111111111111111111111111111111111111111111111111111111111"
+ )
+ UserWalletFactory.with_session(db_session).create(
+ address=raw_address,
+ last_activity=None,
+ )
+ db_session.commit()
+
+ # Mock TonApiService
+ mock_service_class = mocker.patch("indexer_blockchain.tasks.TonApiService")
+ mock_service = mock_service_class.return_value
+
+ # Mock get_account_info
+ account_info = MagicMock()
+ account_info.last_activity = 123456
+ account_info.balance = 1000000000
+ account_info.address.to_raw.return_value = raw_address
+ mock_service.get_account_info = AsyncMock(return_value=account_info)
+
+ # Mock get_all_jetton_balances
+ mock_service.get_all_jetton_balances = AsyncMock(
+ return_value=JettonsBalances(balances=[])
+ )
+
+ # Mock get_all_nft_items_for_user as async generator
+ async def mock_get_nfts(*args, **kwargs):
+ yield NftItems(nft_items=[])
+
+ mock_service.get_all_nft_items_for_user = mock_get_nfts
+
+ # 2. Act
+ fetch_wallet_details(raw_address)
+
+ # 3. Assert: All external services called, and last_activity updated in DB
+ mock_service.get_account_info.assert_called_once_with(raw_address)
+ mock_service.get_all_jetton_balances.assert_called_once_with(raw_address)
+
+ db_session.expire_all()
+ updated_wallet = (
+ db_session.query(UserWallet).filter_by(address=raw_address).one()
+ )
+ assert updated_wallet.last_activity == 123456
+ assert updated_wallet.balance == 1000000000
+
+ def test_fetch_wallet_details_skips_when_last_activity_unchanged(
+ self, db_session: Session, mocker
+ ):
+ # 1. Arrange: Create wallet with last_activity = 123456
+ raw_address = (
+ "0:2222222222222222222222222222222222222222222222222222222222222222"
+ )
+ UserWalletFactory.with_session(db_session).create(
+ address=raw_address,
+ last_activity=123456,
+ balance=5000000000,
+ )
+ db_session.commit()
+
+ # Mock TonApiService
+ mock_service_class = mocker.patch("indexer_blockchain.tasks.TonApiService")
+ mock_service = mock_service_class.return_value
+
+ # Mock get_account_info (same last_activity)
+ account_info = MagicMock()
+ account_info.last_activity = 123456
+ account_info.balance = 5000000000
+ account_info.address.to_raw.return_value = raw_address
+ mock_service.get_account_info = AsyncMock(return_value=account_info)
+
+ # Mock other methods to ensure we fail if they are called
+ mock_service.get_all_jetton_balances = AsyncMock()
+ mock_service.get_all_nft_items_for_user = MagicMock()
+
+ # 2. Act
+ fetch_wallet_details(raw_address)
+
+ # 3. Assert: get_account_info called, but others skipped
+ mock_service.get_account_info.assert_called_once_with(raw_address)
+ mock_service.get_all_jetton_balances.assert_not_called()
+ mock_service.get_all_nft_items_for_user.assert_not_called()
+
+ db_session.expire_all()
+ updated_wallet = (
+ db_session.query(UserWallet).filter_by(address=raw_address).one()
+ )
+ assert updated_wallet.last_activity == 123456
+ assert updated_wallet.balance == 5000000000
+
+ def test_fetch_wallet_details_syncs_when_last_activity_changed(
+ self, db_session: Session, mocker
+ ):
+ # 1. Arrange: Create wallet with last_activity = 123456
+ raw_address = (
+ "0:3333333333333333333333333333333333333333333333333333333333333333"
+ )
+ UserWalletFactory.with_session(db_session).create(
+ address=raw_address,
+ last_activity=123456,
+ balance=5000000000,
+ )
+ db_session.commit()
+
+ # Mock TonApiService
+ mock_service_class = mocker.patch("indexer_blockchain.tasks.TonApiService")
+ mock_service = mock_service_class.return_value
+
+ # Mock get_account_info (new last_activity = 999999)
+ account_info = MagicMock()
+ account_info.last_activity = 999999
+ account_info.balance = 6000000000
+ account_info.address.to_raw.return_value = raw_address
+ mock_service.get_account_info = AsyncMock(return_value=account_info)
+
+ # Mock get_all_jetton_balances
+ mock_service.get_all_jetton_balances = AsyncMock(
+ return_value=JettonsBalances(balances=[])
+ )
+
+ # Mock get_all_nft_items_for_user as async generator
+ async def mock_get_nfts(*args, **kwargs):
+ yield NftItems(nft_items=[])
+
+ mock_service.get_all_nft_items_for_user = mock_get_nfts
+
+ # 2. Act
+ fetch_wallet_details(raw_address)
+
+ # 3. Assert: All external services called, and last_activity updated in DB
+ mock_service.get_account_info.assert_called_once_with(raw_address)
+ mock_service.get_all_jetton_balances.assert_called_once_with(raw_address)
+
+ db_session.expire_all()
+ updated_wallet = (
+ db_session.query(UserWallet).filter_by(address=raw_address).one()
+ )
+ assert updated_wallet.last_activity == 999999
+ assert updated_wallet.balance == 6000000000
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index ef4b4e39..2ff738f2 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -1,14 +1,12 @@
import { ThemeContext } from '@context'
import '@styles/index.scss'
import { TonConnectUIProvider } from '@tonconnect/ui-react'
-import { checkIsMobile, checkStartAppParams } from '@utils'
+import { checkStartAppParams } from '@utils'
import { useContext, useEffect } from 'react'
import { useNavigate, useParams } from 'react-router-dom'
import config from '@config'
-import { AuthService } from '@services'
import { useUser, useUserActions } from '@store'
-import { useAuthQuery } from '@store-new'
import Routes from './Routes'
diff --git a/frontend/src/pages/admin/ChatPage/components/ChatHeader/ChatHeader.tsx b/frontend/src/pages/admin/ChatPage/components/ChatHeader/ChatHeader.tsx
index dc04c6e1..f07b69c0 100644
--- a/frontend/src/pages/admin/ChatPage/components/ChatHeader/ChatHeader.tsx
+++ b/frontend/src/pages/admin/ChatPage/components/ChatHeader/ChatHeader.tsx
@@ -86,6 +86,20 @@ export const ChatHeader = () => {
)}
+ {chat?.insufficientPrivileges && (
+
+
+ ⚠️}
+ text={
+
+ The bot lacks sufficient privileges to manage this chat. Please ensure the bot is an admin with all permissions.
+
+ }
+ />
+
+
+ )}
{
{chat.description}
+ {chat?.insufficientPrivileges && (
+
+
+ ⚠️}
+ text={
+
+ The bot lacks sufficient privileges to manage that chat.
+
+ }
+ />
+
+
+ )}
)
}