diff --git a/frontend/src/api/sessions.ts b/frontend/src/api/sessions.ts index 43691ef1..635d593a 100644 --- a/frontend/src/api/sessions.ts +++ b/frontend/src/api/sessions.ts @@ -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(`/sessions/${id}/save-as-tree`, data) return response.data }, + + // Session Sharing + async createShare(sessionId: string, data: SessionShareCreate): Promise { + const response = await apiClient.post(`/sessions/${sessionId}/shares`, data) + return response.data + }, + + async listMyShares(): Promise { + const response = await apiClient.get('/shares/my-shares') + return response.data + }, + + async revokeShare(shareId: string): Promise { + await apiClient.delete(`/shares/${shareId}`) + }, + + async getSharedSession(shareToken: string): Promise { + const response = await apiClient.get(`/share/${shareToken}`) + return response.data + }, } export default sessionsApi diff --git a/frontend/src/lib/sessionShare.ts b/frontend/src/lib/sessionShare.ts new file mode 100644 index 00000000..040564c6 --- /dev/null +++ b/frontend/src/lib/sessionShare.ts @@ -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] +} diff --git a/frontend/src/types/session.ts b/frontend/src/types/session.ts index d262b3d7..193e1c52 100644 --- a/frontend/src/types/session.ts +++ b/frontend/src/types/session.ts @@ -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 + 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 +}