diff --git a/backend/app/api/v1/auth.py b/backend/app/api/v1/auth.py index fe1eac8..54612b1 100644 --- a/backend/app/api/v1/auth.py +++ b/backend/app/api/v1/auth.py @@ -59,9 +59,15 @@ async def login(request: Request, data: UserLogin, db: DbSession): # Check if user is banned if user.is_banned: + # Return full ban info like in deps.py + ban_info = { + "banned_at": user.banned_at.isoformat() if user.banned_at else None, + "banned_until": user.banned_until.isoformat() if user.banned_until else None, + "reason": user.ban_reason, + } raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, - detail="Your account has been banned", + detail=ban_info, ) # If admin with Telegram linked, require 2FA diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 407cda1..5c990ed 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -61,7 +61,6 @@ function PublicRoute({ children }: { children: React.ReactNode }) { function App() { const banInfo = useAuthStore((state) => state.banInfo) - const isAuthenticated = useAuthStore((state) => state.isAuthenticated) const syncUser = useAuthStore((state) => state.syncUser) // Sync user data with server on app load @@ -69,8 +68,8 @@ function App() { syncUser() }, [syncUser]) - // Show banned screen if user is authenticated and banned - if (isAuthenticated && banInfo) { + // Show banned screen if user is banned (either authenticated or during login attempt) + if (banInfo) { return ( <> diff --git a/frontend/src/components/BannedScreen.tsx b/frontend/src/components/BannedScreen.tsx index 0758bbd..1ab7bb9 100644 --- a/frontend/src/components/BannedScreen.tsx +++ b/frontend/src/components/BannedScreen.tsx @@ -10,6 +10,7 @@ interface BanInfo { interface BannedScreenProps { banInfo: BanInfo + onLogout?: () => void } function formatDate(dateStr: string | null) { @@ -24,8 +25,9 @@ function formatDate(dateStr: string | null) { }) + ' (МСК)' } -export function BannedScreen({ banInfo }: BannedScreenProps) { - const logout = useAuthStore((state) => state.logout) +export function BannedScreen({ banInfo, onLogout }: BannedScreenProps) { + const storeLogout = useAuthStore((state) => state.logout) + const handleLogout = onLogout || storeLogout const bannedAtFormatted = formatDate(banInfo.banned_at) const bannedUntilFormatted = formatDate(banInfo.banned_until) @@ -112,7 +114,7 @@ export function BannedScreen({ banInfo }: BannedScreenProps) { } > Выйти из аккаунта diff --git a/frontend/src/pages/LoginPage.tsx b/frontend/src/pages/LoginPage.tsx index 0a62fe5..64e6bbc 100644 --- a/frontend/src/pages/LoginPage.tsx +++ b/frontend/src/pages/LoginPage.tsx @@ -54,7 +54,8 @@ export function LoginPage() { navigate('/marathons') } catch { - setSubmitError(error || 'Ошибка входа') + // Error is already set in store by login function + // Ban case is handled separately via banInfo state } } diff --git a/frontend/src/store/auth.ts b/frontend/src/store/auth.ts index 06cd4e2..fe62254 100644 --- a/frontend/src/store/auth.ts +++ b/frontend/src/store/auth.ts @@ -60,7 +60,7 @@ export const useAuthStore = create()( banInfo: null, login: async (data) => { - set({ isLoading: true, error: null, pending2FA: null }) + set({ isLoading: true, error: null, pending2FA: null, banInfo: null }) try { const response = await authApi.login(data) @@ -85,9 +85,34 @@ export const useAuthStore = create()( } return { requires2FA: false } } catch (err: unknown) { - const error = err as { response?: { data?: { detail?: string } } } + const error = err as { response?: { status?: number; data?: { detail?: string | BanInfo } } } + + // Check if user is banned (403 with ban info) + if (error.response?.status === 403) { + const detail = error.response?.data?.detail + if (typeof detail === 'object' && detail !== null && 'banned_at' in detail) { + set({ + banInfo: detail as BanInfo, + isLoading: false, + error: null, + }) + throw err + } + } + + // Regular error - translate common messages + let errorMessage = 'Ошибка входа' + const detail = error.response?.data?.detail + if (typeof detail === 'string') { + if (detail === 'Incorrect login or password') { + errorMessage = 'Неверный логин или пароль' + } else { + errorMessage = detail + } + } + set({ - error: error.response?.data?.detail || 'Login failed', + error: errorMessage, isLoading: false, }) throw err