feat: add public SharedSessionPage with tree preview

Add the public-facing shared session page at /share/:shareToken that
renders shared sessions without authentication. Includes error handling
for 401 (redirect to login), 403 (access denied), 404 (not found),
and 410 (expired). The page features a minimal header, session metadata,
SessionTimeline component, and a new SharedSessionTreePreview component
that renders the decision tree structure with the path taken highlighted.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
chihlasm
2026-02-14 16:20:28 -05:00
parent 91ba4fccdf
commit d7641f2f84
3 changed files with 363 additions and 0 deletions

View File

@@ -0,0 +1,88 @@
import { cn } from '@/lib/utils'
interface SharedSessionTreePreviewProps {
treeStructure: Record<string, unknown>
pathTaken: string[]
}
const nodeTypeColors: Record<string, string> = {
root: 'bg-white',
decision: 'bg-blue-400',
action: 'bg-yellow-400',
solution: 'bg-emerald-400',
information: 'bg-white/50',
}
function getNodeTitle(node: Record<string, unknown>): string {
return (
(node.question as string) ||
(node.title as string) ||
(node.node_type as string) ||
'Untitled'
)
}
function TreeNode({
node,
depth,
pathTaken,
}: {
node: Record<string, unknown>
depth: number
pathTaken: string[]
}) {
const nodeId = (node.id as string) || ''
const nodeType = (node.node_type as string) || 'decision'
const isInPath = pathTaken.includes(nodeId)
const children = (node.children as Record<string, unknown>[]) || []
const colorClass = nodeTypeColors[nodeType] || 'bg-white/50'
return (
<>
<div
className={cn(
'flex items-center gap-2 px-3 py-1.5 text-sm',
isInPath
? 'rounded-md border-l-2 border-white/40 bg-white/10 font-medium text-white'
: 'text-white/30'
)}
style={{ paddingLeft: `${depth * 16 + 12}px` }}
>
<span
className={cn('h-2 w-2 shrink-0 rounded-full', colorClass)}
/>
<span className="truncate">{getNodeTitle(node)}</span>
</div>
{children.map((child, index) => (
<TreeNode
key={(child.id as string) || index}
node={child}
depth={depth + 1}
pathTaken={pathTaken}
/>
))}
</>
)
}
export function SharedSessionTreePreview({
treeStructure,
pathTaken,
}: SharedSessionTreePreviewProps) {
if (!treeStructure) {
return null
}
return (
<div className="glass-card rounded-2xl">
<div className="sticky top-0 z-10 rounded-t-2xl border-b border-white/[0.06] bg-black/80 px-6 py-4 backdrop-blur">
<h3 className="text-sm font-semibold text-white">Tree Structure</h3>
</div>
<div className="max-h-[600px] overflow-y-auto py-2">
<TreeNode node={treeStructure} depth={0} pathTaken={pathTaken} />
</div>
</div>
)
}
export default SharedSessionTreePreview