416 lines
14 KiB
TypeScript
416 lines
14 KiB
TypeScript
'use client';
|
||
|
||
import React, { useState, useEffect } from 'react';
|
||
import {
|
||
generateTelegramCode,
|
||
unlinkTelegram,
|
||
getTelegramStatus,
|
||
getTelegramBotInfo,
|
||
} from '@/api/telegram';
|
||
import { useToast } from '@/contexts/ToastContext';
|
||
|
||
interface TelegramSectionProps {
|
||
onLinkedChange?: () => void;
|
||
}
|
||
|
||
export function TelegramSection({ onLinkedChange }: TelegramSectionProps) {
|
||
const { showToast } = useToast();
|
||
const [loading, setLoading] = useState(true);
|
||
const [linked, setLinked] = useState(false);
|
||
const [telegramUsername, setTelegramUsername] = useState('');
|
||
const [showLinkModal, setShowLinkModal] = useState(false);
|
||
const [linkCode, setLinkCode] = useState('');
|
||
const [generating, setGenerating] = useState(false);
|
||
const [unlinking, setUnlinking] = useState(false);
|
||
const [error, setError] = useState<string | null>(null);
|
||
const [success, setSuccess] = useState<string | null>(null);
|
||
const [copied, setCopied] = useState(false);
|
||
const [botLink, setBotLink] = useState('');
|
||
const [botUsername, setBotUsername] = useState('');
|
||
const [showUnlinkConfirm, setShowUnlinkConfirm] = useState(false);
|
||
|
||
const loadStatus = async () => {
|
||
try {
|
||
setLoading(true);
|
||
setError(null);
|
||
const status = await getTelegramStatus();
|
||
setLinked(status.linked);
|
||
setTelegramUsername(status.telegram_username || '');
|
||
} catch (err) {
|
||
console.error('Error loading Telegram status:', err);
|
||
setError('Не удалось загрузить статус Telegram');
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
const loadBotInfo = async () => {
|
||
try {
|
||
const botInfo = await getTelegramBotInfo();
|
||
if (botInfo?.success && botInfo?.username) {
|
||
setBotLink(botInfo.link || `https://t.me/${botInfo.username}`);
|
||
setBotUsername(`@${botInfo.username}`);
|
||
}
|
||
} catch (err) {
|
||
console.error('Failed to fetch bot info:', err);
|
||
}
|
||
};
|
||
|
||
useEffect(() => {
|
||
loadStatus();
|
||
loadBotInfo();
|
||
}, []);
|
||
|
||
useEffect(() => {
|
||
if (showLinkModal && !botUsername) loadBotInfo();
|
||
}, [showLinkModal, botUsername]);
|
||
|
||
const handleGenerateCode = async () => {
|
||
try {
|
||
setGenerating(true);
|
||
setError(null);
|
||
setSuccess(null);
|
||
const response = await generateTelegramCode();
|
||
if (response?.success && response?.code) {
|
||
setLinkCode(response.code);
|
||
setShowLinkModal(true);
|
||
} else {
|
||
setError(response?.error || 'Не удалось сгенерировать код');
|
||
}
|
||
} catch (err) {
|
||
console.error('Error generating code:', err);
|
||
setError('Ошибка генерации кода');
|
||
} finally {
|
||
setGenerating(false);
|
||
}
|
||
};
|
||
|
||
const handleUnlinkClick = () => {
|
||
setShowUnlinkConfirm(true);
|
||
setError(null);
|
||
};
|
||
|
||
const handleUnlinkConfirm = async () => {
|
||
try {
|
||
setUnlinking(true);
|
||
setShowUnlinkConfirm(false);
|
||
setError(null);
|
||
setSuccess(null);
|
||
const response = await unlinkTelegram();
|
||
if (response?.success) {
|
||
setSuccess('Telegram отвязан');
|
||
setLinked(false);
|
||
setTelegramUsername('');
|
||
onLinkedChange?.();
|
||
setTimeout(() => setSuccess(null), 3000);
|
||
} else {
|
||
setError(response?.error || 'Не удалось отвязать');
|
||
}
|
||
} catch (err) {
|
||
console.error('Error unlinking:', err);
|
||
setError('Ошибка отвязки');
|
||
} finally {
|
||
setUnlinking(false);
|
||
}
|
||
};
|
||
|
||
const closeLinkModal = () => {
|
||
setShowLinkModal(false);
|
||
setLinkCode('');
|
||
setCopied(false);
|
||
setTimeout(() => {
|
||
loadStatus();
|
||
onLinkedChange?.();
|
||
}, 2000);
|
||
};
|
||
|
||
const copyLinkCommand = async () => {
|
||
if (!linkCode) return;
|
||
const command = `/link ${linkCode}`;
|
||
try {
|
||
await navigator.clipboard.writeText(command);
|
||
setCopied(true);
|
||
showToast('Команда скопирована', 'success');
|
||
setTimeout(() => setCopied(false), 2000);
|
||
} catch {
|
||
const el = document.createElement('textarea');
|
||
el.value = command;
|
||
el.style.position = 'fixed';
|
||
el.style.opacity = '0';
|
||
document.body.appendChild(el);
|
||
el.select();
|
||
try {
|
||
document.execCommand('copy');
|
||
setCopied(true);
|
||
showToast('Команда скопирована', 'success');
|
||
setTimeout(() => setCopied(false), 2000);
|
||
} finally {
|
||
document.body.removeChild(el);
|
||
}
|
||
}
|
||
};
|
||
|
||
if (loading) {
|
||
return (
|
||
<div style={{ marginTop: 16, padding: '12px 0', fontSize: 13, color: '#858585' }}>
|
||
Telegram: загрузка...
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<>
|
||
<div style={{ marginTop: 16 }}>
|
||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexWrap: 'wrap', gap: 12 }}>
|
||
<div>
|
||
<div style={{ fontSize: 12, fontWeight: 500, color: '#858585', marginBottom: 4 }}>Telegram</div>
|
||
{linked ? (
|
||
<div style={{ fontSize: 14, color: '#282C32' }}>
|
||
Подключён {telegramUsername ? `@${telegramUsername.replace(/^@/, '')}` : ''}
|
||
</div>
|
||
) : (
|
||
<div style={{ fontSize: 13, color: '#858585' }}>
|
||
Подключите для уведомлений о занятиях и сообщениях
|
||
</div>
|
||
)}
|
||
</div>
|
||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||
{error && (
|
||
<span style={{ fontSize: 12, color: 'var(--md-sys-color-error)' }}>{error}</span>
|
||
)}
|
||
{success && (
|
||
<span style={{ fontSize: 12, color: 'var(--md-sys-color-primary)' }}>{success}</span>
|
||
)}
|
||
{linked ? (
|
||
<button
|
||
type="button"
|
||
onClick={handleUnlinkClick}
|
||
disabled={unlinking}
|
||
style={{
|
||
padding: '10px 18px',
|
||
borderRadius: 12,
|
||
border: '1px solid var(--md-sys-color-error)',
|
||
background: 'transparent',
|
||
color: 'var(--md-sys-color-error)',
|
||
fontSize: 13,
|
||
fontWeight: 500,
|
||
cursor: unlinking ? 'not-allowed' : 'pointer',
|
||
opacity: unlinking ? 0.7 : 1,
|
||
}}
|
||
>
|
||
{unlinking ? 'Отвязка...' : 'Отвязать'}
|
||
</button>
|
||
) : (
|
||
<button
|
||
type="button"
|
||
onClick={handleGenerateCode}
|
||
disabled={generating}
|
||
style={{
|
||
padding: '10px 18px',
|
||
borderRadius: 12,
|
||
border: 'none',
|
||
background: 'var(--md-sys-color-primary)',
|
||
color: 'var(--md-sys-color-on-primary)',
|
||
fontSize: 13,
|
||
fontWeight: 500,
|
||
cursor: generating ? 'not-allowed' : 'pointer',
|
||
opacity: generating ? 0.7 : 1,
|
||
}}
|
||
>
|
||
{generating ? 'Генерация...' : 'Связать с Telegram'}
|
||
</button>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{showUnlinkConfirm && (
|
||
<div
|
||
style={{
|
||
marginTop: 12,
|
||
padding: 20,
|
||
borderRadius: 14,
|
||
border: '1px solid var(--md-sys-color-outline-variant, #E6E6E6)',
|
||
background: 'var(--md-sys-color-surface-container-low, #f5f5f5)',
|
||
boxShadow: '0 2px 8px rgba(0,0,0,0.04)',
|
||
}}
|
||
>
|
||
<p style={{ fontSize: 14, color: 'var(--md-sys-color-on-surface, #282C32)', margin: '0 0 16px 0', fontWeight: 500 }}>
|
||
Вы уверены, что хотите отвязать Telegram?
|
||
</p>
|
||
<div style={{ display: 'flex', gap: 10, justifyContent: 'flex-end' }}>
|
||
<button
|
||
type="button"
|
||
onClick={() => setShowUnlinkConfirm(false)}
|
||
style={{
|
||
padding: '12px 20px',
|
||
borderRadius: 14,
|
||
border: '1px solid var(--md-sys-color-outline, #E6E6E6)',
|
||
background: 'var(--md-sys-color-surface, #fff)',
|
||
color: 'var(--md-sys-color-on-surface, #282C32)',
|
||
fontSize: 14,
|
||
fontWeight: 600,
|
||
cursor: 'pointer',
|
||
}}
|
||
>
|
||
Отмена
|
||
</button>
|
||
<button
|
||
type="button"
|
||
onClick={handleUnlinkConfirm}
|
||
disabled={unlinking}
|
||
style={{
|
||
padding: '12px 20px',
|
||
borderRadius: 14,
|
||
border: 'none',
|
||
background: 'var(--md-sys-color-error)',
|
||
color: 'var(--md-sys-color-on-error, #fff)',
|
||
fontSize: 14,
|
||
fontWeight: 600,
|
||
cursor: unlinking ? 'not-allowed' : 'pointer',
|
||
opacity: unlinking ? 0.7 : 1,
|
||
}}
|
||
>
|
||
{unlinking ? 'Отвязка...' : 'Отвязать'}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{showLinkModal && (
|
||
<div
|
||
style={{
|
||
position: 'fixed',
|
||
inset: 0,
|
||
background: 'var(--md-sys-color-scrim, rgba(0,0,0,0.5))',
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
zIndex: 9999,
|
||
padding: 24,
|
||
}}
|
||
>
|
||
<div
|
||
style={{
|
||
background: 'var(--md-sys-color-surface, #fff)',
|
||
borderRadius: 20,
|
||
boxShadow: '0 4px 24px rgba(0,0,0,0.06)',
|
||
maxWidth: 400,
|
||
width: '100%',
|
||
padding: 24,
|
||
}}
|
||
>
|
||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 20 }}>
|
||
<h3 style={{ fontSize: 18, fontWeight: 700, margin: 0, color: 'var(--md-sys-color-on-surface, #282C32)' }}>
|
||
Привязка к Telegram
|
||
</h3>
|
||
<button
|
||
type="button"
|
||
onClick={closeLinkModal}
|
||
style={{
|
||
background: 'none',
|
||
border: 'none',
|
||
cursor: 'pointer',
|
||
padding: 4,
|
||
color: 'var(--md-sys-color-on-surface-variant, #858585)',
|
||
fontSize: 24,
|
||
lineHeight: 1,
|
||
}}
|
||
>
|
||
×
|
||
</button>
|
||
</div>
|
||
<div
|
||
style={{
|
||
background: 'var(--md-sys-color-primary-container, #e8def8)',
|
||
borderRadius: 12,
|
||
padding: 20,
|
||
textAlign: 'center',
|
||
marginBottom: 16,
|
||
}}
|
||
>
|
||
<p style={{ fontSize: 12, color: 'var(--md-sys-color-on-surface-variant, #5c5c5c)', margin: '0 0 8px 0' }}>
|
||
Ваш код связывания:
|
||
</p>
|
||
<div style={{ fontSize: 28, fontWeight: 700, color: 'var(--md-sys-color-on-primary-container, #1d192b)', letterSpacing: 4 }}>
|
||
{linkCode}
|
||
</div>
|
||
<p style={{ fontSize: 11, color: 'var(--md-sys-color-on-surface-variant, #858585)', margin: '8px 0 0 0' }}>
|
||
Действителен 15 минут
|
||
</p>
|
||
</div>
|
||
<div
|
||
style={{
|
||
background: 'var(--md-sys-color-surface-container-low, #f5f5f5)',
|
||
borderRadius: 12,
|
||
padding: 16,
|
||
marginBottom: 16,
|
||
border: '1px solid var(--md-sys-color-outline-variant, #E6E6E6)',
|
||
}}
|
||
>
|
||
<p style={{ fontSize: 12, fontWeight: 600, color: 'var(--md-sys-color-on-surface, #282C32)', margin: '0 0 8px 0' }}>
|
||
Инструкция:
|
||
</p>
|
||
<ol style={{ fontSize: 13, color: 'var(--md-sys-color-on-surface, #282C32)', margin: 0, paddingLeft: 18 }}>
|
||
<li>Откройте Telegram</li>
|
||
<li>
|
||
{botLink && botUsername ? (
|
||
<>
|
||
Найдите бота{' '}
|
||
<a
|
||
href={botLink}
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
style={{ color: 'var(--md-sys-color-primary)', fontWeight: 600 }}
|
||
>
|
||
{botUsername}
|
||
</a>
|
||
</>
|
||
) : (
|
||
<>Найдите бота {botUsername || '(ссылка ниже)'}</>
|
||
)}
|
||
</li>
|
||
<li>
|
||
Отправьте команду:{' '}
|
||
<code
|
||
onClick={copyLinkCommand}
|
||
style={{
|
||
background: 'var(--md-sys-color-surface-container, #f0e6fa)',
|
||
padding: '4px 10px',
|
||
borderRadius: 8,
|
||
cursor: 'pointer',
|
||
fontWeight: 600,
|
||
color: 'var(--md-sys-color-primary)',
|
||
}}
|
||
>
|
||
/link {linkCode}
|
||
</code>
|
||
{copied && <span style={{ marginLeft: 6, color: 'var(--md-sys-color-primary)', fontSize: 11 }}>✓ Скопировано</span>}
|
||
</li>
|
||
</ol>
|
||
</div>
|
||
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
|
||
<button
|
||
type="button"
|
||
onClick={closeLinkModal}
|
||
style={{
|
||
padding: '12px 24px',
|
||
borderRadius: 14,
|
||
border: 'none',
|
||
background: 'var(--md-sys-color-primary)',
|
||
color: 'var(--md-sys-color-on-primary)',
|
||
fontSize: 14,
|
||
fontWeight: 600,
|
||
cursor: 'pointer',
|
||
}}
|
||
>
|
||
Закрыть
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</>
|
||
);
|
||
}
|