import type { IconName } from './components/Icon'; /* The console's table of contents, and the only place a page is declared. * * The rail, the page search and the router all read it, so a page cannot be in the menu * and 404, or be reachable and unnamed. It used to have to agree with a matching list in * Go as well — the gateway rendered the rail and decided which /admin URLs were legal — * which is a duplication the single-page console removes: the gateway now serves the * console for any /admin path and the routing is entirely here. * * `hidden` marks a destination that is reachable and titled but not in the rail: a page * about one person or one television belongs to the thing it is about, not to a menu. */ export interface NavItem { id: string; path: string; label: string; title: string; intro: string; icon?: IconName; hidden?: boolean; /** badge names a live count the rail should show beside this item — today only the * unread activity count. */ badge?: 'notifications'; } export interface NavGroup { id: string; label?: string; items: NavItem[]; } export const nav: NavGroup[] = [ { id: 'overview', items: [ { id: 'overview', path: '/admin', label: 'Overview', title: 'Overview', intro: 'What the gateway is doing right now.', icon: 'overview', }, { id: 'activity', path: '/admin/activity', label: 'Activity', title: 'Activity', intro: 'Every administrative event, newest first.', icon: 'bell', badge: 'notifications', }, ], }, { id: 'people', label: 'People', items: [ { id: 'accounts', path: '/admin/accounts', label: 'Users', title: 'Memby users', intro: 'Who uses Memby, and the devices they are signed in on.', icon: 'people', }, { id: 'account', path: '/admin/accounts/:userId', label: 'User', title: 'User', intro: 'Devices, recommendation setup and synced settings for one person.', hidden: true, }, { id: 'settings-history', path: '/admin/accounts/:userId/settings', label: 'Settings history', title: 'Settings history', intro: "Every change to one person's synced settings, and which devices took it.", hidden: true, }, { id: 'clients', path: '/admin/clients', label: 'Devices', title: 'Devices', intro: 'Which sets have reported in, what they are running and what their build understands.', icon: 'tv', }, { id: 'logins', path: '/admin/logins', label: 'Sign-ins', title: 'Sign-in history', intro: 'Every connection attempt: who, which television, from where, and whether it got in.', icon: 'key', }, { id: 'device', path: '/admin/devices/:deviceId', label: 'Device', title: 'Device', intro: 'One television: how often it connects, at what times, and from which addresses.', hidden: true, }, ], }, { id: 'content', label: 'Content', items: [ { id: 'library', path: '/admin/library', label: 'Library', title: 'Library', intro: 'Import and inspect the catalogue Memby ranks.', icon: 'library', }, { id: 'ratings', path: '/admin/ratings', label: 'Movie ratings', title: 'Movie ratings', intro: 'Optional MDBList scores on films and shows.', icon: 'star', }, { id: 'requests', path: '/admin/requests', label: 'Media requests', title: 'Media requests', intro: 'Who can ask for something the library does not have.', icon: 'inbox', }, ], }, { id: 'personalisation', label: 'Personalisation', items: [ { id: 'recommendations', path: '/admin/recommendations', label: 'For You', title: 'For You', intro: 'The prepared pools personalised rows are drawn from.', icon: 'sparkle', }, { id: 'inspector', path: '/admin/inspector', label: 'Score inspector', title: 'Score inspector', intro: 'Re-run the ranker for one person and read every component.', icon: 'search', }, ], }, { id: 'experience', label: 'Experience', items: [ { id: 'hero', path: '/admin/hero', label: 'Home hero', title: 'Home hero', intro: 'Choose films or television shows for the launcher spotlight while recent releases fill the remaining places.', icon: 'star', }, { id: 'features', path: '/admin/features', label: 'Features', title: 'Features', intro: 'Roll out, stop and recover optional behaviour with no app release.', icon: 'sliders', }, { id: 'playback', path: '/admin/playback', label: 'Playback', title: 'Playback', intro: 'Presentation policy sent with every playback launch.', icon: 'play', }, { id: 'subtitles', path: '/admin/subtitles', label: 'Subtitles', title: 'Subtitles', intro: 'Which providers a viewer may fetch a missing subtitle from.', icon: 'captions', }, { id: 'updates', path: '/admin/updates', label: 'App updates', title: 'App updates', intro: 'Publish an optional or a required client update.', icon: 'upload', }, ], }, { id: 'operations', label: 'Operations', items: [ { id: 'tasks', path: '/admin/tasks', label: 'Scheduled tasks', title: 'Scheduled tasks', intro: 'What the gateway does in the background, when it last ran and whether it worked.', icon: 'clock', }, { id: 'integrations', path: '/admin/integrations', label: 'Integrations', title: 'Integrations', intro: 'Send administrative events to Discord and, in time, elsewhere.', icon: 'plug', }, { id: 'maintenance', path: '/admin/maintenance', label: 'Maintenance', title: 'Maintenance', intro: 'Take Memby offline for every television.', icon: 'wrench', }, { id: 'imports', path: '/admin/imports', label: 'Imports', title: 'Imports', intro: 'Catalogue synchronisation history.', icon: 'database', }, { id: 'logs', path: '/admin/logs', label: 'Server logs', title: 'Server logs', intro: 'Structured gateway events as they happen.', icon: 'list', }, ], }, { id: 'reporting', label: 'Reporting', items: [ { id: 'views', path: '/admin/views', label: 'Views', title: 'App views', intro: 'Home-screen visits, viewers and the times Memby is used.', icon: 'overview', }, { id: 'journeys', path: '/admin/journeys', label: 'Journeys', title: 'User journeys', intro: 'How viewers move through Memby, use features and complete flows.', icon: 'journey', }, { id: 'engagement', path: '/admin/engagement', label: 'Row engagement', title: 'Row engagement', intro: 'Impressions, focus, dwell and selections per launcher row.', icon: 'chart', }, { id: 'searches', path: '/admin/searches', label: 'Searches', title: 'Searches', intro: 'What the household has been looking for, and what it searched just now.', icon: 'search', }, ], }, ]; export const allNavItems: NavItem[] = nav.flatMap((group) => group.items); /** searchableNavItems is what the omni search offers: every page that has a URL of its * own, which is every page that is not addressed by an id in the path. */ export const searchableNavItems = nav.flatMap((group) => group.items.filter((item) => !item.hidden).map((item) => ({ ...item, group: group.label ?? '' })), ); export function navItem(id: string): NavItem | undefined { return allNavItems.find((item) => item.id === id); }