feat: session sharing frontend (#76)
* feat: add session sharing types, API client, and utilities Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add SessionTimeline and ActionMenu reusable components SessionTimeline extracts timeline/checklist rendering from SessionDetailPage into a reusable component for both authenticated and public session views. ActionMenu provides a dropdown action menu with keyboard/click-outside dismiss. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add ShareSessionModal and integrate into SessionDetailPage Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add Share Progress popover to TreeNavigationPage Replace the single "Copy for Ticket" button with a "Share Progress" popover that offers three actions: Copy Progress Summary (existing PSA export flow), Copy Share Link (auto-creates account-only share if needed), and Manage Share Links (opens ShareSessionModal). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * 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> * feat: add My Shares management page with nav link Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address code review issues in session sharing - Add useCallback for loadShares in ShareSessionModal (React hook deps) - Use TreeStructure type instead of Record<string, unknown> for type safety - Fix login redirect format to match LoginPage's expected state shape Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test: add focused tests for session sharing utilities and API Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: resolve tree_structure type compatibility for shared session views - Use TreeStructure & Record<string, unknown> intersection for JSONB flexibility - Add explicit cast in SharedSessionTreePreview for recursive node rendering Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: add session sharing learnings to CLAUDE.md Add gotchas #12 (TreeStructure vs Tree types) and #13 (login redirect state format), note about npm run build strictness, and public route pattern to Common Tasks. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: procedural editor UX improvements Add URL intake field type, fix variable name editing collapsing fields (index-based keys/updates), auto-generate variable names by field type, add section header as first-class step type, and simplify step editor with "More Options" collapsible for advanced fields. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: allow section_header step type in validation, improve tag input - Add 'section_header' to VALID_STEP_TYPES in backend validation so procedural flows with section headers can be published - Replace procedural editor's inline tag input with TagInput component (supports autocomplete, Tab, comma, semicolon, and paste splitting) - Add semicolon delimiter support to TagInput component Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: add type-aware routing for procedural flows Centralizes tree navigation routing via getTreeNavigatePath helper. Fixes all pages to route procedural sessions to /flows/:id/navigate instead of /trees/:id/navigate. Adds safety redirect in troubleshooting navigator and resume support in procedural navigator. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: remove unused index prop from IntakeFieldEditor Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit was merged in pull request #76.
This commit is contained in:
89
frontend/src/components/session/SharedSessionTreePreview.tsx
Normal file
89
frontend/src/components/session/SharedSessionTreePreview.tsx
Normal file
@@ -0,0 +1,89 @@
|
||||
import type { TreeStructure } from '@/types'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
interface SharedSessionTreePreviewProps {
|
||||
treeStructure: TreeStructure
|
||||
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 as unknown as Record<string, unknown>} depth={0} pathTaken={pathTaken} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SharedSessionTreePreview
|
||||
Reference in New Issue
Block a user