{target_user.user_id}\n"
+ f"Telegram ID: {telegram_id}\n\n"
+ "Нажмите кнопку ниже, чтобы открыть профиль в Telegram."
+ )
+
+ button_text = translate("user_card_open_profile_button")
+ if button_text == "user_card_open_profile_button":
+ button_text = "👤 Открыть профиль"
+
+ markup = InlineKeyboardMarkup(
+ inline_keyboard=[[InlineKeyboardButton(text=button_text, url=profile_url)]]
+ )
+
+ try:
+ await send_message_via_queue(
+ queue_manager,
+ int(admin_telegram_id),
+ MessageContent(content_type="text", text=message_text),
+ parse_mode="HTML",
+ disable_web_page_preview=True,
+ reply_markup=markup,
+ )
+ except Exception as exc:
+ logger.warning("Admin profile link message enqueue failed: %s", exc)
+ return _error(502, "send_failed", str(exc))
+
+ return _ok({"queued": True})
+
+
async def admin_user_delete_route(request: web.Request) -> web.Response:
actor_id = _require_admin_user_id(request)
target_id = int(request.match_info["user_id"])
diff --git a/frontend/src/admin/AdminPanel.svelte b/frontend/src/admin/AdminPanel.svelte
index dac7d27..9ff5172 100644
--- a/frontend/src/admin/AdminPanel.svelte
+++ b/frontend/src/admin/AdminPanel.svelte
@@ -59,10 +59,13 @@
} from "../lib/admin/format.js";
import {
createGravatarCache,
+ openTelegramProfileLink,
userAvatarUrl,
userDisplayName,
userInitials,
userSecondaryName,
+ userTelegramProfileLink,
+ userTelegramProfileLinkKind,
} from "../lib/admin/users.js";
export let api;
@@ -679,6 +682,9 @@
{userDisplayName}
{userSecondaryName}
{userInitials}
+ {userTelegramProfileLink}
+ {userTelegramProfileLinkKind}
+ {openTelegramProfileLink}
{paymentStatusVariant}
{trafficPercentValue}
{trafficLeftLabel}
diff --git a/frontend/src/admin/sections/UserDetailModal.svelte b/frontend/src/admin/sections/UserDetailModal.svelte
index c5dc315..5eb1d39 100644
--- a/frontend/src/admin/sections/UserDetailModal.svelte
+++ b/frontend/src/admin/sections/UserDetailModal.svelte
@@ -15,6 +15,7 @@
import {
Copy,
Eye,
+ ExternalLink,
RefreshCw,
Send,
Plus,
@@ -36,6 +37,13 @@
export let trafficOfLabel;
export let userInitials = () => "";
export let fmtDateShort = (v) => v;
+ export let userTelegramProfileLink = () => "";
+ export let userTelegramProfileLinkKind = () => "";
+ export let openTelegramProfileLink = () => false;
+
+ let avatarPreviewOpen = false;
+ let avatarPreviewUrl = "";
+ let avatarPreviewName = "";
function pretty(val) {
if (val === true) return at("yes", {}, "Да");
@@ -67,9 +75,53 @@
$: userLogsHasMore =
Number(userLogsTotal || 0) > (Number(userLogsPage || 0) + 1) * Number(userLogsPageSize || 20);
+ $: openedUserAvatarUrl = openedUser ? resolvedAvatarUrl(openedUser) : "";
+ $: openedUserTelegramProfileLink = openedUser ? userTelegramProfileLink(openedUser) : "";
+ $: openedUserTelegramProfileLinkKind = openedUser ? userTelegramProfileLinkKind(openedUser) : "";
+ $: openedUserTelegramProfileHint =
+ openedUserTelegramProfileLinkKind === "id"
+ ? at(
+ "user_open_tg_profile_id_hint",
+ {},
+ "Профиль будет открыт по Telegram ID. Telegram может заблокировать переход из-за настроек приватности пользователя или ограничений клиента."
+ )
+ : at("user_open_tg_profile_hint", {}, "Открыть профиль Telegram");
+
$: if (openedUser && userDetailTab === "logs" && !userLogsLoading && !userLogsLoaded) {
usersStore.loadUserLogs(0);
}
+
+ $: if (!openedUser) {
+ avatarPreviewOpen = false;
+ avatarPreviewUrl = "";
+ avatarPreviewName = "";
+ }
+
+ function openAvatarPreview() {
+ if (!openedUserAvatarUrl || !openedUser) return;
+ avatarPreviewUrl = openedUserAvatarUrl;
+ avatarPreviewName = userDisplayName(openedUser);
+ avatarPreviewOpen = true;
+ }
+
+ function closeAvatarPreview() {
+ avatarPreviewOpen = false;
+ }
+
+ function openUserTelegramProfile() {
+ if (!openedUserTelegramProfileLink) {
+ usersStore.copyToClipboard(
+ String(openedUser?.telegram_id || ""),
+ at("user_tg_profile_unavailable", {}, "Ссылка на профиль Telegram недоступна")
+ );
+ return;
+ }
+ if (openedUserTelegramProfileLinkKind === "id") {
+ usersStore.sendTelegramProfileLink();
+ return;
+ }
+ openTelegramProfileLink(openedUserTelegramProfileLink);
+ }
+
+