fix: normalize auth error messages, no more raw field codes
- Add parse-api-error.js utility that handles all backend error formats:
{error:{details:{field:[msg]}}} → "Field: msg" per line
{error:{message:"field: text"}} → strips "field: " prefix
{message/detail: "text"} → direct fallback
- Apply parseApiError() on all 4 auth pages (sign-in, sign-up, forgot/reset password)
- Add whiteSpace: pre-line to Alert so multi-field errors render on separate lines
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
d5ebd2898a
commit
6aa98de721
|
|
@ -17,6 +17,7 @@ import { RouterLink } from 'src/routes/components';
|
|||
import { Form, Field } from 'src/components/hook-form';
|
||||
|
||||
import { requestPasswordReset } from 'src/auth/context/jwt';
|
||||
import { parseApiError } from 'src/utils/parse-api-error';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
|
|
@ -50,8 +51,7 @@ export function JwtForgotPasswordView() {
|
|||
setErrorMsg('');
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
const msg = error?.response?.data?.error?.message || error?.response?.data?.message || error?.response?.data?.detail || 'Ошибка отправки запроса. Проверьте правильность email.';
|
||||
setErrorMsg(msg);
|
||||
setErrorMsg(parseApiError(error, 'Ошибка отправки запроса. Проверьте правильность email.'));
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -72,7 +72,7 @@ export function JwtForgotPasswordView() {
|
|||
)}
|
||||
|
||||
{!!errorMsg && (
|
||||
<Alert severity="error" sx={{ mb: 3 }}>
|
||||
<Alert severity="error" sx={{ mb: 3, whiteSpace: 'pre-line' }}>
|
||||
{errorMsg}
|
||||
</Alert>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import { Iconify } from 'src/components/iconify';
|
|||
import { Form, Field } from 'src/components/hook-form';
|
||||
|
||||
import { confirmPasswordReset } from 'src/auth/context/jwt';
|
||||
import { parseApiError } from 'src/utils/parse-api-error';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
|
|
@ -76,8 +77,7 @@ function ResetPasswordContent() {
|
|||
setErrorMsg('');
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
const msg = error?.response?.data?.error?.message || error?.response?.data?.message || error?.response?.data?.detail || 'Не удалось сбросить пароль. Возможно, ссылка устарела — запросите новую.';
|
||||
setErrorMsg(msg);
|
||||
setErrorMsg(parseApiError(error, 'Не удалось сбросить пароль. Возможно, ссылка устарела — запросите новую.'));
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -123,7 +123,7 @@ function ResetPasswordContent() {
|
|||
</Stack>
|
||||
|
||||
{!!errorMsg && (
|
||||
<Alert severity="error" sx={{ mb: 3 }}>
|
||||
<Alert severity="error" sx={{ mb: 3, whiteSpace: 'pre-line' }}>
|
||||
{errorMsg}
|
||||
</Alert>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import { Form, Field } from 'src/components/hook-form';
|
|||
|
||||
import { useAuthContext } from 'src/auth/hooks';
|
||||
import { signInWithPassword } from 'src/auth/context/jwt';
|
||||
import { parseApiError } from 'src/utils/parse-api-error';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
|
|
@ -73,7 +74,7 @@ export function JwtSignInView() {
|
|||
router.replace(returnTo);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
setErrorMsg(error instanceof Error ? error.message : error);
|
||||
setErrorMsg(parseApiError(error, 'Неверный email или пароль'));
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -145,7 +146,7 @@ export function JwtSignInView() {
|
|||
{renderHead}
|
||||
|
||||
{!!errorMsg && (
|
||||
<Alert severity="error" sx={{ mb: 3 }}>
|
||||
<Alert severity="error" sx={{ mb: 3, whiteSpace: 'pre-line' }}>
|
||||
{errorMsg}
|
||||
</Alert>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import { Iconify } from 'src/components/iconify';
|
|||
import { Form, Field } from 'src/components/hook-form';
|
||||
|
||||
import { signUp } from 'src/auth/context/jwt';
|
||||
import { parseApiError } from 'src/utils/parse-api-error';
|
||||
import { useAuthContext } from 'src/auth/hooks';
|
||||
import { searchCities } from 'src/utils/profile-api';
|
||||
|
||||
|
|
@ -178,8 +179,7 @@ export function JwtSignUpView() {
|
|||
router.replace(paths.dashboard.root);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
const msg = error?.response?.data?.error?.message || error?.response?.data?.message || error?.response?.data?.detail || (error instanceof Error ? error.message : 'Ошибка регистрации. Проверьте введённые данные.');
|
||||
setErrorMsg(msg);
|
||||
setErrorMsg(parseApiError(error, 'Ошибка регистрации. Проверьте введённые данные.'));
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -310,7 +310,7 @@ export function JwtSignUpView() {
|
|||
{renderHead}
|
||||
|
||||
{!!errorMsg && (
|
||||
<Alert severity="error" sx={{ mb: 3 }}>
|
||||
<Alert severity="error" sx={{ mb: 3, whiteSpace: 'pre-line' }}>
|
||||
{errorMsg}
|
||||
</Alert>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
/**
|
||||
* Парсит ошибки API и возвращает читаемую строку.
|
||||
*
|
||||
* Форматы бэкенда:
|
||||
* { error: { message: "field: text", details: { field: ["text"] } } }
|
||||
* { message: "text" }
|
||||
* { detail: "text" }
|
||||
*/
|
||||
|
||||
const FIELD_LABELS = {
|
||||
email: 'Email',
|
||||
password: 'Пароль',
|
||||
password_confirm: 'Подтверждение пароля',
|
||||
new_password: 'Новый пароль',
|
||||
new_password_confirm: 'Подтверждение пароля',
|
||||
first_name: 'Имя',
|
||||
last_name: 'Фамилия',
|
||||
city: 'Город',
|
||||
timezone: 'Часовой пояс',
|
||||
token: 'Токен',
|
||||
non_field_errors: '',
|
||||
};
|
||||
|
||||
function label(field) {
|
||||
return FIELD_LABELS[field] ?? field;
|
||||
}
|
||||
|
||||
export function parseApiError(error, fallback = 'Произошла ошибка. Попробуйте ещё раз.') {
|
||||
const data = error?.response?.data;
|
||||
if (!data) return error?.message || fallback;
|
||||
|
||||
// { error: { details: { field: ["msg"] } } }
|
||||
const details = data?.error?.details;
|
||||
if (details && typeof details === 'object') {
|
||||
const lines = Object.entries(details)
|
||||
.flatMap(([field, msgs]) => {
|
||||
const lbl = label(field);
|
||||
const arr = Array.isArray(msgs) ? msgs : [String(msgs)];
|
||||
return arr.map((m) => (lbl ? `${lbl}: ${m}` : m));
|
||||
});
|
||||
if (lines.length) return lines.join('\n');
|
||||
}
|
||||
|
||||
// { error: { message: "field: text" } } — strip "field: " prefix
|
||||
const errMsg = data?.error?.message;
|
||||
if (errMsg) {
|
||||
// Remove leading "fieldname: " pattern
|
||||
return errMsg.replace(/^[\w_]+:\s+/, '');
|
||||
}
|
||||
|
||||
// Flat formats
|
||||
if (data?.message) return data.message;
|
||||
if (data?.detail) return data.detail;
|
||||
if (typeof data === 'string') return data;
|
||||
|
||||
return error?.message || fallback;
|
||||
}
|
||||
Loading…
Reference in New Issue