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