Scrum4Me/components/product-docs/product-doc-viewer.tsx
Madhura68 bb4a71eafa feat(PBI-96/T-1069): doc viewer/editor + delete-button
- app/(app)/products/[id]/docs/[folder]/[slug]/page.tsx: server-route
  die doc laadt (scope-checked via productAccessFilter), frontmatter
  parseert, en op basis van ?edit=1 viewer of editor toont. Fallback
  voor unparseable frontmatter toont errors + raw content in <pre>.
- product-doc-viewer.tsx: server-component met frontmatter-kop
  (title + status-badge + audience/applies_to/last_updated meta) en
  body via <Markdown> (XSS-safe).
- product-doc-editor.tsx: client-wrapper rond MarkdownDocEditor met
  parseProductDocMd validator + updateProductDocAction + cancelHref.
- delete-product-doc-button.tsx: AlertDialog confirm + delete-action
  + DemoTooltip + redirect-na-success. Disabled in demo.
- Edit-knop conditioneel verborgen bij disabled folder (T-1071 voegt
  banner toe); delete blijft altijd zichtbaar voor cleanup.
- Button met `render={<Link/>}` ipv asChild (CLAUDE.md hardstop
  base-ui pattern).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 14:30:13 +02:00

78 lines
2.3 KiB
TypeScript

// Doc-viewer voor Product Docs. Server-component. Rendert een
// frontmatter-kop (title + status-badge + meta) gevolgd door de markdown-
// body via components/markdown.tsx (XSS-safe; iframe/script disabled).
//
// Frontmatter wordt door de pagina geparseerd en als losse props
// doorgegeven — voorkomt dat de viewer parsing-fouten moet afhandelen.
import { debugProps } from '@/lib/debug'
import { Markdown } from '@/components/markdown'
import { ProductDocStatusBadge } from './product-doc-status-badge'
interface Props {
title: string
status: string
body: string
audience?: string | string[] | undefined
applies_to?: string | string[] | undefined
lastUpdated?: string | undefined
}
function renderList(value: string | string[] | undefined): string | null {
if (!value) return null
return Array.isArray(value) ? value.join(', ') : value
}
export function ProductDocViewer({
title,
status,
body,
audience,
applies_to,
lastUpdated,
}: Props) {
const audienceLabel = renderList(audience)
const appliesToLabel = renderList(applies_to)
return (
<article
className="space-y-4"
{...debugProps(
'product-doc-viewer',
'ProductDocViewer',
'components/product-docs/product-doc-viewer.tsx',
)}
>
<header className="space-y-2 pb-3 border-b border-border">
<div className="flex items-center gap-2 flex-wrap">
<h1 className="text-xl font-semibold">{title}</h1>
<ProductDocStatusBadge status={status} />
</div>
{(audienceLabel || appliesToLabel || lastUpdated) && (
<div className="flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-muted-foreground">
{audienceLabel && (
<span>
<span className="uppercase tracking-wide">Audience:</span>{' '}
{audienceLabel}
</span>
)}
{appliesToLabel && (
<span>
<span className="uppercase tracking-wide">Applies to:</span>{' '}
{appliesToLabel}
</span>
)}
{lastUpdated && (
<span>
<span className="uppercase tracking-wide">Bijgewerkt:</span>{' '}
{lastUpdated}
</span>
)}
</div>
)}
</header>
<Markdown>{body}</Markdown>
</article>
)
}