Вход через Telegram: код из бота вместо сломанного deep-link

Deep-link авторизация в Telegram-виджете не работала (не было отдельной
кнопки для завершения флоу). Заменяет на код-флоу: бот выдаёт разовый код,
пользователь вводит его в Login.tsx/LoginButton.tsx, useAuthStore дергает
loginTelegramCode(). api.ts вместо startTelegramLogin/TelegramLoginSession
получает getBotUsername() (кэширует bot_username из GET /api/v1/config) для
кнопки "Открыть бота".

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-08 22:52:04 +07:00
parent 626e3b8fd4
commit 5f6e06589c
4 changed files with 137 additions and 128 deletions
+47 -35
View File
@@ -10,6 +10,7 @@ import {
import { Button } from "@/components/ui/button";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { useAuthStore } from "@/store/useAuthStore";
import { getBotUsername } from "@/lib/api";
// Кнопка входа по seed/Telegram на экране VPN. Панель — не полноэкранный
// Dialog (его затемнение перекрывает весь вьюпорт, включая хедер, и блокирует
@@ -20,27 +21,26 @@ export function LoginButton() {
const { t } = useTranslation();
const [isOpen, setIsOpen] = useState(false);
const [seedInput, setSeedInput] = useState("");
const [codeInput, setCodeInput] = useState("");
const [copied, setCopied] = useState(false);
const [botUsername, setBotUsername] = useState<string | null>(null);
const seed = useAuthStore((s) => s.seed);
const seedLoading = useAuthStore((s) => s.seedLoading);
const seedError = useAuthStore((s) => s.seedError);
const loadSeed = useAuthStore((s) => s.loadSeed);
const loginSeed = useAuthStore((s) => s.loginSeed);
const telegramDeepLink = useAuthStore((s) => s.telegramDeepLink);
const telegramStatus = useAuthStore((s) => s.telegramStatus);
const startTelegram = useAuthStore((s) => s.startTelegram);
const cancelTelegram = useAuthStore((s) => s.cancelTelegram);
const telegramCodeLoading = useAuthStore((s) => s.telegramCodeLoading);
const telegramCodeError = useAuthStore((s) => s.telegramCodeError);
const loginTelegramCode = useAuthStore((s) => s.loginTelegramCode);
useEffect(() => {
if (isOpen) loadSeed();
}, [isOpen, loadSeed]);
useEffect(() => {
if (telegramDeepLink) {
open(telegramDeepLink).catch(() => {});
}
}, [telegramDeepLink]);
getBotUsername().then(setBotUsername);
}, []);
const handleCopy = async () => {
if (!seed) return;
@@ -55,11 +55,21 @@ export function LoginButton() {
await loginSeed(seedInput.trim());
};
const handleTelegramSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!codeInput.trim()) return;
await loginTelegramCode(codeInput.trim());
};
const handleOpenBot = () => {
if (botUsername) open(`https://t.me/${botUsername}`).catch(() => {});
};
return (
<>
<button
onClick={() => setIsOpen(true)}
className="absolute top-3 right-3 z-10 flex items-center gap-1.5 rounded-full border border-white/10 bg-background/30 px-3 py-1.5 text-xs font-medium text-foreground/80 backdrop-blur-md transition-colors hover:border-white/20 hover:text-foreground"
className="absolute top-3 end-3 z-10 flex items-center gap-1.5 rounded-full border border-white/10 bg-background/30 px-3 py-1.5 text-xs font-medium text-foreground/80 backdrop-blur-md transition-colors hover:border-white/20 hover:text-foreground"
>
<IconUserCircle className="size-4" />
{t("login_button")}
@@ -124,33 +134,35 @@ export function LoginButton() {
<TabsContent value="telegram" className="mt-4 flex flex-col gap-3">
<p className="text-sm text-muted-foreground">{t("login_telegram_desc")}</p>
{telegramStatus === "idle" && (
<Button type="button" onClick={startTelegram} className="gap-2">
<IconBrandTelegram className="size-4" />
{t("login_telegram_start")}
<Button
type="button"
variant="outline"
onClick={handleOpenBot}
disabled={!botUsername}
className="gap-2"
>
<IconBrandTelegram className="size-4" />
{t("login_telegram_open_bot")}
</Button>
<form onSubmit={handleTelegramSubmit} className="flex flex-col gap-2">
<label className="text-xs font-bold uppercase tracking-widest text-muted-foreground">
{t("login_telegram_code_label")}
</label>
<input
type="text"
value={codeInput}
onChange={(e) => setCodeInput(e.target.value)}
placeholder={t("login_telegram_code_placeholder")}
className="w-full rounded-xl border border-white/10 bg-white/5 p-3 text-sm text-foreground outline-none transition-colors focus:border-primary"
/>
{telegramCodeError && (
<p className="text-xs text-destructive">{t("login_telegram_error")}</p>
)}
<Button type="submit" disabled={telegramCodeLoading}>
{t("login_telegram_submit")}
</Button>
)}
{telegramStatus === "waiting" && (
<div className="flex flex-col gap-2">
<p className="animate-pulse text-sm text-muted-foreground">
{t("login_telegram_waiting")}
</p>
<Button type="button" variant="outline" onClick={cancelTelegram}>
{t("login_telegram_cancel")}
</Button>
</div>
)}
{telegramStatus === "success" && (
<p className="text-sm font-medium text-primary">{t("login_telegram_success")}</p>
)}
{telegramStatus === "error" && (
<div className="flex flex-col gap-2">
<p className="text-sm text-destructive">{t("login_telegram_error")}</p>
<Button type="button" onClick={startTelegram}>
{t("login_telegram_start")}
</Button>
</div>
)}
</form>
</TabsContent>
</Tabs>
</div>