fixed error display in login and register, added log out button
This commit is contained in:
@@ -106,7 +106,14 @@ router.post(
|
||||
console.error("[UserController] login-v2 error:", err.message);
|
||||
|
||||
// Handle specific error codes
|
||||
if (err.code === "USER_NOT_FOUND" || err.code === "INVALID_CREDENTIALS") {
|
||||
if (err.code === "USER_NOT_FOUND") {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
msg: "User not found. Please sign up for an account.",
|
||||
});
|
||||
}
|
||||
|
||||
if (err.code === "INVALID_CREDENTIALS") {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
msg: "Invalid email or password",
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useEffect } from 'react'
|
||||
import { Outlet, NavLink, useNavigate, useLocation } from 'react-router-dom'
|
||||
import { useControlSocket } from '@/hooks/useControlSocket'
|
||||
import { useAgentControlStore } from '@/stores/agentControlStore'
|
||||
import { useUserStore } from '@/stores/userStore'
|
||||
import { useUserStore, type UserState } from '@/stores/userStore'
|
||||
import { useSidebarCollapsed } from '@/hooks/usePersistedSettings'
|
||||
import { NotificationBell } from './shared/NotificationBell'
|
||||
import {
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
PanelLeft,
|
||||
Settings,
|
||||
Sparkles,
|
||||
LogOut,
|
||||
HelpCircle,
|
||||
ExternalLink,
|
||||
FileText,
|
||||
@@ -53,8 +54,10 @@ const navItems = [
|
||||
export function AgentControlLayout() {
|
||||
const { connect, disconnect, isConnected } = useControlSocket()
|
||||
const hasActiveAgents = useAgentControlStore((state) => state.eventsBuffer.length > 0)
|
||||
const user = useUserStore((state) => state.user)
|
||||
const fullName = useUserStore((state) => state.fullName())
|
||||
const user = useUserStore((state: UserState) => state.user)
|
||||
const fullName = useUserStore((state: UserState) => state.fullName())
|
||||
const signOut = useUserStore((state: UserState) => state.signOut)
|
||||
const isLoggingOut = useUserStore((state: UserState) => state.isLoggingOut)
|
||||
const navigate = useNavigate()
|
||||
const location = useLocation()
|
||||
const { sidebarCollapsed, toggleSidebar } = useSidebarCollapsed()
|
||||
@@ -178,7 +181,8 @@ export function AgentControlLayout() {
|
||||
</nav>
|
||||
</TooltipProvider>
|
||||
|
||||
{/* User Profile Section */}
|
||||
{/* User Profile Section - hidden during logout to prevent red avatar flash */}
|
||||
{!isLoggingOut && (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<div
|
||||
@@ -206,7 +210,7 @@ export function AgentControlLayout() {
|
||||
</div>
|
||||
</div>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent side="top" align="start" alignOffset={12} className="w-48">
|
||||
<DropdownMenuContent side="top" align="start" alignOffset={12} className="w-40">
|
||||
<DropdownMenuItem
|
||||
onClick={() => navigate(`${location.pathname}#settings`)}
|
||||
className="cursor-pointer"
|
||||
@@ -221,8 +225,16 @@ export function AgentControlLayout() {
|
||||
<Sparkles className="mr-2 h-4 w-4" />
|
||||
Upgrade
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => signOut()}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<LogOut className="mr-2 h-4 w-4" />
|
||||
Log out
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
</aside>
|
||||
|
||||
{/* Right side - header bar + content */}
|
||||
@@ -231,7 +243,8 @@ export function AgentControlLayout() {
|
||||
<header className="h-14 flex items-center justify-end gap-2 px-4 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
||||
<LiveIndicator isLive={hasActiveAgents} />
|
||||
|
||||
{/* Connection status */}
|
||||
{/* Connection status - hidden during logout to prevent red flash */}
|
||||
{!isLoggingOut && (
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center gap-1.5 text-xs px-2 py-1 rounded-full',
|
||||
@@ -248,6 +261,7 @@ export function AgentControlLayout() {
|
||||
/>
|
||||
{isConnected ? 'Connected' : 'Disconnected'}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<NotificationBell />
|
||||
|
||||
|
||||
@@ -18,6 +18,16 @@ class ApiClient {
|
||||
this.baseUrl = baseUrl
|
||||
}
|
||||
|
||||
private async parseErrorMessage(response: Response): Promise<string> {
|
||||
const text = await response.text()
|
||||
try {
|
||||
const json = JSON.parse(text)
|
||||
return json.msg || json.message || text
|
||||
} catch {
|
||||
return text
|
||||
}
|
||||
}
|
||||
|
||||
private getHeaders(): HeadersInit {
|
||||
const headers: HeadersInit = {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -34,7 +44,7 @@ class ApiClient {
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new ApiError(response.status, await response.text())
|
||||
throw new ApiError(response.status, await this.parseErrorMessage(response))
|
||||
}
|
||||
|
||||
return response.json()
|
||||
@@ -48,7 +58,7 @@ class ApiClient {
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new ApiError(response.status, await response.text())
|
||||
throw new ApiError(response.status, await this.parseErrorMessage(response))
|
||||
}
|
||||
|
||||
return response.json()
|
||||
@@ -62,7 +72,7 @@ class ApiClient {
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new ApiError(response.status, await response.text())
|
||||
throw new ApiError(response.status, await this.parseErrorMessage(response))
|
||||
}
|
||||
|
||||
return response.json()
|
||||
@@ -75,7 +85,7 @@ class ApiClient {
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new ApiError(response.status, await response.text())
|
||||
throw new ApiError(response.status, await this.parseErrorMessage(response))
|
||||
}
|
||||
|
||||
return response.json()
|
||||
|
||||
@@ -3,12 +3,13 @@ import type { User, Organization } from '@/types/user'
|
||||
import * as userApi from '@/services/userApi'
|
||||
import * as orgApi from '@/services/orgApi'
|
||||
|
||||
interface UserState {
|
||||
export interface UserState {
|
||||
user: User | null
|
||||
roleId: number | null
|
||||
org: Organization | null
|
||||
orgLogo: string | null
|
||||
isLoading: boolean
|
||||
isLoggingOut: boolean
|
||||
|
||||
// Actions
|
||||
setUser: (user: User) => void
|
||||
@@ -28,6 +29,7 @@ export const useUserStore = create<UserState>((set, get) => ({
|
||||
org: null,
|
||||
orgLogo: null,
|
||||
isLoading: false,
|
||||
isLoggingOut: false,
|
||||
|
||||
setUser: (user) => set({ user }),
|
||||
|
||||
@@ -85,6 +87,9 @@ export const useUserStore = create<UserState>((set, get) => ({
|
||||
},
|
||||
|
||||
signOut: (redirectUrl) => {
|
||||
// Set logging out flag FIRST - prevents flash of error states
|
||||
set({ isLoggingOut: true })
|
||||
|
||||
// Clear storage
|
||||
localStorage.removeItem('token')
|
||||
localStorage.removeItem('context_session_id')
|
||||
|
||||
Reference in New Issue
Block a user