@burdenoff/microfe-conversations 2026.830.1 → 2026.924.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"ConversationDetailPage.js","names":[],"sources":["../../../src/conversations/pages/ConversationDetailPage.tsx"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport { useEventBus } from \"@burdenoff/fe-libs/shared/events\";\nimport { isQuotaExhaustedError } from \"@burdenoff/fe-libs/shared/utils\";\nimport { useNavigate, useParams } from \"react-router\";\nimport {\n Archive,\n ArrowLeft,\n BellOff,\n Bell,\n ExternalLink,\n Hash,\n LogOut,\n Loader2,\n MoreVertical,\n Pin,\n PinOff,\n SendHorizontal,\n Users,\n X,\n} from \"lucide-react\";\nimport {\n Button,\n IllustratedEmptyState,\n NextSteps,\n type NextStep,\n} from \"@burdenoff/fe-libs/ui\";\nimport { MessageBubble } from \"../components/MessageBubble\";\nimport {\n useGetConversation,\n useConversationActions,\n} from \"../hooks/useConversationQueries\";\nimport {\n useGetMessages,\n useSendMessage,\n useEditMessage,\n useMessageActions,\n} from \"../hooks/useMessageQueries\";\nimport { useMessageStreamForConversation } from \"../hooks/useMessageSubscriptions\";\nimport { useConversations } from \"../providers/ConversationsProvider\";\nimport { useConversationsStore } from \"../store/conversationsStore\";\nimport { cn } from \"../utils/cn\";\nimport { PermissionGuard } from \"../components/PermissionGuard\";\nimport { QuotaExhaustedDialog } from \"../components/QuotaExhaustedDialog\";\nimport { CONVERSATIONS_PERMISSIONS } from \"../constants/permissions\";\nimport { nativeImpact, nativeNotify } from \"../../utils/nativeBridge\";\nimport type { Message } from \"../types\";\n\nconst MESSAGES_PAGE_SIZE = 100;\nconst MESSAGE_REFRESH_FALLBACK_MS = 30_000;\n\nexport function ConversationDetailPage() {\n const { conversationId } = useParams<{ conversationId: string }>();\n const navigate = useNavigate();\n const { basePath, currentUser } = useConversations();\n const bus = useEventBus();\n const { t } = useI18n();\n const tr = useCallback(\n (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n },\n [t],\n );\n const {\n clearMessageDraft,\n isParticipantsPanelOpen,\n messageDrafts,\n setMessageDraft,\n toggleParticipantsPanel,\n } = useConversationsStore();\n\n const [messageInput, setMessageInput] = useState(\n messageDrafts[conversationId ?? \"\"] ?? \"\",\n );\n const [showMessageQuotaExhausted, setShowMessageQuotaExhausted] =\n useState(false);\n const [locallySentMessages, setLocallySentMessages] = useState<Message[]>([]);\n const messagesEndRef = useRef<HTMLDivElement>(null);\n const composerRef = useRef<HTMLTextAreaElement>(null);\n\n const focusComposer = useCallback(() => {\n composerRef.current?.focus();\n composerRef.current?.scrollIntoView({ behavior: \"smooth\" });\n }, []);\n\n const {\n conversation,\n members: conversationMembers,\n membersTotalCount,\n loading: conversationLoading,\n error: conversationError,\n } = useGetConversation(conversationId ?? \"\");\n const {\n messages,\n loading: messagesLoading,\n error: messagesError,\n refetch: refetchMessages,\n } = useGetMessages(conversationId ?? \"\", {\n first: MESSAGES_PAGE_SIZE,\n pollInterval: MESSAGE_REFRESH_FALLBACK_MS,\n });\n // Subscribe to live message events; the query has a light polling fallback for\n // local/federated transports that keep the stream open but miss events.\n useMessageStreamForConversation(conversationId, MESSAGES_PAGE_SIZE);\n const { sendMessage, loading: sendingMessage } = useSendMessage();\n const { editMessage, loading: editingMessage } = useEditMessage();\n const {\n deleteMessage,\n addReaction,\n removeReaction,\n pinMessage,\n unpinMessage,\n } = useMessageActions();\n const {\n archiveConversation,\n pinConversation,\n unpinConversation,\n muteConversation,\n unmuteConversation,\n leaveConversation,\n } = useConversationActions();\n\n const [moreMenuOpen, setMoreMenuOpen] = useState(false);\n const [editingMessageId, setEditingMessageId] = useState<string | null>(null);\n const [editingContent, setEditingContent] = useState(\"\");\n const [replyingTo, setReplyingTo] = useState<{\n id: string;\n content: string;\n sender: string;\n } | null>(null);\n const moreMenuRef = useRef<HTMLDivElement>(null);\n\n const currentUserId = currentUser?.id ?? \"\";\n const sourceContext = conversation?.sourceContext;\n\n const displayedMessages = useMemo(() => {\n const byId = new Map<string, Message>();\n for (const message of locallySentMessages) {\n if (message.conversationId !== conversationId) continue;\n byId.set(message.id, message);\n }\n for (const message of messages) {\n byId.set(message.id, message);\n }\n return Array.from(byId.values()).sort(\n (a, b) =>\n new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime(),\n );\n }, [conversationId, locallySentMessages, messages]);\n\n useEffect(() => {\n messagesEndRef.current?.scrollIntoView({ behavior: \"smooth\" });\n }, [displayedMessages.length]);\n\n useEffect(() => {\n setLocallySentMessages([]);\n }, [conversationId]);\n\n useEffect(() => {\n if (locallySentMessages.length === 0 || messages.length === 0) return;\n const serverIds = new Set(messages.map((message) => message.id));\n setLocallySentMessages((current) => {\n const pending = current.filter((message) => !serverIds.has(message.id));\n return pending.length === current.length ? current : pending;\n });\n }, [locallySentMessages.length, messages, setLocallySentMessages]);\n\n useEffect(() => {\n if (!conversationId) return;\n if (messageInput.trim()) {\n setMessageDraft(conversationId, messageInput);\n return;\n }\n clearMessageDraft(conversationId);\n }, [messageInput, conversationId, setMessageDraft, clearMessageDraft]);\n\n const messageAuthorNames = useMemo(() => {\n const byAuthor = new Map<string, string>();\n for (const message of displayedMessages) {\n if (!message.authorId || byAuthor.has(message.authorId)) continue;\n if (!message.sender.name) continue;\n byAuthor.set(message.authorId, message.sender.name);\n }\n return byAuthor;\n }, [displayedMessages]);\n\n const participants = useMemo(() => {\n if (conversationMembers.length > 0) {\n return conversationMembers\n .filter((member) => member.status === \"ACTIVE\" && !member.leftAt)\n .map((member) => ({\n id: member.userId,\n name:\n messageAuthorNames.get(member.userId) ||\n member.customTitle ||\n tr(\"conversations.member\", \"Member\"),\n role: member.role,\n }));\n }\n\n return Array.from(messageAuthorNames.entries()).map(([id, name]) => ({\n id,\n name,\n role: tr(\"conversations.member\", \"Member\"),\n }));\n }, [conversationMembers, messageAuthorNames, tr]);\n\n const hiddenParticipantsCount = conversation?.memberCount\n ? Math.max(conversation.memberCount - participants.length, 0)\n : 0;\n\n const handleSendMessage = useCallback(async () => {\n if (!conversationId || !messageInput.trim() || sendingMessage) return;\n let message: Message | null;\n try {\n message = await sendMessage({\n conversationId,\n content: messageInput.trim(),\n type: \"TEXT\",\n parentMessageId: replyingTo?.id,\n refetchFirst: MESSAGES_PAGE_SIZE,\n });\n } catch (error) {\n if (isQuotaExhaustedError(error)) {\n setShowMessageQuotaExhausted(true);\n return;\n }\n nativeNotify(\"error\");\n throw error;\n }\n if (!message) return;\n setLocallySentMessages((current) => [...current, message]);\n bus.emit(\"conversations.message.sent\", {\n messageId: message.id,\n conversationId,\n });\n setMessageInput(\"\");\n setReplyingTo(null);\n clearMessageDraft(conversationId);\n }, [\n bus,\n conversationId,\n messageInput,\n sendingMessage,\n sendMessage,\n clearMessageDraft,\n replyingTo,\n ]);\n\n const handleKeyDown = useCallback(\n (event: React.KeyboardEvent<HTMLTextAreaElement>) => {\n if (event.key !== \"Enter\" || event.shiftKey) return;\n event.preventDefault();\n void handleSendMessage();\n },\n [handleSendMessage],\n );\n\n // Emit opened event so sinks (analytics/audit) can record the view\n useEffect(() => {\n if (!conversationId) return;\n bus.emit(\"conversations.conversation.opened\", { conversationId });\n }, [bus, conversationId]);\n\n // Close more-menu on outside click\n useEffect(() => {\n if (!moreMenuOpen) return;\n const handler = (e: MouseEvent) => {\n if (\n moreMenuRef.current &&\n !moreMenuRef.current.contains(e.target as Node)\n ) {\n setMoreMenuOpen(false);\n }\n };\n document.addEventListener(\"mousedown\", handler);\n return () => document.removeEventListener(\"mousedown\", handler);\n }, [moreMenuOpen]);\n\n const handleReply = useCallback(\n (message: { id: string; content: string; sender: { name: string } }) => {\n setReplyingTo({\n id: message.id,\n content: message.content,\n sender: message.sender.name,\n });\n },\n [],\n );\n\n const handleReact = useCallback(\n async (messageId: string, emoji: string) => {\n try {\n await addReaction(messageId, emoji);\n } catch {\n // If already reacted, toggle off\n await removeReaction(messageId, emoji).catch(() => {});\n }\n },\n [addReaction, removeReaction],\n );\n\n const handleEdit = useCallback((message: { id: string; content: string }) => {\n setEditingMessageId(message.id);\n setEditingContent(message.content);\n }, []);\n\n const handleEditSubmit = useCallback(async () => {\n if (!editingMessageId || !editingContent.trim() || editingMessage) return;\n await editMessage(editingMessageId, editingContent.trim());\n setEditingMessageId(null);\n setEditingContent(\"\");\n }, [editingMessageId, editingContent, editingMessage, editMessage]);\n\n const handleEditCancel = useCallback(() => {\n setEditingMessageId(null);\n setEditingContent(\"\");\n }, []);\n\n const handleDelete = useCallback(\n async (messageId: string) => {\n const confirmed = window.confirm(\n tr(\n \"conversations.confirmDeleteMessage\",\n \"Delete this message? This action cannot be undone.\",\n ),\n );\n if (!confirmed) return;\n nativeImpact(\"medium\");\n try {\n await deleteMessage(messageId);\n void refetchMessages();\n nativeNotify(\"success\");\n } catch (error) {\n console.error(\"[microfe-conversations] message delete failed\", error);\n nativeNotify(\"error\");\n }\n },\n [deleteMessage, refetchMessages, tr],\n );\n\n const handleTogglePinMessage = useCallback(\n async (messageId: string, isPinned: boolean) => {\n if (isPinned) {\n await unpinMessage(messageId);\n return;\n }\n await pinMessage(messageId);\n },\n [pinMessage, unpinMessage],\n );\n\n const handlePin = useCallback(async () => {\n if (!conversationId) return;\n if (conversation?.isPinned) {\n await unpinConversation(conversationId);\n } else {\n await pinConversation(conversationId);\n }\n setMoreMenuOpen(false);\n }, [\n conversationId,\n conversation?.isPinned,\n pinConversation,\n unpinConversation,\n ]);\n\n const handleMute = useCallback(async () => {\n if (!conversationId) return;\n if (conversation?.isMuted) {\n await unmuteConversation(conversationId);\n } else {\n await muteConversation(conversationId);\n }\n setMoreMenuOpen(false);\n }, [\n conversationId,\n conversation?.isMuted,\n muteConversation,\n unmuteConversation,\n ]);\n\n const handleArchive = useCallback(async () => {\n if (!conversationId) return;\n try {\n await archiveConversation(conversationId);\n bus.emit(\"conversations.conversation.archived\", { conversationId });\n setMoreMenuOpen(false);\n navigate(basePath || \"/conversations\");\n } catch (error) {\n console.error(\n \"[microfe-conversations] conversation archive failed\",\n error,\n );\n nativeNotify(\"error\");\n }\n }, [archiveConversation, basePath, bus, conversationId, navigate]);\n\n const handleLeave = useCallback(async () => {\n if (!conversationId) return;\n const confirmed = window.confirm(\n tr(\n \"conversations.confirmLeaveConversation\",\n \"Leave this conversation? You will lose access unless someone adds you again.\",\n ),\n );\n if (!confirmed) return;\n try {\n await leaveConversation(conversationId);\n if (currentUserId) {\n bus.emit(\"conversations.member.removed\", {\n conversationId,\n userId: currentUserId,\n });\n }\n setMoreMenuOpen(false);\n navigate(basePath || \"/conversations\");\n } catch (error) {\n console.error(\"[microfe-conversations] conversation leave failed\", error);\n nativeNotify(\"error\");\n }\n }, [\n basePath,\n bus,\n conversationId,\n currentUserId,\n leaveConversation,\n navigate,\n tr,\n ]);\n\n const handleOpenContext = useCallback(() => {\n if (!sourceContext?.contextHref) return;\n try {\n const url = new URL(sourceContext.contextHref);\n if (url.origin === window.location.origin) {\n navigate(`${url.pathname}${url.search}${url.hash}`);\n return;\n }\n } catch {\n // Fall through to full navigation for relative or invalid URLs.\n }\n\n window.location.assign(sourceContext.contextHref);\n }, [navigate, sourceContext?.contextHref]);\n\n // Concrete next actions surfaced on the (complex) detail page so a new user\n // isn't left staring at an unfamiliar surface. Each step is wired to a real\n // handler already present on this page; steps that don't apply to the\n // current conversation type/state are filtered out.\n const detailNextSteps = useMemo<NextStep[]>(() => {\n const steps: NextStep[] = [\n {\n id: \"send-message\",\n label: tr(\n \"conversations.nextSteps.send.label\",\n \"Send your first reply\",\n ),\n description: tr(\n \"conversations.nextSteps.send.description\",\n \"Jump to the composer and start the conversation.\",\n ),\n icon: <SendHorizontal size={14} aria-hidden=\"true\" />,\n onClick: focusComposer,\n done: displayedMessages.length > 0,\n },\n ];\n if (conversation?.isGroup) {\n steps.push({\n id: \"view-participants\",\n label: tr(\n \"conversations.nextSteps.participants.label\",\n \"Review participants\",\n ),\n description: tr(\n \"conversations.nextSteps.participants.description\",\n \"See who can read and post in this conversation.\",\n ),\n icon: <Users size={14} aria-hidden=\"true\" />,\n onClick: () => {\n if (!isParticipantsPanelOpen) toggleParticipantsPanel();\n },\n });\n }\n if (sourceContext?.contextHref) {\n steps.push({\n id: \"open-context\",\n label: tr(\n \"conversations.nextSteps.context.label\",\n \"Open the related item\",\n ),\n description: tr(\n \"conversations.nextSteps.context.description\",\n \"Jump to the page or record this conversation is about.\",\n ),\n icon: <ExternalLink size={14} aria-hidden=\"true\" />,\n onClick: handleOpenContext,\n });\n }\n steps.push({\n id: \"pin-conversation\",\n label: conversation?.isPinned\n ? tr(\"conversations.nextSteps.unpin.label\", \"Unpin this conversation\")\n : tr(\"conversations.nextSteps.pin.label\", \"Pin for quick access\"),\n description: tr(\n \"conversations.nextSteps.pin.description\",\n \"Keep important conversations at the top of your inbox.\",\n ),\n icon: <Pin size={14} aria-hidden=\"true\" />,\n onClick: () => void handlePin(),\n done: conversation?.isPinned,\n });\n return steps;\n }, [\n conversation?.isGroup,\n conversation?.isPinned,\n displayedMessages.length,\n focusComposer,\n handleOpenContext,\n handlePin,\n isParticipantsPanelOpen,\n sourceContext?.contextHref,\n toggleParticipantsPanel,\n tr,\n ]);\n\n if (!conversation && conversationLoading) {\n return (\n <div className=\"flex items-center justify-center h-[calc(100vh-64px)] text-sm text-text-secondary\">\n {tr(\"conversations.loading\", \"Loading conversation...\")}\n </div>\n );\n }\n\n if (!conversation || conversationError) {\n return (\n <div className=\"flex h-[calc(100vh-64px)] items-center justify-center p-8\">\n <IllustratedEmptyState\n illustration=\"empty-generic\"\n title={\n conversationError\n ? tr(\"conversations.unavailableTitle\", \"Conversation unavailable\")\n : tr(\"conversations.notFound\", \"Conversation not found\")\n }\n description={\n conversationError\n ? tr(\n \"conversations.unavailable\",\n \"We couldn't load this conversation. Please try again.\",\n )\n : tr(\n \"conversations.notFoundDescription\",\n \"This conversation may have been deleted, archived, or you no longer have access to it.\",\n )\n }\n action={\n <Button\n type=\"button\"\n onClick={() => navigate(basePath || \"/conversations\")}\n >\n {tr(\"conversations.back\", \"Back to conversations\")}\n </Button>\n }\n />\n </div>\n );\n }\n\n return (\n <div className=\"relative flex h-[calc(100vh-64px)] bg-bg-surface\">\n <div className=\"flex-1 flex flex-col min-w-0\">\n <header\n data-tour=\"conversation-detail-header\"\n className=\"flex items-center justify-between px-4 py-3 border-b border-border-seam bg-bg-surface/95 backdrop-blur-sm\"\n >\n <div className=\"flex items-center gap-3 min-w-0\">\n <button\n type=\"button\"\n onClick={() => navigate(basePath || \"/conversations\")}\n className=\"flex min-h-10 min-w-10 items-center justify-center rounded-lg text-text-secondary transition-colors hover:bg-bg-sunken/50 hover:text-text-primary\"\n >\n <ArrowLeft size={18} />\n </button>\n <div className=\"min-w-0\">\n <div className=\"flex items-center gap-1.5\">\n {conversation.type === \"CHANNEL\" && (\n <Hash\n size={14}\n className=\"text-text-secondary flex-shrink-0\"\n />\n )}\n <h2 className=\"text-sm font-semibold text-text-primary truncate\">\n {conversation.title}\n </h2>\n </div>\n <p className=\"text-xs text-text-secondary\">\n {conversation.type === \"DIRECT\"\n ? tr(\n \"conversations.directConversation\",\n \"Direct conversation\",\n )\n : conversation.type === \"CHANNEL\"\n ? `${tr(\"conversations.channel\", \"Channel\")} · ${conversation.memberCount} ${tr(\"conversations.membersCountLabel\", \"members\")}`\n : conversation.type === \"ANNOUNCEMENT\"\n ? `${tr(\"conversations.announcement\", \"Announcement\")} · ${conversation.memberCount} ${tr(\"conversations.membersCountLabel\", \"members\")}`\n : `${conversation.memberCount} ${tr(\"conversations.membersCountLabel\", \"members\")}`}\n </p>\n </div>\n </div>\n\n <div className=\"flex shrink-0 items-center gap-1\">\n {sourceContext?.contextHref && (\n <HeaderButton\n dataTour=\"conversation-open-context\"\n icon={<ExternalLink size={16} />}\n label={tr(\"conversations.openContext\", \"Open context\")}\n onClick={handleOpenContext}\n />\n )}\n {conversation.isGroup && (\n <HeaderButton\n dataTour=\"conversation-participants-button\"\n icon={<Users size={16} />}\n label={tr(\"conversations.participants\", \"Participants\")}\n active={isParticipantsPanelOpen}\n onClick={toggleParticipantsPanel}\n />\n )}\n <div className=\"relative\" ref={moreMenuRef}>\n <HeaderButton\n icon={<MoreVertical size={16} />}\n label={tr(\"conversations.moreOptions\", \"More options\")}\n active={moreMenuOpen}\n onClick={() => setMoreMenuOpen((prev) => !prev)}\n />\n {moreMenuOpen && (\n <div className=\"absolute right-0 top-full mt-1 w-48 bg-popover border border-border-subtle rounded-lg shadow-lg z-50 py-1 animate-in fade-in-0 zoom-in-95 duration-100\">\n <DropdownItem\n icon={\n conversation.isPinned ? (\n <PinOff size={14} />\n ) : (\n <Pin size={14} />\n )\n }\n label={\n conversation.isPinned\n ? tr(\n \"conversations.unpinConversation\",\n \"Unpin conversation\",\n )\n : tr(\n \"conversations.pinConversation\",\n \"Pin conversation\",\n )\n }\n onClick={() => void handlePin()}\n />\n <DropdownItem\n icon={\n conversation.isMuted ? (\n <Bell size={14} />\n ) : (\n <BellOff size={14} />\n )\n }\n label={\n conversation.isMuted\n ? tr(\n \"conversations.unmuteConversation\",\n \"Unmute conversation\",\n )\n : tr(\n \"conversations.muteConversation\",\n \"Mute conversation\",\n )\n }\n onClick={() => void handleMute()}\n />\n <div className=\"h-px bg-border my-1\" />\n <DropdownItem\n icon={<Archive size={14} />}\n label={tr(\n \"conversations.archiveConversation\",\n \"Archive conversation\",\n )}\n onClick={() => void handleArchive()}\n destructive\n />\n {conversation.isGroup && (\n <DropdownItem\n icon={<LogOut size={14} />}\n label={tr(\n \"conversations.leaveConversation\",\n \"Leave conversation\",\n )}\n onClick={() => void handleLeave()}\n destructive\n />\n )}\n </div>\n )}\n </div>\n </div>\n </header>\n\n <div className=\"flex-1 overflow-y-auto py-4 space-y-0.5\">\n {sourceContext?.pagePath && (\n <div className=\"mx-4 mb-4 rounded-lg border border-border-subtle bg-bg-sunken/20 px-3 py-2 text-xs text-text-secondary\">\n <span className=\"font-medium text-text-primary\">\n {sourceContext.contextTitle || sourceContext.pagePath}\n </span>\n {sourceContext.entityType && sourceContext.entityId\n ? ` · ${sourceContext.entityType} ${sourceContext.entityId}`\n : \"\"}\n </div>\n )}\n {messagesError && (\n <div className=\"mx-4 mb-4 rounded-lg border border-status-warning-border bg-status-warning-bg px-3 py-2 text-sm text-status-warning-text\">\n {tr(\n \"conversations.messagesUnavailable\",\n \"Messages couldn't be loaded right now. The conversation header and actions are still available.\",\n )}\n </div>\n )}\n {messagesLoading && (\n <div className=\"mx-4 mb-4 flex items-center gap-2 rounded-lg border border-border-subtle bg-bg-sunken/20 px-3 py-2 text-sm text-text-secondary\">\n <Loader2 size={14} className=\"animate-spin\" />\n {tr(\"conversations.loadingMessages\", \"Loading messages...\")}\n </div>\n )}\n {!messagesLoading &&\n !messagesError &&\n displayedMessages.length === 0 && (\n <div className=\"mx-4 mb-4 flex flex-col items-center gap-5 py-6\">\n <IllustratedEmptyState\n illustration=\"empty-vibes\"\n title={tr(\"conversations.noMessagesTitle\", \"No messages yet\")}\n description={tr(\n \"conversations.noMessagesYet\",\n \"Start the conversation below — say hello, ask a question, or share an update.\",\n )}\n action={\n <Button type=\"button\" onClick={focusComposer}>\n <SendHorizontal size={14} aria-hidden=\"true\" />\n {tr(\"conversations.writeFirstMessage\", \"Write a message\")}\n </Button>\n }\n />\n <NextSteps\n className=\"w-full max-w-md\"\n storageKey=\"conversations-detail\"\n title={tr(\n \"conversations.nextSteps.title\",\n \"Get the most out of this conversation\",\n )}\n steps={detailNextSteps}\n />\n </div>\n )}\n {displayedMessages.map((message, index) => {\n const prevMessage = displayedMessages[index - 1];\n const showAvatar =\n !prevMessage ||\n prevMessage.authorId !== message.authorId ||\n prevMessage.type === \"SYSTEM\";\n\n return (\n <div key={message.id}>\n {editingMessageId === message.id ? (\n <div className=\"px-4 py-2\">\n <div className=\"flex items-center gap-2 text-xs text-text-secondary mb-1\">\n <span>\n {tr(\"conversations.editingMessage\", \"Editing message\")}\n </span>\n <button\n type=\"button\"\n onClick={handleEditCancel}\n className=\"hover:text-text-primary\"\n >\n <X size={12} />\n </button>\n </div>\n <div className=\"flex items-end gap-2\">\n <textarea\n aria-label=\"Edit message\"\n value={editingContent}\n onChange={(e) => setEditingContent(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n void handleEditSubmit();\n }\n if (e.key === \"Escape\") handleEditCancel();\n }}\n className=\"flex-1 px-3 py-2 text-sm rounded-lg border border-border-subtle bg-bg-sunken/30 resize-none focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n rows={2}\n />\n <button\n type=\"button\"\n onClick={() => void handleEditSubmit()}\n disabled={!editingContent.trim() || editingMessage}\n className=\"px-3 py-2 text-xs font-medium rounded-lg bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {tr(\"conversations.save\", \"Save\")}\n </button>\n </div>\n </div>\n ) : (\n <MessageBubble\n message={message}\n isOwnMessage={message.authorId === currentUserId}\n showAvatar={showAvatar}\n showSender={showAvatar}\n onReply={handleReply}\n onReact={(id, emoji) => void handleReact(id, emoji)}\n onEdit={handleEdit}\n onDelete={(id) => void handleDelete(id)}\n onTogglePin={(id, isPinned) =>\n void handleTogglePinMessage(id, isPinned)\n }\n />\n )}\n </div>\n );\n })}\n <div ref={messagesEndRef} />\n </div>\n\n <div\n data-tour=\"conversation-message-composer\"\n className=\"border-t border-border-seam px-4 py-3 bg-bg-surface\"\n >\n {/* Reply-to indicator */}\n {replyingTo && (\n <div className=\"flex items-center gap-2 mb-2 px-3 py-1.5 rounded-lg bg-bg-sunken/50 text-xs text-text-secondary\">\n <span>\n {tr(\"conversations.replyingTo\", \"Replying to\")}{\" \"}\n <strong className=\"text-text-primary\">\n {replyingTo.sender}\n </strong>\n : {replyingTo.content.slice(0, 80)}\n {replyingTo.content.length > 80 ? \"…\" : \"\"}\n </span>\n <button\n type=\"button\"\n onClick={() => setReplyingTo(null)}\n aria-label={tr(\"conversations.cancelReply\", \"Cancel reply\")}\n className=\"ml-auto flex min-h-9 min-w-9 items-center justify-center rounded-md hover:bg-bg-sunken/70 hover:text-text-primary\"\n >\n <X size={12} />\n </button>\n </div>\n )}\n <div className=\"flex items-end gap-2\">\n <div className=\"flex-1 relative\">\n <textarea\n aria-label={tr(\n \"conversations.messageComposer\",\n \"Message composer\",\n )}\n ref={composerRef}\n value={messageInput}\n onChange={(event) => setMessageInput(event.target.value)}\n onKeyDown={handleKeyDown}\n placeholder={`${tr(\"conversations.messageConversationPrefix\", \"Message\")} ${conversation.title}...`}\n rows={1}\n className={cn(\n \"w-full px-4 py-2.5 text-sm rounded-xl border border-border-subtle bg-bg-sunken/30\",\n \"resize-none overflow-hidden placeholder:text-text-secondary\",\n \"focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] focus:border-transparent max-h-32\",\n )}\n style={{\n height: \"auto\",\n minHeight: \"40px\",\n }}\n onInput={(event) => {\n const target = event.target as HTMLTextAreaElement;\n target.style.height = \"auto\";\n target.style.height = `${Math.min(target.scrollHeight, 128)}px`;\n }}\n />\n </div>\n\n <button\n type=\"button\"\n aria-label={tr(\"conversations.sendMessage\", \"Send message\")}\n onClick={() => void handleSendMessage()}\n disabled={!messageInput.trim() || sendingMessage}\n className={cn(\n \"p-2.5 rounded-xl transition-all duration-150\",\n messageInput.trim() && !sendingMessage\n ? \"bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg/90 shadow-sm\"\n : \"bg-bg-sunken text-text-secondary cursor-not-allowed\",\n )}\n >\n {sendingMessage ? (\n <Loader2 size={18} className=\"animate-spin\" />\n ) : (\n <SendHorizontal size={18} />\n )}\n </button>\n </div>\n </div>\n </div>\n\n {isParticipantsPanelOpen && conversation.isGroup && (\n <aside className=\"absolute inset-y-0 right-0 z-20 w-full max-w-72 border-l border-border-seam bg-bg-surface shadow-lg animate-in slide-in-from-right-4 duration-200 md:relative md:z-auto md:w-64 md:shadow-none\">\n <div className=\"p-4\">\n <div className=\"flex items-center justify-between mb-4\">\n <h3 className=\"text-sm font-semibold text-text-primary\">\n {tr(\"conversations.members\", \"Members\")} (\n {conversation.memberCount ?? participants.length})\n </h3>\n <button\n type=\"button\"\n onClick={toggleParticipantsPanel}\n className=\"p-1 rounded text-text-secondary hover:text-text-primary\"\n >\n <Pin size={14} />\n </button>\n </div>\n\n <div className=\"space-y-1\">\n {participants.map((participant) => (\n <div\n key={participant.id}\n className=\"flex items-center gap-2.5 px-2 py-1.5 rounded-lg hover:bg-bg-sunken/50\"\n >\n <div className=\"size-7 rounded-full bg-bg-sunken flex items-center justify-center text-xs font-medium text-text-secondary\">\n {participant.name\n .split(\" \")\n .map((word) => word[0])\n .join(\"\")\n .slice(0, 2)}\n </div>\n <div className=\"min-w-0\">\n <p className=\"text-xs font-medium text-text-primary truncate\">\n {participant.name}\n </p>\n <p className=\"text-[10px] text-text-secondary\">\n {participant.role}\n </p>\n </div>\n </div>\n ))}\n {hiddenParticipantsCount > 0 && (\n <p className=\"px-2 pt-2 text-xs text-text-secondary\">\n {tr(\"conversations.moreMembersPrefix\", \"and\")}{\" \"}\n {hiddenParticipantsCount}{\" \"}\n {tr(\"conversations.moreMembersSuffix\", \"more\")}\n </p>\n )}\n </div>\n </div>\n </aside>\n )}\n <QuotaExhaustedDialog\n open={showMessageQuotaExhausted}\n title={tr(\n \"conversations.quota.messages.title\",\n \"Message Quota Reached\",\n )}\n description={tr(\n \"conversations.quota.messages.description\",\n \"You've reached the message limit for your current plan. View billing to upgrade or add more capacity.\",\n )}\n onClose={() => setShowMessageQuotaExhausted(false)}\n onConfirm={() => {\n setShowMessageQuotaExhausted(false);\n navigate(\"/billing\");\n }}\n />\n </div>\n );\n}\n\nfunction DropdownItem({\n icon,\n label,\n onClick,\n destructive,\n}: {\n icon: React.ReactNode;\n label: string;\n onClick: () => void;\n destructive?: boolean;\n}) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n className={cn(\n \"flex items-center gap-2.5 w-full px-3 py-2 text-sm transition-colors text-left\",\n destructive\n ? \"text-status-error-text hover:bg-status-error-bg/10\"\n : \"text-text-primary hover:bg-bg-sunken/50\",\n )}\n >\n {icon}\n <span>{label}</span>\n </button>\n );\n}\n\nfunction HeaderButton({\n icon,\n label,\n active,\n dataTour,\n onClick,\n}: {\n icon: React.ReactNode;\n label: string;\n active?: boolean;\n dataTour?: string;\n onClick?: () => void;\n}) {\n return (\n <PermissionGuard\n permission={CONVERSATIONS_PERMISSIONS.CONVERSATION_READ}\n allowUnavailablePermissions\n >\n <button\n data-tour={dataTour}\n type=\"button\"\n onClick={onClick}\n aria-label={label}\n title={label}\n className={cn(\n \"flex min-h-10 min-w-10 items-center justify-center rounded-lg transition-colors\",\n active\n ? \"bg-action-primary-bg/10 text-primary\"\n : \"text-text-secondary hover:text-text-primary hover:bg-bg-sunken/50\",\n )}\n >\n {icon}\n </button>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ConversationDetailPage.js","names":[],"sources":["../../../src/conversations/pages/ConversationDetailPage.tsx"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { useEventBus } from \"@burdenoff/fe-libs/shared/events\";\nimport { isQuotaExhaustedError } from \"@burdenoff/fe-libs/shared/utils\";\nimport { useNavigate, useParams } from \"react-router\";\nimport {\n Archive,\n ArrowLeft,\n BellOff,\n Bell,\n ExternalLink,\n Hash,\n LogOut,\n Loader2,\n MoreVertical,\n Pin,\n PinOff,\n SendHorizontal,\n Users,\n X,\n} from \"lucide-react\";\nimport {\n Button,\n IllustratedEmptyState,\n NextSteps,\n type NextStep,\n} from \"@burdenoff/fe-libs/ui\";\nimport { MessageBubble } from \"../components/MessageBubble\";\nimport {\n useGetConversation,\n useConversationActions,\n} from \"../hooks/useConversationQueries\";\nimport {\n useGetMessages,\n useSendMessage,\n useEditMessage,\n useMessageActions,\n} from \"../hooks/useMessageQueries\";\nimport { useMessageStreamForConversation } from \"../hooks/useMessageSubscriptions\";\nimport { useConversations } from \"../providers/ConversationsProvider\";\nimport { useConversationsStore } from \"../store/conversationsStore\";\nimport { cn } from \"../utils/cn\";\nimport { PermissionGuard } from \"../components/PermissionGuard\";\nimport { QuotaExhaustedDialog } from \"../components/QuotaExhaustedDialog\";\nimport { CONVERSATIONS_PERMISSIONS } from \"../constants/permissions\";\nimport { nativeImpact, nativeNotify } from \"../../utils/nativeBridge\";\nimport { useTr } from \"../utils/i18n\";\nimport type { Message } from \"../types\";\n\nconst MESSAGES_PAGE_SIZE = 100;\nconst MESSAGE_REFRESH_FALLBACK_MS = 30_000;\n\nexport function ConversationDetailPage() {\n const { conversationId } = useParams<{ conversationId: string }>();\n const navigate = useNavigate();\n const { basePath, currentUser } = useConversations();\n const bus = useEventBus();\n const tr = useTr();\n const {\n clearMessageDraft,\n isParticipantsPanelOpen,\n messageDrafts,\n setMessageDraft,\n toggleParticipantsPanel,\n } = useConversationsStore();\n\n const [messageInput, setMessageInput] = useState(\n messageDrafts[conversationId ?? \"\"] ?? \"\",\n );\n const [showMessageQuotaExhausted, setShowMessageQuotaExhausted] =\n useState(false);\n const [locallySentMessages, setLocallySentMessages] = useState<Message[]>([]);\n const messagesEndRef = useRef<HTMLDivElement>(null);\n const composerRef = useRef<HTMLTextAreaElement>(null);\n\n const focusComposer = useCallback(() => {\n composerRef.current?.focus();\n composerRef.current?.scrollIntoView({ behavior: \"smooth\" });\n }, []);\n\n const {\n conversation,\n members: conversationMembers,\n membersTotalCount,\n loading: conversationLoading,\n error: conversationError,\n } = useGetConversation(conversationId ?? \"\");\n const {\n messages,\n loading: messagesLoading,\n error: messagesError,\n refetch: refetchMessages,\n } = useGetMessages(conversationId ?? \"\", {\n first: MESSAGES_PAGE_SIZE,\n pollInterval: MESSAGE_REFRESH_FALLBACK_MS,\n });\n // Subscribe to live message events; the query has a light polling fallback for\n // local/federated transports that keep the stream open but miss events.\n useMessageStreamForConversation(conversationId, MESSAGES_PAGE_SIZE);\n const { sendMessage, loading: sendingMessage } = useSendMessage();\n const { editMessage, loading: editingMessage } = useEditMessage();\n const {\n deleteMessage,\n addReaction,\n removeReaction,\n pinMessage,\n unpinMessage,\n } = useMessageActions();\n const {\n archiveConversation,\n pinConversation,\n unpinConversation,\n muteConversation,\n unmuteConversation,\n leaveConversation,\n } = useConversationActions();\n\n const [moreMenuOpen, setMoreMenuOpen] = useState(false);\n const [editingMessageId, setEditingMessageId] = useState<string | null>(null);\n const [editingContent, setEditingContent] = useState(\"\");\n const [replyingTo, setReplyingTo] = useState<{\n id: string;\n content: string;\n sender: string;\n } | null>(null);\n const moreMenuRef = useRef<HTMLDivElement>(null);\n\n const currentUserId = currentUser?.id ?? \"\";\n const sourceContext = conversation?.sourceContext;\n\n const displayedMessages = useMemo(() => {\n const byId = new Map<string, Message>();\n for (const message of locallySentMessages) {\n if (message.conversationId !== conversationId) continue;\n byId.set(message.id, message);\n }\n for (const message of messages) {\n byId.set(message.id, message);\n }\n return Array.from(byId.values()).sort(\n (a, b) =>\n new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime(),\n );\n }, [conversationId, locallySentMessages, messages]);\n\n useEffect(() => {\n messagesEndRef.current?.scrollIntoView({ behavior: \"smooth\" });\n }, [displayedMessages.length]);\n\n useEffect(() => {\n setLocallySentMessages([]);\n }, [conversationId]);\n\n useEffect(() => {\n if (locallySentMessages.length === 0 || messages.length === 0) return;\n const serverIds = new Set(messages.map((message) => message.id));\n setLocallySentMessages((current) => {\n const pending = current.filter((message) => !serverIds.has(message.id));\n return pending.length === current.length ? current : pending;\n });\n }, [locallySentMessages.length, messages, setLocallySentMessages]);\n\n useEffect(() => {\n if (!conversationId) return;\n if (messageInput.trim()) {\n setMessageDraft(conversationId, messageInput);\n return;\n }\n clearMessageDraft(conversationId);\n }, [messageInput, conversationId, setMessageDraft, clearMessageDraft]);\n\n const messageAuthorNames = useMemo(() => {\n const byAuthor = new Map<string, string>();\n for (const message of displayedMessages) {\n if (!message.authorId || byAuthor.has(message.authorId)) continue;\n if (!message.sender.name) continue;\n byAuthor.set(message.authorId, message.sender.name);\n }\n return byAuthor;\n }, [displayedMessages]);\n\n const participants = useMemo(() => {\n if (conversationMembers.length > 0) {\n return conversationMembers\n .filter((member) => member.status === \"ACTIVE\" && !member.leftAt)\n .map((member) => ({\n id: member.userId,\n name:\n messageAuthorNames.get(member.userId) ||\n member.customTitle ||\n tr(\"conversations.member\", \"Member\"),\n role: member.role,\n }));\n }\n\n return Array.from(messageAuthorNames.entries()).map(([id, name]) => ({\n id,\n name,\n role: tr(\"conversations.member\", \"Member\"),\n }));\n }, [conversationMembers, messageAuthorNames, tr]);\n\n const hiddenParticipantsCount = conversation?.memberCount\n ? Math.max(conversation.memberCount - participants.length, 0)\n : 0;\n\n const handleSendMessage = useCallback(async () => {\n if (!conversationId || !messageInput.trim() || sendingMessage) return;\n let message: Message | null;\n try {\n message = await sendMessage({\n conversationId,\n content: messageInput.trim(),\n type: \"TEXT\",\n parentMessageId: replyingTo?.id,\n refetchFirst: MESSAGES_PAGE_SIZE,\n });\n } catch (error) {\n if (isQuotaExhaustedError(error)) {\n setShowMessageQuotaExhausted(true);\n return;\n }\n nativeNotify(\"error\");\n throw error;\n }\n if (!message) return;\n setLocallySentMessages((current) => [...current, message]);\n bus.emit(\"conversations.message.sent\", {\n messageId: message.id,\n conversationId,\n });\n setMessageInput(\"\");\n setReplyingTo(null);\n clearMessageDraft(conversationId);\n }, [\n bus,\n conversationId,\n messageInput,\n sendingMessage,\n sendMessage,\n clearMessageDraft,\n replyingTo,\n ]);\n\n const handleKeyDown = useCallback(\n (event: React.KeyboardEvent<HTMLTextAreaElement>) => {\n if (event.key !== \"Enter\" || event.shiftKey) return;\n event.preventDefault();\n void handleSendMessage();\n },\n [handleSendMessage],\n );\n\n // Emit opened event so sinks (analytics/audit) can record the view\n useEffect(() => {\n if (!conversationId) return;\n bus.emit(\"conversations.conversation.opened\", { conversationId });\n }, [bus, conversationId]);\n\n // Close more-menu on outside click\n useEffect(() => {\n if (!moreMenuOpen) return;\n const handler = (e: MouseEvent) => {\n if (\n moreMenuRef.current &&\n !moreMenuRef.current.contains(e.target as Node)\n ) {\n setMoreMenuOpen(false);\n }\n };\n document.addEventListener(\"mousedown\", handler);\n return () => document.removeEventListener(\"mousedown\", handler);\n }, [moreMenuOpen]);\n\n const handleReply = useCallback(\n (message: { id: string; content: string; sender: { name: string } }) => {\n setReplyingTo({\n id: message.id,\n content: message.content,\n sender: message.sender.name,\n });\n },\n [],\n );\n\n const handleReact = useCallback(\n async (messageId: string, emoji: string) => {\n try {\n await addReaction(messageId, emoji);\n } catch {\n // If already reacted, toggle off\n await removeReaction(messageId, emoji).catch(() => {});\n }\n },\n [addReaction, removeReaction],\n );\n\n const handleEdit = useCallback((message: { id: string; content: string }) => {\n setEditingMessageId(message.id);\n setEditingContent(message.content);\n }, []);\n\n const handleEditSubmit = useCallback(async () => {\n if (!editingMessageId || !editingContent.trim() || editingMessage) return;\n await editMessage(editingMessageId, editingContent.trim());\n setEditingMessageId(null);\n setEditingContent(\"\");\n }, [editingMessageId, editingContent, editingMessage, editMessage]);\n\n const handleEditCancel = useCallback(() => {\n setEditingMessageId(null);\n setEditingContent(\"\");\n }, []);\n\n const handleDelete = useCallback(\n async (messageId: string) => {\n const confirmed = window.confirm(\n tr(\n \"conversations.confirmDeleteMessage\",\n \"Delete this message? This action cannot be undone.\",\n ),\n );\n if (!confirmed) return;\n nativeImpact(\"medium\");\n try {\n await deleteMessage(messageId);\n void refetchMessages();\n nativeNotify(\"success\");\n } catch (error) {\n console.error(\"[microfe-conversations] message delete failed\", error);\n nativeNotify(\"error\");\n }\n },\n [deleteMessage, refetchMessages, tr],\n );\n\n const handleTogglePinMessage = useCallback(\n async (messageId: string, isPinned: boolean) => {\n if (isPinned) {\n await unpinMessage(messageId);\n return;\n }\n await pinMessage(messageId);\n },\n [pinMessage, unpinMessage],\n );\n\n const handlePin = useCallback(async () => {\n if (!conversationId) return;\n if (conversation?.isPinned) {\n await unpinConversation(conversationId);\n } else {\n await pinConversation(conversationId);\n }\n setMoreMenuOpen(false);\n }, [\n conversationId,\n conversation?.isPinned,\n pinConversation,\n unpinConversation,\n ]);\n\n const handleMute = useCallback(async () => {\n if (!conversationId) return;\n if (conversation?.isMuted) {\n await unmuteConversation(conversationId);\n } else {\n await muteConversation(conversationId);\n }\n setMoreMenuOpen(false);\n }, [\n conversationId,\n conversation?.isMuted,\n muteConversation,\n unmuteConversation,\n ]);\n\n const handleArchive = useCallback(async () => {\n if (!conversationId) return;\n try {\n await archiveConversation(conversationId);\n bus.emit(\"conversations.conversation.archived\", { conversationId });\n setMoreMenuOpen(false);\n navigate(basePath || \"/conversations\");\n } catch (error) {\n console.error(\n \"[microfe-conversations] conversation archive failed\",\n error,\n );\n nativeNotify(\"error\");\n }\n }, [archiveConversation, basePath, bus, conversationId, navigate]);\n\n const handleLeave = useCallback(async () => {\n if (!conversationId) return;\n const confirmed = window.confirm(\n tr(\n \"conversations.confirmLeaveConversation\",\n \"Leave this conversation? You will lose access unless someone adds you again.\",\n ),\n );\n if (!confirmed) return;\n try {\n await leaveConversation(conversationId);\n if (currentUserId) {\n bus.emit(\"conversations.member.removed\", {\n conversationId,\n userId: currentUserId,\n });\n }\n setMoreMenuOpen(false);\n navigate(basePath || \"/conversations\");\n } catch (error) {\n console.error(\"[microfe-conversations] conversation leave failed\", error);\n nativeNotify(\"error\");\n }\n }, [\n basePath,\n bus,\n conversationId,\n currentUserId,\n leaveConversation,\n navigate,\n tr,\n ]);\n\n const handleOpenContext = useCallback(() => {\n if (!sourceContext?.contextHref) return;\n try {\n const url = new URL(sourceContext.contextHref);\n if (url.origin === window.location.origin) {\n navigate(`${url.pathname}${url.search}${url.hash}`);\n return;\n }\n } catch {\n // Fall through to full navigation for relative or invalid URLs.\n }\n\n window.location.assign(sourceContext.contextHref);\n }, [navigate, sourceContext?.contextHref]);\n\n // Concrete next actions surfaced on the (complex) detail page so a new user\n // isn't left staring at an unfamiliar surface. Each step is wired to a real\n // handler already present on this page; steps that don't apply to the\n // current conversation type/state are filtered out.\n const detailNextSteps = useMemo<NextStep[]>(() => {\n const steps: NextStep[] = [\n {\n id: \"send-message\",\n label: tr(\n \"conversations.nextSteps.send.label\",\n \"Send your first reply\",\n ),\n description: tr(\n \"conversations.nextSteps.send.description\",\n \"Jump to the composer and start the conversation.\",\n ),\n icon: <SendHorizontal size={14} aria-hidden=\"true\" />,\n onClick: focusComposer,\n done: displayedMessages.length > 0,\n },\n ];\n if (conversation?.isGroup) {\n steps.push({\n id: \"view-participants\",\n label: tr(\n \"conversations.nextSteps.participants.label\",\n \"Review participants\",\n ),\n description: tr(\n \"conversations.nextSteps.participants.description\",\n \"See who can read and post in this conversation.\",\n ),\n icon: <Users size={14} aria-hidden=\"true\" />,\n onClick: () => {\n if (!isParticipantsPanelOpen) toggleParticipantsPanel();\n },\n });\n }\n if (sourceContext?.contextHref) {\n steps.push({\n id: \"open-context\",\n label: tr(\n \"conversations.nextSteps.context.label\",\n \"Open the related item\",\n ),\n description: tr(\n \"conversations.nextSteps.context.description\",\n \"Jump to the page or record this conversation is about.\",\n ),\n icon: <ExternalLink size={14} aria-hidden=\"true\" />,\n onClick: handleOpenContext,\n });\n }\n steps.push({\n id: \"pin-conversation\",\n label: conversation?.isPinned\n ? tr(\"conversations.nextSteps.unpin.label\", \"Unpin this conversation\")\n : tr(\"conversations.nextSteps.pin.label\", \"Pin for quick access\"),\n description: tr(\n \"conversations.nextSteps.pin.description\",\n \"Keep important conversations at the top of your inbox.\",\n ),\n icon: <Pin size={14} aria-hidden=\"true\" />,\n onClick: () => void handlePin(),\n done: conversation?.isPinned,\n });\n return steps;\n }, [\n conversation?.isGroup,\n conversation?.isPinned,\n displayedMessages.length,\n focusComposer,\n handleOpenContext,\n handlePin,\n isParticipantsPanelOpen,\n sourceContext?.contextHref,\n toggleParticipantsPanel,\n tr,\n ]);\n\n if (!conversation && conversationLoading) {\n return (\n <div className=\"flex items-center justify-center h-[calc(100vh-64px)] text-sm text-text-secondary\">\n {tr(\"conversations.loading\", \"Loading conversation...\")}\n </div>\n );\n }\n\n if (!conversation || conversationError) {\n return (\n <div className=\"flex h-[calc(100vh-64px)] items-center justify-center p-8\">\n <IllustratedEmptyState\n illustration=\"empty-generic\"\n title={\n conversationError\n ? tr(\"conversations.unavailableTitle\", \"Conversation unavailable\")\n : tr(\"conversations.notFound\", \"Conversation not found\")\n }\n description={\n conversationError\n ? tr(\n \"conversations.unavailable\",\n \"We couldn't load this conversation. Please try again.\",\n )\n : tr(\n \"conversations.notFoundDescription\",\n \"This conversation may have been deleted, archived, or you no longer have access to it.\",\n )\n }\n action={\n <Button\n type=\"button\"\n onClick={() => navigate(basePath || \"/conversations\")}\n >\n {tr(\"conversations.back\", \"Back to conversations\")}\n </Button>\n }\n />\n </div>\n );\n }\n\n return (\n <div className=\"relative flex h-[calc(100vh-64px)] bg-bg-surface\">\n <div className=\"flex-1 flex flex-col min-w-0\">\n <header\n data-tour=\"conversation-detail-header\"\n className=\"flex items-center justify-between px-4 py-3 border-b border-border-seam bg-bg-surface/95 backdrop-blur-sm\"\n >\n <div className=\"flex items-center gap-3 min-w-0\">\n <button\n type=\"button\"\n onClick={() => navigate(basePath || \"/conversations\")}\n className=\"flex min-h-10 min-w-10 items-center justify-center rounded-lg text-text-secondary transition-colors hover:bg-bg-sunken/50 hover:text-text-primary\"\n >\n <ArrowLeft size={18} />\n </button>\n <div className=\"min-w-0\">\n <div className=\"flex items-center gap-1.5\">\n {conversation.type === \"CHANNEL\" && (\n <Hash\n size={14}\n className=\"text-text-secondary flex-shrink-0\"\n />\n )}\n <h2 className=\"text-sm font-semibold text-text-primary truncate\">\n {conversation.title}\n </h2>\n </div>\n <p className=\"text-xs text-text-secondary\">\n {conversation.type === \"DIRECT\"\n ? tr(\n \"conversations.directConversation\",\n \"Direct conversation\",\n )\n : conversation.type === \"CHANNEL\"\n ? `${tr(\"conversations.channel\", \"Channel\")} · ${conversation.memberCount} ${tr(\"conversations.membersCountLabel\", \"members\")}`\n : conversation.type === \"ANNOUNCEMENT\"\n ? `${tr(\"conversations.announcement\", \"Announcement\")} · ${conversation.memberCount} ${tr(\"conversations.membersCountLabel\", \"members\")}`\n : `${conversation.memberCount} ${tr(\"conversations.membersCountLabel\", \"members\")}`}\n </p>\n </div>\n </div>\n\n <div className=\"flex shrink-0 items-center gap-1\">\n {sourceContext?.contextHref && (\n <HeaderButton\n dataTour=\"conversation-open-context\"\n icon={<ExternalLink size={16} />}\n label={tr(\"conversations.openContext\", \"Open context\")}\n onClick={handleOpenContext}\n />\n )}\n {conversation.isGroup && (\n <HeaderButton\n dataTour=\"conversation-participants-button\"\n icon={<Users size={16} />}\n label={tr(\"conversations.participants\", \"Participants\")}\n active={isParticipantsPanelOpen}\n onClick={toggleParticipantsPanel}\n />\n )}\n <div className=\"relative\" ref={moreMenuRef}>\n <HeaderButton\n icon={<MoreVertical size={16} />}\n label={tr(\"conversations.moreOptions\", \"More options\")}\n active={moreMenuOpen}\n onClick={() => setMoreMenuOpen((prev) => !prev)}\n />\n {moreMenuOpen && (\n <div className=\"absolute right-0 top-full mt-1 w-48 bg-popover border border-border-subtle rounded-lg shadow-lg z-50 py-1 animate-in fade-in-0 zoom-in-95 duration-100\">\n <DropdownItem\n icon={\n conversation.isPinned ? (\n <PinOff size={14} />\n ) : (\n <Pin size={14} />\n )\n }\n label={\n conversation.isPinned\n ? tr(\n \"conversations.unpinConversation\",\n \"Unpin conversation\",\n )\n : tr(\n \"conversations.pinConversation\",\n \"Pin conversation\",\n )\n }\n onClick={() => void handlePin()}\n />\n <DropdownItem\n icon={\n conversation.isMuted ? (\n <Bell size={14} />\n ) : (\n <BellOff size={14} />\n )\n }\n label={\n conversation.isMuted\n ? tr(\n \"conversations.unmuteConversation\",\n \"Unmute conversation\",\n )\n : tr(\n \"conversations.muteConversation\",\n \"Mute conversation\",\n )\n }\n onClick={() => void handleMute()}\n />\n <div className=\"h-px bg-border my-1\" />\n <DropdownItem\n icon={<Archive size={14} />}\n label={tr(\n \"conversations.archiveConversation\",\n \"Archive conversation\",\n )}\n onClick={() => void handleArchive()}\n destructive\n />\n {conversation.isGroup && (\n <DropdownItem\n icon={<LogOut size={14} />}\n label={tr(\n \"conversations.leaveConversation\",\n \"Leave conversation\",\n )}\n onClick={() => void handleLeave()}\n destructive\n />\n )}\n </div>\n )}\n </div>\n </div>\n </header>\n\n <div className=\"flex-1 overflow-y-auto py-4 space-y-0.5\">\n {sourceContext?.pagePath && (\n <div className=\"mx-4 mb-4 rounded-lg border border-border-subtle bg-bg-sunken/20 px-3 py-2 text-xs text-text-secondary\">\n <span className=\"font-medium text-text-primary\">\n {sourceContext.contextTitle || sourceContext.pagePath}\n </span>\n {sourceContext.entityType && sourceContext.entityId\n ? tr(\n \"conversations.contextEntityMeta\",\n \" · {{entityType}} {{entityId}}\",\n {\n entityType: sourceContext.entityType,\n entityId: sourceContext.entityId,\n },\n )\n : \"\"}\n </div>\n )}\n {messagesError && (\n <div className=\"mx-4 mb-4 rounded-lg border border-status-warning-border bg-status-warning-bg px-3 py-2 text-sm text-status-warning-text\">\n {tr(\n \"conversations.messagesUnavailable\",\n \"Messages couldn't be loaded right now. The conversation header and actions are still available.\",\n )}\n </div>\n )}\n {messagesLoading && (\n <div className=\"mx-4 mb-4 flex items-center gap-2 rounded-lg border border-border-subtle bg-bg-sunken/20 px-3 py-2 text-sm text-text-secondary\">\n <Loader2 size={14} className=\"animate-spin\" />\n {tr(\"conversations.loadingMessages\", \"Loading messages...\")}\n </div>\n )}\n {!messagesLoading &&\n !messagesError &&\n displayedMessages.length === 0 && (\n <div className=\"mx-4 mb-4 flex flex-col items-center gap-5 py-6\">\n <IllustratedEmptyState\n illustration=\"empty-vibes\"\n title={tr(\"conversations.noMessagesTitle\", \"No messages yet\")}\n description={tr(\n \"conversations.noMessagesYet\",\n \"Start the conversation below — say hello, ask a question, or share an update.\",\n )}\n action={\n <Button type=\"button\" onClick={focusComposer}>\n <SendHorizontal size={14} aria-hidden=\"true\" />\n {tr(\"conversations.writeFirstMessage\", \"Write a message\")}\n </Button>\n }\n />\n <NextSteps\n className=\"w-full max-w-md\"\n storageKey=\"conversations-detail\"\n title={tr(\n \"conversations.nextSteps.title\",\n \"Get the most out of this conversation\",\n )}\n steps={detailNextSteps}\n />\n </div>\n )}\n {displayedMessages.map((message, index) => {\n const prevMessage = displayedMessages[index - 1];\n const showAvatar =\n !prevMessage ||\n prevMessage.authorId !== message.authorId ||\n prevMessage.type === \"SYSTEM\";\n\n return (\n <div key={message.id}>\n {editingMessageId === message.id ? (\n <div className=\"px-4 py-2\">\n <div className=\"flex items-center gap-2 text-xs text-text-secondary mb-1\">\n <span>\n {tr(\"conversations.editingMessage\", \"Editing message\")}\n </span>\n <button\n type=\"button\"\n onClick={handleEditCancel}\n className=\"hover:text-text-primary\"\n >\n <X size={12} />\n </button>\n </div>\n <div className=\"flex items-end gap-2\">\n <textarea\n aria-label={tr(\n \"conversations.editMessageAria\",\n \"Edit message\",\n )}\n value={editingContent}\n onChange={(e) => setEditingContent(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n void handleEditSubmit();\n }\n if (e.key === \"Escape\") handleEditCancel();\n }}\n className=\"flex-1 px-3 py-2 text-sm rounded-lg border border-border-subtle bg-bg-sunken/30 resize-none focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n rows={2}\n />\n <button\n type=\"button\"\n onClick={() => void handleEditSubmit()}\n disabled={!editingContent.trim() || editingMessage}\n className=\"px-3 py-2 text-xs font-medium rounded-lg bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {tr(\"conversations.save\", \"Save\")}\n </button>\n </div>\n </div>\n ) : (\n <MessageBubble\n message={message}\n isOwnMessage={message.authorId === currentUserId}\n showAvatar={showAvatar}\n showSender={showAvatar}\n onReply={handleReply}\n onReact={(id, emoji) => void handleReact(id, emoji)}\n onEdit={handleEdit}\n onDelete={(id) => void handleDelete(id)}\n onTogglePin={(id, isPinned) =>\n void handleTogglePinMessage(id, isPinned)\n }\n />\n )}\n </div>\n );\n })}\n <div ref={messagesEndRef} />\n </div>\n\n <div\n data-tour=\"conversation-message-composer\"\n className=\"border-t border-border-seam px-4 py-3 bg-bg-surface\"\n >\n {/* Reply-to indicator */}\n {replyingTo && (\n <div className=\"flex items-center gap-2 mb-2 px-3 py-1.5 rounded-lg bg-bg-sunken/50 text-xs text-text-secondary\">\n <span>\n {tr(\"conversations.replyingTo\", \"Replying to\")}{\" \"}\n <strong className=\"text-text-primary\">\n {replyingTo.sender}\n </strong>\n : {replyingTo.content.slice(0, 80)}\n {replyingTo.content.length > 80 ? \"…\" : \"\"}\n </span>\n <button\n type=\"button\"\n onClick={() => setReplyingTo(null)}\n aria-label={tr(\"conversations.cancelReply\", \"Cancel reply\")}\n className=\"ml-auto flex min-h-9 min-w-9 items-center justify-center rounded-md hover:bg-bg-sunken/70 hover:text-text-primary\"\n >\n <X size={12} />\n </button>\n </div>\n )}\n <div className=\"flex items-end gap-2\">\n <div className=\"flex-1 relative\">\n <textarea\n aria-label={tr(\n \"conversations.messageComposer\",\n \"Message composer\",\n )}\n ref={composerRef}\n value={messageInput}\n onChange={(event) => setMessageInput(event.target.value)}\n onKeyDown={handleKeyDown}\n placeholder={`${tr(\"conversations.messageConversationPrefix\", \"Message\")} ${conversation.title}...`}\n rows={1}\n className={cn(\n \"w-full px-4 py-2.5 text-sm rounded-xl border border-border-subtle bg-bg-sunken/30\",\n \"resize-none overflow-hidden placeholder:text-text-secondary\",\n \"focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] focus:border-transparent max-h-32\",\n )}\n style={{\n height: \"auto\",\n minHeight: \"40px\",\n }}\n onInput={(event) => {\n const target = event.target as HTMLTextAreaElement;\n target.style.height = \"auto\";\n target.style.height = `${Math.min(target.scrollHeight, 128)}px`;\n }}\n />\n </div>\n\n <button\n type=\"button\"\n aria-label={tr(\"conversations.sendMessage\", \"Send message\")}\n onClick={() => void handleSendMessage()}\n disabled={!messageInput.trim() || sendingMessage}\n className={cn(\n \"p-2.5 rounded-xl transition-all duration-150\",\n messageInput.trim() && !sendingMessage\n ? \"bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg/90 shadow-sm\"\n : \"bg-bg-sunken text-text-secondary cursor-not-allowed\",\n )}\n >\n {sendingMessage ? (\n <Loader2 size={18} className=\"animate-spin\" />\n ) : (\n <SendHorizontal size={18} />\n )}\n </button>\n </div>\n </div>\n </div>\n\n {isParticipantsPanelOpen && conversation.isGroup && (\n <aside className=\"absolute inset-y-0 right-0 z-20 w-full max-w-72 border-l border-border-seam bg-bg-surface shadow-lg animate-in slide-in-from-right-4 duration-200 md:relative md:z-auto md:w-64 md:shadow-none\">\n <div className=\"p-4\">\n <div className=\"flex items-center justify-between mb-4\">\n <h3 className=\"text-sm font-semibold text-text-primary\">\n {tr(\"conversations.members\", \"Members\")} (\n {conversation.memberCount ?? participants.length})\n </h3>\n <button\n type=\"button\"\n onClick={toggleParticipantsPanel}\n className=\"p-1 rounded text-text-secondary hover:text-text-primary\"\n >\n <Pin size={14} />\n </button>\n </div>\n\n <div className=\"space-y-1\">\n {participants.map((participant) => (\n <div\n key={participant.id}\n className=\"flex items-center gap-2.5 px-2 py-1.5 rounded-lg hover:bg-bg-sunken/50\"\n >\n <div className=\"size-7 rounded-full bg-bg-sunken flex items-center justify-center text-xs font-medium text-text-secondary\">\n {participant.name\n .split(\" \")\n .map((word) => word[0])\n .join(\"\")\n .slice(0, 2)}\n </div>\n <div className=\"min-w-0\">\n <p className=\"text-xs font-medium text-text-primary truncate\">\n {participant.name}\n </p>\n <p className=\"text-[10px] text-text-secondary\">\n {participant.role}\n </p>\n </div>\n </div>\n ))}\n {hiddenParticipantsCount > 0 && (\n <p className=\"px-2 pt-2 text-xs text-text-secondary\">\n {tr(\"conversations.moreMembersPrefix\", \"and\")}{\" \"}\n {hiddenParticipantsCount}{\" \"}\n {tr(\"conversations.moreMembersSuffix\", \"more\")}\n </p>\n )}\n </div>\n </div>\n </aside>\n )}\n <QuotaExhaustedDialog\n open={showMessageQuotaExhausted}\n title={tr(\n \"conversations.quota.messages.title\",\n \"Message Quota Reached\",\n )}\n description={tr(\n \"conversations.quota.messages.description\",\n \"You've reached the message limit for your current plan. View billing to upgrade or add more capacity.\",\n )}\n onClose={() => setShowMessageQuotaExhausted(false)}\n onConfirm={() => {\n setShowMessageQuotaExhausted(false);\n navigate(\"/billing\");\n }}\n />\n </div>\n );\n}\n\nfunction DropdownItem({\n icon,\n label,\n onClick,\n destructive,\n}: {\n icon: React.ReactNode;\n label: string;\n onClick: () => void;\n destructive?: boolean;\n}) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n className={cn(\n \"flex items-center gap-2.5 w-full px-3 py-2 text-sm transition-colors text-left\",\n destructive\n ? \"text-status-error-text hover:bg-status-error-bg/10\"\n : \"text-text-primary hover:bg-bg-sunken/50\",\n )}\n >\n {icon}\n <span>{label}</span>\n </button>\n );\n}\n\nfunction HeaderButton({\n icon,\n label,\n active,\n dataTour,\n onClick,\n}: {\n icon: React.ReactNode;\n label: string;\n active?: boolean;\n dataTour?: string;\n onClick?: () => void;\n}) {\n return (\n <PermissionGuard\n permission={CONVERSATIONS_PERMISSIONS.CONVERSATION_READ}\n allowUnavailablePermissions\n >\n <button\n data-tour={dataTour}\n type=\"button\"\n onClick={onClick}\n aria-label={label}\n title={label}\n className={cn(\n \"flex min-h-10 min-w-10 items-center justify-center rounded-lg transition-colors\",\n active\n ? \"bg-action-primary-bg/10 text-primary\"\n : \"text-text-secondary hover:text-text-primary hover:bg-bg-sunken/50\",\n )}\n >\n {icon}\n </button>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
@@ -3,9 +3,9 @@ import { useI18n as t } from "@burdenoff/fe-libs/shared/providers/shell/I18nProv
3
3
  //#region src/conversations/utils/i18n.ts
4
4
  function n() {
5
5
  let { t: n } = t();
6
- return e((e, t) => {
7
- let r = n(e);
8
- return r === e ? t : r;
6
+ return e((e, t, r) => {
7
+ let i = n(e), a = i === e ? t : i;
8
+ return r ? Object.entries(r).reduce((e, [t, n]) => e.replace(RegExp(`\\{\\{${t}\\}\\}`, "g"), String(n)), a) : a;
9
9
  }, [n]);
10
10
  }
11
11
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"i18n.js","names":[],"sources":["../../../src/conversations/utils/i18n.ts"],"sourcesContent":["import { useCallback } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\n\n/**\n * Stable translate helper used throughout the conversations MFE.\n *\n * Wraps the shell's `useI18n().t()` with a two-argument signature that\n * returns the English `fallback` when the translation key is not\n * resolved. Memoises around `t` so the returned function is referentially\n * stable across renders — safe to list in `useMemo` / `useEffect` dep\n * arrays without triggering churn on every render.\n *\n * @example\n * const tr = useTr();\n * const label = tr(\"conversations.inbox\", \"Inbox\");\n */\nexport function useTr(): (key: string, fallback: string) => string {\n const { t } = useI18n();\n return useCallback(\n (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n },\n [t],\n );\n}\n"],"mappings":";;;AAgBA,SAAgB,IAAmD;CACjE,IAAM,EAAE,SAAM,GAAS;AACvB,QAAO,GACJ,GAAa,MAA6B;EACzC,IAAM,IAAQ,EAAE,EAAI;AACpB,SAAO,MAAU,IAAM,IAAW;IAEpC,CAAC,EAAE,CACJ"}
1
+ {"version":3,"file":"i18n.js","names":[],"sources":["../../../src/conversations/utils/i18n.ts"],"sourcesContent":["import { useCallback } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\n\n/**\n * Stable translate helper used throughout the conversations MFE.\n *\n * Wraps the shell's `useI18n().t()` with a fallback-aware signature that\n * returns the English `fallback` when the translation key is not\n * resolved. Memoises around `t` so the returned function is referentially\n * stable across renders — safe to list in `useMemo` / `useEffect` dep\n * arrays without triggering churn on every render.\n *\n * Supports optional interpolation params using `{{key}}` placeholders.\n *\n * @example\n * const tr = useTr();\n * const label = tr(\"conversations.inbox\", \"Inbox\");\n * const countLabel = tr(\n * \"conversations.selectedCount\",\n * \"{{count}} selected\",\n * { count: 5 },\n * );\n */\nexport function useTr(): (\n key: string,\n fallback: string,\n params?: Record<string, string | number>,\n) => string {\n const { t } = useI18n();\n return useCallback(\n (\n key: string,\n fallback: string,\n params?: Record<string, string | number>,\n ): string => {\n const value = t(key);\n const raw = value === key ? fallback : value;\n if (!params) return raw;\n return Object.entries(params).reduce(\n (str, [paramKey, paramValue]) =>\n str.replace(\n new RegExp(`\\\\{\\\\{${paramKey}\\\\}\\\\}`, \"g\"),\n String(paramValue),\n ),\n raw,\n );\n },\n [t],\n );\n}\n"],"mappings":";;;AAuBA,SAAgB,IAIJ;CACV,IAAM,EAAE,SAAM,GAAS;AACvB,QAAO,GAEH,GACA,GACA,MACW;EACX,IAAM,IAAQ,EAAE,EAAI,EACd,IAAM,MAAU,IAAM,IAAW;AAEvC,SADK,IACE,OAAO,QAAQ,EAAO,CAAC,QAC3B,GAAK,CAAC,GAAU,OACf,EAAI,QACE,OAAO,SAAS,EAAS,SAAS,IAAI,EAC1C,OAAO,EAAW,CACnB,EACH,EACD,GARmB;IAUtB,CAAC,EAAE,CACJ"}
@@ -26,7 +26,7 @@ function e(e) {
26
26
  lastMessage: e.lastMessage ? t(e.lastMessage) : void 0,
27
27
  isPinned: !!(e.currentUserMembership && e.currentUserMembership.isPinned),
28
28
  isMuted: !!(e.currentUserMembership && e.currentUserMembership.isMuted),
29
- features: e.features ? a(e.features) : s(),
29
+ features: e.features ? o(e.features) : c(),
30
30
  createdBy: {
31
31
  id: String(e.createdBy && typeof e.createdBy == "object" ? e.createdBy.id : e.createdBy ?? ""),
32
32
  name: String(e.createdBy && typeof e.createdBy == "object" ? e.createdBy.name : "")
@@ -36,19 +36,19 @@ function e(e) {
36
36
  };
37
37
  }
38
38
  function t(e) {
39
- let t = Array.isArray(e.reactions) ? e.reactions.map(n) : [], a = Array.isArray(e.mentions) ? e.mentions.map(r) : [], s = Array.isArray(e.attachments) ? e.attachments.map(i) : [];
39
+ let t = Array.isArray(e.reactions) ? e.reactions.map(n) : [], o = Array.isArray(e.mentions) ? e.mentions.map(r) : [], c = Array.isArray(e.attachments) ? e.attachments.map(i) : [];
40
40
  return {
41
41
  id: String(e.id ?? ""),
42
42
  conversationId: String(e.conversationId ?? ""),
43
43
  type: String(e.type ?? "TEXT"),
44
44
  status: String(e.status ?? "SENT"),
45
- content: l(e.content),
45
+ content: u(e.content),
46
46
  authorId: String(e.authorId ?? ""),
47
47
  authorType: String(e.authorType ?? "user"),
48
48
  isAnonymous: !!e.isAnonymous,
49
49
  sender: {
50
50
  id: String(e.authorId ?? ""),
51
- name: String(e.sender && typeof e.sender == "object" ? e.sender.name : e.authorId ?? "Unknown"),
51
+ name: a(e),
52
52
  avatarUrl: e.sender && typeof e.sender == "object" ? e.sender.avatarUrl : void 0
53
53
  },
54
54
  parentMessageId: e.parentMessageId ? String(e.parentMessageId) : void 0,
@@ -59,9 +59,9 @@ function t(e) {
59
59
  editedAt: e.editedAt ? String(e.editedAt) : void 0,
60
60
  isPinned: !!e.isPinned,
61
61
  reactions: t,
62
- mentions: a,
63
- attachments: s,
64
- permissions: e.permissions ? o(e.permissions) : c(),
62
+ mentions: o,
63
+ attachments: c,
64
+ permissions: e.permissions ? s(e.permissions) : l(),
65
65
  createdAt: String(e.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()),
66
66
  updatedAt: String(e.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString())
67
67
  };
@@ -102,6 +102,14 @@ function i(e) {
102
102
  };
103
103
  }
104
104
  function a(e) {
105
+ if (typeof e.authorName == "string" && e.authorName.trim()) return e.authorName;
106
+ if (e.sender && typeof e.sender == "object") {
107
+ let t = e.sender.name;
108
+ if (typeof t == "string" && t.trim()) return t;
109
+ }
110
+ return "Unknown";
111
+ }
112
+ function o(e) {
105
113
  return {
106
114
  threads: !!(e.threads ?? !0),
107
115
  reactions: !!(e.reactions ?? !0),
@@ -114,7 +122,7 @@ function a(e) {
114
122
  recording: !!e.recording
115
123
  };
116
124
  }
117
- function o(e) {
125
+ function s(e) {
118
126
  return {
119
127
  canEdit: !!e.canEdit,
120
128
  canDelete: !!e.canDelete,
@@ -125,7 +133,7 @@ function o(e) {
125
133
  canReport: !!(e.canReport ?? !0)
126
134
  };
127
135
  }
128
- function s() {
136
+ function c() {
129
137
  return {
130
138
  threads: !0,
131
139
  reactions: !0,
@@ -138,7 +146,7 @@ function s() {
138
146
  recording: !1
139
147
  };
140
148
  }
141
- function c() {
149
+ function l() {
142
150
  return {
143
151
  canEdit: !1,
144
152
  canDelete: !1,
@@ -149,7 +157,7 @@ function c() {
149
157
  canReport: !0
150
158
  };
151
159
  }
152
- function l(e) {
160
+ function u(e) {
153
161
  if (typeof e == "string") return e;
154
162
  if (e && typeof e == "object") {
155
163
  let t = e;
@@ -1 +1 @@
1
- {"version":3,"file":"mappers.js","names":[],"sources":["../../../src/conversations/utils/mappers.ts"],"sourcesContent":["import type {\n Conversation,\n Message,\n Reaction,\n Attachment,\n Mention,\n MessagePermissions,\n ConversationFeatures,\n} from \"../types\";\n\n/**\n * Map a GraphQL Conversation response to the frontend Conversation type.\n * Gracefully handles undefined/null fields from partial queries.\n */\nexport function mapConversation(raw: Record<string, unknown>): Conversation {\n const settings =\n raw.settings && typeof raw.settings === \"object\"\n ? (raw.settings as Record<string, unknown>)\n : undefined;\n const sourceContext =\n settings &&\n typeof settings.metadata === \"object\" &&\n settings.metadata !== null &&\n typeof (settings.metadata as Record<string, unknown>).sourceContext ===\n \"object\" &&\n (settings.metadata as Record<string, unknown>).sourceContext !== null\n ? ((settings.metadata as Record<string, unknown>).sourceContext as Record<\n string,\n unknown\n >)\n : undefined;\n\n return {\n id: String(raw.id ?? \"\"),\n title: String(raw.title ?? \"Untitled\"),\n description: raw.description ? String(raw.description) : undefined,\n avatar: raw.avatar ? String(raw.avatar) : undefined,\n settings,\n sourceContext: sourceContext\n ? {\n product: sourceContext.product\n ? String(sourceContext.product)\n : undefined,\n pagePath: String(sourceContext.pagePath ?? \"\"),\n contextHref: String(sourceContext.contextHref ?? \"\"),\n contextTitle: sourceContext.contextTitle\n ? String(sourceContext.contextTitle)\n : undefined,\n entityType: sourceContext.entityType\n ? String(sourceContext.entityType)\n : undefined,\n entityId: sourceContext.entityId\n ? String(sourceContext.entityId)\n : undefined,\n activeTab: sourceContext.activeTab\n ? String(sourceContext.activeTab)\n : undefined,\n }\n : undefined,\n type: String(raw.type ?? \"DIRECT\") as Conversation[\"type\"],\n state: String(raw.state ?? \"ACTIVE\") as Conversation[\"state\"],\n visibility: String(\n raw.visibility ?? \"PRIVATE\",\n ) as Conversation[\"visibility\"],\n isGroup: Boolean(raw.isGroup) || String(raw.type ?? \"DIRECT\") !== \"DIRECT\",\n memberCount: Number(raw.memberCount ?? 0),\n unreadCount: Number(raw.unreadCount ?? 0),\n mentionCount: Number(raw.mentionCount ?? 0),\n lastMessage: raw.lastMessage\n ? mapMessage(raw.lastMessage as Record<string, unknown>)\n : undefined,\n isPinned: Boolean(\n raw.currentUserMembership\n ? (raw.currentUserMembership as Record<string, unknown>).isPinned\n : false,\n ),\n isMuted: Boolean(\n raw.currentUserMembership\n ? (raw.currentUserMembership as Record<string, unknown>).isMuted\n : false,\n ),\n features: raw.features\n ? mapConversationFeatures(raw.features as Record<string, unknown>)\n : defaultFeatures(),\n createdBy: {\n id: String(\n raw.createdBy && typeof raw.createdBy === \"object\"\n ? (raw.createdBy as Record<string, unknown>).id\n : (raw.createdBy ?? \"\"),\n ),\n name: String(\n raw.createdBy && typeof raw.createdBy === \"object\"\n ? (raw.createdBy as Record<string, unknown>).name\n : \"\",\n ),\n },\n createdAt: String(raw.createdAt ?? new Date().toISOString()),\n updatedAt: String(raw.updatedAt ?? new Date().toISOString()),\n };\n}\n\n/**\n * Map a GraphQL Message response to the frontend Message type.\n */\nexport function mapMessage(raw: Record<string, unknown>): Message {\n const reactions = Array.isArray(raw.reactions)\n ? (raw.reactions as Record<string, unknown>[]).map(mapReaction)\n : [];\n\n const mentions = Array.isArray(raw.mentions)\n ? (raw.mentions as Record<string, unknown>[]).map(mapMention)\n : [];\n\n const attachments = Array.isArray(raw.attachments)\n ? (raw.attachments as Record<string, unknown>[]).map(mapAttachment)\n : [];\n\n return {\n id: String(raw.id ?? \"\"),\n conversationId: String(raw.conversationId ?? \"\"),\n type: String(raw.type ?? \"TEXT\") as Message[\"type\"],\n status: String(raw.status ?? \"SENT\") as Message[\"status\"],\n content: extractTextContent(raw.content),\n authorId: String(raw.authorId ?? \"\"),\n authorType: String(raw.authorType ?? \"user\"),\n isAnonymous: Boolean(raw.isAnonymous),\n sender: {\n id: String(raw.authorId ?? \"\"),\n name: String(\n raw.sender && typeof raw.sender === \"object\"\n ? (raw.sender as Record<string, unknown>).name\n : (raw.authorId ?? \"Unknown\"),\n ),\n avatarUrl:\n raw.sender && typeof raw.sender === \"object\"\n ? ((raw.sender as Record<string, unknown>).avatarUrl as\n string | undefined)\n : undefined,\n },\n parentMessageId: raw.parentMessageId\n ? String(raw.parentMessageId)\n : undefined,\n threadRootId: raw.threadRootId ? String(raw.threadRootId) : undefined,\n threadLevel: Number(raw.threadLevel ?? 0),\n replyCount: Number(raw.replyCount ?? 0),\n isEdited: Boolean(raw.isEdited),\n editedAt: raw.editedAt ? String(raw.editedAt) : undefined,\n isPinned: Boolean(raw.isPinned),\n reactions,\n mentions,\n attachments,\n permissions: raw.permissions\n ? mapMessagePermissions(raw.permissions as Record<string, unknown>)\n : defaultMessagePermissions(),\n createdAt: String(raw.createdAt ?? new Date().toISOString()),\n updatedAt: String(raw.updatedAt ?? new Date().toISOString()),\n };\n}\n\n/**\n * Map a GraphQL Reaction response\n */\nfunction mapReaction(raw: Record<string, unknown>): Reaction {\n return {\n id: String(raw.id ?? \"\"),\n emoji: String(raw.emoji ?? \"\"),\n count: Number(raw.count ?? 1),\n users: Array.isArray(raw.users)\n ? (raw.users as Record<string, unknown>[]).map((u) => ({\n id: String(u.id ?? \"\"),\n name: String(u.name ?? \"\"),\n }))\n : [],\n currentUserReacted: Boolean(raw.currentUserReacted),\n };\n}\n\n/**\n * Map a GraphQL Mention response\n */\nfunction mapMention(raw: Record<string, unknown>): Mention {\n return {\n id: String(raw.id ?? \"\"),\n mentionedUser: raw.mentionedUser ? String(raw.mentionedUser) : undefined,\n mentionType: String(raw.mentionType ?? \"USER\") as Mention[\"mentionType\"],\n offset: Number(raw.offset ?? 0),\n length: Number(raw.length ?? 0),\n };\n}\n\n/**\n * Map a GraphQL Attachment response\n */\nfunction mapAttachment(raw: Record<string, unknown>): Attachment {\n return {\n id: String(raw.id ?? \"\"),\n fileId: String(raw.fileId ?? \"\"),\n fileName: String(raw.fileName ?? \"file\"),\n fileSize: Number(raw.fileSize ?? 0),\n mimeType: String(raw.mimeType ?? \"application/octet-stream\"),\n downloadUrl: raw.downloadUrl ? String(raw.downloadUrl) : undefined,\n width: raw.width ? Number(raw.width) : undefined,\n height: raw.height ? Number(raw.height) : undefined,\n duration: raw.duration ? Number(raw.duration) : undefined,\n thumbnail: raw.thumbnail ? String(raw.thumbnail) : undefined,\n };\n}\n\n// ============================================================================\n// HELPERS\n// ============================================================================\n\nfunction mapConversationFeatures(\n raw: Record<string, unknown>,\n): ConversationFeatures {\n return {\n threads: Boolean(raw.threads ?? true),\n reactions: Boolean(raw.reactions ?? true),\n mentions: Boolean(raw.mentions ?? true),\n calls: Boolean(raw.calls ?? true),\n apps: Boolean(raw.apps ?? true),\n polls: Boolean(raw.polls ?? true),\n anonymousMode: Boolean(raw.anonymousMode),\n screenShare: Boolean(raw.screenShare ?? true),\n recording: Boolean(raw.recording),\n };\n}\n\nfunction mapMessagePermissions(\n raw: Record<string, unknown>,\n): MessagePermissions {\n return {\n canEdit: Boolean(raw.canEdit),\n canDelete: Boolean(raw.canDelete),\n canReact: Boolean(raw.canReact ?? true),\n canReply: Boolean(raw.canReply ?? true),\n canPin: Boolean(raw.canPin),\n canHide: Boolean(raw.canHide),\n canReport: Boolean(raw.canReport ?? true),\n };\n}\n\nfunction defaultFeatures(): ConversationFeatures {\n return {\n threads: true,\n reactions: true,\n mentions: true,\n calls: true,\n apps: true,\n polls: true,\n anonymousMode: false,\n screenShare: true,\n recording: false,\n };\n}\n\nfunction defaultMessagePermissions(): MessagePermissions {\n return {\n canEdit: false,\n canDelete: false,\n canReact: true,\n canReply: true,\n canPin: false,\n canHide: false,\n canReport: true,\n };\n}\n\n/**\n * Extract text content from a JSON content field.\n * The GraphQL schema stores content as JSON — this handles\n * both string and structured rich-text formats.\n */\nfunction extractTextContent(content: unknown): string {\n if (typeof content === \"string\") return content;\n if (content && typeof content === \"object\") {\n const obj = content as Record<string, unknown>;\n if (typeof obj.text === \"string\") return obj.text;\n if (typeof obj.payload === \"string\") return obj.payload;\n }\n return \"\";\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"mappers.js","names":[],"sources":["../../../src/conversations/utils/mappers.ts"],"sourcesContent":["import type {\n Conversation,\n Message,\n Reaction,\n Attachment,\n Mention,\n MessagePermissions,\n ConversationFeatures,\n} from \"../types\";\n\n/**\n * Map a GraphQL Conversation response to the frontend Conversation type.\n * Gracefully handles undefined/null fields from partial queries.\n */\nexport function mapConversation(raw: Record<string, unknown>): Conversation {\n const settings =\n raw.settings && typeof raw.settings === \"object\"\n ? (raw.settings as Record<string, unknown>)\n : undefined;\n const sourceContext =\n settings &&\n typeof settings.metadata === \"object\" &&\n settings.metadata !== null &&\n typeof (settings.metadata as Record<string, unknown>).sourceContext ===\n \"object\" &&\n (settings.metadata as Record<string, unknown>).sourceContext !== null\n ? ((settings.metadata as Record<string, unknown>).sourceContext as Record<\n string,\n unknown\n >)\n : undefined;\n\n return {\n id: String(raw.id ?? \"\"),\n title: String(raw.title ?? \"Untitled\"),\n description: raw.description ? String(raw.description) : undefined,\n avatar: raw.avatar ? String(raw.avatar) : undefined,\n settings,\n sourceContext: sourceContext\n ? {\n product: sourceContext.product\n ? String(sourceContext.product)\n : undefined,\n pagePath: String(sourceContext.pagePath ?? \"\"),\n contextHref: String(sourceContext.contextHref ?? \"\"),\n contextTitle: sourceContext.contextTitle\n ? String(sourceContext.contextTitle)\n : undefined,\n entityType: sourceContext.entityType\n ? String(sourceContext.entityType)\n : undefined,\n entityId: sourceContext.entityId\n ? String(sourceContext.entityId)\n : undefined,\n activeTab: sourceContext.activeTab\n ? String(sourceContext.activeTab)\n : undefined,\n }\n : undefined,\n type: String(raw.type ?? \"DIRECT\") as Conversation[\"type\"],\n state: String(raw.state ?? \"ACTIVE\") as Conversation[\"state\"],\n visibility: String(\n raw.visibility ?? \"PRIVATE\",\n ) as Conversation[\"visibility\"],\n isGroup: Boolean(raw.isGroup) || String(raw.type ?? \"DIRECT\") !== \"DIRECT\",\n memberCount: Number(raw.memberCount ?? 0),\n unreadCount: Number(raw.unreadCount ?? 0),\n mentionCount: Number(raw.mentionCount ?? 0),\n lastMessage: raw.lastMessage\n ? mapMessage(raw.lastMessage as Record<string, unknown>)\n : undefined,\n isPinned: Boolean(\n raw.currentUserMembership\n ? (raw.currentUserMembership as Record<string, unknown>).isPinned\n : false,\n ),\n isMuted: Boolean(\n raw.currentUserMembership\n ? (raw.currentUserMembership as Record<string, unknown>).isMuted\n : false,\n ),\n features: raw.features\n ? mapConversationFeatures(raw.features as Record<string, unknown>)\n : defaultFeatures(),\n createdBy: {\n id: String(\n raw.createdBy && typeof raw.createdBy === \"object\"\n ? (raw.createdBy as Record<string, unknown>).id\n : (raw.createdBy ?? \"\"),\n ),\n name: String(\n raw.createdBy && typeof raw.createdBy === \"object\"\n ? (raw.createdBy as Record<string, unknown>).name\n : \"\",\n ),\n },\n createdAt: String(raw.createdAt ?? new Date().toISOString()),\n updatedAt: String(raw.updatedAt ?? new Date().toISOString()),\n };\n}\n\n/**\n * Map a GraphQL Message response to the frontend Message type.\n */\nexport function mapMessage(raw: Record<string, unknown>): Message {\n const reactions = Array.isArray(raw.reactions)\n ? (raw.reactions as Record<string, unknown>[]).map(mapReaction)\n : [];\n\n const mentions = Array.isArray(raw.mentions)\n ? (raw.mentions as Record<string, unknown>[]).map(mapMention)\n : [];\n\n const attachments = Array.isArray(raw.attachments)\n ? (raw.attachments as Record<string, unknown>[]).map(mapAttachment)\n : [];\n\n return {\n id: String(raw.id ?? \"\"),\n conversationId: String(raw.conversationId ?? \"\"),\n type: String(raw.type ?? \"TEXT\") as Message[\"type\"],\n status: String(raw.status ?? \"SENT\") as Message[\"status\"],\n content: extractTextContent(raw.content),\n authorId: String(raw.authorId ?? \"\"),\n authorType: String(raw.authorType ?? \"user\"),\n isAnonymous: Boolean(raw.isAnonymous),\n sender: {\n id: String(raw.authorId ?? \"\"),\n name: resolveSenderName(raw),\n avatarUrl:\n raw.sender && typeof raw.sender === \"object\"\n ? ((raw.sender as Record<string, unknown>).avatarUrl as\n | string\n | undefined)\n : undefined,\n },\n parentMessageId: raw.parentMessageId\n ? String(raw.parentMessageId)\n : undefined,\n threadRootId: raw.threadRootId ? String(raw.threadRootId) : undefined,\n threadLevel: Number(raw.threadLevel ?? 0),\n replyCount: Number(raw.replyCount ?? 0),\n isEdited: Boolean(raw.isEdited),\n editedAt: raw.editedAt ? String(raw.editedAt) : undefined,\n isPinned: Boolean(raw.isPinned),\n reactions,\n mentions,\n attachments,\n permissions: raw.permissions\n ? mapMessagePermissions(raw.permissions as Record<string, unknown>)\n : defaultMessagePermissions(),\n createdAt: String(raw.createdAt ?? new Date().toISOString()),\n updatedAt: String(raw.updatedAt ?? new Date().toISOString()),\n };\n}\n\n/**\n * Map a GraphQL Reaction response\n */\nfunction mapReaction(raw: Record<string, unknown>): Reaction {\n return {\n id: String(raw.id ?? \"\"),\n emoji: String(raw.emoji ?? \"\"),\n count: Number(raw.count ?? 1),\n users: Array.isArray(raw.users)\n ? (raw.users as Record<string, unknown>[]).map((u) => ({\n id: String(u.id ?? \"\"),\n name: String(u.name ?? \"\"),\n }))\n : [],\n currentUserReacted: Boolean(raw.currentUserReacted),\n };\n}\n\n/**\n * Map a GraphQL Mention response\n */\nfunction mapMention(raw: Record<string, unknown>): Mention {\n return {\n id: String(raw.id ?? \"\"),\n mentionedUser: raw.mentionedUser ? String(raw.mentionedUser) : undefined,\n mentionType: String(raw.mentionType ?? \"USER\") as Mention[\"mentionType\"],\n offset: Number(raw.offset ?? 0),\n length: Number(raw.length ?? 0),\n };\n}\n\n/**\n * Map a GraphQL Attachment response\n */\nfunction mapAttachment(raw: Record<string, unknown>): Attachment {\n return {\n id: String(raw.id ?? \"\"),\n fileId: String(raw.fileId ?? \"\"),\n fileName: String(raw.fileName ?? \"file\"),\n fileSize: Number(raw.fileSize ?? 0),\n mimeType: String(raw.mimeType ?? \"application/octet-stream\"),\n downloadUrl: raw.downloadUrl ? String(raw.downloadUrl) : undefined,\n width: raw.width ? Number(raw.width) : undefined,\n height: raw.height ? Number(raw.height) : undefined,\n duration: raw.duration ? Number(raw.duration) : undefined,\n thumbnail: raw.thumbnail ? String(raw.thumbnail) : undefined,\n };\n}\n\n// ============================================================================\n// HELPERS\n// ============================================================================\n\n/**\n * Resolve the sender display name for a message.\n *\n * Prefers the server-resolved `authorName` (batch-resolved against the\n * workspace member directory, with a neutral-label fallback), then a nested\n * `sender.name` object when a query happens to provide one. Never falls back\n * to `authorId` — rendering the internal UUID as a name is the bug this\n * ordering prevents (BOFF-7852).\n */\nfunction resolveSenderName(raw: Record<string, unknown>): string {\n if (typeof raw.authorName === \"string\" && raw.authorName.trim()) {\n return raw.authorName;\n }\n if (raw.sender && typeof raw.sender === \"object\") {\n const name = (raw.sender as Record<string, unknown>).name;\n if (typeof name === \"string\" && name.trim()) return name;\n }\n return \"Unknown\";\n}\n\nfunction mapConversationFeatures(\n raw: Record<string, unknown>,\n): ConversationFeatures {\n return {\n threads: Boolean(raw.threads ?? true),\n reactions: Boolean(raw.reactions ?? true),\n mentions: Boolean(raw.mentions ?? true),\n calls: Boolean(raw.calls ?? true),\n apps: Boolean(raw.apps ?? true),\n polls: Boolean(raw.polls ?? true),\n anonymousMode: Boolean(raw.anonymousMode),\n screenShare: Boolean(raw.screenShare ?? true),\n recording: Boolean(raw.recording),\n };\n}\n\nfunction mapMessagePermissions(\n raw: Record<string, unknown>,\n): MessagePermissions {\n return {\n canEdit: Boolean(raw.canEdit),\n canDelete: Boolean(raw.canDelete),\n canReact: Boolean(raw.canReact ?? true),\n canReply: Boolean(raw.canReply ?? true),\n canPin: Boolean(raw.canPin),\n canHide: Boolean(raw.canHide),\n canReport: Boolean(raw.canReport ?? true),\n };\n}\n\nfunction defaultFeatures(): ConversationFeatures {\n return {\n threads: true,\n reactions: true,\n mentions: true,\n calls: true,\n apps: true,\n polls: true,\n anonymousMode: false,\n screenShare: true,\n recording: false,\n };\n}\n\nfunction defaultMessagePermissions(): MessagePermissions {\n return {\n canEdit: false,\n canDelete: false,\n canReact: true,\n canReply: true,\n canPin: false,\n canHide: false,\n canReport: true,\n };\n}\n\n/**\n * Extract text content from a JSON content field.\n * The GraphQL schema stores content as JSON — this handles\n * both string and structured rich-text formats.\n */\nfunction extractTextContent(content: unknown): string {\n if (typeof content === \"string\") return content;\n if (content && typeof content === \"object\") {\n const obj = content as Record<string, unknown>;\n if (typeof obj.text === \"string\") return obj.text;\n if (typeof obj.payload === \"string\") return obj.payload;\n }\n return \"\";\n}\n"],"mappings":"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"}
@@ -169,6 +169,7 @@ var d = e`
169
169
  content
170
170
  authorId
171
171
  authorType
172
+ authorName
172
173
  isAnonymous
173
174
  parentMessageId
174
175
  threadRootId
@@ -514,6 +515,7 @@ var L = e`
514
515
  content
515
516
  authorId
516
517
  authorType
518
+ authorName
517
519
  createdAt
518
520
  }
519
521
  currentUserMembership {
@@ -576,6 +578,7 @@ var z = e`
576
578
  content
577
579
  authorId
578
580
  authorType
581
+ authorName
579
582
  isAnonymous
580
583
  parentMessageId
581
584
  threadRootId
@@ -667,6 +670,7 @@ e`
667
670
  content
668
671
  authorId
669
672
  authorType
673
+ authorName
670
674
  isAnonymous
671
675
  parentMessageId
672
676
  threadRootId
@@ -769,6 +773,7 @@ var U = e`
769
773
  content
770
774
  authorId
771
775
  authorType
776
+ authorName
772
777
  createdAt
773
778
  }
774
779
  currentUserMembership {
@@ -814,6 +819,7 @@ e`
814
819
  content
815
820
  authorId
816
821
  authorType
822
+ authorName
817
823
  isAnonymous
818
824
  parentMessageId
819
825
  threadRootId
@@ -931,6 +937,7 @@ var G = e`
931
937
  content
932
938
  authorId
933
939
  authorType
940
+ authorName
934
941
  isAnonymous
935
942
  parentMessageId
936
943
  threadRootId