Simplify navigation and modernize profile and sign-in
Magent CI/CD / verify (push) Successful in 10m55s
Magent CI/CD / deploy-prod (push) Skipped
Magent CI/CD / deploy-beta (push) Successful in 1m58s

This commit is contained in:
2026-09-06 18:24:18 +12:00
parent b5e4c57e93
commit 4d67567d4c
15 changed files with 620 additions and 755 deletions
+4 -8
View File
@@ -1,7 +1,7 @@
'use client'
import type { ReactNode } from 'react'
import AdminSidebar from './AdminSidebar'
import SettingsNavigation from './SettingsNavigation'
type AdminShellProps = {
title: string
@@ -12,13 +12,9 @@ type AdminShellProps = {
}
export default function AdminShell({ title, subtitle, actions, rail, children }: AdminShellProps) {
const hasRail = Boolean(rail)
return (
<div className={`admin-shell ${hasRail ? 'admin-shell--with-rail' : 'admin-shell--no-rail'}`}>
<aside className="admin-shell-nav">
<AdminSidebar />
</aside>
<div className="admin-shell admin-shell--top-nav">
<SettingsNavigation />
<main className="card admin-card">
<div className="admin-header">
<div>
@@ -29,8 +25,8 @@ export default function AdminShell({ title, subtitle, actions, rail, children }:
{actions}
</div>
{children}
{rail && <details className="admin-supplemental"><summary>Additional information</summary>{rail}</details>}
</main>
{hasRail ? <aside className="admin-shell-rail">{rail}</aside> : null}
</div>
)
}
-38
View File
@@ -1,38 +0,0 @@
'use client'
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" 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>
</nav>
)
}
+24
View File
@@ -0,0 +1,24 @@
'use client'
import { usePathname } from 'next/navigation'
import BrandingLogo from './BrandingLogo'
import HeaderActions from './HeaderActions'
import HeaderIdentity from './HeaderIdentity'
import SiteStatus from './SiteStatus'
import UserViewBanner from './UserViewBanner'
import WorkspaceNavigation from './WorkspaceNavigation'
export default function ApplicationChrome() {
const pathname = usePathname()
if (pathname === '/login') return null
return <>
<header className="header">
<div className="header-left"><a className="brand-link" href="/"><BrandingLogo className="brand-logo brand-logo--header" /><div className="brand-stack"><div className="brand">Magent</div><div className="tagline">GrizzlyFlix media operations</div></div></a></div>
<div className="header-right"><span className="beta-chip" title="Beta environment">Beta</span><HeaderIdentity /></div>
<div className="header-nav"><HeaderActions /></div>
</header>
<WorkspaceNavigation />
<UserViewBanner />
<SiteStatus />
</>
}
+1 -1
View File
@@ -63,7 +63,7 @@ export default function HeaderActions() {
{
href: '/admin',
label: 'Config',
match: (path: string) => path.startsWith('/admin'),
match: (path: string) => path.startsWith('/admin') || path.startsWith('/users'),
},
]
: [
+3
View File
@@ -0,0 +1,3 @@
export default function MagentMark() {
return <svg className="magent-mark" viewBox="0 0 40 40" fill="none" aria-hidden="true"><rect x=".5" y=".5" width="39" height="39" rx="11" fill="#242329" stroke="#45434f" /><path d="M10 29V11h4l6 9 6-9h4v18h-4V18l-6 8-6-8v11h-4Z" fill="#dedaff" /></svg>
}
+18
View File
@@ -0,0 +1,18 @@
'use client'
import { usePathname, useRouter } from 'next/navigation'
import { CONFIG_GROUPS } from '../admin/configNavigation'
export default function SettingsNavigation() {
const pathname = usePathname()
const router = useRouter()
const current = CONFIG_GROUPS.flatMap((group) => group.items).find((item) => item.href === pathname)
if (pathname === '/admin') return null
return <nav className="settings-top-navigation" aria-label="Settings navigation">
<a href="/admin"> All settings</a>
<label><span>Jump to</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>
</nav>
}
+1 -20
View File
@@ -3,7 +3,6 @@
import { usePathname } from 'next/navigation'
import { useEffect, useState } from 'react'
import { authFetch, getApiBase, getToken } from '../lib/auth'
import BrandingLogo from './BrandingLogo'
type NavigationItem = {
href: string
@@ -19,7 +18,7 @@ const NAVIGATION: NavigationItem[] = [
{ href: '/new-requests', label: 'New Request', shortLabel: 'New', icon: 'media', match: (path) => path === '/new-requests' },
{ href: '/portal/issues', label: 'Issues', shortLabel: 'Issues', icon: 'issues', match: (path) => path.startsWith('/portal/issues') },
{ href: '/profile/invites', label: 'Invites', shortLabel: 'Invites', icon: 'invites', match: (path) => path.startsWith('/profile/invites') },
{ href: '/admin', label: 'Configuration', shortLabel: 'Config', icon: 'settings', adminOnly: true, match: (path) => path.startsWith('/admin') },
{ href: '/admin', label: 'Configuration', shortLabel: 'Config', icon: 'settings', adminOnly: true, match: (path) => path.startsWith('/admin') || path.startsWith('/users') },
]
const HIDDEN_ROUTES = ['/login', '/signup', '/forgot-password', '/reset-password', '/how-it-works']
@@ -67,24 +66,6 @@ export default function WorkspaceNavigation() {
return (
<>
{!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>
{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}>
<NavigationIcon name={item.icon} /><span>{item.label}</span>
</a>
))}
</nav>
<div className="workspace-sidebar-footer">
<a href="/feedback">Support</a>
<a href={role === 'admin' ? '/admin' : '/profile'}>Settings</a>
</div>
</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}>