Добавлена форма изменения ника при регистрации аккаунта на случай, когда уже существует аакунт с ником, совпадающим с ТГ тегом регистрируемого аккаунта

This commit is contained in:
2026-06-17 16:17:14 +03:00
parent 65bebf8b85
commit 865c967d5e
10 changed files with 409 additions and 28 deletions
+103 -16
View File
@@ -11,13 +11,21 @@ import {
useMe,
useStubLogin,
useTelegramLogin,
useTelegramRegister,
} from "../hooks/auth";
// Данные виджета Telegram, отложенные до выбора ника (когда тег занят).
interface PendingTelegram {
payload: Record<string, unknown>;
suggested: string;
}
export function LoginPage() {
const { data: me, isLoading } = useMe();
const { data: config } = useAuthConfig();
const navigate = useNavigate();
const [error, setError] = useState<string | null>(null);
const [pendingTg, setPendingTg] = useState<PendingTelegram | null>(null);
const methods = config?.methods ?? [];
const hasTelegram = methods.includes("telegram");
@@ -36,6 +44,12 @@ export function LoginPage() {
await telegram.mutateAsync(user);
navigate("/", { replace: true });
} catch (e) {
// Тег занят/некорректен → просим выбрать ник и регистрируем тем же payload.
if (e instanceof ApiError && e.code === "TELEGRAM_NICKNAME_REQUIRED") {
const suggested = (e.details as { suggested?: string } | undefined)?.suggested ?? "";
setPendingTg({ payload: user, suggested });
return;
}
setError(e instanceof ApiError ? e.message : "Не удалось войти через Telegram");
}
};
@@ -48,24 +62,35 @@ export function LoginPage() {
</h1>
<p className="muted center">Учёт партий</p>
{hasTelegram && (
<div className="card">
<h3>Вход через Telegram</h3>
{config?.telegram_bot_username ? (
<TelegramLoginButton
botUsername={config.telegram_bot_username}
onAuth={(u) => onTelegram(u as unknown as Record<string, unknown>)}
/>
) : (
<p className="muted small">
Telegram не настроен — укажите TELEGRAM_BOT_USERNAME и TELEGRAM_BOT_TOKEN
в .env (бот от @BotFather + /setdomain на ваш домен).
</p>
)}
</div>
{pendingTg ? (
<TelegramNicknamePicker
pending={pendingTg}
onCancel={() => {
setPendingTg(null);
setError(null);
}}
onDone={() => navigate("/", { replace: true })}
/>
) : (
hasTelegram && (
<div className="card">
<h3>Вход через Telegram</h3>
{config?.telegram_bot_username ? (
<TelegramLoginButton
botUsername={config.telegram_bot_username}
onAuth={(u) => onTelegram(u as unknown as Record<string, unknown>)}
/>
) : (
<p className="muted small">
Telegram не настроен — укажите TELEGRAM_BOT_USERNAME и TELEGRAM_BOT_TOKEN
в .env (бот от @BotFather + /setdomain на ваш домен).
</p>
)}
</div>
)
)}
{showStub && <DevLogin onError={setError} />}
{showStub && !pendingTg && <DevLogin onError={setError} />}
{error && <p className="error-text center">{error}</p>}
@@ -83,6 +108,68 @@ export function LoginPage() {
);
}
// Выбор ника при регистрации через Telegram, когда тег занят/некорректен.
// Повторяет регистрацию тем же payload виджета (подпись на сервере проверяется заново).
function TelegramNicknamePicker({
pending,
onCancel,
onDone,
}: {
pending: PendingTelegram;
onCancel: () => void;
onDone: () => void;
}) {
const register = useTelegramRegister();
const [nickname, setNickname] = useState(pending.suggested);
const [error, setError] = useState<string | null>(
pending.suggested ? `Ник «${pending.suggested}» уже занят — выберите другой.` : null,
);
const submit = async () => {
const nick = nickname.trim();
if (!nick) {
setError("Введите никнейм.");
return;
}
setError(null);
try {
await register.mutateAsync({ ...pending.payload, nickname: nick });
onDone();
} catch (e) {
setError(e instanceof ApiError ? e.message : "Не удалось зарегистрироваться");
}
};
return (
<div className="card">
<h3>Выберите никнейм</h3>
<p className="muted small">
Ваш ник в Telegram уже занят другим игроком. Укажите другой никнейм для регистрации.
</p>
<div className="field">
<div className="row">
<input
placeholder="Никнейм"
value={nickname}
autoFocus
onChange={(e) => setNickname(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") submit();
}}
/>
<button className="btn btn-primary" onClick={submit} disabled={register.isPending}>
Зарегистрироваться
</button>
</div>
</div>
{error && <p className="error-text">{error}</p>}
<button className="btn btn-ghost small" onClick={onCancel}>
Назад
</button>
</div>
);
}
// DEV-вход по нику. Вынесен в отдельный компонент; импортируется только в dev-ветке
// LoginPage, поэтому в прод-сборке (import.meta.env.DEV=false) удаляется тришейкингом.
function DevLogin({ onError }: { onError: (msg: string | null) => void }) {