From 2f15f38ab110a831166b7500681705b2f5c4bd6b Mon Sep 17 00:00:00 2001 From: "pouriade.com" Date: Tue, 27 Oct 2026 22:45:52 -0700 Subject: [PATCH] Share detail pages' back link and not-found page BackLink replaces the backLink class on five pages, and MissingPage the four hand-built pages for a role, model, plugin, or key that isn't there. --- .../src/features/admin/pages/ModelAdmin.tsx | 18 ++++----- .../features/admin/pages/NewPluginAdmin.tsx | 11 ++---- .../src/features/admin/pages/PluginAdmin.tsx | 23 +++++------ .../src/features/admin/pages/RoleAdmin.tsx | 18 ++++----- .../settings/pages/ApiKeyModelSettings.tsx | 18 ++++----- apps/web/src/shared/ui/SettingsPage.tsx | 38 +++++++++++++++++-- 6 files changed, 73 insertions(+), 53 deletions(-) diff --git a/apps/web/src/features/admin/pages/ModelAdmin.tsx b/apps/web/src/features/admin/pages/ModelAdmin.tsx index 89d75d4..b97c3f9 100644 --- a/apps/web/src/features/admin/pages/ModelAdmin.tsx +++ b/apps/web/src/features/admin/pages/ModelAdmin.tsx @@ -1,14 +1,14 @@ import { useMutation, useSuspenseQuery } from '@tanstack/react-query' -import { Link, useNavigate } from '@tanstack/react-router' +import { useNavigate } from '@tanstack/react-router' import { api, json, read } from '../../../shared/api.ts' import { formKey, useAppForm } from '../../../shared/form.tsx' -import { ArrowLeftIcon } from '../../../shared/icons.tsx' import { ErrorAlert } from '../../../shared/ui/Alert.tsx' import { Code } from '../../../shared/ui/Code.tsx' import { - backLink, + BackLink, Callout, ConfirmButton, + MissingPage, type Save, SaveBar, SettingRow, @@ -51,15 +51,15 @@ export function ModelAdmin(props: ModelAdminProps) { (candidate) => candidate.provider === props.provider && candidate.id === props.modelId, ) const back = ( - - Plugin - + + Plugin + ) if (!model) return ( - -

This model isn't offered.

-
+ + This model isn't offered. + ) return ( - Plugins - - } + back={Plugins} > diff --git a/apps/web/src/features/admin/pages/PluginAdmin.tsx b/apps/web/src/features/admin/pages/PluginAdmin.tsx index f73fbf1..05ef336 100644 --- a/apps/web/src/features/admin/pages/PluginAdmin.tsx +++ b/apps/web/src/features/admin/pages/PluginAdmin.tsx @@ -1,11 +1,16 @@ import { useMutation, useSuspenseQuery } from '@tanstack/react-query' -import { Link, useNavigate } from '@tanstack/react-router' +import { useNavigate } from '@tanstack/react-router' import { api, json, read } from '../../../shared/api.ts' import { issuesOf, messageOf } from '../../../shared/errors.ts' import { formKey } from '../../../shared/form.tsx' -import { ArrowLeftIcon } from '../../../shared/icons.tsx' import { Code } from '../../../shared/ui/Code.tsx' -import { backLink, Callout, type Save, SettingsPage } from '../../../shared/ui/SettingsPage.tsx' +import { + BackLink, + Callout, + MissingPage, + type Save, + SettingsPage, +} from '../../../shared/ui/SettingsPage.tsx' import { type PluginChange, PluginForm } from '../components/PluginForm.tsx' import { PluginStatus } from '../components/PluginStatus.tsx' import { usePluginsChanged } from '../hooks/changes.ts' @@ -35,16 +40,12 @@ export function PluginAdmin(props: PluginAdminProps) { }, }) const instance = plugins.find((candidate) => candidate.id === props.id) - const back = ( - - Plugins - - ) + const back = Plugins if (!instance) return ( - -

This plugin isn't configured.

-
+ + This plugin isn't configured. + ) return ( candidate.name === props.name) - const back = ( - - Roles - - ) + const back = Roles if (!role) return ( - -

This role doesn't exist.

-
+ + This role doesn't exist. + ) return ( candidate.id === props.id) - const back = ( - - API keys - - ) + const back = API keys if (!credential) return ( - -

This key isn't saved anymore.

-
+ + This key isn't saved anymore. + ) const provider = providers.find((p) => p.id === credential.providerId) const providerName = provider?.name ?? credential.providerId diff --git a/apps/web/src/shared/ui/SettingsPage.tsx b/apps/web/src/shared/ui/SettingsPage.tsx index a154cc9..5836804 100644 --- a/apps/web/src/shared/ui/SettingsPage.tsx +++ b/apps/web/src/shared/ui/SettingsPage.tsx @@ -1,9 +1,10 @@ import { useStore } from '@tanstack/react-form' +import { Link, type LinkProps } from '@tanstack/react-router' import { type ReactNode, useId, useRef, useState } from 'react' import { flushSync } from 'react-dom' import { cn } from '../cn.ts' import { useFormContext } from '../form.tsx' -import { CheckIcon } from '../icons.tsx' +import { ArrowLeftIcon, CheckIcon } from '../icons.tsx' import { ErrorAlert } from './Alert.tsx' import { Button, IconButton } from './Button.tsx' import { Radio } from './controls.tsx' @@ -21,7 +22,7 @@ interface PageProps { title: ReactNode /** One line under the title saying what the section is for. */ description?: ReactNode - /** A link above the title back to where this page was opened from, styled with backLink. */ + /** A link above the title back to where this page was opened from, a BackLink. */ back?: ReactNode /** Buttons or links beside the title that act on the whole page, such as adding an item. */ actions?: ReactNode @@ -53,8 +54,39 @@ export function SettingsPage(props: PageProps) { ) } +interface BackLinkProps extends Omit { + /** The page it leads back to, such as "Roles". */ + children: string +} + /** The way back to the page this one was opened from, quiet above the title. */ -export const backLink = 'inline-flex items-center gap-1.5 text-muted no-underline hover:text-ink' +export function BackLink(props: BackLinkProps) { + const { children, ...link } = props + return ( + + {children} + + ) +} + +interface MissingProps { + title: string + back: ReactNode + /** What is missing, such as "This role doesn't exist." */ + children: ReactNode +} + +/** A detail page for something that isn't there, as when it was deleted in another tab. */ +export function MissingPage(props: MissingProps) { + return ( + +

{props.children}

+
+ ) +} interface GroupProps { title?: string -- knotmirror 0.0.1