arm-core/web/src/auth/useMe.ts
Andrey Epifancev d76ca570d8 fix(web): клиент — сетевые/не-JSON ошибки, ошибка входа в prod, мелочи (T13.1–T13.3, T13.5–T13.10)
apiFetch оборачивает сбой сети в ApiError(0, NETWORK) и не-JSON тело 2xx в ApiError(BAD_RESPONSE); AbortError пробрасывается как есть, пустое тело даёт undefined.
В production-ветке входа показываются ошибка загрузки профиля и кнопка «Повторить», форма dev-входа по-прежнему вырезается из бандла литералом process.env.NODE_ENV.
Мелочи: «Сессия истекла» при 401, trim и whitespace-правило для email, центрирование по 100svh, union-типы прав, срез хвостовых слэшей в API_BASE.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 00:39:59 +04:00

66 lines
2.0 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.

import { useCallback, useEffect, useState } from 'react';
import { UNAUTHORIZED_EVENT, api } from '../api/client';
import type { Me } from '../api/types';
import { getToken } from './token';
type MeState = { me: Me | null; loading: boolean; error: string | null; reload: () => void };
/**
* Профиль текущего пользователя (`GET /me`). Запрос уходит только при наличии токена; ответ после размонтирования
* или после повторного `reload` (устаревший) отбрасывается. На событие `armka:unauthorized` профиль сбрасывается.
*/
export function useMe(): MeState {
const [me, setMe] = useState<Me | null>(null);
const [loading, setLoading] = useState<boolean>(() => getToken() !== null);
const [error, setError] = useState<string | null>(null);
const [reloadCount, setReloadCount] = useState(0);
useEffect(() => {
// reloadCount в зависимостях: его смена перезапускает загрузку
void reloadCount;
if (!getToken()) {
setMe(null);
setLoading(false);
return;
}
let cancelled = false;
setLoading(true);
setError(null);
api
.get<Me>('/me')
.then((data) => {
if (!cancelled) {
setMe(data);
}
})
.catch((e: unknown) => {
if (!cancelled) {
setMe(null);
setError(e instanceof Error ? e.message : 'Не удалось загрузить профиль');
}
})
.finally(() => {
if (!cancelled) {
setLoading(false);
}
});
return () => {
cancelled = true;
};
}, [reloadCount]);
useEffect(() => {
const onUnauthorized = () => {
setMe(null);
setLoading(false);
setError('Сессия истекла');
};
window.addEventListener(UNAUTHORIZED_EVENT, onUnauthorized);
return () => window.removeEventListener(UNAUTHORIZED_EVENT, onUnauthorized);
}, []);
const reload = useCallback(() => setReloadCount((n) => n + 1), []);
return { me, loading, error, reload };
}