Fix ban screen
This commit is contained in:
@@ -59,9 +59,15 @@ async def login(request: Request, data: UserLogin, db: DbSession):
|
|||||||
|
|
||||||
# Check if user is banned
|
# Check if user is banned
|
||||||
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(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_403_FORBIDDEN,
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
detail="Your account has been banned",
|
detail=ban_info,
|
||||||
)
|
)
|
||||||
|
|
||||||
# If admin with Telegram linked, require 2FA
|
# If admin with Telegram linked, require 2FA
|
||||||
|
|||||||
@@ -61,7 +61,6 @@ function PublicRoute({ children }: { children: React.ReactNode }) {
|
|||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const banInfo = useAuthStore((state) => state.banInfo)
|
const banInfo = useAuthStore((state) => state.banInfo)
|
||||||
const isAuthenticated = useAuthStore((state) => state.isAuthenticated)
|
|
||||||
const syncUser = useAuthStore((state) => state.syncUser)
|
const syncUser = useAuthStore((state) => state.syncUser)
|
||||||
|
|
||||||
// Sync user data with server on app load
|
// Sync user data with server on app load
|
||||||
@@ -69,8 +68,8 @@ function App() {
|
|||||||
syncUser()
|
syncUser()
|
||||||
}, [syncUser])
|
}, [syncUser])
|
||||||
|
|
||||||
// Show banned screen if user is authenticated and banned
|
// Show banned screen if user is banned (either authenticated or during login attempt)
|
||||||
if (isAuthenticated && banInfo) {
|
if (banInfo) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ToastContainer />
|
<ToastContainer />
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ interface BanInfo {
|
|||||||
|
|
||||||
interface BannedScreenProps {
|
interface BannedScreenProps {
|
||||||
banInfo: BanInfo
|
banInfo: BanInfo
|
||||||
|
onLogout?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatDate(dateStr: string | null) {
|
function formatDate(dateStr: string | null) {
|
||||||
@@ -24,8 +25,9 @@ function formatDate(dateStr: string | null) {
|
|||||||
}) + ' (МСК)'
|
}) + ' (МСК)'
|
||||||
}
|
}
|
||||||
|
|
||||||
export function BannedScreen({ banInfo }: BannedScreenProps) {
|
export function BannedScreen({ banInfo, onLogout }: BannedScreenProps) {
|
||||||
const logout = useAuthStore((state) => state.logout)
|
const storeLogout = useAuthStore((state) => state.logout)
|
||||||
|
const handleLogout = onLogout || storeLogout
|
||||||
|
|
||||||
const bannedAtFormatted = formatDate(banInfo.banned_at)
|
const bannedAtFormatted = formatDate(banInfo.banned_at)
|
||||||
const bannedUntilFormatted = formatDate(banInfo.banned_until)
|
const bannedUntilFormatted = formatDate(banInfo.banned_until)
|
||||||
@@ -112,7 +114,7 @@ export function BannedScreen({ banInfo }: BannedScreenProps) {
|
|||||||
<NeonButton
|
<NeonButton
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
size="lg"
|
size="lg"
|
||||||
onClick={logout}
|
onClick={handleLogout}
|
||||||
icon={<LogOut className="w-5 h-5" />}
|
icon={<LogOut className="w-5 h-5" />}
|
||||||
>
|
>
|
||||||
Выйти из аккаунта
|
Выйти из аккаунта
|
||||||
|
|||||||
@@ -54,7 +54,8 @@ export function LoginPage() {
|
|||||||
|
|
||||||
navigate('/marathons')
|
navigate('/marathons')
|
||||||
} catch {
|
} catch {
|
||||||
setSubmitError(error || 'Ошибка входа')
|
// Error is already set in store by login function
|
||||||
|
// Ban case is handled separately via banInfo state
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ export const useAuthStore = create<AuthState>()(
|
|||||||
banInfo: null,
|
banInfo: null,
|
||||||
|
|
||||||
login: async (data) => {
|
login: async (data) => {
|
||||||
set({ isLoading: true, error: null, pending2FA: null })
|
set({ isLoading: true, error: null, pending2FA: null, banInfo: null })
|
||||||
try {
|
try {
|
||||||
const response = await authApi.login(data)
|
const response = await authApi.login(data)
|
||||||
|
|
||||||
@@ -85,9 +85,34 @@ export const useAuthStore = create<AuthState>()(
|
|||||||
}
|
}
|
||||||
return { requires2FA: false }
|
return { requires2FA: false }
|
||||||
} catch (err: unknown) {
|
} 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({
|
set({
|
||||||
error: error.response?.data?.detail || 'Login failed',
|
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: errorMessage,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
})
|
})
|
||||||
throw err
|
throw err
|
||||||
|
|||||||
Reference in New Issue
Block a user