mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 09:33:46 +00:00
fix(ui): нормализовать detail из ответов API перед рендером — краш на 422
Что: все обработчики ошибок, которые клали сырой response.data.detail в
состояние и затем в JSX, переведены на существующий хелпер
getApiErrorMessage (src/utils/api-error.ts):
- Login.tsx — Telegram-auth, вход/регистрация по email, восстановление пароля
- Profile.tsx — смена email (запрос и подтверждение кода), повторная
отправка письма верификации
- ConnectedAccounts.tsx — привязка email к аккаунту
- admin/AnalyticsTab.tsx, admin/MenuEditorTab.tsx — сохранение настроек
Почему: на невалидный по схеме запрос FastAPI/Pydantic отвечает 422, где
detail — не строка, а массив объектов {type, loc, msg, input, ctx}.
Компоненты рендерили его как есть, React падал с «Objects are not valid
as a React child», и вся страница (включая логин) уходила в ErrorBoundary
«Something went wrong» — пользователь не видел причину отказа вообще.
Зачем: getApiErrorMessage сводит Pydantic-массив к строке
«field: message; ...», поэтому вместо краша форма показывает настоящий
текст ошибки валидации. Ветвления по detail.includes(...) сохранены и
работают по нормализованной строке — поведение прежнее для строковых
detail, но безопасное для любой формы ответа.
This commit is contained in:
@@ -16,6 +16,7 @@ import {
|
||||
type EmailAuthEnabled,
|
||||
} from '../api/branding';
|
||||
import { getAndClearReturnUrl, tokenStorage } from '../utils/token';
|
||||
import { getApiErrorMessage } from '../utils/api-error';
|
||||
import { isInTelegramWebApp, getTelegramInitData, useTelegramSDK } from '../hooks/useTelegramSDK';
|
||||
import { closeMiniApp } from '@telegram-apps/sdk-react';
|
||||
import LanguageSwitcher from '../components/LanguageSwitcher';
|
||||
@@ -190,9 +191,9 @@ export default function Login() {
|
||||
navigate(getReturnUrl(), { replace: true });
|
||||
return;
|
||||
} catch (err) {
|
||||
const error = err as { response?: { status?: number; data?: { detail?: string } } };
|
||||
const error = err as { response?: { status?: number } };
|
||||
const status = error.response?.status;
|
||||
const detail = error.response?.data?.detail;
|
||||
const detail = getApiErrorMessage(err, '');
|
||||
if (import.meta.env.DEV)
|
||||
console.warn(`Telegram auth attempt ${attempt + 1} failed:`, status, detail);
|
||||
|
||||
@@ -268,14 +269,14 @@ export default function Login() {
|
||||
setRegisteredEmail(result.email);
|
||||
}
|
||||
} catch (err: unknown) {
|
||||
const error = err as { response?: { status?: number; data?: { detail?: string } } };
|
||||
const error = err as { response?: { status?: number } };
|
||||
const status = error.response?.status;
|
||||
const detail = error.response?.data?.detail;
|
||||
const detail = getApiErrorMessage(err, '');
|
||||
|
||||
if (status === 400 && detail?.includes('already registered')) {
|
||||
if (status === 400 && detail.includes('already registered')) {
|
||||
setError(t('auth.emailAlreadyRegistered', 'This email is already registered'));
|
||||
} else if (status === 401 || status === 403) {
|
||||
if (detail?.includes('verify your email')) {
|
||||
if (detail.includes('verify your email')) {
|
||||
setError(t('auth.emailNotVerified', 'Please verify your email first'));
|
||||
} else {
|
||||
setError(t('auth.invalidCredentials', 'Invalid email or password'));
|
||||
@@ -304,9 +305,7 @@ export default function Login() {
|
||||
await authApi.forgotPassword(forgotPasswordEmail.trim());
|
||||
setForgotPasswordSent(true);
|
||||
} catch (err: unknown) {
|
||||
const error = err as { response?: { status?: number; data?: { detail?: string } } };
|
||||
const detail = error.response?.data?.detail;
|
||||
setForgotPasswordError(detail || t('common.error'));
|
||||
setForgotPasswordError(getApiErrorMessage(err, t('common.error')));
|
||||
} finally {
|
||||
setForgotPasswordLoading(false);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user