feat: add session sharing types, API client, and utilities

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
chihlasm
2026-02-14 16:06:31 -05:00
parent 6b4304ab92
commit c24e84d8a0
3 changed files with 90 additions and 1 deletions

View File

@@ -1,5 +1,5 @@
import apiClient from './client'
import type { Session, SessionCreate, SessionUpdate, SessionExport, SaveAsTreeRequest, SaveAsTreeResponse, SessionComplete, RedactionSummary } from '@/types'
import type { Session, SessionCreate, SessionUpdate, SessionExport, SaveAsTreeRequest, SaveAsTreeResponse, SessionComplete, RedactionSummary, SessionShareCreate, SessionShare, SharedSessionView } from '@/types'
export interface SessionListParams {
page?: number
@@ -85,6 +85,26 @@ export const sessionsApi = {
const response = await apiClient.post<SaveAsTreeResponse>(`/sessions/${id}/save-as-tree`, data)
return response.data
},
// Session Sharing
async createShare(sessionId: string, data: SessionShareCreate): Promise<SessionShare> {
const response = await apiClient.post<SessionShare>(`/sessions/${sessionId}/shares`, data)
return response.data
},
async listMyShares(): Promise<SessionShare[]> {
const response = await apiClient.get<SessionShare[]>('/shares/my-shares')
return response.data
},
async revokeShare(shareId: string): Promise<void> {
await apiClient.delete(`/shares/${shareId}`)
},
async getSharedSession(shareToken: string): Promise<SharedSessionView> {
const response = await apiClient.get<SharedSessionView>(`/share/${shareToken}`)
return response.data
},
}
export default sessionsApi

View File

@@ -0,0 +1,27 @@
import type { SessionShare } from '@/types'
/**
* Build the full share URL from a SessionShare object.
* Uses share.share_url if present, otherwise constructs from token.
*/
export function buildSessionShareUrl(share: SessionShare): string {
if (share.share_url) return share.share_url
return `${window.location.origin}/share/${share.share_token}`
}
/**
* Filter shares to only those belonging to a specific session.
*/
export function filterSharesForSession(shares: SessionShare[], sessionId: string): SessionShare[] {
return shares.filter(s => s.session_id === sessionId && s.is_active)
}
/**
* Get the most recent active share for a given session.
* Returns null if no active shares exist.
*/
export function getLatestActiveShareForSession(shares: SessionShare[], sessionId: string): SessionShare | null {
const sessionShares = filterSharesForSession(shares, sessionId)
if (sessionShares.length === 0) return null
return sessionShares.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())[0]
}

View File

@@ -125,3 +125,45 @@ export interface SaveAsTreeResponse {
tree_name: string
message: string
}
// Session Sharing
export type SessionShareVisibility = 'public' | 'account'
export interface SessionShareCreate {
visibility: SessionShareVisibility
share_name?: string
expires_at?: string // ISO datetime string
}
export interface SessionShare {
id: string
session_id: string
account_id: string
share_token: string
share_name: string | null
visibility: SessionShareVisibility
created_by: string
created_at: string
updated_at: string
expires_at: string | null
view_count: number
last_viewed_at: string | null
is_active: boolean
share_url: string | null
}
export interface SharedSessionView {
session_id: string
tree_name: string
tree_description: string | null
tree_structure: Record<string, unknown>
path_taken: string[]
decisions: DecisionRecord[]
custom_steps: CustomStep[]
started_at: string
completed_at: string | null
ticket_number: string | null
client_name: string | null
share_name: string | null
visibility: SessionShareVisibility
}