@burdenoff/microfe-conversations 2026.522.1 → 2026.523.2

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.
Files changed (47) hide show
  1. package/dist/conversations/ConversationsUserRoutes.d.ts.map +1 -1
  2. package/dist/conversations/ConversationsUserRoutes.js +17 -19
  3. package/dist/conversations/ConversationsUserRoutes.js.map +1 -1
  4. package/dist/conversations/components/ConversationCard.d.ts.map +1 -1
  5. package/dist/conversations/components/ConversationCard.js +2 -1
  6. package/dist/conversations/components/ConversationCard.js.map +1 -1
  7. package/dist/conversations/components/LazyPageBoundary.d.ts +3 -3
  8. package/dist/conversations/components/LazyPageBoundary.d.ts.map +1 -1
  9. package/dist/conversations/components/LazyPageBoundary.js.map +1 -1
  10. package/dist/conversations/components/MessageBubble.d.ts.map +1 -1
  11. package/dist/conversations/components/MessageBubble.js +22 -16
  12. package/dist/conversations/components/MessageBubble.js.map +1 -1
  13. package/dist/conversations/components/PageLayout.d.ts +3 -1
  14. package/dist/conversations/components/PageLayout.d.ts.map +1 -1
  15. package/dist/conversations/components/PageLayout.js +2 -1
  16. package/dist/conversations/components/PageLayout.js.map +1 -1
  17. package/dist/conversations/hooks/useConversationQueries.js +1 -1
  18. package/dist/conversations/hooks/useConversationQueries.js.map +1 -1
  19. package/dist/conversations/hooks/useMessageQueries.d.ts +1 -0
  20. package/dist/conversations/hooks/useMessageQueries.d.ts.map +1 -1
  21. package/dist/conversations/hooks/useMessageQueries.js +48 -42
  22. package/dist/conversations/hooks/useMessageQueries.js.map +1 -1
  23. package/dist/conversations/index.js +1 -1
  24. package/dist/conversations/pages/ConversationAdminPages.d.ts.map +1 -1
  25. package/dist/conversations/pages/ConversationAdminPages.js +139 -138
  26. package/dist/conversations/pages/ConversationAdminPages.js.map +1 -1
  27. package/dist/conversations/pages/ConversationDetailPage.d.ts.map +1 -1
  28. package/dist/conversations/pages/ConversationDetailPage.js +256 -230
  29. package/dist/conversations/pages/ConversationDetailPage.js.map +1 -1
  30. package/dist/conversations/pages/ConversationListPage.d.ts +1 -2
  31. package/dist/conversations/pages/ConversationListPage.d.ts.map +1 -1
  32. package/dist/conversations/pages/ConversationListPage.js +138 -125
  33. package/dist/conversations/pages/ConversationListPage.js.map +1 -1
  34. package/dist/conversations/pages/ConversationsLandingPage.d.ts.map +1 -1
  35. package/dist/conversations/pages/ConversationsLandingPage.js +3 -1
  36. package/dist/conversations/pages/ConversationsLandingPage.js.map +1 -1
  37. package/dist/conversations/pages/ConversationsSettingsPage.d.ts.map +1 -1
  38. package/dist/conversations/pages/ConversationsSettingsPage.js +5 -4
  39. package/dist/conversations/pages/ConversationsSettingsPage.js.map +1 -1
  40. package/dist/conversations/pages/NewConversationPage.d.ts.map +1 -1
  41. package/dist/conversations/pages/NewConversationPage.js +4 -0
  42. package/dist/conversations/pages/NewConversationPage.js.map +1 -1
  43. package/dist/conversations/providers/ConversationsProvider.d.ts.map +1 -1
  44. package/dist/conversations/providers/ConversationsProvider.js +10 -8
  45. package/dist/conversations/providers/ConversationsProvider.js.map +1 -1
  46. package/dist/index.js +5 -5
  47. package/package.json +1 -1
@@ -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 { useNavigate, useParams } from \"react-router-dom\";\nimport {\n Archive,\n ArrowLeft,\n BellOff,\n Bell,\n ExternalLink,\n Hash,\n Loader2,\n MessageCircle,\n MoreVertical,\n Pin,\n PinOff,\n SendHorizontal,\n Users,\n X,\n} from \"lucide-react\";\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 { CONVERSATIONS_PERMISSIONS } from \"../constants/permissions\";\nimport { nativeImpact, nativeNotify } from \"../../utils/nativeBridge\";\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 = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\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 messagesEndRef = useRef<HTMLDivElement>(null);\n\n const {\n conversation,\n loading: conversationLoading,\n error: conversationError,\n } = useGetConversation(conversationId ?? \"\");\n const {\n messages,\n loading: messagesLoading,\n error: messagesError,\n } = useGetMessages(conversationId ?? \"\", {\n first: 100,\n });\n // Subscribe to live message events so the list updates without polling.\n // Apollo cache merges by id; the hook handles append / evict explicitly.\n useMessageStreamForConversation(conversationId);\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 } = 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 useEffect(() => {\n messagesEndRef.current?.scrollIntoView({ behavior: \"smooth\" });\n }, [messages.length]);\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 participants = useMemo(() => {\n const byAuthor = new Map<string, { id: string; name: string }>();\n for (const message of messages) {\n if (!message.authorId || byAuthor.has(message.authorId)) continue;\n byAuthor.set(message.authorId, {\n id: message.authorId,\n name: message.sender.name || message.authorId,\n });\n }\n return Array.from(byAuthor.values());\n }, [messages]);\n\n const handleSendMessage = useCallback(async () => {\n if (!conversationId || !messageInput.trim() || sendingMessage) return;\n const message = await sendMessage({\n conversationId,\n content: messageInput.trim(),\n type: \"TEXT\",\n parentMessageId: replyingTo?.id,\n });\n if (!message) return;\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 nativeImpact(\"medium\");\n try {\n await deleteMessage(messageId);\n nativeNotify(\"success\");\n } catch (error) {\n nativeNotify(\"error\");\n throw error;\n }\n },\n [deleteMessage],\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 await archiveConversation(conversationId);\n bus.emit(\"conversations.conversation.archived\", { conversationId });\n setMoreMenuOpen(false);\n navigate(basePath || \"/conversations\");\n }, [archiveConversation, basePath, bus, conversationId, navigate]);\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 if (!conversation && conversationLoading) {\n return (\n <div className=\"flex items-center justify-center h-[calc(100vh-64px)] text-sm text-muted-foreground\">\n {tr(\"conversations.loading\", \"Loading conversation...\")}\n </div>\n );\n }\n\n if (!conversation || conversationError) {\n return (\n <div className=\"flex flex-col items-center justify-center h-[calc(100vh-64px)] text-center gap-3 px-4\">\n <MessageCircle className=\"h-8 w-8 text-muted-foreground\" />\n <p className=\"text-sm text-muted-foreground\">\n {conversationError\n ? tr(\n \"conversations.unavailable\",\n \"We couldn't load this conversation. Please try again.\",\n )\n : tr(\"conversations.notFound\", \"Conversation not found\")}\n </p>\n <button\n type=\"button\"\n onClick={() => navigate(basePath || \"/conversations\")}\n className=\"px-3 py-2 text-sm font-medium rounded-lg border border-border hover:bg-muted/50\"\n >\n {tr(\"conversations.back\", \"Back to conversations\")}\n </button>\n </div>\n );\n }\n\n return (\n <div className=\"flex h-[calc(100vh-64px)] bg-background\">\n <div className=\"flex-1 flex flex-col min-w-0\">\n <header className=\"flex items-center justify-between px-4 py-3 border-b border-border bg-background/95 backdrop-blur-sm\">\n <div className=\"flex items-center gap-3 min-w-0\">\n <button\n type=\"button\"\n onClick={() => navigate(basePath || \"/conversations\")}\n className=\"p-1.5 rounded-lg text-muted-foreground hover:text-foreground hover:bg-muted/50 transition-colors\"\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-muted-foreground flex-shrink-0\"\n />\n )}\n <h2 className=\"text-sm font-semibold text-foreground truncate\">\n {conversation.title}\n </h2>\n </div>\n <p className=\"text-xs text-muted-foreground\">\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 items-center gap-1\">\n {sourceContext?.contextHref && (\n <HeaderButton\n icon={<ExternalLink size={16} />}\n label={tr(\"conversations.openContext\", \"Open context\")}\n onClick={handleOpenContext}\n />\n )}\n {conversation.isGroup && (\n <HeaderButton\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 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 </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 bg-muted/20 px-3 py-2 text-xs text-muted-foreground\">\n <span className=\"font-medium text-foreground\">\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 bg-muted/20 px-3 py-2 text-sm text-muted-foreground\">\n <Loader2 size={14} className=\"animate-spin\" />\n {tr(\"conversations.loadingMessages\", \"Loading messages...\")}\n </div>\n )}\n {!messagesLoading && !messagesError && messages.length === 0 && (\n <div className=\"mx-4 mb-4 rounded-lg border border-dashed border-border bg-muted/20 px-3 py-6 text-center text-sm text-muted-foreground\">\n {tr(\n \"conversations.noMessagesYet\",\n \"No messages yet. Start the conversation below.\",\n )}\n </div>\n )}\n {messages.map((message, index) => {\n const prevMessage = messages[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-muted-foreground mb-1\">\n <span>\n {tr(\"conversations.editingMessage\", \"Editing message\")}\n </span>\n <button\n type=\"button\"\n onClick={handleEditCancel}\n className=\"hover:text-foreground\"\n >\n <X size={12} />\n </button>\n </div>\n <div className=\"flex items-end gap-2\">\n <textarea\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 bg-muted/30 resize-none focus:outline-none focus:ring-2 focus:ring-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-primary text-primary-foreground hover:bg-primary/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 className=\"border-t border-border px-4 py-3 bg-background\">\n {/* Reply-to indicator */}\n {replyingTo && (\n <div className=\"flex items-center gap-2 mb-2 px-3 py-1.5 rounded-lg bg-muted/50 text-xs text-muted-foreground\">\n <span>\n {tr(\"conversations.replyingTo\", \"Replying to\")}{\" \"}\n <strong className=\"text-foreground\">{replyingTo.sender}</strong>\n : {replyingTo.content.slice(0, 80)}\n {replyingTo.content.length > 80 ? \"…\" : \"\"}\n </span>\n <button\n type=\"button\"\n onClick={() => setReplyingTo(null)}\n className=\"ml-auto hover:text-foreground\"\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 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 bg-muted/30\",\n \"resize-none overflow-hidden placeholder:text-muted-foreground\",\n \"focus:outline-none focus:ring-2 focus:ring-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-primary text-primary-foreground hover:bg-primary/90 shadow-sm\"\n : \"bg-muted text-muted-foreground 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=\"w-64 border-l border-border bg-background overflow-y-auto animate-in slide-in-from-right-4 duration-200\">\n <div className=\"p-4\">\n <div className=\"flex items-center justify-between mb-4\">\n <h3 className=\"text-sm font-semibold text-foreground\">\n {tr(\"conversations.members\", \"Members\")} (\n {participants.length || conversation.memberCount})\n </h3>\n <button\n type=\"button\"\n onClick={toggleParticipantsPanel}\n className=\"p-1 rounded text-muted-foreground hover:text-foreground\"\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-muted/50\"\n >\n <div className=\"w-7 h-7 rounded-full bg-muted flex items-center justify-center text-xs font-medium text-muted-foreground\">\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-foreground truncate\">\n {participant.name}\n </p>\n <p className=\"text-[10px] text-muted-foreground\">\n {tr(\"conversations.member\", \"Member\")}\n </p>\n </div>\n </div>\n ))}\n </div>\n </div>\n </aside>\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-destructive hover:bg-destructive/10\"\n : \"text-foreground hover:bg-muted/50\",\n )}\n >\n {icon}\n <span>{label}</span>\n </button>\n );\n}\n\nfunction HeaderButton({\n icon,\n label,\n active,\n onClick,\n}: {\n icon: React.ReactNode;\n label: string;\n active?: boolean;\n onClick?: () => void;\n}) {\n return (\n <PermissionGuard\n permission={CONVERSATIONS_PERMISSIONS.CONVERSATION_READ}\n allowUnavailablePermissions\n >\n <button\n type=\"button\"\n onClick={onClick}\n aria-label={label}\n title={label}\n className={cn(\n \"p-2 rounded-lg transition-colors\",\n active\n ? \"bg-primary/10 text-primary\"\n : \"text-muted-foreground hover:text-foreground hover:bg-muted/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 { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport { useEventBus } from \"@burdenoff/fe-libs/shared/events\";\nimport { useNavigate, useParams } from \"react-router-dom\";\nimport {\n Archive,\n ArrowLeft,\n BellOff,\n Bell,\n ExternalLink,\n Hash,\n Loader2,\n MessageCircle,\n MoreVertical,\n Pin,\n PinOff,\n SendHorizontal,\n Users,\n X,\n} from \"lucide-react\";\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 { CONVERSATIONS_PERMISSIONS } from \"../constants/permissions\";\nimport { nativeImpact, nativeNotify } from \"../../utils/nativeBridge\";\nimport type { Message } from \"../types\";\n\nconst MESSAGES_PAGE_SIZE = 100;\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 = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\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 [locallySentMessages, setLocallySentMessages] = useState<Message[]>([]);\n const messagesEndRef = useRef<HTMLDivElement>(null);\n\n const {\n conversation,\n loading: conversationLoading,\n error: conversationError,\n } = useGetConversation(conversationId ?? \"\");\n const {\n messages,\n loading: messagesLoading,\n error: messagesError,\n } = useGetMessages(conversationId ?? \"\", {\n first: MESSAGES_PAGE_SIZE,\n });\n // Subscribe to live message events so the list updates without polling.\n // Apollo cache merges by id; the hook handles append / evict explicitly.\n useMessageStreamForConversation(conversationId);\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 } = 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]);\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 participants = useMemo(() => {\n const byAuthor = new Map<string, { id: string; name: string }>();\n for (const message of displayedMessages) {\n if (!message.authorId || byAuthor.has(message.authorId)) continue;\n byAuthor.set(message.authorId, {\n id: message.authorId,\n name: message.sender.name || message.authorId,\n });\n }\n return Array.from(byAuthor.values());\n }, [displayedMessages]);\n\n const handleSendMessage = useCallback(async () => {\n if (!conversationId || !messageInput.trim() || sendingMessage) return;\n const message = await sendMessage({\n conversationId,\n content: messageInput.trim(),\n type: \"TEXT\",\n parentMessageId: replyingTo?.id,\n refetchFirst: MESSAGES_PAGE_SIZE,\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 nativeImpact(\"medium\");\n try {\n await deleteMessage(messageId);\n nativeNotify(\"success\");\n } catch (error) {\n nativeNotify(\"error\");\n throw error;\n }\n },\n [deleteMessage],\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 await archiveConversation(conversationId);\n bus.emit(\"conversations.conversation.archived\", { conversationId });\n setMoreMenuOpen(false);\n navigate(basePath || \"/conversations\");\n }, [archiveConversation, basePath, bus, conversationId, navigate]);\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 if (!conversation && conversationLoading) {\n return (\n <div className=\"flex items-center justify-center h-[calc(100vh-64px)] text-sm text-muted-foreground\">\n {tr(\"conversations.loading\", \"Loading conversation...\")}\n </div>\n );\n }\n\n if (!conversation || conversationError) {\n return (\n <div className=\"flex flex-col items-center justify-center h-[calc(100vh-64px)] text-center gap-3 px-4\">\n <MessageCircle className=\"h-8 w-8 text-muted-foreground\" />\n <p className=\"text-sm text-muted-foreground\">\n {conversationError\n ? tr(\n \"conversations.unavailable\",\n \"We couldn't load this conversation. Please try again.\",\n )\n : tr(\"conversations.notFound\", \"Conversation not found\")}\n </p>\n <button\n type=\"button\"\n onClick={() => navigate(basePath || \"/conversations\")}\n className=\"px-3 py-2 text-sm font-medium rounded-lg border border-border hover:bg-muted/50\"\n >\n {tr(\"conversations.back\", \"Back to conversations\")}\n </button>\n </div>\n );\n }\n\n return (\n <div className=\"relative flex h-[calc(100vh-64px)] bg-background\">\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 bg-background/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-muted-foreground transition-colors hover:bg-muted/50 hover:text-foreground\"\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-muted-foreground flex-shrink-0\"\n />\n )}\n <h2 className=\"text-sm font-semibold text-foreground truncate\">\n {conversation.title}\n </h2>\n </div>\n <p className=\"text-xs text-muted-foreground\">\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 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 </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 bg-muted/20 px-3 py-2 text-xs text-muted-foreground\">\n <span className=\"font-medium text-foreground\">\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 bg-muted/20 px-3 py-2 text-sm text-muted-foreground\">\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 rounded-lg border border-dashed border-border bg-muted/20 px-3 py-6 text-center text-sm text-muted-foreground\">\n {tr(\n \"conversations.noMessagesYet\",\n \"No messages yet. Start the conversation below.\",\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-muted-foreground mb-1\">\n <span>\n {tr(\"conversations.editingMessage\", \"Editing message\")}\n </span>\n <button\n type=\"button\"\n onClick={handleEditCancel}\n className=\"hover:text-foreground\"\n >\n <X size={12} />\n </button>\n </div>\n <div className=\"flex items-end gap-2\">\n <textarea\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 bg-muted/30 resize-none focus:outline-none focus:ring-2 focus:ring-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-primary text-primary-foreground hover:bg-primary/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 px-4 py-3 bg-background\"\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-muted/50 text-xs text-muted-foreground\">\n <span>\n {tr(\"conversations.replyingTo\", \"Replying to\")}{\" \"}\n <strong className=\"text-foreground\">{replyingTo.sender}</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-muted/70 hover:text-foreground\"\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 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 bg-muted/30\",\n \"resize-none overflow-hidden placeholder:text-muted-foreground\",\n \"focus:outline-none focus:ring-2 focus:ring-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-primary text-primary-foreground hover:bg-primary/90 shadow-sm\"\n : \"bg-muted text-muted-foreground 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 bg-background 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-foreground\">\n {tr(\"conversations.members\", \"Members\")} (\n {participants.length || conversation.memberCount})\n </h3>\n <button\n type=\"button\"\n onClick={toggleParticipantsPanel}\n className=\"p-1 rounded text-muted-foreground hover:text-foreground\"\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-muted/50\"\n >\n <div className=\"w-7 h-7 rounded-full bg-muted flex items-center justify-center text-xs font-medium text-muted-foreground\">\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-foreground truncate\">\n {participant.name}\n </p>\n <p className=\"text-[10px] text-muted-foreground\">\n {tr(\"conversations.member\", \"Member\")}\n </p>\n </div>\n </div>\n ))}\n </div>\n </div>\n </aside>\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-destructive hover:bg-destructive/10\"\n : \"text-foreground hover:bg-muted/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-primary/10 text-primary\"\n : \"text-muted-foreground hover:text-foreground hover:bg-muted/50\",\n )}\n >\n {icon}\n </button>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
@@ -1,6 +1,5 @@
1
- interface ConversationListPageProps {
1
+ export interface ConversationListPageProps {
2
2
  forcedFilterParam?: "channel" | "direct" | "group" | "archived";
3
3
  }
4
4
  export declare function ConversationListPage({ forcedFilterParam, }?: ConversationListPageProps): import("react/jsx-runtime").JSX.Element;
5
- export {};
6
5
  //# sourceMappingURL=ConversationListPage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ConversationListPage.d.ts","sourceRoot":"","sources":["../../../src/conversations/pages/ConversationListPage.tsx"],"names":[],"mappings":"AAuDA,UAAU,yBAAyB;IACjC,iBAAiB,CAAC,EAAE,SAAS,GAAG,QAAQ,GAAG,OAAO,GAAG,UAAU,CAAC;CACjE;AAED,wBAAgB,oBAAoB,CAAC,EACnC,iBAAiB,GAClB,GAAE,yBAA8B,2CAgYhC"}
1
+ {"version":3,"file":"ConversationListPage.d.ts","sourceRoot":"","sources":["../../../src/conversations/pages/ConversationListPage.tsx"],"names":[],"mappings":"AA2EA,MAAM,WAAW,yBAAyB;IACxC,iBAAiB,CAAC,EAAE,SAAS,GAAG,QAAQ,GAAG,OAAO,GAAG,UAAU,CAAC;CACjE;AAED,wBAAgB,oBAAoB,CAAC,EACnC,iBAAiB,GAClB,GAAE,yBAA8B,2CA0YhC"}
@@ -2,15 +2,15 @@ import { useConversations as e } from "../providers/ConversationsProvider.js";
2
2
  import { joinPath as t } from "../utils/navigation.js";
3
3
  import { useTr as n } from "../utils/i18n.js";
4
4
  import { cn as r } from "../utils/cn.js";
5
- import { ConversationCard as i } from "../components/ConversationCard.js";
6
- import { EmptyState as a, PageLayout as o } from "../components/PageLayout.js";
7
- import { useGetConversations as ee, useSearchConversations as te } from "../hooks/useConversationQueries.js";
8
- import { useConversationStream as ne } from "../hooks/useMessageSubscriptions.js";
9
- import { useConversationsStore as s } from "../store/conversationsStore.js";
5
+ import { EmptyState as i, PageLayout as ee } from "../components/PageLayout.js";
6
+ import { useConversationsStore as a } from "../store/conversationsStore.js";
7
+ import { useGetConversations as o, useSearchConversations as te } from "../hooks/useConversationQueries.js";
8
+ import { useConversationStream as s } from "../hooks/useMessageSubscriptions.js";
10
9
  import { PermissionGuard as c } from "../components/PermissionGuard.js";
11
10
  import { CONVERSATIONS_PERMISSIONS as l } from "../constants/permissions.js";
12
- import { useCallback as re, useEffect as u, useMemo as d, useState as ie } from "react";
13
- import { Archive as ae, Filter as f, LayoutGrid as p, LayoutList as oe, MessageCircle as m, Pin as h, Plus as se, Rows3 as ce, Search as g, SlidersHorizontal as _, X as v } from "lucide-react";
11
+ import { ConversationCard as u } from "../components/ConversationCard.js";
12
+ import { useCallback as ne, useDeferredValue as d, useEffect as f, useMemo as p, useState as m } from "react";
13
+ import { Archive as re, Filter as h, LayoutGrid as ie, LayoutList as ae, MessageCircle as g, Pin as _, Plus as oe, Rows3 as se, Search as ce, SlidersHorizontal as le, X as v } from "lucide-react";
14
14
  import { useNavigate as y, useSearchParams as b } from "react-router-dom";
15
15
  import { useEventBus as x } from "@burdenoff/fe-libs/shared/events";
16
16
  import { jsx as S, jsxs as C } from "react/jsx-runtime";
@@ -23,12 +23,19 @@ var w = {
23
23
  isPinned: void 0,
24
24
  isMuted: void 0
25
25
  };
26
- function T({ forcedFilterParam: c } = {}) {
27
- let l = y(), { basePath: T, onConversationSelect: O } = e(), k = x(), A = n(), { searchQuery: j, setSearchQuery: M, viewMode: N, setViewMode: P, conversationFilter: F, updateConversationFilter: I, activeConversationId: L, setActiveConversationId: R } = s(), [z, B] = ie(!1), [V] = b();
28
- u(() => {
29
- let e = c ?? V.get("filter");
26
+ function T(e, t) {
27
+ let [n, r] = m(e);
28
+ return f(() => {
29
+ let n = window.setTimeout(() => r(e), t);
30
+ return () => window.clearTimeout(n);
31
+ }, [t, e]), n;
32
+ }
33
+ function E({ forcedFilterParam: c } = {}) {
34
+ let l = y(), { basePath: E, onConversationSelect: k } = e(), A = x(), j = n(), { searchQuery: M, setSearchQuery: N, viewMode: P, setViewMode: F, conversationFilter: I, updateConversationFilter: L, activeConversationId: R, setActiveConversationId: z } = a(), [B, V] = m(!1), [H] = b(), U = d(T(M, 250)), W = U.trim().length >= 2;
35
+ f(() => {
36
+ let e = c ?? H.get("filter");
30
37
  if (!e) {
31
- I(w), B(!1);
38
+ L(w), V(!1);
32
39
  return;
33
40
  }
34
41
  let t = {
@@ -38,34 +45,34 @@ function T({ forcedFilterParam: c } = {}) {
38
45
  archived: "ARCHIVED"
39
46
  }[e.toLowerCase()];
40
47
  if (!t) {
41
- I(w), B(!1);
48
+ L(w), V(!1);
42
49
  return;
43
50
  }
44
- I(t === "ARCHIVED" ? {
51
+ L(t === "ARCHIVED" ? {
45
52
  ...w,
46
53
  state: "ARCHIVED"
47
54
  } : {
48
55
  ...w,
49
56
  type: t
50
- }), B(!0);
57
+ }), V(!0);
51
58
  }, [
52
59
  c,
53
- V,
54
- I
60
+ H,
61
+ L
55
62
  ]);
56
- let { conversations: H, loading: U, error: W } = ee({
63
+ let { conversations: G, loading: ue, error: de } = o({
57
64
  first: 50,
58
- types: F.type === "all" ? void 0 : [F.type],
59
- hasUnread: F.hasUnread,
60
- isPinned: F.isPinned
65
+ types: I.type === "all" ? void 0 : [I.type],
66
+ hasUnread: I.hasUnread,
67
+ isPinned: I.isPinned
61
68
  });
62
- ne();
63
- let { conversations: G, loading: K, error: le } = te(j), q = d(() => j.trim() ? G : H, [
64
- j,
65
- G,
66
- H
67
- ]), J = d(() => F.state === "all" ? q : q.filter((e) => e.state === F.state), [q, F.state]), Y = d(() => J.filter((e) => e.isPinned), [J]), X = d(() => J.filter((e) => !e.isPinned), [J]), Z = d(() => J.reduce((e, t) => e + t.unreadCount, 0), [J]), Q = re((e) => {
68
- if (R(e.id), O?.(e), k.emit("conversations.conversation.opened", {
69
+ s();
70
+ let { conversations: K, loading: fe, error: pe } = te(U), q = p(() => W ? K : G, [
71
+ W,
72
+ K,
73
+ G
74
+ ]), J = p(() => I.state === "all" ? q : q.filter((e) => e.state === I.state), [q, I.state]), Y = p(() => J.filter((e) => e.isPinned), [J]), X = p(() => J.filter((e) => !e.isPinned), [J]), Z = p(() => J.reduce((e, t) => e + t.unreadCount, 0), [J]), Q = ne((e) => {
75
+ if (z(e.id), k?.(e), A.emit("conversations.conversation.opened", {
69
76
  conversationId: e.id,
70
77
  conversationType: e.type
71
78
  }), e.sourceContext?.contextHref) try {
@@ -75,195 +82,201 @@ function T({ forcedFilterParam: c } = {}) {
75
82
  return;
76
83
  }
77
84
  } catch {}
78
- l(t(T || "/conversations", e.id));
85
+ l(t(E || "/conversations", e.id));
79
86
  }, [
80
- T,
81
- k,
87
+ E,
88
+ A,
82
89
  l,
83
- O,
84
- R
85
- ]), ue = U || K, $ = W ?? le, de = [`${J.length} ${A("conversations.countLabel", "conversations")}`, Z > 0 ? `${Z} ${A("conversations.unreadCountLabel", "unread")}` : null].filter(Boolean).join(" · ");
86
- return /* @__PURE__ */ C(o, {
87
- title: A("conversations.title", "Conversations"),
88
- subtitle: de,
89
- icon: /* @__PURE__ */ S(m, { size: 24 }),
90
+ k,
91
+ z
92
+ ]), me = ue || fe, $ = de ?? pe, he = [`${J.length} ${j("conversations.countLabel", "conversations")}`, Z > 0 ? `${Z} ${j("conversations.unreadCountLabel", "unread")}` : null].filter(Boolean).join(" · ");
93
+ return /* @__PURE__ */ C(ee, {
94
+ title: j("conversations.title", "Conversations"),
95
+ subtitle: he,
96
+ icon: /* @__PURE__ */ S(g, { size: 24 }),
90
97
  maxWidth: "2xl",
91
98
  headerActions: /* @__PURE__ */ C("button", {
99
+ "data-tour": "conversation-new-button",
92
100
  type: "button",
93
- onClick: () => l(t(T || "/conversations", "new")),
94
- "aria-label": A("conversations.createNewConversation", "Create a new conversation"),
101
+ onClick: () => l(t(E || "/conversations", "new")),
102
+ "aria-label": j("conversations.createNewConversation", "Create a new conversation"),
95
103
  className: "inline-flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-lg bg-primary text-primary-foreground hover:bg-primary/90 transition-colors shadow-sm",
96
- children: [/* @__PURE__ */ S(se, { size: 16 }), A("conversations.newConversation", "New Conversation")]
104
+ children: [/* @__PURE__ */ S(oe, { size: 16 }), j("conversations.newConversation", "New Conversation")]
97
105
  }),
98
106
  children: [/* @__PURE__ */ C("div", {
107
+ "data-tour": "conversation-list-header",
99
108
  className: "space-y-3",
100
109
  children: [/* @__PURE__ */ C("div", {
101
- className: "flex items-center gap-2",
110
+ className: "flex flex-wrap items-center gap-2",
102
111
  children: [
103
112
  /* @__PURE__ */ C("div", {
104
- className: "relative flex-1",
113
+ className: "relative min-w-[14rem] flex-1",
105
114
  children: [
106
- /* @__PURE__ */ S(g, {
115
+ /* @__PURE__ */ S(ce, {
107
116
  size: 16,
108
117
  className: "absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground"
109
118
  }),
110
119
  /* @__PURE__ */ S("input", {
111
- "aria-label": A("conversations.search", "Search conversations"),
120
+ "data-tour": "conversation-search",
121
+ "aria-label": j("conversations.search", "Search conversations"),
112
122
  type: "text",
113
- placeholder: A("conversations.search", "Search conversations..."),
114
- value: j,
115
- onChange: (e) => M(e.target.value),
123
+ placeholder: j("conversations.search", "Search conversations..."),
124
+ value: M,
125
+ onChange: (e) => N(e.target.value),
116
126
  className: "w-full pl-9 pr-8 py-2 text-sm rounded-lg border border-border bg-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring"
117
127
  }),
118
- j && /* @__PURE__ */ S("button", {
128
+ M && /* @__PURE__ */ S("button", {
119
129
  type: "button",
120
- onClick: () => M(""),
121
- "aria-label": A("conversations.clearSearch", "Clear conversation search"),
130
+ onClick: () => N(""),
131
+ "aria-label": j("conversations.clearSearch", "Clear conversation search"),
122
132
  className: "absolute right-2 top-1/2 -translate-y-1/2 p-0.5 rounded text-muted-foreground hover:text-foreground",
123
133
  children: /* @__PURE__ */ S(v, { size: 14 })
124
134
  })
125
135
  ]
126
136
  }),
127
137
  /* @__PURE__ */ S("button", {
138
+ "data-tour": "conversation-filters",
128
139
  type: "button",
129
- onClick: () => B((e) => !e),
130
- "aria-label": z ? A("conversations.hideFilters", "Hide conversation filters") : A("conversations.showFilters", "Show conversation filters"),
131
- "aria-pressed": z,
132
- className: r("p-2 rounded-lg border transition-colors", z ? "bg-primary/10 border-primary/30 text-primary" : "border-border text-muted-foreground hover:text-foreground hover:bg-muted/50"),
133
- children: /* @__PURE__ */ S(_, { size: 16 })
140
+ onClick: () => V((e) => !e),
141
+ "aria-label": B ? j("conversations.hideFilters", "Hide conversation filters") : j("conversations.showFilters", "Show conversation filters"),
142
+ "aria-pressed": B,
143
+ className: r("p-2 rounded-lg border transition-colors", B ? "bg-primary/10 border-primary/30 text-primary" : "border-border text-muted-foreground hover:text-foreground hover:bg-muted/50"),
144
+ children: /* @__PURE__ */ S(le, { size: 16 })
134
145
  }),
135
146
  /* @__PURE__ */ C("div", {
147
+ "data-tour": "conversation-view-modes",
136
148
  className: "flex items-center border border-border rounded-lg overflow-hidden",
137
149
  children: [
138
- /* @__PURE__ */ S(E, {
139
- active: N === "list",
140
- icon: /* @__PURE__ */ S(oe, { size: 14 }),
141
- label: A("conversations.listView", "List view"),
142
- onClick: () => P("list")
150
+ /* @__PURE__ */ S(D, {
151
+ active: P === "list",
152
+ icon: /* @__PURE__ */ S(ae, { size: 14 }),
153
+ label: j("conversations.listView", "List view"),
154
+ onClick: () => F("list")
143
155
  }),
144
- /* @__PURE__ */ S(E, {
145
- active: N === "grid",
146
- icon: /* @__PURE__ */ S(p, { size: 14 }),
147
- label: A("conversations.gridView", "Grid view"),
148
- onClick: () => P("grid")
156
+ /* @__PURE__ */ S(D, {
157
+ active: P === "grid",
158
+ icon: /* @__PURE__ */ S(ie, { size: 14 }),
159
+ label: j("conversations.gridView", "Grid view"),
160
+ onClick: () => F("grid")
149
161
  }),
150
- /* @__PURE__ */ S(E, {
151
- active: N === "compact",
152
- icon: /* @__PURE__ */ S(ce, { size: 14 }),
153
- label: A("conversations.compactView", "Compact view"),
154
- onClick: () => P("compact")
162
+ /* @__PURE__ */ S(D, {
163
+ active: P === "compact",
164
+ icon: /* @__PURE__ */ S(se, { size: 14 }),
165
+ label: j("conversations.compactView", "Compact view"),
166
+ onClick: () => F("compact")
155
167
  })
156
168
  ]
157
169
  })
158
170
  ]
159
- }), z && /* @__PURE__ */ C("div", {
171
+ }), B && /* @__PURE__ */ C("div", {
160
172
  className: "flex flex-wrap gap-2 animate-in fade-in-0 slide-in-from-top-2 duration-200",
161
173
  children: [
162
- /* @__PURE__ */ S(D, {
163
- label: A("conversations.all", "All"),
164
- active: F.type === "all",
165
- onClick: () => I({ type: "all" })
174
+ /* @__PURE__ */ S(O, {
175
+ label: j("conversations.all", "All"),
176
+ active: I.type === "all",
177
+ onClick: () => L({ type: "all" })
166
178
  }),
167
- /* @__PURE__ */ S(D, {
168
- label: A("conversations.direct", "Direct"),
169
- active: F.type === "DIRECT",
170
- onClick: () => I({ type: "DIRECT" })
179
+ /* @__PURE__ */ S(O, {
180
+ label: j("conversations.direct", "Direct"),
181
+ active: I.type === "DIRECT",
182
+ onClick: () => L({ type: "DIRECT" })
171
183
  }),
172
- /* @__PURE__ */ S(D, {
173
- label: A("conversations.groups", "Groups"),
174
- active: F.type === "GROUP",
175
- onClick: () => I({ type: "GROUP" })
184
+ /* @__PURE__ */ S(O, {
185
+ label: j("conversations.groups", "Groups"),
186
+ active: I.type === "GROUP",
187
+ onClick: () => L({ type: "GROUP" })
176
188
  }),
177
- /* @__PURE__ */ S(D, {
178
- label: A("conversations.channels", "Channels"),
179
- active: F.type === "CHANNEL",
180
- onClick: () => I({ type: "CHANNEL" })
189
+ /* @__PURE__ */ S(O, {
190
+ label: j("conversations.channels", "Channels"),
191
+ active: I.type === "CHANNEL",
192
+ onClick: () => L({ type: "CHANNEL" })
181
193
  }),
182
194
  /* @__PURE__ */ S("div", { className: "w-px h-6 bg-border self-center" }),
183
- /* @__PURE__ */ S(D, {
184
- label: A("conversations.unread", "Unread"),
185
- icon: /* @__PURE__ */ S(f, { size: 12 }),
186
- active: F.hasUnread === !0,
187
- onClick: () => I({ hasUnread: F.hasUnread ? void 0 : !0 })
188
- }),
189
- /* @__PURE__ */ S(D, {
190
- label: A("conversations.pinned", "Pinned"),
195
+ /* @__PURE__ */ S(O, {
196
+ label: j("conversations.unread", "Unread"),
191
197
  icon: /* @__PURE__ */ S(h, { size: 12 }),
192
- active: F.isPinned === !0,
193
- onClick: () => I({ isPinned: F.isPinned ? void 0 : !0 })
198
+ active: I.hasUnread === !0,
199
+ onClick: () => L({ hasUnread: I.hasUnread ? void 0 : !0 })
200
+ }),
201
+ /* @__PURE__ */ S(O, {
202
+ label: j("conversations.pinned", "Pinned"),
203
+ icon: /* @__PURE__ */ S(_, { size: 12 }),
204
+ active: I.isPinned === !0,
205
+ onClick: () => L({ isPinned: I.isPinned ? void 0 : !0 })
194
206
  }),
195
- /* @__PURE__ */ S(D, {
196
- label: A("conversations.archived", "Archived"),
197
- icon: /* @__PURE__ */ S(ae, { size: 12 }),
198
- active: F.state === "ARCHIVED",
199
- onClick: () => I({ state: F.state === "ARCHIVED" ? "all" : "ARCHIVED" })
207
+ /* @__PURE__ */ S(O, {
208
+ label: j("conversations.archived", "Archived"),
209
+ icon: /* @__PURE__ */ S(re, { size: 12 }),
210
+ active: I.state === "ARCHIVED",
211
+ onClick: () => L({ state: I.state === "ARCHIVED" ? "all" : "ARCHIVED" })
200
212
  })
201
213
  ]
202
214
  })]
203
- }), ue ? /* @__PURE__ */ S("div", {
215
+ }), me ? /* @__PURE__ */ S("div", {
204
216
  className: "px-4 py-8 text-sm text-muted-foreground",
205
- children: A("conversations.loading", "Loading conversations...")
206
- }) : $ ? /* @__PURE__ */ S(a, {
207
- icon: /* @__PURE__ */ S(m, { size: 32 }),
208
- title: A("conversations.couldNotLoad", "Could not load conversations"),
217
+ children: j("conversations.loading", "Loading conversations...")
218
+ }) : $ ? /* @__PURE__ */ S(i, {
219
+ icon: /* @__PURE__ */ S(g, { size: 32 }),
220
+ title: j("conversations.couldNotLoad", "Could not load conversations"),
209
221
  description: $.message
210
- }) : J.length === 0 ? /* @__PURE__ */ S(a, {
211
- icon: /* @__PURE__ */ S(m, { size: 32 }),
212
- title: A("conversations.noConversations", "No conversations found"),
213
- description: j ? `${A("conversations.noMatchingConversations", "No conversations matching")} "${j}"` : A("conversations.startConversationPrompt", "Start a new conversation to get going")
222
+ }) : J.length === 0 ? /* @__PURE__ */ S(i, {
223
+ icon: /* @__PURE__ */ S(g, { size: 32 }),
224
+ title: j("conversations.noConversations", "No conversations found"),
225
+ description: W ? `${j("conversations.noMatchingConversations", "No conversations matching")} "${M}"` : j("conversations.startConversationPrompt", "Start a new conversation to get going")
214
226
  }) : /* @__PURE__ */ C("div", {
227
+ "data-tour": "conversation-list-results",
215
228
  className: "space-y-1",
216
229
  children: [Y.length > 0 && /* @__PURE__ */ C("div", { children: [/* @__PURE__ */ C("div", {
217
230
  className: "flex items-center gap-1.5 px-4 py-2",
218
- children: [/* @__PURE__ */ S(h, {
231
+ children: [/* @__PURE__ */ S(_, {
219
232
  size: 12,
220
233
  className: "text-muted-foreground"
221
234
  }), /* @__PURE__ */ S("span", {
222
235
  className: "text-xs font-medium text-muted-foreground uppercase tracking-wider",
223
- children: A("conversations.pinned", "Pinned")
236
+ children: j("conversations.pinned", "Pinned")
224
237
  })]
225
- }), Y.map((e) => /* @__PURE__ */ S(i, {
238
+ }), Y.map((e) => /* @__PURE__ */ S(u, {
226
239
  conversation: e,
227
- isActive: L === e.id,
240
+ isActive: R === e.id,
228
241
  onClick: Q
229
242
  }, e.id))] }), X.length > 0 && /* @__PURE__ */ C("div", { children: [Y.length > 0 && /* @__PURE__ */ S("div", {
230
243
  className: "flex items-center gap-1.5 px-4 py-2 mt-2",
231
244
  children: /* @__PURE__ */ S("span", {
232
245
  className: "text-xs font-medium text-muted-foreground uppercase tracking-wider",
233
- children: A("conversations.allConversations", "All Conversations")
246
+ children: j("conversations.allConversations", "All Conversations")
234
247
  })
235
- }), X.map((e) => /* @__PURE__ */ S(i, {
248
+ }), X.map((e) => /* @__PURE__ */ S(u, {
236
249
  conversation: e,
237
- isActive: L === e.id,
250
+ isActive: R === e.id,
238
251
  onClick: Q
239
252
  }, e.id))] })]
240
253
  })]
241
254
  });
242
255
  }
243
- function E({ active: e, icon: t, label: n, onClick: i }) {
256
+ function D({ active: e, icon: t, label: n, onClick: i }) {
244
257
  return /* @__PURE__ */ S("button", {
245
258
  type: "button",
246
259
  onClick: i,
247
260
  "aria-label": n,
248
261
  "aria-pressed": e,
249
- className: r("p-1.5 transition-colors", e ? "bg-primary/10 text-primary" : "text-muted-foreground hover:text-foreground"),
262
+ className: r("flex min-h-10 min-w-10 items-center justify-center p-2 transition-colors", e ? "bg-primary/10 text-primary" : "text-muted-foreground hover:text-foreground"),
250
263
  children: t
251
264
  });
252
265
  }
253
- function D({ label: e, icon: t, active: n, onClick: i }) {
266
+ function O({ label: e, icon: t, active: n, onClick: i }) {
254
267
  return /* @__PURE__ */ S(c, {
255
- permission: l.CONVERSATION_LIST,
268
+ permission: l.CONVERSATION_READ,
256
269
  allowUnavailablePermissions: !0,
257
270
  children: /* @__PURE__ */ C("button", {
258
271
  type: "button",
259
272
  onClick: i,
260
273
  "aria-pressed": n,
261
- className: r("inline-flex items-center gap-1 px-3 py-1 text-xs font-medium rounded-full border transition-colors", n ? "bg-primary/10 border-primary/30 text-primary" : "bg-background border-border text-muted-foreground hover:text-foreground hover:bg-muted/50"),
274
+ className: r("inline-flex min-h-9 items-center gap-1 rounded-full border px-3 py-1.5 text-xs font-medium transition-colors", n ? "bg-primary/10 border-primary/30 text-primary" : "bg-background border-border text-muted-foreground hover:text-foreground hover:bg-muted/50"),
262
275
  children: [t, e]
263
276
  })
264
277
  });
265
278
  }
266
279
  //#endregion
267
- export { T as ConversationListPage };
280
+ export { E as ConversationListPage };
268
281
 
269
282
  //# sourceMappingURL=ConversationListPage.js.map