Add telegram bot
This commit is contained in:
22
frontend/src/api/telegram.ts
Normal file
22
frontend/src/api/telegram.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import client from './client'
|
||||
|
||||
export interface TelegramLinkToken {
|
||||
token: string
|
||||
bot_url: string
|
||||
}
|
||||
|
||||
export interface TelegramStatus {
|
||||
telegram_id: number | null
|
||||
telegram_username: string | null
|
||||
}
|
||||
|
||||
export const telegramApi = {
|
||||
generateLinkToken: async (): Promise<TelegramLinkToken> => {
|
||||
const response = await client.post<TelegramLinkToken>('/telegram/generate-link-token')
|
||||
return response.data
|
||||
},
|
||||
|
||||
unlinkTelegram: async (): Promise<void> => {
|
||||
await client.post('/users/me/telegram/unlink')
|
||||
},
|
||||
}
|
||||
186
frontend/src/components/TelegramLink.tsx
Normal file
186
frontend/src/components/TelegramLink.tsx
Normal file
@@ -0,0 +1,186 @@
|
||||
import { useState } from 'react'
|
||||
import { MessageCircle, ExternalLink, X, Loader2 } from 'lucide-react'
|
||||
import { telegramApi } from '@/api/telegram'
|
||||
import { useAuthStore } from '@/store/auth'
|
||||
|
||||
export function TelegramLink() {
|
||||
const { user, updateUser } = useAuthStore()
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [botUrl, setBotUrl] = useState<string | null>(null)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
const isLinked = !!user?.telegram_id
|
||||
|
||||
const handleGenerateLink = async () => {
|
||||
setLoading(true)
|
||||
setError(null)
|
||||
try {
|
||||
const { bot_url } = await telegramApi.generateLinkToken()
|
||||
setBotUrl(bot_url)
|
||||
} catch (err) {
|
||||
setError('Не удалось сгенерировать ссылку')
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleUnlink = async () => {
|
||||
setLoading(true)
|
||||
setError(null)
|
||||
try {
|
||||
await telegramApi.unlinkTelegram()
|
||||
updateUser({ telegram_id: null, telegram_username: null })
|
||||
setIsOpen(false)
|
||||
} catch (err) {
|
||||
setError('Не удалось отвязать аккаунт')
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleOpenBot = () => {
|
||||
if (botUrl) {
|
||||
window.open(botUrl, '_blank')
|
||||
setIsOpen(false)
|
||||
setBotUrl(null)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
onClick={() => setIsOpen(true)}
|
||||
className={`p-2 rounded-lg transition-colors ${
|
||||
isLinked
|
||||
? 'text-blue-400 hover:text-blue-300 hover:bg-gray-700'
|
||||
: 'text-gray-400 hover:text-white hover:bg-gray-700'
|
||||
}`}
|
||||
title={isLinked ? 'Telegram привязан' : 'Привязать Telegram'}
|
||||
>
|
||||
<MessageCircle className="w-5 h-5" />
|
||||
</button>
|
||||
|
||||
{isOpen && (
|
||||
<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)
|
||||
}}
|
||||
className="absolute top-4 right-4 text-gray-400 hover:text-white"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<div className="w-12 h-12 bg-blue-500/20 rounded-full flex items-center justify-center">
|
||||
<MessageCircle className="w-6 h-6 text-blue-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-white">Telegram</h2>
|
||||
<p className="text-sm text-gray-400">
|
||||
{isLinked ? 'Аккаунт привязан' : 'Привяжи аккаунт'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="mb-4 p-3 bg-red-500/20 border border-red-500/50 rounded-lg text-red-400 text-sm">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isLinked ? (
|
||||
<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>
|
||||
</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>
|
||||
</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"
|
||||
>
|
||||
{loading ? (
|
||||
<Loader2 className="w-5 h-5 animate-spin mx-auto" />
|
||||
) : (
|
||||
'Отвязать аккаунт'
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
) : botUrl ? (
|
||||
<div className="space-y-4">
|
||||
<p className="text-gray-300">
|
||||
Нажми кнопку ниже, чтобы открыть бота и завершить привязку:
|
||||
</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">
|
||||
<p className="text-gray-300">
|
||||
Привяжи Telegram, чтобы получать уведомления о важных событиях:
|
||||
</p>
|
||||
|
||||
<ul className="text-sm text-gray-400 space-y-2">
|
||||
<li className="flex items-center gap-2">
|
||||
<span className="text-yellow-400">🌟</span>
|
||||
Golden Hour - очки x1.5
|
||||
</li>
|
||||
<li className="flex items-center gap-2">
|
||||
<span className="text-yellow-400">🎰</span>
|
||||
Jackpot - очки x3
|
||||
</li>
|
||||
<li className="flex items-center gap-2">
|
||||
<span className="text-yellow-400">⚡</span>
|
||||
Double Risk и другие события
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<button
|
||||
onClick={handleGenerateLink}
|
||||
disabled={loading}
|
||||
className="w-full py-3 px-4 bg-blue-500 hover:bg-blue-600 text-white rounded-lg font-medium transition-colors disabled:opacity-50 flex items-center justify-center gap-2"
|
||||
>
|
||||
{loading ? (
|
||||
<Loader2 className="w-5 h-5 animate-spin" />
|
||||
) : (
|
||||
<>
|
||||
<MessageCircle className="w-5 h-5" />
|
||||
Привязать Telegram
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Outlet, Link, useNavigate } from 'react-router-dom'
|
||||
import { useAuthStore } from '@/store/auth'
|
||||
import { Gamepad2, LogOut, Trophy, User } from 'lucide-react'
|
||||
import { TelegramLink } from '@/components/TelegramLink'
|
||||
|
||||
export function Layout() {
|
||||
const { user, isAuthenticated, logout } = useAuthStore()
|
||||
@@ -38,6 +39,8 @@ export function Layout() {
|
||||
<span>{user?.nickname}</span>
|
||||
</div>
|
||||
|
||||
<TelegramLink />
|
||||
|
||||
<button
|
||||
onClick={handleLogout}
|
||||
className="p-2 text-gray-400 hover:text-white transition-colors"
|
||||
|
||||
@@ -17,6 +17,7 @@ interface AuthState {
|
||||
clearError: () => void
|
||||
setPendingInviteCode: (code: string | null) => void
|
||||
consumePendingInviteCode: () => string | null
|
||||
updateUser: (updates: Partial<User>) => void
|
||||
}
|
||||
|
||||
export const useAuthStore = create<AuthState>()(
|
||||
@@ -89,6 +90,13 @@ export const useAuthStore = create<AuthState>()(
|
||||
set({ pendingInviteCode: null })
|
||||
return code
|
||||
},
|
||||
|
||||
updateUser: (updates) => {
|
||||
const currentUser = get().user
|
||||
if (currentUser) {
|
||||
set({ user: { ...currentUser, ...updates } })
|
||||
}
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: 'auth-storage',
|
||||
|
||||
@@ -7,6 +7,8 @@ export interface User {
|
||||
nickname: string
|
||||
avatar_url: string | null
|
||||
role: UserRole
|
||||
telegram_id: number | null
|
||||
telegram_username: string | null
|
||||
created_at: string
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user