Simplify navigation and modernize profile and sign-in
This commit is contained in:
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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 />
|
||||
</>
|
||||
}
|
||||
@@ -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'),
|
||||
},
|
||||
]
|
||||
: [
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
@@ -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>
|
||||
}
|
||||
@@ -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}>
|
||||
|
||||
Reference in New Issue
Block a user