fix: broken functionality - auth errors, toast logic, role update, routing, step library
- Extract backend error detail in auth store login/register - Fix inverted 4xx toast logic and add 429 rate limit handling - Send account_role field to match backend schema in role update - Use type-aware routing for Repeat Last Session button - Add step library placeholder page and route, remove dot badge Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -25,7 +25,7 @@ export const accountsApi = {
|
|||||||
async updateMemberRole(userId: string, role: string): Promise<AccountMember> {
|
async updateMemberRole(userId: string, role: string): Promise<AccountMember> {
|
||||||
const response = await apiClient.patch<AccountMember>(
|
const response = await apiClient.patch<AccountMember>(
|
||||||
`/accounts/me/members/${userId}/role`,
|
`/accounts/me/members/${userId}/role`,
|
||||||
{ role }
|
{ account_role: role }
|
||||||
)
|
)
|
||||||
return response.data
|
return response.data
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -32,13 +32,15 @@ function handleGlobalError(error: AxiosError) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client errors (4xx)
|
// Rate limit
|
||||||
|
if (status === 429) {
|
||||||
|
toast.error(data?.detail || 'Too many requests — please try again shortly')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client errors (4xx) — show backend detail if present
|
||||||
if (status >= 400 && status < 500) {
|
if (status >= 400 && status < 500) {
|
||||||
const message = data?.detail || 'Invalid request'
|
toast.error(data?.detail || 'Invalid request')
|
||||||
// Only show generic messages - pages handle specific errors
|
|
||||||
if (!data?.detail) {
|
|
||||||
toast.error(message)
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ export function Sidebar() {
|
|||||||
<NavItem href="/my-trees" icon={PenLine} label="Flow Editor" />
|
<NavItem href="/my-trees" icon={PenLine} label="Flow Editor" />
|
||||||
<NavItem href="/sessions" icon={Clock} label="Sessions" badge={activeSessionCount || undefined} />
|
<NavItem href="/sessions" icon={Clock} label="Sessions" badge={activeSessionCount || undefined} />
|
||||||
<NavItem href="/shares" icon={FileText} label="Exports" />
|
<NavItem href="/shares" icon={FileText} label="Exports" />
|
||||||
<NavItem href="/step-library" icon={Bookmark} label="Step Library" badge="dot" />
|
<NavItem href="/step-library" icon={Bookmark} label="Step Library" />
|
||||||
<NavItem href="/analytics" icon={BarChart3} label="Analytics" />
|
<NavItem href="/analytics" icon={BarChart3} label="Analytics" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
25
frontend/src/pages/StepLibraryPage.tsx
Normal file
25
frontend/src/pages/StepLibraryPage.tsx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { Bookmark } from 'lucide-react'
|
||||||
|
|
||||||
|
export default function StepLibraryPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-6 sm:px-6 sm:py-8">
|
||||||
|
<div className="mb-8">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<span title="Step Library"><Bookmark className="h-8 w-8 text-muted-foreground" /></span>
|
||||||
|
<h1 className="text-2xl font-bold font-heading text-foreground sm:text-3xl">Step Library</h1>
|
||||||
|
</div>
|
||||||
|
<p className="mt-2 text-muted-foreground">Reusable steps for your flows — coming soon.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col items-center justify-center py-16 text-center">
|
||||||
|
<div className="rounded-full bg-primary/10 p-4 mb-4">
|
||||||
|
<Bookmark className="h-8 w-8 text-primary" />
|
||||||
|
</div>
|
||||||
|
<h2 className="text-lg font-semibold text-foreground mb-2">Coming Soon</h2>
|
||||||
|
<p className="max-w-md text-sm text-muted-foreground">
|
||||||
|
The Step Library will let you create, share, and reuse common troubleshooting steps across all your flows.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -75,7 +75,7 @@ export function TreeLibraryPage() {
|
|||||||
const lastSessionData = (() => {
|
const lastSessionData = (() => {
|
||||||
const raw = safeGetItem('last-session')
|
const raw = safeGetItem('last-session')
|
||||||
if (!raw) return null
|
if (!raw) return null
|
||||||
try { return JSON.parse(raw) as { tree_id: string; tree_name: string; client_name: string; ticket_number: string } }
|
try { return JSON.parse(raw) as { tree_id: string; tree_name: string; client_name: string; ticket_number: string; tree_type?: string } }
|
||||||
catch { return null }
|
catch { return null }
|
||||||
})()
|
})()
|
||||||
|
|
||||||
@@ -450,7 +450,7 @@ export function TreeLibraryPage() {
|
|||||||
{lastSessionData && (
|
{lastSessionData && (
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate(`/trees/${lastSessionData.tree_id}/navigate`, {
|
onClick={() => navigate(getSessionResumePath(lastSessionData.tree_id, lastSessionData.tree_type), {
|
||||||
state: { prefillClientName: lastSessionData.client_name, prefillTicketNumber: lastSessionData.ticket_number },
|
state: { prefillClientName: lastSessionData.client_name, prefillTicketNumber: lastSessionData.ticket_number },
|
||||||
})}
|
})}
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ const MySharesPage = lazy(() => import('@/pages/MySharesPage'))
|
|||||||
const TeamAnalyticsPage = lazy(() => import('@/pages/TeamAnalyticsPage'))
|
const TeamAnalyticsPage = lazy(() => import('@/pages/TeamAnalyticsPage'))
|
||||||
const MyAnalyticsPage = lazy(() => import('@/pages/MyAnalyticsPage'))
|
const MyAnalyticsPage = lazy(() => import('@/pages/MyAnalyticsPage'))
|
||||||
const FeedbackPage = lazy(() => import('@/pages/FeedbackPage'))
|
const FeedbackPage = lazy(() => import('@/pages/FeedbackPage'))
|
||||||
|
const StepLibraryPage = lazy(() => import('@/pages/StepLibraryPage'))
|
||||||
const AccountSettingsPage = lazy(() => import('@/pages/AccountSettingsPage'))
|
const AccountSettingsPage = lazy(() => import('@/pages/AccountSettingsPage'))
|
||||||
// Admin pages
|
// Admin pages
|
||||||
const AdminLayout = lazy(() => import('@/components/admin/AdminLayout'))
|
const AdminLayout = lazy(() => import('@/components/admin/AdminLayout'))
|
||||||
@@ -235,6 +236,14 @@ export const router = createBrowserRouter([
|
|||||||
</Suspense>
|
</Suspense>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'step-library',
|
||||||
|
element: (
|
||||||
|
<Suspense fallback={<PageLoader />}>
|
||||||
|
<StepLibraryPage />
|
||||||
|
</Suspense>
|
||||||
|
),
|
||||||
|
},
|
||||||
// Admin routes
|
// Admin routes
|
||||||
{
|
{
|
||||||
path: 'admin',
|
path: 'admin',
|
||||||
|
|||||||
@@ -48,7 +48,8 @@ export const useAuthStore = create<AuthState>()(
|
|||||||
// Fetch user info
|
// Fetch user info
|
||||||
await get().fetchUser()
|
await get().fetchUser()
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
const message = error instanceof Error ? error.message : 'Login failed'
|
const axiosErr = error as { response?: { data?: { detail?: string } } }
|
||||||
|
const message = axiosErr.response?.data?.detail || (error instanceof Error ? error.message : 'Login failed')
|
||||||
set({ error: message, isLoading: false })
|
set({ error: message, isLoading: false })
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
@@ -61,7 +62,8 @@ export const useAuthStore = create<AuthState>()(
|
|||||||
// After registration, log the user in
|
// After registration, log the user in
|
||||||
await get().login({ email: data.email, password: data.password })
|
await get().login({ email: data.email, password: data.password })
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
const message = error instanceof Error ? error.message : 'Registration failed'
|
const axiosErr = error as { response?: { data?: { detail?: string } } }
|
||||||
|
const message = axiosErr.response?.data?.detail || (error instanceof Error ? error.message : 'Registration failed')
|
||||||
set({ error: message, isLoading: false })
|
set({ error: message, isLoading: false })
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user