uchill/front_material/components/profile/TelegramSection.tsx

416 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'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>
)}
</>
);
}