'use client' import * as React from 'react' import { formatRelativeTime } from '../../utils/date-utils' import { cn } from '../../utils/cn' /** * THE generic JSON detail renderer — readable key/value prose for machine * payloads whose field names are NOT guaranteed (evidence blobs, audit * payloads, webhook bodies). No schema knowledge: keys are humanised, * ISO-dates become relative times, nested objects indent, and anything * unparseable falls back to raw. Use this instead of `
{JSON.stringify}`
* or a per-shape bespoke renderer.
*/
const ISO_DATE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/
function humanizeKey(key: string): string {
const spaced = key
.replace(/[_-]+/g, ' ')
.replace(/([a-z])([A-Z])/g, '$1 $2')
.trim()
return spaced.charAt(0).toUpperCase() + spaced.slice(1)
}
function Value({ value, depth }: { value: unknown; depth: number }) {
if (value === null || value === undefined) {
return —
}
if (typeof value === 'string') {
if (ISO_DATE.test(value)) {
return (
{formatRelativeTime(value)}
)
}
return {value}
}
if (typeof value === 'number' || typeof value === 'boolean') {
return {String(value)}
}
if (Array.isArray(value)) {
if (value.every(v => typeof v === 'string' || typeof v === 'number')) {
return {value.join(', ')}
}
return (
{value.map((v, i) => (
))}
)
}
if (typeof value === 'object' && depth < 3) {
return
}
return (
{JSON.stringify(value, null, 2)}
)
}
export interface JsonDetailListProps {
/** Object, array, JSON string, or any scalar — rendered faithfully either way. */
data: unknown
/** Internal nesting level — leave at the default from call sites. */
depth?: number
}
export function JsonDetailList({ data, depth = 0 }: JsonDetailListProps) {
let parseFailed = false
const obj =
typeof data === 'string'
? (() => {
try {
return JSON.parse(data) as unknown
} catch {
parseFailed = true
return null
}
})()
: data
if (!obj || typeof obj !== 'object' || Array.isArray(obj)) {
// A string that ISN'T JSON is prose — show it as-is.
if (typeof data === 'string' && parseFailed) {
return {data}
}
// Everything else is a real (parsed or given) non-object VALUE — an array,
// scalar, or null. Render the value through `Value`, never the source
// string: `'["a","b"]'` must render as the array, not as raw JSON text.
return (
)
}
return (