Add info if linked acc
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { useState } from 'react'
|
||||
import { MessageCircle, ExternalLink, X, Loader2 } from 'lucide-react'
|
||||
import { useState, useEffect, useRef } from 'react'
|
||||
import { MessageCircle, ExternalLink, X, Loader2, RefreshCw, CheckCircle, User, Link2, Link2Off } from 'lucide-react'
|
||||
import { telegramApi } from '@/api/telegram'
|
||||
import { authApi } from '@/api/auth'
|
||||
import { useAuthStore } from '@/store/auth'
|
||||
|
||||
export function TelegramLink() {
|
||||
@@ -9,16 +10,74 @@ export function TelegramLink() {
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [botUrl, setBotUrl] = useState<string | null>(null)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [isPolling, setIsPolling] = useState(false)
|
||||
const [linkSuccess, setLinkSuccess] = useState(false)
|
||||
const pollingRef = useRef<ReturnType<typeof setInterval> | null>(null)
|
||||
|
||||
const isLinked = !!user?.telegram_id
|
||||
|
||||
// Cleanup polling on unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (pollingRef.current) {
|
||||
clearInterval(pollingRef.current)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
const startPolling = () => {
|
||||
setIsPolling(true)
|
||||
let attempts = 0
|
||||
const maxAttempts = 60 // 5 minutes (5 sec intervals)
|
||||
|
||||
pollingRef.current = setInterval(async () => {
|
||||
attempts++
|
||||
try {
|
||||
const userData = await authApi.me()
|
||||
if (userData.telegram_id) {
|
||||
// Success! User linked their account
|
||||
updateUser({
|
||||
telegram_id: userData.telegram_id,
|
||||
telegram_username: userData.telegram_username,
|
||||
telegram_first_name: userData.telegram_first_name,
|
||||
telegram_last_name: userData.telegram_last_name,
|
||||
telegram_avatar_url: userData.telegram_avatar_url
|
||||
})
|
||||
setLinkSuccess(true)
|
||||
setIsPolling(false)
|
||||
setBotUrl(null)
|
||||
if (pollingRef.current) {
|
||||
clearInterval(pollingRef.current)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Ignore errors, continue polling
|
||||
}
|
||||
|
||||
if (attempts >= maxAttempts) {
|
||||
setIsPolling(false)
|
||||
if (pollingRef.current) {
|
||||
clearInterval(pollingRef.current)
|
||||
}
|
||||
}
|
||||
}, 5000)
|
||||
}
|
||||
|
||||
const stopPolling = () => {
|
||||
setIsPolling(false)
|
||||
if (pollingRef.current) {
|
||||
clearInterval(pollingRef.current)
|
||||
}
|
||||
}
|
||||
|
||||
const handleGenerateLink = async () => {
|
||||
setLoading(true)
|
||||
setError(null)
|
||||
setLinkSuccess(false)
|
||||
try {
|
||||
const { bot_url } = await telegramApi.generateLinkToken()
|
||||
setBotUrl(bot_url)
|
||||
} catch (err) {
|
||||
} catch {
|
||||
setError('Не удалось сгенерировать ссылку')
|
||||
} finally {
|
||||
setLoading(false)
|
||||
@@ -30,9 +89,15 @@ export function TelegramLink() {
|
||||
setError(null)
|
||||
try {
|
||||
await telegramApi.unlinkTelegram()
|
||||
updateUser({ telegram_id: null, telegram_username: null })
|
||||
updateUser({
|
||||
telegram_id: null,
|
||||
telegram_username: null,
|
||||
telegram_first_name: null,
|
||||
telegram_last_name: null,
|
||||
telegram_avatar_url: null
|
||||
})
|
||||
setIsOpen(false)
|
||||
} catch (err) {
|
||||
} catch {
|
||||
setError('Не удалось отвязать аккаунт')
|
||||
} finally {
|
||||
setLoading(false)
|
||||
@@ -42,11 +107,18 @@ export function TelegramLink() {
|
||||
const handleOpenBot = () => {
|
||||
if (botUrl) {
|
||||
window.open(botUrl, '_blank')
|
||||
setIsOpen(false)
|
||||
setBotUrl(null)
|
||||
startPolling()
|
||||
}
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
setIsOpen(false)
|
||||
setBotUrl(null)
|
||||
setError(null)
|
||||
setLinkSuccess(false)
|
||||
stopPolling()
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
@@ -65,11 +137,7 @@ export function TelegramLink() {
|
||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
|
||||
<div className="bg-gray-800 rounded-xl max-w-md w-full p-6 relative">
|
||||
<button
|
||||
onClick={() => {
|
||||
setIsOpen(false)
|
||||
setBotUrl(null)
|
||||
setError(null)
|
||||
}}
|
||||
onClick={handleClose}
|
||||
className="absolute top-4 right-4 text-gray-400 hover:text-white"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
@@ -93,53 +161,124 @@ export function TelegramLink() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isLinked ? (
|
||||
{isLinked || linkSuccess ? (
|
||||
<div className="space-y-4">
|
||||
<div className="p-4 bg-gray-700/50 rounded-lg">
|
||||
<p className="text-sm text-gray-400 mb-1">Привязан к:</p>
|
||||
<p className="text-white font-medium">
|
||||
{user?.telegram_username ? `@${user.telegram_username}` : `ID: ${user?.telegram_id}`}
|
||||
</p>
|
||||
{linkSuccess && (
|
||||
<div className="p-4 bg-green-500/20 border border-green-500/50 rounded-lg flex items-center gap-3">
|
||||
<CheckCircle className="w-6 h-6 text-green-400 flex-shrink-0" />
|
||||
<p className="text-green-400 font-medium">Аккаунт успешно привязан!</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* User Profile Card */}
|
||||
<div className="p-4 bg-gradient-to-br from-gray-700/50 to-gray-800/50 rounded-xl border border-gray-600/50">
|
||||
<div className="flex items-center gap-4">
|
||||
{/* Avatar - prefer Telegram avatar */}
|
||||
<div className="relative">
|
||||
{user?.telegram_avatar_url || user?.avatar_url ? (
|
||||
<img
|
||||
src={user.telegram_avatar_url || user.avatar_url || ''}
|
||||
alt={user.nickname}
|
||||
className="w-16 h-16 rounded-full object-cover border-2 border-blue-500/50"
|
||||
/>
|
||||
) : (
|
||||
<div className="w-16 h-16 rounded-full bg-gradient-to-br from-blue-500 to-purple-600 flex items-center justify-center border-2 border-blue-500/50">
|
||||
<User className="w-8 h-8 text-white" />
|
||||
</div>
|
||||
)}
|
||||
{/* Link indicator */}
|
||||
<div className="absolute -bottom-1 -right-1 w-6 h-6 bg-green-500 rounded-full flex items-center justify-center border-2 border-gray-800">
|
||||
<Link2 className="w-3 h-3 text-white" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* User Info */}
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-lg font-bold text-white truncate">
|
||||
{[user?.telegram_first_name, user?.telegram_last_name].filter(Boolean).join(' ') || user?.nickname}
|
||||
</p>
|
||||
{user?.telegram_username && (
|
||||
<p className="text-blue-400 font-medium truncate">@{user.telegram_username}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-sm text-gray-400">
|
||||
<p className="mb-2">Ты будешь получать уведомления о:</p>
|
||||
<ul className="list-disc list-inside space-y-1">
|
||||
<li>Начале и окончании событий</li>
|
||||
<li>Старте и завершении марафонов</li>
|
||||
<li>Спорах по заданиям</li>
|
||||
</ul>
|
||||
{/* Notifications Info */}
|
||||
<div className="p-4 bg-gray-700/30 rounded-lg">
|
||||
<p className="text-sm text-gray-300 font-medium mb-3">Уведомления включены:</p>
|
||||
<div className="grid grid-cols-1 gap-2">
|
||||
<div className="flex items-center gap-2 text-sm text-gray-400">
|
||||
<span className="text-yellow-400">🌟</span>
|
||||
<span>События (Golden Hour, Jackpot)</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm text-gray-400">
|
||||
<span className="text-green-400">🚀</span>
|
||||
<span>Старт и финиш марафонов</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm text-gray-400">
|
||||
<span className="text-red-400">⚠️</span>
|
||||
<span>Споры по заданиям</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={handleUnlink}
|
||||
disabled={loading}
|
||||
className="w-full py-3 px-4 bg-red-500/20 hover:bg-red-500/30 text-red-400 rounded-lg font-medium transition-colors disabled:opacity-50"
|
||||
className="w-full py-3 px-4 bg-red-500/10 hover:bg-red-500/20 text-red-400 rounded-lg font-medium transition-colors disabled:opacity-50 flex items-center justify-center gap-2 border border-red-500/30"
|
||||
>
|
||||
{loading ? (
|
||||
<Loader2 className="w-5 h-5 animate-spin mx-auto" />
|
||||
<Loader2 className="w-5 h-5 animate-spin" />
|
||||
) : (
|
||||
'Отвязать аккаунт'
|
||||
<>
|
||||
<Link2Off className="w-4 h-4" />
|
||||
Отвязать аккаунт
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
) : botUrl ? (
|
||||
<div className="space-y-4">
|
||||
<p className="text-gray-300">
|
||||
Нажми кнопку ниже, чтобы открыть бота и завершить привязку:
|
||||
</p>
|
||||
{isPolling ? (
|
||||
<>
|
||||
<div className="p-4 bg-blue-500/20 border border-blue-500/50 rounded-lg">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<RefreshCw className="w-5 h-5 text-blue-400 animate-spin" />
|
||||
<p className="text-blue-400 font-medium">Ожидание привязки...</p>
|
||||
</div>
|
||||
<p className="text-sm text-gray-400">
|
||||
Открой бота в Telegram и нажми Start. Статус обновится автоматически.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={handleOpenBot}
|
||||
className="w-full py-3 px-4 bg-blue-500 hover:bg-blue-600 text-white rounded-lg font-medium transition-colors flex items-center justify-center gap-2"
|
||||
>
|
||||
<ExternalLink className="w-5 h-5" />
|
||||
Открыть Telegram
|
||||
</button>
|
||||
<button
|
||||
onClick={handleOpenBot}
|
||||
className="w-full py-3 px-4 bg-blue-500 hover:bg-blue-600 text-white rounded-lg font-medium transition-colors flex items-center justify-center gap-2"
|
||||
>
|
||||
<ExternalLink className="w-5 h-5" />
|
||||
Открыть Telegram снова
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<p className="text-gray-300">
|
||||
Нажми кнопку ниже, чтобы открыть бота и завершить привязку:
|
||||
</p>
|
||||
|
||||
<p className="text-sm text-gray-500 text-center">
|
||||
Ссылка действительна 10 минут
|
||||
</p>
|
||||
<button
|
||||
onClick={handleOpenBot}
|
||||
className="w-full py-3 px-4 bg-blue-500 hover:bg-blue-600 text-white rounded-lg font-medium transition-colors flex items-center justify-center gap-2"
|
||||
>
|
||||
<ExternalLink className="w-5 h-5" />
|
||||
Открыть Telegram
|
||||
</button>
|
||||
|
||||
<p className="text-sm text-gray-500 text-center">
|
||||
Ссылка действительна 10 минут
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
|
||||
@@ -9,6 +9,9 @@ export interface User {
|
||||
role: UserRole
|
||||
telegram_id: number | null
|
||||
telegram_username: string | null
|
||||
telegram_first_name: string | null
|
||||
telegram_last_name: string | null
|
||||
telegram_avatar_url: string | null
|
||||
created_at: string
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user