diff --git a/frontend/src/pages/TreeNavigationPage.tsx b/frontend/src/pages/TreeNavigationPage.tsx index 3a11fb20..e8a1c0f6 100644 --- a/frontend/src/pages/TreeNavigationPage.tsx +++ b/frontend/src/pages/TreeNavigationPage.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react' +import { useEffect, useRef, useState } from 'react' import { useParams, useNavigate, useLocation } from 'react-router-dom' import { treesApi } from '@/api/trees' import { sessionsApi } from '@/api/sessions' @@ -10,9 +10,11 @@ import { cn, safeGetItem, safeSetItem } from '@/lib/utils' import { MarkdownContent } from '@/components/ui/MarkdownContent' import { CustomStepModal } from '@/components/step-library/CustomStepModal' import { PostStepActionModal, ContinuationModal, ForkTreeModal, ScratchpadSidebar, SessionOutcomeModal } from '@/components/session' -import { Plus, CheckCircle, ArrowRight, Clock, Terminal, Clipboard, Check, Copy, HelpCircle } from 'lucide-react' +import { Plus, CheckCircle, ArrowRight, Clock, Terminal, Clipboard, Check, Copy, HelpCircle, Link2, ChevronDown, Settings } from 'lucide-react' import { toast } from '@/lib/toast' import { Modal } from '@/components/common/Modal' +import { ShareSessionModal } from '@/components/session/ShareSessionModal' +import { buildSessionShareUrl, getLatestActiveShareForSession } from '@/lib/sessionShare' interface LocationState { sessionId?: string @@ -48,6 +50,11 @@ export function TreeNavigationPage() { const [selectingOption, setSelectingOption] = useState(null) const [copiedForTicket, setCopiedForTicket] = useState(false) const [isCopyingForTicket, setIsCopyingForTicket] = useState(false) + const [showSharePopover, setShowSharePopover] = useState(false) + const [showShareModal, setShowShareModal] = useState(false) + const [copiedShareLink, setCopiedShareLink] = useState(false) + const [isCopyingShareLink, setIsCopyingShareLink] = useState(false) + const sharePopoverRef = useRef(null) const handleCopyCommand = (text: string) => { navigator.clipboard.writeText(text) @@ -78,6 +85,55 @@ export function TreeNavigationPage() { } } + const handleCopyShareLink = async () => { + if (!session || isCopyingShareLink) return + setIsCopyingShareLink(true) + try { + const allShares = await sessionsApi.listMyShares() + const existingShare = getLatestActiveShareForSession(allShares, session.id) + let shareUrl: string + if (existingShare) { + shareUrl = buildSessionShareUrl(existingShare) + } else { + const newShare = await sessionsApi.createShare(session.id, { visibility: 'account' }) + shareUrl = buildSessionShareUrl(newShare) + } + await navigator.clipboard.writeText(shareUrl) + setCopiedShareLink(true) + toast.success('Share link copied to clipboard') + setTimeout(() => setCopiedShareLink(false), 2000) + } catch (err) { + console.error('Copy share link failed:', err) + toast.error('Failed to copy share link') + } finally { + setIsCopyingShareLink(false) + } + } + + // Close share popover on outside click + useEffect(() => { + if (!showSharePopover) return + const handleMouseDown = (e: MouseEvent) => { + if (sharePopoverRef.current && !sharePopoverRef.current.contains(e.target as Node)) { + setShowSharePopover(false) + } + } + document.addEventListener('mousedown', handleMouseDown) + return () => document.removeEventListener('mousedown', handleMouseDown) + }, [showSharePopover]) + + // Close share popover on Escape key + useEffect(() => { + if (!showSharePopover) return + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Escape') { + setShowSharePopover(false) + } + } + document.addEventListener('keydown', handleKeyDown) + return () => document.removeEventListener('keydown', handleKeyDown) + }, [showSharePopover]) + // Session metadata (prefill from Repeat Last Session) const [ticketNumber, setTicketNumber] = useState(locationState?.prefillTicketNumber || '') const [clientName, setClientName] = useState(locationState?.prefillClientName || '') @@ -576,18 +632,70 @@ export function TreeNavigationPage() { )}
- + {showSharePopover && ( +
+ {/* Copy Progress Summary */} + + {/* Copy Share Link */} + + {/* Divider */} +
+ {/* Manage Share Links */} + +
)} - > - {copiedForTicket ? : } - {copiedForTicket ? 'Copied!' : 'Copy for Ticket'} - +
+ + {/* Share Session Modal */} + {session && ( + setShowShareModal(false)} + /> + )}