Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
new-api
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Unverified
Commit
521cebf5
authored
Sep 06, 2026
by
CaIon
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix(dashboard): simplify completed setup guide
parent
3f8a50cf
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
461 additions
and
181 deletions
+461
-181
web/src/features/dashboard/components/overview/__tests__/setup-guide.test.tsx
+241
-0
web/src/features/dashboard/components/overview/overview-dashboard.tsx
+196
-158
web/src/features/dashboard/index.tsx
+24
-23
No files found.
web/src/features/dashboard/components/overview/__tests__/setup-guide.test.tsx
0 → 100644
View file @
521cebf5
/*
Copyright (C) 2023-2026 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import
{
QueryClient
,
QueryClientProvider
}
from
'@tanstack/react-query'
import
{
createMemoryHistory
,
createRootRoute
,
createRouter
,
RouterProvider
,
}
from
'@tanstack/react-router'
import
{
act
,
cleanup
,
render
,
screen
,
waitFor
}
from
'@testing-library/react'
import
userEvent
from
'@testing-library/user-event'
import
{
afterEach
,
beforeEach
,
describe
,
expect
,
it
,
vi
}
from
'vitest'
import
{
api
}
from
'@/lib/api'
import
{
useAuthStore
}
from
'@/stores/auth-store'
import
{
useSystemConfigStore
}
from
'@/stores/system-config-store'
import
{
OverviewDashboard
}
from
'../overview-dashboard'
const
storageKey
=
'dashboard_overview_setup_guide_expanded'
let
client
:
QueryClient
let
keyLookupError
:
Error
|
null
beforeEach
(()
=>
{
window
.
localStorage
.
clear
()
useSystemConfigStore
.
setState
(
useSystemConfigStore
.
getInitialState
(),
true
)
useAuthStore
.
getState
().
auth
.
setUser
({
id
:
1
,
username
:
'dashboard-user'
,
role
:
1
,
quota
:
1000000
,
used_quota
:
1000
,
request_count
:
1
,
})
client
=
new
QueryClient
({
defaultOptions
:
{
queries
:
{
retry
:
false
}
},
})
keyLookupError
=
null
vi
.
spyOn
(
api
,
'get'
).
mockImplementation
(
async
(
url
)
=>
{
switch
(
url
)
{
case
'/api/token/?p=1&size=10'
:
if
(
keyLookupError
)
throw
keyLookupError
return
{
data
:
{
success
:
true
,
data
:
{
items
:
[{
id
:
1
,
name
:
'App key'
,
key
:
'masked'
,
status
:
1
}],
},
},
}
case
'/api/status'
:
return
{
data
:
{
data
:
{
api_info_enabled
:
false
,
announcements_enabled
:
false
,
faq_enabled
:
false
,
uptime_kuma_enabled
:
false
,
},
},
}
case
'/api/user/models'
:
return
{
data
:
{
success
:
true
,
data
:
[
'gpt-4o-mini'
]
}
}
case
'/api/data/self'
:
return
{
data
:
{
success
:
true
,
data
:
[]
}
}
default
:
throw
new
Error
(
`Unexpected dashboard request:
${
url
}
`
)
}
})
})
afterEach
(()
=>
{
cleanup
()
client
.
clear
()
useAuthStore
.
setState
(
useAuthStore
.
getInitialState
(),
true
)
useSystemConfigStore
.
setState
(
useSystemConfigStore
.
getInitialState
(),
true
)
window
.
localStorage
.
clear
()
})
async
function
renderOverview
()
{
const
router
=
createRouter
({
routeTree
:
createRootRoute
({
component
:
OverviewDashboard
}),
history
:
createMemoryHistory
({
initialEntries
:
[
'/'
]
}),
})
await
router
.
load
()
return
render
(
<
QueryClientProvider
client=
{
client
}
>
<
RouterProvider
router=
{
router
}
/>
</
QueryClientProvider
>
)
}
describe
(
'overview setup guide'
,
()
=>
{
it
(
'shows usage first and only a header entry when setup is complete'
,
async
()
=>
{
await
renderOverview
()
const
toggle
=
await
screen
.
findByRole
(
'button'
,
{
name
:
'Setup guide'
})
expect
(
toggle
).
toHaveAttribute
(
'aria-expanded'
,
'false'
)
expect
(
screen
.
getAllByRole
(
'heading'
).
map
((
heading
)
=>
heading
.
textContent
)
).
toEqual
([
'Overview'
,
'Usage at a glance'
])
expect
(
screen
.
queryByText
(
'Setup guide complete'
)).
not
.
toBeInTheDocument
()
expect
(
screen
.
queryByText
(
'Setup progress: 3/3'
)).
not
.
toBeInTheDocument
()
for
(
const
name
of
[
'API Keys'
,
'Channels'
,
'Usage Logs'
,
'Pricing'
])
{
expect
(
screen
.
queryByRole
(
'button'
,
{
name
})).
not
.
toBeInTheDocument
()
}
const
panel
=
document
.
getElementById
(
toggle
.
getAttribute
(
'aria-controls'
)
??
''
)
expect
(
panel
).
toBeInTheDocument
()
expect
(
panel
).
not
.
toBeVisible
()
})
it
(
'toggles the completed guide with the keyboard and restores focus after hiding it'
,
async
()
=>
{
const
user
=
userEvent
.
setup
()
await
renderOverview
()
const
toggle
=
await
screen
.
findByRole
(
'button'
,
{
name
:
'Setup guide'
})
await
user
.
tab
()
expect
(
toggle
).
toHaveFocus
()
await
user
.
keyboard
(
'{Enter}'
)
expect
(
toggle
).
toHaveAttribute
(
'aria-expanded'
,
'true'
)
expect
(
document
.
getElementById
(
toggle
.
getAttribute
(
'aria-controls'
)
??
''
)
).
toBeVisible
()
expect
(
screen
.
getByRole
(
'heading'
,
{
name
:
'Build on your API gateway in minutes'
,
})
).
toBeVisible
()
await
waitFor
(()
=>
expect
(
screen
.
getByRole
(
'button'
,
{
name
:
/^API Keys/
})).
toBeVisible
()
)
await
user
.
keyboard
(
' '
)
expect
(
toggle
).
toHaveAttribute
(
'aria-expanded'
,
'false'
)
expect
(
toggle
).
toHaveFocus
()
await
user
.
click
(
toggle
)
await
user
.
click
(
screen
.
getByRole
(
'button'
,
{
name
:
'Hide setup guide'
}))
expect
(
toggle
).
toHaveAttribute
(
'aria-expanded'
,
'false'
)
expect
(
toggle
).
toHaveFocus
()
})
it
(
'restores the completed guide preference after remounting'
,
async
()
=>
{
const
user
=
userEvent
.
setup
()
const
first
=
await
renderOverview
()
await
user
.
click
(
await
screen
.
findByRole
(
'button'
,
{
name
:
'Setup guide'
}))
first
.
unmount
()
const
second
=
await
renderOverview
()
expect
(
await
screen
.
findByRole
(
'button'
,
{
name
:
'Setup guide'
})
).
toHaveAttribute
(
'aria-expanded'
,
'true'
)
await
user
.
click
(
screen
.
getByRole
(
'button'
,
{
name
:
'Hide setup guide'
}))
second
.
unmount
()
await
renderOverview
()
expect
(
await
screen
.
findByRole
(
'button'
,
{
name
:
'Setup guide'
})
).
toHaveAttribute
(
'aria-expanded'
,
'false'
)
expect
(
screen
.
queryByText
(
'Setup guide complete'
)).
not
.
toBeInTheDocument
()
})
it
(
'keeps the existing progress banner when an incomplete guide is manually collapsed'
,
async
()
=>
{
const
user
=
userEvent
.
setup
()
useAuthStore
.
getState
()
.
auth
.
setUser
({
id
:
1
,
username
:
'new-user'
,
role
:
1
})
await
renderOverview
()
await
user
.
click
(
await
screen
.
findByRole
(
'button'
,
{
name
:
'Hide setup guide'
})
)
expect
(
screen
.
getByText
(
'Setup progress: 1/3'
)).
toBeVisible
()
expect
(
screen
.
getByText
(
'Setup guide is collapsed. Expand it anytime.'
)
).
toBeVisible
()
expect
(
screen
.
getByRole
(
'button'
,
{
name
:
'API Keys'
})).
toBeVisible
()
expect
(
screen
.
queryByRole
(
'button'
,
{
name
:
'Setup guide'
})
).
not
.
toBeInTheDocument
()
await
user
.
click
(
screen
.
getByRole
(
'button'
,
{
name
:
'Show setup guide'
}))
expect
(
screen
.
getByRole
(
'button'
,
{
name
:
'Hide setup guide'
})
).
toBeVisible
()
})
it
(
'removes the collapsed progress banner when the remaining setup step completes'
,
async
()
=>
{
useAuthStore
.
getState
().
auth
.
setUser
({
id
:
1
,
username
:
'dashboard-user'
,
role
:
1
,
quota
:
1000000
,
})
window
.
localStorage
.
setItem
(
storageKey
,
'collapsed'
)
await
renderOverview
()
expect
(
await
screen
.
findByText
(
'Setup progress: 2/3'
)).
toBeVisible
()
act
(()
=>
{
useAuthStore
.
getState
().
auth
.
setUser
({
id
:
1
,
username
:
'dashboard-user'
,
role
:
1
,
quota
:
1000000
,
request_count
:
1
,
})
})
expect
(
await
screen
.
findByRole
(
'button'
,
{
name
:
'Setup guide'
})
).
toHaveAttribute
(
'aria-expanded'
,
'false'
)
expect
(
screen
.
queryByText
(
/Setup progress:/
)).
not
.
toBeInTheDocument
()
})
it
(
'does not show a completed setup entry when the key lookup fails'
,
async
()
=>
{
keyLookupError
=
new
Error
(
'Key lookup unavailable'
)
await
renderOverview
()
expect
(
await
screen
.
findByRole
(
'button'
,
{
name
:
'Hide setup guide'
})
).
toBeVisible
()
expect
(
screen
.
queryByRole
(
'button'
,
{
name
:
'Setup guide'
})
).
not
.
toBeInTheDocument
()
})
})
web/src/features/dashboard/components/overview/overview-dashboard.tsx
View file @
521cebf5
...
...
@@ -37,10 +37,11 @@ import {
type
LucideIcon
,
}
from
'lucide-react'
import
{
motion
,
useReducedMotion
}
from
'motion/react'
import
{
use
Memo
,
useState
}
from
'react'
import
{
use
Id
,
useMemo
,
useRef
,
useState
}
from
'react'
import
{
useTranslation
}
from
'react-i18next'
import
{
toast
}
from
'sonner'
import
{
SectionPageLayout
}
from
'@/components/layout'
import
{
CardStaggerContainer
,
CardStaggerItem
,
...
...
@@ -457,6 +458,8 @@ function CompactQuickAction(props: { action: QuickAction }) {
export
function
OverviewDashboard
()
{
const
{
t
}
=
useTranslation
()
const
setupGuideId
=
useId
()
const
setupGuideToggleRef
=
useRef
<
HTMLButtonElement
>
(
null
)
const
user
=
useAuthStore
((
state
)
=>
state
.
auth
.
user
)
const
{
items
:
apiInfoItems
}
=
useApiInfo
()
const
{
...
...
@@ -615,188 +618,223 @@ export function OverviewDashboard() {
const
nextExpanded
=
!
setupGuideExpanded
setManualSetupGuideExpanded
(
nextExpanded
)
saveSetupGuideExpanded
(
nextExpanded
)
if
(
!
nextExpanded
&&
setupComplete
)
{
setupGuideToggleRef
.
current
?.
focus
()
}
}
return
(
<
div
className=
'flex flex-col gap-4'
>
{
setupGuideExpanded
?
(
<
CardStaggerContainer
className=
'grid items-stretch gap-4 xl:grid-cols-[minmax(0,1fr)_22rem]'
>
<
CardStaggerItem
className=
'bg-card h-full overflow-hidden rounded-2xl border shadow-xs'
>
<
div
className=
'relative h-full overflow-hidden p-4 sm:p-5'
>
<
SetupGuideBackdrop
/>
<
div
className=
'relative grid gap-5 lg:grid-cols-[minmax(0,1fr)_21rem]'
>
<
div
className=
'flex min-w-0 flex-col gap-5'
>
<
div
className=
'flex flex-wrap items-start justify-between gap-3'
>
<
div
className=
'flex max-w-2xl flex-col gap-1'
>
<
div
className=
'text-muted-foreground flex items-center gap-2 text-xs font-medium tracking-wider uppercase'
>
<
ListChecks
className=
'size-3.5'
aria
-
hidden=
'true'
/>
{
t
(
'Get started'
)
}
<
SectionPageLayout
>
<
SectionPageLayout
.
Title
>
{
t
(
'Overview'
)
}
</
SectionPageLayout
.
Title
>
<
SectionPageLayout
.
Actions
>
{
setupStatusReady
&&
setupComplete
&&
(
<
Button
ref=
{
setupGuideToggleRef
}
variant=
'ghost'
size=
'sm'
className=
'text-muted-foreground hover:text-foreground h-auto min-h-7 max-w-[60vw] whitespace-normal'
aria
-
expanded=
{
setupGuideExpanded
}
aria
-
controls=
{
setupGuideId
}
onClick=
{
handleSetupGuideToggle
}
>
{
t
(
'Setup guide'
)
}
</
Button
>
)
}
</
SectionPageLayout
.
Actions
>
<
SectionPageLayout
.
Content
>
<
div
className=
'flex flex-col gap-4'
>
<
div
id=
{
setupGuideId
}
hidden=
{
!
setupGuideExpanded
}
>
{
setupGuideExpanded
&&
(
<
CardStaggerContainer
className=
'grid items-stretch gap-4 xl:grid-cols-[minmax(0,1fr)_22rem]'
>
<
CardStaggerItem
className=
'bg-card h-full overflow-hidden rounded-2xl border shadow-xs'
>
<
div
className=
'relative h-full overflow-hidden p-4 sm:p-5'
>
<
SetupGuideBackdrop
/>
<
div
className=
'relative grid gap-5 lg:grid-cols-[minmax(0,1fr)_21rem]'
>
<
div
className=
'flex min-w-0 flex-col gap-5'
>
<
div
className=
'flex flex-wrap items-start justify-between gap-3'
>
<
div
className=
'flex max-w-2xl flex-col gap-1'
>
<
div
className=
'text-muted-foreground flex items-center gap-2 text-xs font-medium tracking-wider uppercase'
>
<
ListChecks
className=
'size-3.5'
aria
-
hidden=
'true'
/>
{
t
(
'Get started'
)
}
</
div
>
<
h3
className=
'text-xl font-semibold tracking-tight sm:text-2xl'
>
{
t
(
'Build on your API gateway in minutes'
)
}
</
h3
>
<
p
className=
'text-muted-foreground max-w-xl text-sm leading-relaxed'
>
{
t
(
'A focused home for keys, balance, routing, and service health.'
)
}
</
p
>
</
div
>
<
div
className=
'flex flex-wrap items-center gap-2'
>
<
Button
variant=
'outline'
size=
'sm'
aria
-
expanded=
{
setupGuideExpanded
}
aria
-
controls=
{
setupGuideId
}
onClick=
{
handleSetupGuideToggle
}
>
<
ChevronUp
data
-
icon=
'inline-start'
/>
{
t
(
'Hide setup guide'
)
}
</
Button
>
<
Button
size=
'sm'
render=
{
<
Link
to=
'/keys'
/>
}
>
<
KeyRound
data
-
icon=
'inline-start'
/>
{
t
(
'Create API Key'
)
}
</
Button
>
</
div
>
</
div
>
<
ol
className=
'bg-background/45 rounded-2xl border p-2 backdrop-blur'
>
{
startSteps
.
map
((
step
,
index
)
=>
(
<
StartStepItem
key=
{
step
.
title
}
step=
{
step
}
index=
{
index
}
isLast=
{
index
===
startSteps
.
length
-
1
}
/>
))
}
</
ol
>
</
div
>
<
RequestPreview
example=
{
requestExample
}
signals=
{
heroSignals
}
/>
</
div
>
</
div
>
</
CardStaggerItem
>
<
CardStaggerItem
className=
'bg-card h-full rounded-2xl border p-4 shadow-xs sm:p-5'
>
<
div
className=
'flex h-full flex-col gap-4'
>
<
div
className=
'flex flex-col gap-1'
>
<
div
className=
'text-muted-foreground text-xs font-medium tracking-wider uppercase'
>
{
t
(
'Recommended actions'
)
}
</
div
>
<
h3
className=
'text-
xl font-semibold tracking-tight sm:text-2xl
'
>
{
t
(
'
Build on your API gateway in minutes
'
)
}
<
h3
className=
'text-
lg font-semibold tracking-tight
'
>
{
t
(
'
Keep the platform ready
'
)
}
</
h3
>
<
p
className=
'text-muted-foreground max-w-xl text-sm leading-relaxed'
>
{
t
(
'A focused home for keys, balance, routing, and service health.'
)
}
</
p
>
</
div
>
<
div
className=
'grid gap-2'
>
{
visibleQuickActions
.
map
((
action
)
=>
(
<
QuickActionItem
key=
{
action
.
title
}
action=
{
action
}
/>
))
}
</
div
>
</
div
>
</
CardStaggerItem
>
</
CardStaggerContainer
>
)
}
</
div
>
{
!
setupGuideExpanded
&&
!
setupComplete
&&
(
<
CardStaggerContainer
>
<
CardStaggerItem
className=
'bg-card overflow-hidden rounded-2xl border shadow-xs'
>
<
div
className=
'relative overflow-hidden px-4 py-3 sm:px-5'
>
<
SetupGuideBackdrop
compact
/>
<
div
className=
'relative flex flex-wrap items-center justify-between gap-3'
>
<
div
className=
'flex min-w-0 items-center gap-3'
>
<
span
className=
'bg-background/70 flex size-9 shrink-0 items-center justify-center rounded-xl border shadow-xs'
>
<
Check
className=
'text-success size-4'
aria
-
hidden=
'true'
/>
</
span
>
<
div
className=
'min-w-0'
>
<
div
className=
'flex items-center gap-2'
>
<
h3
className=
'truncate text-sm font-semibold'
>
{
t
(
'Setup guide'
)
}
</
h3
>
<
span
className=
'text-muted-foreground bg-background/60 rounded-md border px-2 py-0.5 text-xs'
>
{
t
(
'
Setup
progress
:
{{
completed
}}
/{{total}}', {
completed
:
completedStepCount
,
total
:
startSteps
.
length
,
})
}
</
span
>
</
div
>
<
p
className=
'text-muted-foreground line-clamp-1 text-xs'
>
{
t
(
'Setup guide is collapsed. Expand it anytime.'
)
}
</
p
>
</
div
>
</
div
>
<
div
className=
'flex flex-wrap items-center gap-2'
>
{
visibleQuickActions
.
map
((
action
)
=>
(
<
CompactQuickAction
key=
{
action
.
title
}
action=
{
action
}
/>
))
}
<
Button
variant=
'outline'
size=
'sm'
className=
'bg-background/70 h-8 min-w-28'
aria
-
expanded=
{
setupGuideExpanded
}
aria
-
controls=
{
setupGuideId
}
onClick=
{
handleSetupGuideToggle
}
>
<
ChevronUp
data
-
icon=
'inline-start'
/>
{
t
(
'Hide setup guide'
)
}
</
Button
>
<
Button
size=
'sm'
render=
{
<
Link
to=
'/keys'
/>
}
>
<
KeyRound
data
-
icon=
'inline-start'
/>
{
t
(
'Create API Key'
)
}
<
ChevronDown
data
-
icon=
'inline-start'
/>
{
t
(
'Show setup guide'
)
}
</
Button
>
</
div
>
</
div
>
<
ol
className=
'bg-background/45 rounded-2xl border p-2 backdrop-blur'
>
{
startSteps
.
map
((
step
,
index
)
=>
(
<
StartStepItem
key=
{
step
.
title
}
step=
{
step
}
index=
{
index
}
isLast=
{
index
===
startSteps
.
length
-
1
}
/>
))
}
</
ol
>
</
div
>
<
RequestPreview
example=
{
requestExample
}
signals=
{
heroSignals
}
/>
</
div
>
</
div
>
</
CardStaggerItem
>
<
CardStaggerItem
className=
'bg-card h-full rounded-2xl border p-4 shadow-xs sm:p-5'
>
<
div
className=
'flex h-full flex-col gap-4'
>
<
div
className=
'flex flex-col gap-1'
>
<
div
className=
'text-muted-foreground text-xs font-medium tracking-wider uppercase'
>
{
t
(
'Recommended actions'
)
}
</
div
>
<
h3
className=
'text-lg font-semibold tracking-tight'
>
{
t
(
'Keep the platform ready'
)
}
</
h3
>
</
div
>
<
div
className=
'grid gap-2'
>
{
visibleQuickActions
.
map
((
action
)
=>
(
<
QuickActionItem
key=
{
action
.
title
}
action=
{
action
}
/>
))
}
</
div
>
</
div
>
</
CardStaggerItem
>
</
CardStaggerContainer
>
)
:
(
<
CardStaggerContainer
>
<
CardStaggerItem
className=
'bg-card overflow-hidden rounded-2xl border shadow-xs'
>
<
div
className=
'relative overflow-hidden px-4 py-3 sm:px-5'
>
<
SetupGuideBackdrop
compact
/>
<
div
className=
'relative flex flex-wrap items-center justify-between gap-3'
>
<
div
className=
'flex min-w-0 items-center gap-3'
>
<
span
className=
'bg-background/70 flex size-9 shrink-0 items-center justify-center rounded-xl border shadow-xs'
>
<
Check
className=
'text-success size-4'
aria
-
hidden=
'true'
/>
</
span
>
<
div
className=
'min-w-0'
>
<
div
className=
'flex items-center gap-2'
>
<
h3
className=
'truncate text-sm font-semibold'
>
{
setupComplete
?
t
(
'Setup guide complete'
)
:
t
(
'Setup guide'
)
}
</
h3
>
<
span
className=
'text-muted-foreground bg-background/60 rounded-md border px-2 py-0.5 text-xs'
>
{
t
(
'
Setup
progress
:
{{
completed
}}
/{{total}}', {
completed
:
completedStepCount
,
total
:
startSteps
.
length
,
})
}
</
span
>
</
div
>
<
p
className=
'text-muted-foreground line-clamp-1 text-xs'
>
{
setupComplete
?
t
(
'Your setup guide is collapsed so usage stays in focus.'
)
:
t
(
'Setup guide is collapsed. Expand it anytime.'
)
}
</
p
>
</
div
>
</
div
>
<
div
className=
'flex flex-wrap items-center gap-2'
>
{
visibleQuickActions
.
map
((
action
)
=>
(
<
CompactQuickAction
key=
{
action
.
title
}
action=
{
action
}
/>
))
}
<
Button
variant=
'outline'
size=
'sm'
className=
'bg-background/70 h-8 min-w-28'
onClick=
{
handleSetupGuideToggle
}
>
<
ChevronDown
data
-
icon=
'inline-start'
/>
{
t
(
'Show setup guide'
)
}
</
Button
>
</
div
>
</
div
>
</
div
>
</
CardStaggerItem
>
</
CardStaggerContainer
>
)
}
</
CardStaggerItem
>
</
CardStaggerContainer
>
)
}
<
SummaryCards
/>
<
SummaryCards
/>
{
showContentPanels
&&
(
<
CardStaggerContainer
className=
{
cn
(
'grid grid-cols-1 gap-4'
,
showLeftContentPanels
&&
showUptimePanel
&&
'xl:grid-cols-[minmax(0,1fr)_22rem]'
)
}
>
{
showLeftContentPanels
&&
(
<
div
{
showContentPanels
&&
(
<
CardStaggerContainer
className=
{
cn
(
'grid min-w-0 grid-cols-1 gap-4'
,
(
showApiInfoPanel
||
showAnnouncementsPanel
||
showFAQPanel
)
&&
'lg:grid-cols-2'
'grid grid-cols-1 gap-4'
,
showLeftContentPanels
&&
showUptimePanel
&&
'xl:grid-cols-[minmax(0,1fr)_22rem]'
)
}
>
{
isAdmin
&&
(
<
CardStaggerItem
className=
'lg:col-span-2'
>
<
PerformanceHealthPanel
/>
</
CardStaggerItem
>
)
}
{
showApiInfoPanel
&&
(
<
CardStaggerItem
>
<
ApiInfoPanel
/>
</
CardStaggerItem
>
)
}
{
showAnnouncementsPanel
&&
(
<
CardStaggerItem
>
<
AnnouncementsPanel
/>
</
CardStaggerItem
>
{
showLeftContentPanels
&&
(
<
div
className=
{
cn
(
'grid min-w-0 grid-cols-1 gap-4'
,
(
showApiInfoPanel
||
showAnnouncementsPanel
||
showFAQPanel
)
&&
'lg:grid-cols-2'
)
}
>
{
isAdmin
&&
(
<
CardStaggerItem
className=
'lg:col-span-2'
>
<
PerformanceHealthPanel
/>
</
CardStaggerItem
>
)
}
{
showApiInfoPanel
&&
(
<
CardStaggerItem
>
<
ApiInfoPanel
/>
</
CardStaggerItem
>
)
}
{
showAnnouncementsPanel
&&
(
<
CardStaggerItem
>
<
AnnouncementsPanel
/>
</
CardStaggerItem
>
)
}
{
showFAQPanel
&&
(
<
CardStaggerItem
>
<
FAQPanel
/>
</
CardStaggerItem
>
)
}
</
div
>
)
}
{
show
FAQ
Panel
&&
(
{
show
Uptime
Panel
&&
(
<
CardStaggerItem
>
<
FAQ
Panel
/>
<
Uptime
Panel
/>
</
CardStaggerItem
>
)
}
</
div
>
</
CardStaggerContainer
>
)
}
{
showUptimePanel
&&
(
<
CardStaggerItem
>
<
UptimePanel
/>
</
CardStaggerItem
>
)
}
</
CardStaggerContainer
>
)
}
</
div
>
</
div
>
</
SectionPageLayout
.
Content
>
</
SectionPageLayout
>
)
}
web/src/features/dashboard/index.tsx
View file @
521cebf5
...
...
@@ -317,34 +317,35 @@ export function Dashboard() {
)
:
null
const
sectionActions
=
modelActions
??
flowActions
if
(
activeSection
===
'overview'
)
{
return
<
OverviewDashboard
/>
}
return
(
<
SectionPageLayout
>
<
SectionPageLayout
.
Title
>
{
t
(
meta
.
titleKey
)
}
</
SectionPageLayout
.
Title
>
<
SectionPageLayout
.
Content
>
<
div
className=
'space-y-3 sm:space-y-4'
>
{
activeSection
!==
'overview'
&&
(
<
div
className=
'flex flex-wrap items-center justify-between gap-1.5 sm:gap-2'
>
{
showSectionTabs
?
(
<
Tabs
value=
{
activeSection
}
onValueChange=
{
handleSectionChange
}
>
<
TabsList
className=
'max-w-full flex-wrap justify-start group-data-horizontal/tabs:h-auto'
>
{
visibleSections
.
map
((
section
)
=>
(
<
TabsTrigger
key=
{
section
}
value=
{
section
}
>
{
t
(
SECTION_META
[
section
].
titleKey
)
}
</
TabsTrigger
>
))
}
</
TabsList
>
</
Tabs
>
)
:
(
<
div
/>
)
}
{
sectionActions
!=
null
&&
(
<
div
className=
'flex shrink-0 flex-wrap items-center gap-1.5 sm:gap-2'
>
{
sectionActions
}
</
div
>
)
}
</
div
>
)
}
{
activeSection
===
'overview'
&&
<
OverviewDashboard
/>
}
<
div
className=
'flex flex-wrap items-center justify-between gap-1.5 sm:gap-2'
>
{
showSectionTabs
?
(
<
Tabs
value=
{
activeSection
}
onValueChange=
{
handleSectionChange
}
>
<
TabsList
className=
'max-w-full flex-wrap justify-start group-data-horizontal/tabs:h-auto'
>
{
visibleSections
.
map
((
section
)
=>
(
<
TabsTrigger
key=
{
section
}
value=
{
section
}
>
{
t
(
SECTION_META
[
section
].
titleKey
)
}
</
TabsTrigger
>
))
}
</
TabsList
>
</
Tabs
>
)
:
(
<
div
/>
)
}
{
sectionActions
!=
null
&&
(
<
div
className=
'flex shrink-0 flex-wrap items-center gap-1.5 sm:gap-2'
>
{
sectionActions
}
</
div
>
)
}
</
div
>
{
activeSection
===
'models'
&&
(
<>
<
FadeIn
>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment