Loading portal-gui/src/app/config/navigation.ts +14 −1 Changes for portal-gui/src/app/config/navigation.ts: 14 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -14,6 +14,7 @@ import { import { ROUTES } from './route'; import type { BreadcrumbItem } from '@/types/common'; import type { Api } from '@/types/api'; export const applicationNavItems: NavItem[] = [ { Loading Loading @@ -130,7 +131,10 @@ function findNavPath(items: NavItem[], pathname: string): NavItem[] | null { return null; } export function getBreadcrumbItems(pathname: string): BreadcrumbItem[] { export function getBreadcrumbItems( pathname: string, api?: Pick<Api, 'id' | 'title'> | null, ): BreadcrumbItem[] { // Detail pages such as /apis/:id are not in the nav, so walk up to the // nearest page that is. let navPath: NavItem[] | null = null; Loading @@ -148,6 +152,15 @@ export function getBreadcrumbItems(pathname: string): BreadcrumbItem[] { if (pathname.startsWith(`${ROUTES.APIS}/`) && trail[0]?.to !== ROUTES.APIS) { trail.unshift({ title: 'API Catalog', to: ROUTES.APIS }); } if (api && trail.at(-1)?.to === ROUTES.APIS) { const basePath = `${ROUTES.APIS}/${api.id}`; if (pathname === basePath || pathname.startsWith(`${basePath}/`)) { trail.push({ title: api.title, to: basePath }); if (pathname === `${basePath}/overview`) { trail.push({ title: 'Overview', to: pathname }); } } } return trail[0]?.to === ROUTES.HOME ? trail : [{ title: 'Home', to: ROUTES.HOME }, ...trail]; Loading portal-gui/src/components/common/app-breadcrumbs.tsx +4 −3 Changes for portal-gui/src/components/common/app-breadcrumbs.tsx: 4 added lines, 3 removed lines. Original line number Diff line number Diff line Loading @@ -10,10 +10,11 @@ import { } from '@/components/ui/breadcrumb'; import { getBreadcrumbItems } from '@/app/config/navigation'; import type { Api } from '@/types/api'; export function AppBreadcrumbs() { export function AppBreadcrumbs({ api }: { api?: Api | null }) { const { pathname } = useLocation(); const items = getBreadcrumbItems(pathname); const items = getBreadcrumbItems(pathname, api); return ( <Breadcrumb> Loading @@ -23,7 +24,7 @@ export function AppBreadcrumbs() { const isCurrent = item.to === pathname; return ( <Fragment key={`{item.title}-${index}`}> <Fragment key={item.to}> <BreadcrumbItem> {isCurrent ? ( <BreadcrumbPage>{item.title}</BreadcrumbPage> Loading portal-gui/src/components/common/site-header.tsx +3 −2 Changes for portal-gui/src/components/common/site-header.tsx: 3 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -4,8 +4,9 @@ import { Separator } from '@/components/ui/separator'; import { SidebarTrigger } from '@/components/ui/sidebar'; import { LogoIcon } from '@/lib/icons'; import { ThemeToggle } from './theme-toggle'; import type { Api } from '@/types/api'; export function SiteHeader() { export function SiteHeader({ api }: { api?: Api | null }) { return ( <header className="sticky top-0 z-50 flex h-(--header-height) shrink-0 items-center border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60"> <div className="flex w-full items-center gap-2 px-3 lg:px-6"> Loading @@ -20,7 +21,7 @@ export function SiteHeader() { <Separator orientation="vertical" className="hidden h-4 md:block" /> <div className="min-w-0 flex-1"> <div className="hidden md:block"> <AppBreadcrumbs /> <AppBreadcrumbs api={api} /> </div> </div> <ThemeToggle /> Loading portal-gui/src/components/common/workflow.tsx 0 → 100644 +42 −0 Changes for portal-gui/src/components/common/workflow.tsx: 42 added lines, 0 removed lines. Original line number Diff line number Diff line import { ArrowRight } from 'lucide-react'; import type { WorkflowStep } from '@/types/common'; export function Workflow({ steps }: { steps: WorkflowStep[] }) { if (steps.length === 0) return null; return ( <figure aria-label="How it works" className="@container border-b pb-6"> <figcaption className="mb-5 text-sm font-semibold text-foreground"> How it works </figcaption> <ol role="list" aria-label="Workflow steps" className="grid grid-cols-2 gap-x-8 gap-y-6 @2xl:grid-cols-4" > {steps.map(({ title, description, icon: Icon }, index) => ( <li key={title} className="relative min-w-0 text-center"> <div className="mx-auto mb-3 flex size-12 items-center justify-center rounded-xl bg-primary/10 text-primary"> <Icon aria-hidden className="size-6" strokeWidth={1.75} /> </div> <p className="text-sm leading-5 font-semibold text-foreground"> <span aria-hidden className="text-muted-foreground"> {index + 1}. </span>{' '} {title} </p> <p className="mx-auto mt-1 max-w-40 text-xs leading-5 text-muted-foreground"> {description} </p> {index < steps.length - 1 && ( <ArrowRight aria-hidden className="absolute top-3 -right-4 hidden size-5 translate-x-1/2 text-muted-foreground/60 @2xl:block" /> )} </li> ))} </ol> </figure> ); } portal-gui/src/features/apis/api-details-page.tsx +7 −64 Changes for portal-gui/src/features/apis/api-details-page.tsx: 7 added lines, 64 removed lines. Original line number Diff line number Diff line Loading @@ -3,6 +3,7 @@ import { SectionTitle } from '@/components/common/section-title'; import { PageContent } from '@/components/common/page-content'; import { Markdown } from '@/components/common/markdown'; import { ExpandableMarkdown } from '@/components/common/expandable-markdown'; import { Workflow } from '@/components/common/workflow'; import { Card, CardContent, Loading @@ -10,7 +11,6 @@ import { CardHeader, CardTitle, } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import type { Api } from '@/types/api'; import { apiDocumentation } from '@/lib/data/api-documentation'; Loading @@ -34,6 +34,9 @@ export function ApiDetailsPage() { </CardTitle> </CardHeader> <CardContent className="space-y-4 px-6 py-6"> {documentation?.workflow && ( <Workflow steps={documentation.workflow} /> )} <ExpandableMarkdown key={api.id}> {documentation?.description ?? api.description} </ExpandableMarkdown> Loading Loading @@ -112,10 +115,9 @@ export function ApiDetailsPage() { </section> ) : null} <section className="space-y-4"> <SectionTitle title="API Usage" /> <div className="space-y-6"> {documentation?.instructions && ( <section className="space-y-4"> <SectionTitle title="How to use" /> <Card> <CardHeader> <CardTitle className="text-xl font-semibold text-sidebar-primary"> Loading @@ -126,67 +128,8 @@ export function ApiDetailsPage() { <Markdown>{documentation.instructions}</Markdown> </CardContent> </Card> )} <Card> <CardHeader> <CardTitle className="text-xl font-semibold text-sidebar-primary"> API operations </CardTitle> <CardDescription> Operations documented in the API specification. </CardDescription> </CardHeader> <CardContent className="space-y-5"> {api.operations && api.operations.length > 0 ? ( <div> <ul className="grid gap-3 md:grid-cols-2"> {api.operations.map((operation) => ( <li key={`${operation.method} ${operation.path}`} className="rounded-md border bg-muted/30 p-3" > <div className="flex flex-wrap items-center gap-2"> <Badge>{operation.method}</Badge> <code className="text-sm font-medium"> {operation.path} </code> </div> <p className="mt-2 text-sm font-medium"> {operation.name} </p> <p className="mt-1 text-sm text-muted-foreground"> {operation.description} </p> </li> ))} </ul> </div> ) : ( api.functionalities.length > 0 && ( <div> <h3 className="text-sm font-semibold">Operations</h3> <ul className="mt-3 space-y-2"> {api.functionalities.map((operation) => ( <li key={operation} className="rounded-md border bg-muted/30 px-3 py-2 text-sm text-muted-foreground" > {operation} </li> ))} </ul> </div> ) )} </CardContent> </Card> </div> </section> )} <section className="space-y-4"> <SectionTitle title="API reference" /> Loading Loading
portal-gui/src/app/config/navigation.ts +14 −1 Changes for portal-gui/src/app/config/navigation.ts: 14 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -14,6 +14,7 @@ import { import { ROUTES } from './route'; import type { BreadcrumbItem } from '@/types/common'; import type { Api } from '@/types/api'; export const applicationNavItems: NavItem[] = [ { Loading Loading @@ -130,7 +131,10 @@ function findNavPath(items: NavItem[], pathname: string): NavItem[] | null { return null; } export function getBreadcrumbItems(pathname: string): BreadcrumbItem[] { export function getBreadcrumbItems( pathname: string, api?: Pick<Api, 'id' | 'title'> | null, ): BreadcrumbItem[] { // Detail pages such as /apis/:id are not in the nav, so walk up to the // nearest page that is. let navPath: NavItem[] | null = null; Loading @@ -148,6 +152,15 @@ export function getBreadcrumbItems(pathname: string): BreadcrumbItem[] { if (pathname.startsWith(`${ROUTES.APIS}/`) && trail[0]?.to !== ROUTES.APIS) { trail.unshift({ title: 'API Catalog', to: ROUTES.APIS }); } if (api && trail.at(-1)?.to === ROUTES.APIS) { const basePath = `${ROUTES.APIS}/${api.id}`; if (pathname === basePath || pathname.startsWith(`${basePath}/`)) { trail.push({ title: api.title, to: basePath }); if (pathname === `${basePath}/overview`) { trail.push({ title: 'Overview', to: pathname }); } } } return trail[0]?.to === ROUTES.HOME ? trail : [{ title: 'Home', to: ROUTES.HOME }, ...trail]; Loading
portal-gui/src/components/common/app-breadcrumbs.tsx +4 −3 Changes for portal-gui/src/components/common/app-breadcrumbs.tsx: 4 added lines, 3 removed lines. Original line number Diff line number Diff line Loading @@ -10,10 +10,11 @@ import { } from '@/components/ui/breadcrumb'; import { getBreadcrumbItems } from '@/app/config/navigation'; import type { Api } from '@/types/api'; export function AppBreadcrumbs() { export function AppBreadcrumbs({ api }: { api?: Api | null }) { const { pathname } = useLocation(); const items = getBreadcrumbItems(pathname); const items = getBreadcrumbItems(pathname, api); return ( <Breadcrumb> Loading @@ -23,7 +24,7 @@ export function AppBreadcrumbs() { const isCurrent = item.to === pathname; return ( <Fragment key={`{item.title}-${index}`}> <Fragment key={item.to}> <BreadcrumbItem> {isCurrent ? ( <BreadcrumbPage>{item.title}</BreadcrumbPage> Loading
portal-gui/src/components/common/site-header.tsx +3 −2 Changes for portal-gui/src/components/common/site-header.tsx: 3 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -4,8 +4,9 @@ import { Separator } from '@/components/ui/separator'; import { SidebarTrigger } from '@/components/ui/sidebar'; import { LogoIcon } from '@/lib/icons'; import { ThemeToggle } from './theme-toggle'; import type { Api } from '@/types/api'; export function SiteHeader() { export function SiteHeader({ api }: { api?: Api | null }) { return ( <header className="sticky top-0 z-50 flex h-(--header-height) shrink-0 items-center border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60"> <div className="flex w-full items-center gap-2 px-3 lg:px-6"> Loading @@ -20,7 +21,7 @@ export function SiteHeader() { <Separator orientation="vertical" className="hidden h-4 md:block" /> <div className="min-w-0 flex-1"> <div className="hidden md:block"> <AppBreadcrumbs /> <AppBreadcrumbs api={api} /> </div> </div> <ThemeToggle /> Loading
portal-gui/src/components/common/workflow.tsx 0 → 100644 +42 −0 Changes for portal-gui/src/components/common/workflow.tsx: 42 added lines, 0 removed lines. Original line number Diff line number Diff line import { ArrowRight } from 'lucide-react'; import type { WorkflowStep } from '@/types/common'; export function Workflow({ steps }: { steps: WorkflowStep[] }) { if (steps.length === 0) return null; return ( <figure aria-label="How it works" className="@container border-b pb-6"> <figcaption className="mb-5 text-sm font-semibold text-foreground"> How it works </figcaption> <ol role="list" aria-label="Workflow steps" className="grid grid-cols-2 gap-x-8 gap-y-6 @2xl:grid-cols-4" > {steps.map(({ title, description, icon: Icon }, index) => ( <li key={title} className="relative min-w-0 text-center"> <div className="mx-auto mb-3 flex size-12 items-center justify-center rounded-xl bg-primary/10 text-primary"> <Icon aria-hidden className="size-6" strokeWidth={1.75} /> </div> <p className="text-sm leading-5 font-semibold text-foreground"> <span aria-hidden className="text-muted-foreground"> {index + 1}. </span>{' '} {title} </p> <p className="mx-auto mt-1 max-w-40 text-xs leading-5 text-muted-foreground"> {description} </p> {index < steps.length - 1 && ( <ArrowRight aria-hidden className="absolute top-3 -right-4 hidden size-5 translate-x-1/2 text-muted-foreground/60 @2xl:block" /> )} </li> ))} </ol> </figure> ); }
portal-gui/src/features/apis/api-details-page.tsx +7 −64 Changes for portal-gui/src/features/apis/api-details-page.tsx: 7 added lines, 64 removed lines. Original line number Diff line number Diff line Loading @@ -3,6 +3,7 @@ import { SectionTitle } from '@/components/common/section-title'; import { PageContent } from '@/components/common/page-content'; import { Markdown } from '@/components/common/markdown'; import { ExpandableMarkdown } from '@/components/common/expandable-markdown'; import { Workflow } from '@/components/common/workflow'; import { Card, CardContent, Loading @@ -10,7 +11,6 @@ import { CardHeader, CardTitle, } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import type { Api } from '@/types/api'; import { apiDocumentation } from '@/lib/data/api-documentation'; Loading @@ -34,6 +34,9 @@ export function ApiDetailsPage() { </CardTitle> </CardHeader> <CardContent className="space-y-4 px-6 py-6"> {documentation?.workflow && ( <Workflow steps={documentation.workflow} /> )} <ExpandableMarkdown key={api.id}> {documentation?.description ?? api.description} </ExpandableMarkdown> Loading Loading @@ -112,10 +115,9 @@ export function ApiDetailsPage() { </section> ) : null} <section className="space-y-4"> <SectionTitle title="API Usage" /> <div className="space-y-6"> {documentation?.instructions && ( <section className="space-y-4"> <SectionTitle title="How to use" /> <Card> <CardHeader> <CardTitle className="text-xl font-semibold text-sidebar-primary"> Loading @@ -126,67 +128,8 @@ export function ApiDetailsPage() { <Markdown>{documentation.instructions}</Markdown> </CardContent> </Card> )} <Card> <CardHeader> <CardTitle className="text-xl font-semibold text-sidebar-primary"> API operations </CardTitle> <CardDescription> Operations documented in the API specification. </CardDescription> </CardHeader> <CardContent className="space-y-5"> {api.operations && api.operations.length > 0 ? ( <div> <ul className="grid gap-3 md:grid-cols-2"> {api.operations.map((operation) => ( <li key={`${operation.method} ${operation.path}`} className="rounded-md border bg-muted/30 p-3" > <div className="flex flex-wrap items-center gap-2"> <Badge>{operation.method}</Badge> <code className="text-sm font-medium"> {operation.path} </code> </div> <p className="mt-2 text-sm font-medium"> {operation.name} </p> <p className="mt-1 text-sm text-muted-foreground"> {operation.description} </p> </li> ))} </ul> </div> ) : ( api.functionalities.length > 0 && ( <div> <h3 className="text-sm font-semibold">Operations</h3> <ul className="mt-3 space-y-2"> {api.functionalities.map((operation) => ( <li key={operation} className="rounded-md border bg-muted/30 px-3 py-2 text-sm text-muted-foreground" > {operation} </li> ))} </ul> </div> ) )} </CardContent> </Card> </div> </section> )} <section className="space-y-4"> <SectionTitle title="API reference" /> Loading