Simplify settings workspace and fix responsive admin controls
This commit is contained in:
@@ -22,7 +22,7 @@ export default function AdminShell({ title, subtitle, actions, rail, children }:
|
||||
<main className="card admin-card">
|
||||
<div className="admin-header">
|
||||
<div>
|
||||
<span className="section-kicker">Beta stream</span>
|
||||
<span className="section-kicker">Configuration</span>
|
||||
<h1>{title}</h1>
|
||||
{subtitle && <p className="lede">{subtitle}</p>}
|
||||
</div>
|
||||
|
||||
@@ -1,85 +1,38 @@
|
||||
'use client'
|
||||
|
||||
import { usePathname } from 'next/navigation'
|
||||
|
||||
const NAV_GROUPS = [
|
||||
{
|
||||
title: 'Configuration',
|
||||
items: [
|
||||
{ href: '/admin', label: 'Config overview' },
|
||||
{ href: '/admin/general', label: 'Application & proxy' },
|
||||
{ href: '/admin/site', label: 'Site & login' },
|
||||
{ href: '/admin/notifications', label: 'Notifications' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Media Services',
|
||||
items: [
|
||||
{ href: '/admin/seerr', label: 'Seerr' },
|
||||
{ href: '/admin/jellyfin', label: 'Jellyfin' },
|
||||
{ href: '/admin/sonarr', label: 'Sonarr' },
|
||||
{ href: '/admin/radarr', label: 'Radarr' },
|
||||
{ href: '/admin/bazarr', label: 'Bazarr' },
|
||||
{ href: '/admin/prowlarr', label: 'Prowlarr' },
|
||||
{ href: '/admin/qbittorrent', label: 'qBittorrent' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Request Pipeline',
|
||||
items: [
|
||||
{ href: '/admin/requests', label: 'Sync & retention' },
|
||||
{ href: '/admin/issue-workflow', label: 'Issue workflow' },
|
||||
{ href: '/admin/cache', label: 'Request cache' },
|
||||
{ href: '/admin/artwork', label: 'Artwork cache' },
|
||||
{ href: '/admin/requests-all', label: 'All requests' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Users & Access',
|
||||
items: [
|
||||
{ href: '/users', label: 'Users' },
|
||||
{ href: '/admin/invites', label: 'Invite management' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'System',
|
||||
items: [
|
||||
{ href: '/admin/diagnostics', label: 'System health' },
|
||||
{ href: '/admin/logs', label: 'Activity log' },
|
||||
{ href: '/admin/maintenance', label: 'Maintenance' },
|
||||
{ href: '/admin/system', label: 'How it works' },
|
||||
],
|
||||
},
|
||||
]
|
||||
import { usePathname, useRouter } from 'next/navigation'
|
||||
import { CONFIG_GROUPS } from '../admin/configNavigation'
|
||||
|
||||
export default function AdminSidebar() {
|
||||
const pathname = usePathname()
|
||||
const router = useRouter()
|
||||
const links = CONFIG_GROUPS.flatMap((group) => group.items)
|
||||
const current = links.find((item) => item.href === pathname)
|
||||
const renderLinks = (items: typeof links) => (
|
||||
<div className="admin-nav-links">
|
||||
{items.map((item) => <a key={item.href} href={item.href} aria-current={pathname === item.href ? 'page' : undefined} className={pathname === item.href ? 'is-active' : ''}>{item.label}</a>)}
|
||||
</div>
|
||||
)
|
||||
return (
|
||||
<nav className="admin-sidebar">
|
||||
<div className="admin-sidebar-identity">
|
||||
<strong>Magent Admin</strong>
|
||||
<span><i aria-hidden="true" />Configuration workspace</span>
|
||||
<nav className="admin-sidebar" aria-label="Settings navigation">
|
||||
<label className="config-mobile-picker">
|
||||
<span>Settings</span>
|
||||
<select aria-label="Settings section" value={current?.href ?? '/admin'} onChange={(event) => router.push(event.target.value)}>
|
||||
<option value="/admin">Settings overview</option>
|
||||
{CONFIG_GROUPS.map((group) => <optgroup label={group.title} key={group.title}>{group.items.map((item) => <option key={item.href} value={item.href}>{item.label}</option>)}</optgroup>)}
|
||||
</select>
|
||||
</label>
|
||||
<div className="config-desktop-navigation">
|
||||
<a href="/admin" className="config-sidebar-home" aria-current={pathname === '/admin' ? 'page' : undefined}>Settings <span aria-hidden="true">↗</span></a>
|
||||
{CONFIG_GROUPS.map((group) => group.advanced ? (
|
||||
<details className="admin-nav-group config-nav-advanced" key={pathname + group.title} open={group.items.some((item) => item.href === pathname) || undefined}>
|
||||
<summary>{group.title}</summary>{renderLinks(group.items)}
|
||||
</details>
|
||||
) : (
|
||||
<div className="admin-nav-group" key={group.title}><span className="admin-nav-title">{group.title}</span>{renderLinks(group.items)}</div>
|
||||
))}
|
||||
<a href="/" className="config-sidebar-back">← My requests</a>
|
||||
</div>
|
||||
<a className="admin-new-request" href="/new-requests"><span>+</span> New request</a>
|
||||
{NAV_GROUPS.map((group) => (
|
||||
<div key={group.title} className="admin-nav-group">
|
||||
<span className="admin-nav-title">{group.title}</span>
|
||||
<div className="admin-nav-links">
|
||||
{group.items.map((item) => {
|
||||
const isActive =
|
||||
pathname === item.href ||
|
||||
(item.href !== '/' &&
|
||||
item.href !== '/admin' &&
|
||||
pathname.startsWith(`${item.href}/`))
|
||||
return (
|
||||
<a key={item.href} href={item.href} className={isActive ? 'is-active' : ''}>
|
||||
{item.label}
|
||||
</a>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ export default function WorkspaceNavigation() {
|
||||
const pathname = usePathname()
|
||||
const [role, setRole] = useState<string | null>(null)
|
||||
const [ready, setReady] = useState(false)
|
||||
const [showRequestsNav, setShowRequestsNav] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
const token = getToken()
|
||||
@@ -46,28 +47,32 @@ export default function WorkspaceNavigation() {
|
||||
setReady(true)
|
||||
return
|
||||
}
|
||||
authFetch(`${getApiBase()}/auth/me`)
|
||||
.then(async (response) => {
|
||||
Promise.all([
|
||||
authFetch(`${getApiBase()}/auth/me`),
|
||||
fetch(`${getApiBase()}/site/public`).catch(() => null),
|
||||
])
|
||||
.then(async ([response, siteResponse]) => {
|
||||
if (response.ok) setRole((await response.json())?.role ?? 'user')
|
||||
if (siteResponse?.ok) setShowRequestsNav((await siteResponse.json())?.navigation?.showRequests !== false)
|
||||
})
|
||||
.catch(() => undefined)
|
||||
.finally(() => setReady(true))
|
||||
}, [])
|
||||
|
||||
if (!ready || !getToken() || HIDDEN_ROUTES.some((route) => pathname.startsWith(route)) || pathname.startsWith('/admin')) {
|
||||
if (!ready || !getToken() || HIDDEN_ROUTES.some((route) => pathname.startsWith(route))) {
|
||||
return null
|
||||
}
|
||||
|
||||
const items = NAVIGATION.filter((item) => !item.adminOnly || role === 'admin')
|
||||
const items = NAVIGATION.filter((item) => (!item.adminOnly || role === 'admin') && (showRequestsNav || item.href !== '/new-requests'))
|
||||
|
||||
return (
|
||||
<>
|
||||
<aside className="workspace-sidebar" aria-label="Workspace navigation">
|
||||
{!pathname.startsWith('/admin') && <aside className="workspace-sidebar" aria-label="Workspace navigation">
|
||||
<div className="workspace-sidebar-identity">
|
||||
<BrandingLogo className="workspace-sidebar-logo" />
|
||||
<div><strong>{role === 'admin' ? 'Magent Admin' : 'Magent'}</strong><span>Media operations workspace</span></div>
|
||||
</div>
|
||||
<a className="workspace-new-request" href="/new-requests"><span>+</span> New request</a>
|
||||
{showRequestsNav && <a className="workspace-new-request" href="/new-requests"><span>+</span> New request</a>}
|
||||
<nav>
|
||||
{items.map((item) => (
|
||||
<a key={item.href} href={item.href} className={item.match(pathname) ? 'is-active' : undefined}>
|
||||
@@ -79,7 +84,7 @@ export default function WorkspaceNavigation() {
|
||||
<a href="/feedback">Support</a>
|
||||
<a href={role === 'admin' ? '/admin' : '/profile'}>Settings</a>
|
||||
</div>
|
||||
</aside>
|
||||
</aside>}
|
||||
<nav className="workspace-mobile-nav" aria-label="Mobile navigation">
|
||||
{items.slice(0, 5).map((item) => (
|
||||
<a key={item.href} href={item.href} className={item.match(pathname) ? 'is-active' : undefined}>
|
||||
|
||||
Reference in New Issue
Block a user