@burdenoff/microfe-conversations 2026.928.1 → 2026.1006.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.
- package/dist/conversations/components/ConversationRow.js +1 -1
- package/dist/conversations/components/ConversationRow.js.map +1 -1
- package/dist/conversations/components/NewConversationDialog.js +135 -133
- package/dist/conversations/components/NewConversationDialog.js.map +1 -1
- package/dist/conversations/hooks/useConversationQueries.js +66 -49
- package/dist/conversations/hooks/useConversationQueries.js.map +1 -1
- package/dist/generated/wspace-operations.js +87 -32
- package/dist/generated/wspace-operations.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NewConversationDialog.js","names":[],"sources":["../../../src/conversations/components/NewConversationDialog.tsx"],"sourcesContent":["import { useState, useCallback, useMemo } from \"react\";\nimport { useEventBus } from \"@burdenoff/fe-libs/shared/events\";\nimport { isQuotaExhaustedError } from \"@burdenoff/fe-libs/shared/utils\";\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from \"@burdenoff/fe-libs/ui\";\nimport {\n Globe,\n Hash,\n Lock,\n Megaphone,\n MessageCircle,\n Shield,\n Users,\n} from \"lucide-react\";\nimport { useNavigate } from \"react-router\";\nimport { cn } from \"../utils/cn\";\nimport { useTr } from \"../utils/i18n\";\nimport {\n useCreateConversation,\n type WorkspaceDirectoryMember,\n} from \"../hooks/useConversationQueries\";\nimport { useConversations } from \"../providers/ConversationsProvider\";\nimport { MemberPicker } from \"./MemberPicker\";\nimport { QuotaExhaustedDialog } from \"./QuotaExhaustedDialog\";\nimport type { ConversationType, ConversationVisibility } from \"../types\";\nimport { nativeImpact, nativeNotify } from \"../../utils/nativeBridge\";\n\n/** Extract the first meaningful GraphQL error message from an ApolloError. */\nfunction getErrorMessage(error: Error): string {\n if (\"graphQLErrors\" in error) {\n const gqlErrors = (error as { graphQLErrors?: Array<{ message?: string }> })\n .graphQLErrors;\n if (gqlErrors && gqlErrors.length > 0 && gqlErrors[0]?.message) {\n return gqlErrors[0].message;\n }\n }\n return error.message;\n}\n\ninterface ConversationTypeOption {\n type: ConversationType;\n label: string;\n description: string;\n icon: React.ReactNode;\n}\n\nexport interface NewConversationDialogProps {\n /** Whether the dialog is open. */\n open: boolean;\n /** Open-state change handler. Fires with `false` on close (overlay click,\n * Escape, X button, Cancel). */\n onOpenChange: (open: boolean) => void;\n /**\n * Optional quota-failure hook for route wrappers that need to close the\n * create modal without running their normal close navigation.\n */\n onQuotaExhausted?: () => void;\n /** Called when the quota dialog is dismissed without opening billing. */\n onQuotaDialogClose?: () => void;\n /**\n * Called after a conversation is successfully created. Receives the new\n * conversation id so the parent can navigate to the detail route.\n * The dialog also closes itself (via `onOpenChange(false)`) before this\n * fires, so the parent does not need to close the dialog manually.\n */\n onCreated?: (conversationId: string) => void;\n}\n\n/**\n * NewConversationDialog — modal form for creating a new conversation.\n *\n * Renders inside the canonical `<Dialog>` primitive from\n * `@burdenoff/fe-libs/ui` so it inherits theme tokens, focus management,\n * and overlay behaviour from the shell.\n *\n * Form state is owned locally; on successful submit the dialog clears\n * state, closes itself, and invokes `onCreated` with the new\n * conversation id.\n */\nexport function NewConversationDialog({\n open,\n onOpenChange,\n onQuotaExhausted,\n onQuotaDialogClose,\n onCreated,\n}: NewConversationDialogProps) {\n const { createConversation, loading, error } = useCreateConversation();\n const { currentUser } = useConversations();\n const bus = useEventBus();\n const tr = useTr();\n const navigate = useNavigate();\n const [showQuotaExhausted, setShowQuotaExhausted] = useState(false);\n\n const CONVERSATION_TYPES: ConversationTypeOption[] = useMemo(\n () => [\n {\n type: \"DIRECT\" as ConversationType,\n label: tr(\"conversations.directMessage\", \"Direct Message\"),\n description: tr(\n \"conversations.directMessageDescription\",\n \"One-on-one private conversation\",\n ),\n icon: <MessageCircle size={20} />,\n },\n {\n type: \"GROUP\" as ConversationType,\n label: tr(\"conversations.groupChat\", \"Group Chat\"),\n description: tr(\n \"conversations.groupChatDescription\",\n \"Invite multiple people to a group conversation\",\n ),\n icon: <Users size={20} />,\n },\n {\n type: \"CHANNEL\" as ConversationType,\n label: tr(\"conversations.channel\", \"Channel\"),\n description: tr(\n \"conversations.channelDescription\",\n \"A topic-based channel that anyone can join\",\n ),\n icon: <Hash size={20} />,\n },\n {\n type: \"ANNOUNCEMENT\" as ConversationType,\n label: tr(\"conversations.announcement\", \"Announcement\"),\n description: tr(\n \"conversations.announcementDescription\",\n \"Broadcast messages to a large audience\",\n ),\n icon: <Megaphone size={20} />,\n },\n ],\n [tr],\n );\n\n const VISIBILITY_OPTIONS: Array<{\n value: ConversationVisibility;\n label: string;\n description: string;\n icon: React.ReactNode;\n }> = useMemo(\n () => [\n {\n value: \"PUBLIC\" as ConversationVisibility,\n label: tr(\"conversations.public\", \"Public\"),\n description: tr(\n \"conversations.publicDescription\",\n \"Anyone in the workspace can discover and join\",\n ),\n icon: <Globe size={16} />,\n },\n {\n value: \"PRIVATE\" as ConversationVisibility,\n label: tr(\"conversations.private\", \"Private\"),\n description: tr(\n \"conversations.privateDescription\",\n \"Only invited members can access\",\n ),\n icon: <Lock size={16} />,\n },\n {\n value: \"PROTECTED\" as ConversationVisibility,\n label: tr(\"conversations.protected\", \"Protected\"),\n description: tr(\n \"conversations.protectedDescription\",\n \"Discoverable but requires approval to join\",\n ),\n icon: <Shield size={16} />,\n },\n ],\n [tr],\n );\n\n const [selectedType, setSelectedType] = useState<ConversationType>(\"GROUP\");\n const [title, setTitle] = useState(\"\");\n const [description, setDescription] = useState(\"\");\n const [visibility, setVisibility] =\n useState<ConversationVisibility>(\"PRIVATE\");\n const [selectedMembers, setSelectedMembers] = useState<\n WorkspaceDirectoryMember[]\n >([]);\n\n const isDirectMessage = selectedType === \"DIRECT\";\n const needsTitle = !isDirectMessage;\n\n const participantIds = useMemo(\n () => selectedMembers.map((member) => member.id),\n [selectedMembers],\n );\n\n const resetForm = useCallback(() => {\n setSelectedType(\"GROUP\");\n setTitle(\"\");\n setDescription(\"\");\n setVisibility(\"PRIVATE\");\n setSelectedMembers([]);\n }, []);\n\n const addMember = useCallback(\n (member: WorkspaceDirectoryMember) => {\n setSelectedMembers((prev) => {\n if (prev.some((existing) => existing.id === member.id)) return prev;\n // A direct message has exactly one recipient.\n return isDirectMessage ? [member] : [...prev, member];\n });\n },\n [isDirectMessage],\n );\n\n const removeMember = useCallback((userId: string) => {\n setSelectedMembers((prev) => prev.filter((member) => member.id !== userId));\n }, []);\n\n const requiresParticipants =\n selectedType === \"DIRECT\" || selectedType === \"GROUP\";\n\n // Never offer the current user as a participant — they are added implicitly\n // as the conversation creator.\n const excludeIds = useMemo(\n () => (currentUser?.id ? [currentUser.id] : []),\n [currentUser?.id],\n );\n\n const handleOpenChange = useCallback(\n (nextOpen: boolean) => {\n if (!nextOpen) {\n resetForm();\n }\n onOpenChange(nextOpen);\n },\n [onOpenChange, resetForm],\n );\n\n const handleCreate = useCallback(async () => {\n if (requiresParticipants && participantIds.length === 0) return;\n if (needsTitle && !title.trim()) return;\n\n nativeImpact(\"light\");\n try {\n const conversation = await createConversation({\n type: selectedType,\n title: needsTitle ? title.trim() : undefined,\n description: description.trim() || undefined,\n participantIds,\n visibility,\n });\n\n if (!conversation) {\n nativeNotify(\"error\");\n return;\n }\n nativeNotify(\"success\");\n bus.emit(\"conversations.conversation.created\", {\n conversationId: conversation.id,\n conversationType: selectedType,\n source: \"new-conversation-dialog\",\n });\n resetForm();\n onOpenChange(false);\n onCreated?.(conversation.id);\n } catch (error) {\n if (isQuotaExhaustedError(error)) {\n resetForm();\n setShowQuotaExhausted(true);\n if (onQuotaExhausted) {\n onQuotaExhausted();\n } else {\n onOpenChange(false);\n }\n return;\n }\n nativeNotify(\"error\");\n throw error;\n }\n }, [\n bus,\n requiresParticipants,\n participantIds,\n needsTitle,\n title,\n createConversation,\n selectedType,\n description,\n visibility,\n resetForm,\n onOpenChange,\n onQuotaExhausted,\n onCreated,\n ]);\n\n const handleQuotaDialogClose = useCallback(() => {\n setShowQuotaExhausted(false);\n onQuotaDialogClose?.();\n }, [onQuotaDialogClose]);\n\n const canCreate =\n (!requiresParticipants || participantIds.length > 0) &&\n (!needsTitle || title.trim().length > 0);\n\n return (\n <>\n <Dialog open={open} onOpenChange={handleOpenChange}>\n <DialogContent\n className=\"sm:max-w-[640px] max-h-[90vh] overflow-y-auto\"\n data-tour=\"new-conversation-dialog\"\n >\n <DialogHeader>\n <DialogTitle>\n {tr(\"conversations.newConversation\", \"New Conversation\")}\n </DialogTitle>\n <DialogDescription>\n {tr(\n \"conversations.newConversationSubtitle\",\n \"Start a new conversation with your team\",\n )}\n </DialogDescription>\n </DialogHeader>\n\n <div className=\"space-y-6\">\n <section\n data-tour=\"conversation-type-selector\"\n className=\"space-y-3\"\n >\n <h3 className=\"text-sm font-semibold text-text-primary\">\n {tr(\"conversations.conversationType\", \"Conversation Type\")}\n </h3>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-3\">\n {CONVERSATION_TYPES.map((option) => (\n <button\n key={option.type}\n type=\"button\"\n onClick={() => {\n setSelectedType(option.type);\n if (option.type === \"DIRECT\") {\n setSelectedMembers((prev) => prev.slice(0, 1));\n }\n }}\n className={cn(\n \"flex items-start gap-3 p-4 rounded-xl border text-left transition-all duration-150\",\n selectedType === option.type\n ? \"border-primary bg-action-primary-bg/5 ring-1 ring-primary/20\"\n : \"border-border-subtle hover:border-muted-foreground/30 hover:bg-bg-sunken/30\",\n )}\n >\n <div\n className={cn(\n \"p-2 rounded-lg\",\n selectedType === option.type\n ? \"bg-action-primary-bg/10 text-primary\"\n : \"bg-bg-sunken text-text-secondary\",\n )}\n >\n {option.icon}\n </div>\n <div>\n <p className=\"text-sm font-medium text-text-primary\">\n {option.label}\n </p>\n <p className=\"text-xs text-text-secondary mt-0.5\">\n {option.description}\n </p>\n </div>\n </button>\n ))}\n </div>\n </section>\n\n {!isDirectMessage && (\n <section\n data-tour=\"conversation-visibility-selector\"\n className=\"space-y-3\"\n >\n <h3 className=\"text-sm font-semibold text-text-primary\">\n {tr(\"conversations.visibility\", \"Visibility\")}\n </h3>\n <div className=\"flex gap-2 flex-wrap\">\n {VISIBILITY_OPTIONS.map((option) => (\n <button\n key={option.value}\n type=\"button\"\n onClick={() => setVisibility(option.value)}\n className={cn(\n \"flex items-center gap-2 px-4 py-2.5 rounded-lg border text-sm transition-all\",\n visibility === option.value\n ? \"border-primary bg-action-primary-bg/5 text-primary font-medium\"\n : \"border-border-subtle text-text-secondary hover:text-text-primary hover:bg-bg-sunken/30\",\n )}\n >\n {option.icon}\n {option.label}\n </button>\n ))}\n </div>\n <p className=\"text-xs text-text-secondary\">\n {\n VISIBILITY_OPTIONS.find(\n (option) => option.value === visibility,\n )?.description\n }\n </p>\n </section>\n )}\n\n {needsTitle && (\n <section\n data-tour=\"conversation-create-details\"\n className=\"space-y-3\"\n >\n <div>\n <label\n htmlFor=\"conv-title\"\n className=\"text-sm font-semibold text-text-primary\"\n >\n {tr(\"conversations.name\", \"Name\")}{\" \"}\n <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"conv-title\"\n type=\"text\"\n value={title}\n onChange={(event) => setTitle(event.target.value)}\n placeholder={\n selectedType === \"CHANNEL\"\n ? tr(\n \"conversations.channelNamePlaceholder\",\n \"e.g., engineering-general\",\n )\n : tr(\n \"conversations.conversationNamePlaceholder\",\n \"e.g., Product Launch Q1\",\n )\n }\n maxLength={200}\n className=\"mt-1.5 w-full px-3 py-2 text-sm rounded-lg border border-border-subtle bg-bg-surface placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </div>\n <div>\n <label\n htmlFor=\"conv-desc\"\n className=\"text-sm font-semibold text-text-primary\"\n >\n {tr(\"conversations.description\", \"Description\")}\n </label>\n <textarea\n id=\"conv-desc\"\n value={description}\n onChange={(event) => setDescription(event.target.value)}\n placeholder={tr(\n \"conversations.descriptionPlaceholder\",\n \"What is this conversation about?\",\n )}\n rows={2}\n maxLength={1000}\n className=\"mt-1.5 w-full px-3 py-2 text-sm rounded-lg border border-border-subtle bg-bg-surface placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] resize-none\"\n />\n </div>\n </section>\n )}\n\n <section\n data-tour=\"conversation-participants\"\n className=\"space-y-3\"\n >\n <h3 className=\"text-sm font-semibold text-text-primary\">\n {isDirectMessage\n ? tr(\"conversations.recipient\", \"Recipient\")\n : tr(\"conversations.participants\", \"Participants\")}\n {requiresParticipants ? (\n <span className=\"ml-1 text-status-error-text\">*</span>\n ) : (\n <span className=\"ml-1.5 text-xs font-normal text-text-secondary\">\n ({tr(\"conversations.optional\", \"optional\")})\n </span>\n )}\n </h3>\n\n <MemberPicker\n selected={selectedMembers}\n onAdd={addMember}\n onRemove={removeMember}\n single={isDirectMessage}\n excludeIds={excludeIds}\n />\n </section>\n\n {error && !isQuotaExhaustedError(error) && (\n <div className=\"text-xs text-status-error-text rounded-lg border border-status-error-border/30 bg-status-error-bg/10 px-3 py-2\">\n {getErrorMessage(error)}\n </div>\n )}\n </div>\n\n <DialogFooter>\n <button\n type=\"button\"\n onClick={() => handleOpenChange(false)}\n className=\"px-4 py-2 text-sm font-medium rounded-lg text-text-secondary hover:text-text-primary hover:bg-bg-sunken/50 transition-colors\"\n >\n {tr(\"conversations.cancel\", \"Cancel\")}\n </button>\n <button\n type=\"button\"\n onClick={() => void handleCreate()}\n disabled={!canCreate || loading}\n className={cn(\n \"inline-flex items-center gap-2 px-5 py-2 text-sm font-medium rounded-lg transition-all\",\n canCreate && !loading\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 {loading\n ? tr(\"conversations.creating\", \"Creating...\")\n : tr(\"conversations.createConversation\", \"Create Conversation\")}\n </button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n <QuotaExhaustedDialog\n open={showQuotaExhausted}\n title={tr(\n \"conversations.quota.conversations.title\",\n \"Conversation Quota Reached\",\n )}\n description={tr(\n \"conversations.quota.conversations.description\",\n \"You've reached the conversation limit for your current plan. View billing to upgrade or add more capacity.\",\n )}\n onClose={handleQuotaDialogClose}\n onConfirm={() => {\n setShowQuotaExhausted(false);\n navigate(\"/billing\");\n }}\n />\n </>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"NewConversationDialog.js","names":[],"sources":["../../../src/conversations/components/NewConversationDialog.tsx"],"sourcesContent":["import { useState, useCallback, useMemo } from \"react\";\nimport { useEventBus } from \"@burdenoff/fe-libs/shared/events\";\nimport { isQuotaExhaustedError } from \"@burdenoff/fe-libs/shared/utils\";\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from \"@burdenoff/fe-libs/ui\";\nimport {\n Globe,\n Hash,\n Lock,\n Megaphone,\n MessageCircle,\n Shield,\n Users,\n} from \"lucide-react\";\nimport { useNavigate } from \"react-router\";\nimport { cn } from \"../utils/cn\";\nimport { useTr } from \"../utils/i18n\";\nimport {\n useCreateConversation,\n useStartDirectConversation,\n type WorkspaceDirectoryMember,\n} from \"../hooks/useConversationQueries\";\nimport { useConversations } from \"../providers/ConversationsProvider\";\nimport { MemberPicker } from \"./MemberPicker\";\nimport { QuotaExhaustedDialog } from \"./QuotaExhaustedDialog\";\nimport type { ConversationType, ConversationVisibility } from \"../types\";\nimport { nativeImpact, nativeNotify } from \"../../utils/nativeBridge\";\n\n/** Extract the first meaningful GraphQL error message from an ApolloError. */\nfunction getErrorMessage(error: Error): string {\n if (\"graphQLErrors\" in error) {\n const gqlErrors = (error as { graphQLErrors?: Array<{ message?: string }> })\n .graphQLErrors;\n if (gqlErrors && gqlErrors.length > 0 && gqlErrors[0]?.message) {\n return gqlErrors[0].message;\n }\n }\n return error.message;\n}\n\ninterface ConversationTypeOption {\n type: ConversationType;\n label: string;\n description: string;\n icon: React.ReactNode;\n}\n\nexport interface NewConversationDialogProps {\n /** Whether the dialog is open. */\n open: boolean;\n /** Open-state change handler. Fires with `false` on close (overlay click,\n * Escape, X button, Cancel). */\n onOpenChange: (open: boolean) => void;\n /**\n * Optional quota-failure hook for route wrappers that need to close the\n * create modal without running their normal close navigation.\n */\n onQuotaExhausted?: () => void;\n /** Called when the quota dialog is dismissed without opening billing. */\n onQuotaDialogClose?: () => void;\n /**\n * Called after a conversation is successfully created. Receives the new\n * conversation id so the parent can navigate to the detail route.\n * The dialog also closes itself (via `onOpenChange(false)`) before this\n * fires, so the parent does not need to close the dialog manually.\n */\n onCreated?: (conversationId: string) => void;\n}\n\n/**\n * NewConversationDialog — modal form for creating a new conversation.\n *\n * Renders inside the canonical `<Dialog>` primitive from\n * `@burdenoff/fe-libs/ui` so it inherits theme tokens, focus management,\n * and overlay behaviour from the shell.\n *\n * Form state is owned locally; on successful submit the dialog clears\n * state, closes itself, and invokes `onCreated` with the new\n * conversation id.\n */\nexport function NewConversationDialog({\n open,\n onOpenChange,\n onQuotaExhausted,\n onQuotaDialogClose,\n onCreated,\n}: NewConversationDialogProps) {\n const {\n createConversation,\n loading,\n error: createError,\n } = useCreateConversation();\n const {\n startDirectConversation,\n loading: startDirectLoading,\n error: startDirectError,\n } = useStartDirectConversation();\n const { currentUser } = useConversations();\n const bus = useEventBus();\n const tr = useTr();\n const navigate = useNavigate();\n const [showQuotaExhausted, setShowQuotaExhausted] = useState(false);\n\n const CONVERSATION_TYPES: ConversationTypeOption[] = useMemo(\n () => [\n {\n type: \"DIRECT\" as ConversationType,\n label: tr(\"conversations.directMessage\", \"Direct Message\"),\n description: tr(\n \"conversations.directMessageDescription\",\n \"One-on-one private conversation\",\n ),\n icon: <MessageCircle size={20} />,\n },\n {\n type: \"GROUP\" as ConversationType,\n label: tr(\"conversations.groupChat\", \"Group Chat\"),\n description: tr(\n \"conversations.groupChatDescription\",\n \"Invite multiple people to a group conversation\",\n ),\n icon: <Users size={20} />,\n },\n {\n type: \"CHANNEL\" as ConversationType,\n label: tr(\"conversations.channel\", \"Channel\"),\n description: tr(\n \"conversations.channelDescription\",\n \"A topic-based channel that anyone can join\",\n ),\n icon: <Hash size={20} />,\n },\n {\n type: \"ANNOUNCEMENT\" as ConversationType,\n label: tr(\"conversations.announcement\", \"Announcement\"),\n description: tr(\n \"conversations.announcementDescription\",\n \"Broadcast messages to a large audience\",\n ),\n icon: <Megaphone size={20} />,\n },\n ],\n [tr],\n );\n\n const VISIBILITY_OPTIONS: Array<{\n value: ConversationVisibility;\n label: string;\n description: string;\n icon: React.ReactNode;\n }> = useMemo(\n () => [\n {\n value: \"PUBLIC\" as ConversationVisibility,\n label: tr(\"conversations.public\", \"Public\"),\n description: tr(\n \"conversations.publicDescription\",\n \"Anyone in the workspace can discover and join\",\n ),\n icon: <Globe size={16} />,\n },\n {\n value: \"PRIVATE\" as ConversationVisibility,\n label: tr(\"conversations.private\", \"Private\"),\n description: tr(\n \"conversations.privateDescription\",\n \"Only invited members can access\",\n ),\n icon: <Lock size={16} />,\n },\n {\n value: \"PROTECTED\" as ConversationVisibility,\n label: tr(\"conversations.protected\", \"Protected\"),\n description: tr(\n \"conversations.protectedDescription\",\n \"Discoverable but requires approval to join\",\n ),\n icon: <Shield size={16} />,\n },\n ],\n [tr],\n );\n\n const [selectedType, setSelectedType] = useState<ConversationType>(\"GROUP\");\n const [title, setTitle] = useState(\"\");\n const [description, setDescription] = useState(\"\");\n const [visibility, setVisibility] =\n useState<ConversationVisibility>(\"PRIVATE\");\n const [selectedMembers, setSelectedMembers] = useState<\n WorkspaceDirectoryMember[]\n >([]);\n\n const isDirectMessage = selectedType === \"DIRECT\";\n const needsTitle = !isDirectMessage;\n\n // The error banner shows whichever hook ran last (createConversation for\n // non-direct types, startDirectConversation for DMs). For DMs\n // createConversation is never called, so its error is always null there.\n const error = isDirectMessage ? startDirectError : createError;\n const isSubmitting = isDirectMessage ? startDirectLoading : loading;\n\n const participantIds = useMemo(\n () => selectedMembers.map((member) => member.id),\n [selectedMembers],\n );\n\n const resetForm = useCallback(() => {\n setSelectedType(\"GROUP\");\n setTitle(\"\");\n setDescription(\"\");\n setVisibility(\"PRIVATE\");\n setSelectedMembers([]);\n }, []);\n\n const addMember = useCallback(\n (member: WorkspaceDirectoryMember) => {\n setSelectedMembers((prev) => {\n if (prev.some((existing) => existing.id === member.id)) return prev;\n // A direct message has exactly one recipient.\n return isDirectMessage ? [member] : [...prev, member];\n });\n },\n [isDirectMessage],\n );\n\n const removeMember = useCallback((userId: string) => {\n setSelectedMembers((prev) => prev.filter((member) => member.id !== userId));\n }, []);\n\n const requiresParticipants =\n selectedType === \"DIRECT\" || selectedType === \"GROUP\";\n\n // Never offer the current user as a participant — they are added implicitly\n // as the conversation creator.\n const excludeIds = useMemo(\n () => (currentUser?.id ? [currentUser.id] : []),\n [currentUser?.id],\n );\n\n const handleOpenChange = useCallback(\n (nextOpen: boolean) => {\n if (!nextOpen) {\n resetForm();\n }\n onOpenChange(nextOpen);\n },\n [onOpenChange, resetForm],\n );\n\n const handleCreate = useCallback(async () => {\n if (requiresParticipants && participantIds.length === 0) return;\n if (needsTitle && !title.trim()) return;\n\n nativeImpact(\"light\");\n try {\n // Direct messages MUST go through getOrCreateDirectConversation so\n // repeated clicks for the same pair reuse the existing thread (the\n // createConversation mutation always mints a new row, which produced\n // duplicate DMs in the inbox — BOFF-8227). For other types the\n // mutation is fine.\n const conversation = isDirectMessage\n ? await startDirectConversation(participantIds[0])\n : await createConversation({\n type: selectedType,\n title: needsTitle ? title.trim() : undefined,\n description: description.trim() || undefined,\n participantIds,\n visibility,\n });\n\n if (!conversation) {\n nativeNotify(\"error\");\n return;\n }\n nativeNotify(\"success\");\n bus.emit(\"conversations.conversation.created\", {\n conversationId: conversation.id,\n conversationType: selectedType,\n source: \"new-conversation-dialog\",\n });\n resetForm();\n onOpenChange(false);\n onCreated?.(conversation.id);\n } catch (error) {\n if (isQuotaExhaustedError(error)) {\n resetForm();\n setShowQuotaExhausted(true);\n if (onQuotaExhausted) {\n onQuotaExhausted();\n } else {\n onOpenChange(false);\n }\n return;\n }\n nativeNotify(\"error\");\n throw error;\n }\n }, [\n bus,\n requiresParticipants,\n participantIds,\n needsTitle,\n title,\n createConversation,\n startDirectConversation,\n selectedType,\n description,\n visibility,\n resetForm,\n onOpenChange,\n onQuotaExhausted,\n onCreated,\n isDirectMessage,\n ]);\n\n const handleQuotaDialogClose = useCallback(() => {\n setShowQuotaExhausted(false);\n onQuotaDialogClose?.();\n }, [onQuotaDialogClose]);\n\n const canCreate =\n (!requiresParticipants || participantIds.length > 0) &&\n (!needsTitle || title.trim().length > 0);\n\n return (\n <>\n <Dialog open={open} onOpenChange={handleOpenChange}>\n <DialogContent\n className=\"sm:max-w-[640px] max-h-[90vh] overflow-y-auto\"\n data-tour=\"new-conversation-dialog\"\n >\n <DialogHeader>\n <DialogTitle>\n {tr(\"conversations.newConversation\", \"New Conversation\")}\n </DialogTitle>\n <DialogDescription>\n {tr(\n \"conversations.newConversationSubtitle\",\n \"Start a new conversation with your team\",\n )}\n </DialogDescription>\n </DialogHeader>\n\n <div className=\"space-y-6\">\n <section\n data-tour=\"conversation-type-selector\"\n className=\"space-y-3\"\n >\n <h3 className=\"text-sm font-semibold text-text-primary\">\n {tr(\"conversations.conversationType\", \"Conversation Type\")}\n </h3>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-3\">\n {CONVERSATION_TYPES.map((option) => (\n <button\n key={option.type}\n type=\"button\"\n onClick={() => {\n setSelectedType(option.type);\n if (option.type === \"DIRECT\") {\n setSelectedMembers((prev) => prev.slice(0, 1));\n }\n }}\n className={cn(\n \"flex items-start gap-3 p-4 rounded-xl border text-left transition-all duration-150\",\n selectedType === option.type\n ? \"border-primary bg-action-primary-bg/5 ring-1 ring-primary/20\"\n : \"border-border-subtle hover:border-muted-foreground/30 hover:bg-bg-sunken/30\",\n )}\n >\n <div\n className={cn(\n \"p-2 rounded-lg\",\n selectedType === option.type\n ? \"bg-action-primary-bg/10 text-primary\"\n : \"bg-bg-sunken text-text-secondary\",\n )}\n >\n {option.icon}\n </div>\n <div>\n <p className=\"text-sm font-medium text-text-primary\">\n {option.label}\n </p>\n <p className=\"text-xs text-text-secondary mt-0.5\">\n {option.description}\n </p>\n </div>\n </button>\n ))}\n </div>\n </section>\n\n {!isDirectMessage && (\n <section\n data-tour=\"conversation-visibility-selector\"\n className=\"space-y-3\"\n >\n <h3 className=\"text-sm font-semibold text-text-primary\">\n {tr(\"conversations.visibility\", \"Visibility\")}\n </h3>\n <div className=\"flex gap-2 flex-wrap\">\n {VISIBILITY_OPTIONS.map((option) => (\n <button\n key={option.value}\n type=\"button\"\n onClick={() => setVisibility(option.value)}\n className={cn(\n \"flex items-center gap-2 px-4 py-2.5 rounded-lg border text-sm transition-all\",\n visibility === option.value\n ? \"border-primary bg-action-primary-bg/5 text-primary font-medium\"\n : \"border-border-subtle text-text-secondary hover:text-text-primary hover:bg-bg-sunken/30\",\n )}\n >\n {option.icon}\n {option.label}\n </button>\n ))}\n </div>\n <p className=\"text-xs text-text-secondary\">\n {\n VISIBILITY_OPTIONS.find(\n (option) => option.value === visibility,\n )?.description\n }\n </p>\n </section>\n )}\n\n {needsTitle && (\n <section\n data-tour=\"conversation-create-details\"\n className=\"space-y-3\"\n >\n <div>\n <label\n htmlFor=\"conv-title\"\n className=\"text-sm font-semibold text-text-primary\"\n >\n {tr(\"conversations.name\", \"Name\")}{\" \"}\n <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"conv-title\"\n type=\"text\"\n value={title}\n onChange={(event) => setTitle(event.target.value)}\n placeholder={\n selectedType === \"CHANNEL\"\n ? tr(\n \"conversations.channelNamePlaceholder\",\n \"e.g., engineering-general\",\n )\n : tr(\n \"conversations.conversationNamePlaceholder\",\n \"e.g., Product Launch Q1\",\n )\n }\n maxLength={200}\n className=\"mt-1.5 w-full px-3 py-2 text-sm rounded-lg border border-border-subtle bg-bg-surface placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </div>\n <div>\n <label\n htmlFor=\"conv-desc\"\n className=\"text-sm font-semibold text-text-primary\"\n >\n {tr(\"conversations.description\", \"Description\")}\n </label>\n <textarea\n id=\"conv-desc\"\n value={description}\n onChange={(event) => setDescription(event.target.value)}\n placeholder={tr(\n \"conversations.descriptionPlaceholder\",\n \"What is this conversation about?\",\n )}\n rows={2}\n maxLength={1000}\n className=\"mt-1.5 w-full px-3 py-2 text-sm rounded-lg border border-border-subtle bg-bg-surface placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] resize-none\"\n />\n </div>\n </section>\n )}\n\n <section\n data-tour=\"conversation-participants\"\n className=\"space-y-3\"\n >\n <h3 className=\"text-sm font-semibold text-text-primary\">\n {isDirectMessage\n ? tr(\"conversations.recipient\", \"Recipient\")\n : tr(\"conversations.participants\", \"Participants\")}\n {requiresParticipants ? (\n <span className=\"ml-1 text-status-error-text\">*</span>\n ) : (\n <span className=\"ml-1.5 text-xs font-normal text-text-secondary\">\n ({tr(\"conversations.optional\", \"optional\")})\n </span>\n )}\n </h3>\n\n <MemberPicker\n selected={selectedMembers}\n onAdd={addMember}\n onRemove={removeMember}\n single={isDirectMessage}\n excludeIds={excludeIds}\n />\n </section>\n\n {error && !isQuotaExhaustedError(error) && (\n <div className=\"text-xs text-status-error-text rounded-lg border border-status-error-border/30 bg-status-error-bg/10 px-3 py-2\">\n {getErrorMessage(error)}\n </div>\n )}\n </div>\n\n <DialogFooter>\n <button\n type=\"button\"\n onClick={() => handleOpenChange(false)}\n className=\"px-4 py-2 text-sm font-medium rounded-lg text-text-secondary hover:text-text-primary hover:bg-bg-sunken/50 transition-colors\"\n >\n {tr(\"conversations.cancel\", \"Cancel\")}\n </button>\n <button\n type=\"button\"\n onClick={() => void handleCreate()}\n disabled={!canCreate || isSubmitting}\n className={cn(\n \"inline-flex items-center gap-2 px-5 py-2 text-sm font-medium rounded-lg transition-all\",\n canCreate && !isSubmitting\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 {isSubmitting\n ? tr(\"conversations.creating\", \"Creating...\")\n : tr(\"conversations.createConversation\", \"Create Conversation\")}\n </button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n <QuotaExhaustedDialog\n open={showQuotaExhausted}\n title={tr(\n \"conversations.quota.conversations.title\",\n \"Conversation Quota Reached\",\n )}\n description={tr(\n \"conversations.quota.conversations.description\",\n \"You've reached the conversation limit for your current plan. View billing to upgrade or add more capacity.\",\n )}\n onClose={handleQuotaDialogClose}\n onConfirm={() => {\n setShowQuotaExhausted(false);\n navigate(\"/billing\");\n }}\n />\n </>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { useConversations as e } from "../providers/ConversationsProvider.js";
|
|
2
|
-
import { useArchiveConversationMutation as t, useCreateConversationMutation as n, useGetConversationQuery as r, useGetConversationsQuery as i,
|
|
3
|
-
import { mapConversation as
|
|
4
|
-
import { applyMarkConversationAsReadToCache as
|
|
5
|
-
import { useCallback as
|
|
2
|
+
import { useArchiveConversationMutation as t, useCreateConversationMutation as n, useGetConversationQuery as r, useGetConversationsQuery as i, useGetOrCreateDirectConversationLazyQuery as a, useGetWorkspaceDirectoryQuery as o, useLeaveConversationMutation as s, useMarkConversationAsReadMutation as c, useMuteConversationMutation as l, usePinConversationMutation as u, useSearchConversationsQuery as d, useUnarchiveConversationMutation as f, useUnmuteConversationMutation as p, useUnpinConversationMutation as m, useUpdateConversationByHookMutation as h } from "../../generated/wspace-operations.js";
|
|
3
|
+
import { mapConversation as g } from "../utils/mappers.js";
|
|
4
|
+
import { applyMarkConversationAsReadToCache as _ } from "../utils/readReceiptCache.js";
|
|
5
|
+
import { useCallback as v, useEffect as y, useMemo as b, useState as x } from "react";
|
|
6
6
|
//#region src/conversations/hooks/useConversationQueries.ts
|
|
7
|
-
function
|
|
7
|
+
function S(t) {
|
|
8
8
|
let { workspaceId: n } = e(), r = i({
|
|
9
9
|
variables: {
|
|
10
10
|
first: t?.first ?? 25,
|
|
@@ -17,7 +17,7 @@ function x(t) {
|
|
|
17
17
|
}
|
|
18
18
|
},
|
|
19
19
|
skip: !n
|
|
20
|
-
}), a =
|
|
20
|
+
}), a = b(() => r.data?.conversations.edges.map((e) => g(e.node)) ?? [], [r.data]), o = r.data?.conversations.pageInfo.hasNextPage ?? !1;
|
|
21
21
|
return {
|
|
22
22
|
conversations: a,
|
|
23
23
|
loading: r.loading,
|
|
@@ -41,13 +41,13 @@ function x(t) {
|
|
|
41
41
|
}
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
|
-
function
|
|
44
|
+
function C(t) {
|
|
45
45
|
let { workspaceId: n } = e(), i = r({
|
|
46
46
|
variables: { id: t },
|
|
47
47
|
skip: !n || !t
|
|
48
48
|
});
|
|
49
49
|
return {
|
|
50
|
-
conversation: i.data?.conversation ?
|
|
50
|
+
conversation: i.data?.conversation ? g(i.data.conversation) : null,
|
|
51
51
|
members: i.data?.conversation?.members.edges.map((e) => e.node).filter((e) => !!e) ?? [],
|
|
52
52
|
membersTotalCount: i.data?.conversation?.members.totalCount ?? 0,
|
|
53
53
|
loading: i.loading,
|
|
@@ -57,8 +57,8 @@ function S(t) {
|
|
|
57
57
|
}
|
|
58
58
|
};
|
|
59
59
|
}
|
|
60
|
-
function
|
|
61
|
-
let { workspaceId: n } = e(), r = t.trim(), i =
|
|
60
|
+
function w(t) {
|
|
61
|
+
let { workspaceId: n } = e(), r = t.trim(), i = d({
|
|
62
62
|
variables: {
|
|
63
63
|
query: r,
|
|
64
64
|
first: 25
|
|
@@ -66,25 +66,25 @@ function C(t) {
|
|
|
66
66
|
skip: !n || r.length < 2
|
|
67
67
|
});
|
|
68
68
|
return {
|
|
69
|
-
conversations: i.data?.searchConversations.edges.map((e) =>
|
|
69
|
+
conversations: i.data?.searchConversations.edges.map((e) => g(e.node)) ?? [],
|
|
70
70
|
loading: i.loading,
|
|
71
71
|
error: i.error ?? null
|
|
72
72
|
};
|
|
73
73
|
}
|
|
74
|
-
var
|
|
75
|
-
function
|
|
76
|
-
let { workspaceId: n } = e(), [r, i] =
|
|
77
|
-
|
|
74
|
+
var T = 25, E = 250;
|
|
75
|
+
function D(t) {
|
|
76
|
+
let { workspaceId: n } = e(), [r, i] = x(t.trim());
|
|
77
|
+
y(() => {
|
|
78
78
|
let e = t.trim(), n = setTimeout(() => {
|
|
79
79
|
i(e);
|
|
80
|
-
},
|
|
80
|
+
}, E);
|
|
81
81
|
return () => clearTimeout(n);
|
|
82
82
|
}, [t]);
|
|
83
|
-
let
|
|
83
|
+
let a = o({
|
|
84
84
|
variables: {
|
|
85
85
|
filter: r ? { search: r } : void 0,
|
|
86
86
|
pagination: {
|
|
87
|
-
limit:
|
|
87
|
+
limit: T,
|
|
88
88
|
offset: 0
|
|
89
89
|
}
|
|
90
90
|
},
|
|
@@ -92,16 +92,16 @@ function E(t) {
|
|
|
92
92
|
fetchPolicy: "cache-and-network"
|
|
93
93
|
});
|
|
94
94
|
return {
|
|
95
|
-
members:
|
|
96
|
-
totalCount:
|
|
97
|
-
loading:
|
|
98
|
-
error:
|
|
95
|
+
members: b(() => a.data?.workspaceDirectory.edges ?? [], [a.data]),
|
|
96
|
+
totalCount: a.data?.workspaceDirectory.totalCount ?? 0,
|
|
97
|
+
loading: a.loading,
|
|
98
|
+
error: a.error ?? null
|
|
99
99
|
};
|
|
100
100
|
}
|
|
101
|
-
function
|
|
101
|
+
function O() {
|
|
102
102
|
let { workspaceId: t } = e(), [r, i] = n();
|
|
103
103
|
return {
|
|
104
|
-
createConversation:
|
|
104
|
+
createConversation: v(async (e) => {
|
|
105
105
|
if (!t) throw Error("Workspace context is required to create a conversation");
|
|
106
106
|
try {
|
|
107
107
|
let { data: n, error: i } = await r({ variables: { input: {
|
|
@@ -113,7 +113,7 @@ function D() {
|
|
|
113
113
|
} } });
|
|
114
114
|
if (i) throw i;
|
|
115
115
|
let a = n?.createConversation.conversation;
|
|
116
|
-
return a ?
|
|
116
|
+
return a ? g(a) : null;
|
|
117
117
|
} catch (e) {
|
|
118
118
|
throw e instanceof Error ? e : /* @__PURE__ */ Error("Failed to create conversation");
|
|
119
119
|
}
|
|
@@ -122,10 +122,27 @@ function D() {
|
|
|
122
122
|
error: i.error ?? null
|
|
123
123
|
};
|
|
124
124
|
}
|
|
125
|
-
function
|
|
126
|
-
let [e, t] =
|
|
125
|
+
function k() {
|
|
126
|
+
let [e, t] = a();
|
|
127
|
+
return {
|
|
128
|
+
startDirectConversation: v(async (t) => {
|
|
129
|
+
try {
|
|
130
|
+
let { data: n, error: r } = await e({ variables: { userId: t } });
|
|
131
|
+
if (r) throw r;
|
|
132
|
+
let i = n?.getOrCreateDirectConversation;
|
|
133
|
+
return i ? g(i) : null;
|
|
134
|
+
} catch (e) {
|
|
135
|
+
throw e instanceof Error ? e : /* @__PURE__ */ Error("Failed to open direct conversation");
|
|
136
|
+
}
|
|
137
|
+
}, [e]),
|
|
138
|
+
loading: t.loading,
|
|
139
|
+
error: t.error ?? null
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function A() {
|
|
143
|
+
let [e, t] = c();
|
|
127
144
|
return {
|
|
128
|
-
markConversationAsRead:
|
|
145
|
+
markConversationAsRead: v(async (t) => {
|
|
129
146
|
try {
|
|
130
147
|
let { data: n, error: r } = await e({
|
|
131
148
|
variables: {
|
|
@@ -134,7 +151,7 @@ function O() {
|
|
|
134
151
|
},
|
|
135
152
|
update: (e, t) => {
|
|
136
153
|
let n = t.data?.markConversationAsRead;
|
|
137
|
-
n &&
|
|
154
|
+
n && _(e, n);
|
|
138
155
|
}
|
|
139
156
|
});
|
|
140
157
|
if (r) throw r;
|
|
@@ -147,10 +164,10 @@ function O() {
|
|
|
147
164
|
error: t.error ?? null
|
|
148
165
|
};
|
|
149
166
|
}
|
|
150
|
-
function
|
|
151
|
-
let [e, n] = t(), [r, i] =
|
|
167
|
+
function j() {
|
|
168
|
+
let [e, n] = t(), [r, i] = f(), [a, o] = u(), [c, d] = m(), [h, g] = l(), [_, y] = p(), [b, x] = s();
|
|
152
169
|
return {
|
|
153
|
-
archiveConversation:
|
|
170
|
+
archiveConversation: v(async (t) => {
|
|
154
171
|
let { data: n } = await e({
|
|
155
172
|
variables: { id: t },
|
|
156
173
|
refetchQueries: ["GetConversations"],
|
|
@@ -158,7 +175,7 @@ function k() {
|
|
|
158
175
|
});
|
|
159
176
|
return n?.archiveConversation.id === t;
|
|
160
177
|
}, [e]),
|
|
161
|
-
unarchiveConversation:
|
|
178
|
+
unarchiveConversation: v(async (e) => {
|
|
162
179
|
let { data: t } = await r({
|
|
163
180
|
variables: { id: e },
|
|
164
181
|
refetchQueries: ["GetConversations"],
|
|
@@ -166,23 +183,23 @@ function k() {
|
|
|
166
183
|
});
|
|
167
184
|
return t?.unarchiveConversation.id === e;
|
|
168
185
|
}, [r]),
|
|
169
|
-
pinConversation:
|
|
186
|
+
pinConversation: v(async (e) => {
|
|
170
187
|
let { data: t } = await a({ variables: { conversationId: e } });
|
|
171
188
|
return !!t?.pinConversation?.id;
|
|
172
189
|
}, [a]),
|
|
173
|
-
unpinConversation:
|
|
174
|
-
let { data: t } = await
|
|
190
|
+
unpinConversation: v(async (e) => {
|
|
191
|
+
let { data: t } = await c({ variables: { conversationId: e } });
|
|
175
192
|
return !!t?.unpinConversation?.id;
|
|
176
|
-
}, [
|
|
177
|
-
muteConversation:
|
|
193
|
+
}, [c]),
|
|
194
|
+
muteConversation: v(async (e) => {
|
|
178
195
|
let { data: t } = await h({ variables: { conversationId: e } });
|
|
179
196
|
return !!t?.muteConversation?.id;
|
|
180
197
|
}, [h]),
|
|
181
|
-
unmuteConversation:
|
|
182
|
-
let { data: t } = await
|
|
198
|
+
unmuteConversation: v(async (e) => {
|
|
199
|
+
let { data: t } = await _({ variables: { conversationId: e } });
|
|
183
200
|
return !!t?.unmuteConversation?.id;
|
|
184
|
-
}, [
|
|
185
|
-
leaveConversation:
|
|
201
|
+
}, [_]),
|
|
202
|
+
leaveConversation: v(async (e) => {
|
|
186
203
|
let { data: t } = await b({
|
|
187
204
|
variables: { conversationId: e },
|
|
188
205
|
refetchQueries: ["GetConversations"],
|
|
@@ -190,17 +207,17 @@ function k() {
|
|
|
190
207
|
});
|
|
191
208
|
return t?.leaveConversation.success ?? !1;
|
|
192
209
|
}, [b]),
|
|
193
|
-
loading: n.loading || i.loading ||
|
|
194
|
-
error: n.error ?? i.error ??
|
|
210
|
+
loading: n.loading || i.loading || o.loading || d.loading || g.loading || y.loading || x.loading,
|
|
211
|
+
error: n.error ?? i.error ?? o.error ?? d.error ?? g.error ?? y.error ?? x.error ?? null
|
|
195
212
|
};
|
|
196
213
|
}
|
|
197
|
-
function
|
|
198
|
-
let [e, t] =
|
|
214
|
+
function M() {
|
|
215
|
+
let [e, t] = h();
|
|
199
216
|
return {
|
|
200
|
-
updateConversation:
|
|
217
|
+
updateConversation: v(async (t) => {
|
|
201
218
|
try {
|
|
202
219
|
let { data: n } = await e({ variables: { input: t } }), r = n?.updateConversation?.conversation;
|
|
203
|
-
return r ?
|
|
220
|
+
return r ? g(r) : null;
|
|
204
221
|
} catch (e) {
|
|
205
222
|
throw e instanceof Error ? e : /* @__PURE__ */ Error("Failed to update conversation");
|
|
206
223
|
}
|
|
@@ -210,6 +227,6 @@ function A() {
|
|
|
210
227
|
};
|
|
211
228
|
}
|
|
212
229
|
//#endregion
|
|
213
|
-
export {
|
|
230
|
+
export { j as useConversationActions, O as useCreateConversation, C as useGetConversation, S as useGetConversations, A as useMarkConversationAsRead, w as useSearchConversations, k as useStartDirectConversation, M as useUpdateConversation, D as useWorkspaceDirectory };
|
|
214
231
|
|
|
215
232
|
//# sourceMappingURL=useConversationQueries.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useConversationQueries.js","names":[],"sources":["../../../src/conversations/hooks/useConversationQueries.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useState } from \"react\";\nimport { type ApolloCache, type FetchResult } from \"@apollo/client\";\nimport {\n type GetConversationQuery,\n type GetWorkspaceDirectoryQuery,\n type MarkConversationAsReadMutation,\n useArchiveConversationMutation,\n useCreateConversationMutation,\n useGetConversationQuery,\n useGetConversationsQuery,\n useGetWorkspaceDirectoryQuery,\n useMarkConversationAsReadMutation,\n useMuteConversationMutation,\n useLeaveConversationMutation,\n usePinConversationMutation,\n useSearchConversationsQuery,\n useUnarchiveConversationMutation,\n useUnmuteConversationMutation,\n useUnpinConversationMutation,\n useUpdateConversationByHookMutation,\n} from \"../../generated/wspace-operations\";\nimport type * as WspaceTypes from \"../../generated/wspace-types\";\nimport type {\n Conversation,\n ConversationState,\n ConversationType,\n ConversationVisibility,\n} from \"../types\";\nimport { useConversations } from \"../providers/ConversationsProvider\";\nimport { mapConversation } from \"../utils/mappers\";\nimport { applyMarkConversationAsReadToCache } from \"../utils/readReceiptCache\";\n\ninterface UseGetConversationsOptions {\n first?: number;\n after?: string;\n types?: ConversationType[];\n /**\n * Conversation lifecycle states to include. When omitted the backend\n * returns ACTIVE conversations by default (filtering is union, not\n * exclusion, so `[\"ACTIVE\"]` and `undefined` are interchangeable for the\n * default inbox). Pass `[\"ARCHIVED\"]` to drive the archived tab.\n */\n states?: ConversationState[];\n hasUnread?: boolean;\n isPinned?: boolean;\n}\n\ninterface UseGetConversationsResult {\n conversations: Conversation[];\n loading: boolean;\n error: Error | null;\n hasNextPage: boolean;\n fetchMore: () => void;\n refetch: () => void;\n}\n\n/**\n * Hook: Fetch a paginated list of conversations.\n */\nexport function useGetConversations(\n options?: UseGetConversationsOptions,\n): UseGetConversationsResult {\n const { workspaceId } = useConversations();\n const queryResult = useGetConversationsQuery({\n variables: {\n first: options?.first ?? 25,\n after: options?.after,\n filter: {\n types: options?.types as WspaceTypes.InputMaybe<\n WspaceTypes.ConversationType[]\n >,\n states: options?.states as WspaceTypes.InputMaybe<\n WspaceTypes.ConversationState[]\n >,\n hasUnread: options?.hasUnread,\n isPinned: options?.isPinned,\n },\n },\n skip: !workspaceId,\n });\n\n const conversations = useMemo(\n () =>\n queryResult.data?.conversations.edges.map(\n (edge: { node: Record<string, unknown> }) => mapConversation(edge.node),\n ) ?? [],\n [queryResult.data],\n );\n\n const hasNextPage =\n queryResult.data?.conversations.pageInfo.hasNextPage ?? false;\n\n return {\n conversations,\n loading: queryResult.loading,\n error: queryResult.error ?? null,\n hasNextPage,\n fetchMore: () => {\n const endCursor = queryResult.data?.conversations.pageInfo.endCursor;\n if (!endCursor || !hasNextPage) return;\n void queryResult.fetchMore({\n variables: {\n first: options?.first ?? 25,\n after: endCursor,\n filter: {\n types: options?.types as WspaceTypes.InputMaybe<\n WspaceTypes.ConversationType[]\n >,\n states: options?.states as WspaceTypes.InputMaybe<\n WspaceTypes.ConversationState[]\n >,\n hasUnread: options?.hasUnread,\n isPinned: options?.isPinned,\n },\n },\n });\n },\n refetch: () => {\n void queryResult.refetch();\n },\n };\n}\n\ninterface UseGetConversationResult {\n conversation: Conversation | null;\n members: ConversationMember[];\n membersTotalCount: number;\n loading: boolean;\n error: Error | null;\n refetch: () => void;\n}\n\nexport type ConversationMember = NonNullable<\n GetConversationQuery[\"conversation\"]\n>[\"members\"][\"edges\"][number][\"node\"];\ntype ConversationMemberEdge = NonNullable<\n GetConversationQuery[\"conversation\"]\n>[\"members\"][\"edges\"][number];\n\n/**\n * Hook: Fetch a single conversation by ID.\n */\nexport function useGetConversation(\n conversationId: string,\n): UseGetConversationResult {\n const { workspaceId } = useConversations();\n const queryResult = useGetConversationQuery({\n variables: { id: conversationId },\n skip: !workspaceId || !conversationId,\n });\n\n return {\n conversation: queryResult.data?.conversation\n ? mapConversation(queryResult.data.conversation)\n : null,\n members:\n queryResult.data?.conversation?.members.edges\n .map((edge: ConversationMemberEdge) => edge.node)\n .filter(\n (member: ConversationMember | null): member is ConversationMember =>\n Boolean(member),\n ) ?? [],\n membersTotalCount: queryResult.data?.conversation?.members.totalCount ?? 0,\n loading: queryResult.loading,\n error: queryResult.error ?? null,\n refetch: () => {\n void queryResult.refetch();\n },\n };\n}\n\ninterface UseSearchConversationsResult {\n conversations: Conversation[];\n loading: boolean;\n error: Error | null;\n}\n\n/**\n * Hook: Search conversations by query string.\n */\nexport function useSearchConversations(\n query: string,\n): UseSearchConversationsResult {\n const { workspaceId } = useConversations();\n const trimmedQuery = query.trim();\n const queryResult = useSearchConversationsQuery({\n variables: { query: trimmedQuery, first: 25 },\n skip: !workspaceId || trimmedQuery.length < 2,\n });\n\n return {\n conversations:\n queryResult.data?.searchConversations.edges.map(\n (edge: { node: Record<string, unknown> }) => mapConversation(edge.node),\n ) ?? [],\n loading: queryResult.loading,\n error: queryResult.error ?? null,\n };\n}\n\n/**\n * A workspace member surfaced by the directory, resolved to a friendly\n * identity. `id` is the member's user id (used for `participantIds`).\n */\nexport type WorkspaceDirectoryMember =\n GetWorkspaceDirectoryQuery[\"workspaceDirectory\"][\"edges\"][number];\n\ninterface UseWorkspaceDirectoryResult {\n members: WorkspaceDirectoryMember[];\n totalCount: number;\n loading: boolean;\n error: Error | null;\n}\n\nconst DIRECTORY_PAGE_SIZE = 25;\nconst DIRECTORY_SEARCH_DEBOUNCE_MS = 250;\n\n/**\n * Hook: Search the current workspace's member directory (server-side,\n * debounced). Backed by the conversations `workspaceDirectory` query, which is\n * a thin, identity-resolved wrapper over wspace-workspace-svc. An empty search\n * lists the first page of members so the picker shows suggestions before the\n * user types.\n */\nexport function useWorkspaceDirectory(\n search: string,\n): UseWorkspaceDirectoryResult {\n const { workspaceId } = useConversations();\n const [debouncedSearch, setDebouncedSearch] = useState(search.trim());\n\n useEffect(() => {\n const trimmed = search.trim();\n const handle = setTimeout(() => {\n setDebouncedSearch(trimmed);\n }, DIRECTORY_SEARCH_DEBOUNCE_MS);\n return () => clearTimeout(handle);\n }, [search]);\n\n const queryResult = useGetWorkspaceDirectoryQuery({\n variables: {\n filter: debouncedSearch ? { search: debouncedSearch } : undefined,\n pagination: { limit: DIRECTORY_PAGE_SIZE, offset: 0 },\n },\n skip: !workspaceId,\n fetchPolicy: \"cache-and-network\",\n });\n\n const members = useMemo(\n () => queryResult.data?.workspaceDirectory.edges ?? [],\n [queryResult.data],\n );\n\n return {\n members,\n totalCount: queryResult.data?.workspaceDirectory.totalCount ?? 0,\n loading: queryResult.loading,\n error: queryResult.error ?? null,\n };\n}\n\ninterface CreateConversationInput {\n type: ConversationType;\n title?: string;\n description?: string;\n participantIds: string[];\n visibility?: ConversationVisibility;\n}\n\ninterface UseCreateConversationResult {\n createConversation: (\n input: CreateConversationInput,\n ) => Promise<Conversation | null>;\n loading: boolean;\n error: Error | null;\n}\n\n/**\n * Hook: Create a new conversation.\n */\nexport function useCreateConversation(): UseCreateConversationResult {\n const { workspaceId } = useConversations();\n const [createConversationMutation, mutationState] =\n useCreateConversationMutation();\n\n const createConversation = useCallback(\n async (input: CreateConversationInput): Promise<Conversation | null> => {\n if (!workspaceId) {\n throw new Error(\n \"Workspace context is required to create a conversation\",\n );\n }\n\n try {\n const { data, error } = await createConversationMutation({\n variables: {\n input: {\n workspaceId,\n type: input.type as WspaceTypes.ConversationType,\n title: input.title,\n description: input.description,\n participantIds: input.participantIds,\n },\n },\n });\n if (error) throw error;\n\n const conversation = data?.createConversation.conversation;\n return conversation ? mapConversation(conversation) : null;\n } catch (err) {\n throw err instanceof Error\n ? err\n : new Error(\"Failed to create conversation\");\n }\n },\n [createConversationMutation, workspaceId],\n );\n\n return {\n createConversation,\n loading: mutationState.loading,\n error: mutationState.error ?? null,\n };\n}\n\ninterface UseMarkConversationAsReadResult {\n markConversationAsRead: (input: {\n conversationId: string;\n messageId?: string;\n }) => Promise<boolean>;\n loading: boolean;\n error: Error | null;\n}\n\n/**\n * Hook: Mark a conversation as read for the current viewer.\n *\n * Pass the newest message the viewer has seen as `messageId`; when omitted\n * the backend marks the whole conversation read. On success the cache update\n * zeroes the viewer-scoped `unreadCount`/`mentionCount` fields on the cached\n * Conversation entity — the list badge reads those top-level fields, which\n * membership normalization alone would leave stale — and drops the\n * conversation from any cached `hasUnread: true` list (see\n * `applyMarkConversationAsReadToCache`).\n */\nexport function useMarkConversationAsRead(): UseMarkConversationAsReadResult {\n const [markConversationAsReadMutation, mutationState] =\n useMarkConversationAsReadMutation();\n\n const markConversationAsRead = useCallback(\n async (input: {\n conversationId: string;\n messageId?: string;\n }): Promise<boolean> => {\n try {\n const { data, error } = await markConversationAsReadMutation({\n variables: {\n conversationId: input.conversationId,\n messageId: input.messageId,\n },\n update: (\n cache: ApolloCache,\n result: FetchResult<MarkConversationAsReadMutation>,\n ) => {\n const membership = result.data?.markConversationAsRead;\n if (!membership) return;\n applyMarkConversationAsReadToCache(cache, membership);\n },\n });\n if (error) throw error;\n return Boolean(data?.markConversationAsRead);\n } catch (err) {\n throw err instanceof Error\n ? err\n : new Error(\"Failed to mark conversation as read\");\n }\n },\n [markConversationAsReadMutation],\n );\n\n return {\n markConversationAsRead,\n loading: mutationState.loading,\n error: mutationState.error ?? null,\n };\n}\n\ninterface UseUpdateConversationResult {\n archiveConversation: (id: string) => Promise<boolean>;\n unarchiveConversation: (id: string) => Promise<boolean>;\n pinConversation: (id: string) => Promise<boolean>;\n unpinConversation: (id: string) => Promise<boolean>;\n muteConversation: (id: string) => Promise<boolean>;\n unmuteConversation: (id: string) => Promise<boolean>;\n leaveConversation: (id: string) => Promise<boolean>;\n loading: boolean;\n error: Error | null;\n}\n\n/**\n * Hook: Conversation mutation operations.\n */\nexport function useConversationActions(): UseUpdateConversationResult {\n const [archiveConversationMutation, archiveState] =\n useArchiveConversationMutation();\n const [unarchiveConversationMutation, unarchiveState] =\n useUnarchiveConversationMutation();\n const [pinConversationMutation, pinState] = usePinConversationMutation();\n const [unpinConversationMutation, unpinState] =\n useUnpinConversationMutation();\n const [muteConversationMutation, muteState] = useMuteConversationMutation();\n const [unmuteConversationMutation, unmuteState] =\n useUnmuteConversationMutation();\n const [leaveConversationMutation, leaveState] =\n useLeaveConversationMutation();\n\n const archiveConversation = useCallback(\n async (id: string): Promise<boolean> => {\n // refetch GetConversations so the row drops out of the inbox (and the\n // Archived tab gains it) without waiting for a manual refresh.\n const { data } = await archiveConversationMutation({\n variables: { id },\n refetchQueries: [\"GetConversations\"],\n awaitRefetchQueries: true,\n });\n return data?.archiveConversation.id === id;\n },\n [archiveConversationMutation],\n );\n\n const unarchiveConversation = useCallback(\n async (id: string): Promise<boolean> => {\n const { data } = await unarchiveConversationMutation({\n variables: { id },\n refetchQueries: [\"GetConversations\"],\n awaitRefetchQueries: true,\n });\n return data?.unarchiveConversation.id === id;\n },\n [unarchiveConversationMutation],\n );\n\n const pinConversation = useCallback(\n async (id: string): Promise<boolean> => {\n const { data } = await pinConversationMutation({\n variables: { conversationId: id },\n });\n return Boolean(data?.pinConversation?.id);\n },\n [pinConversationMutation],\n );\n\n const unpinConversation = useCallback(\n async (id: string): Promise<boolean> => {\n const { data } = await unpinConversationMutation({\n variables: { conversationId: id },\n });\n return Boolean(data?.unpinConversation?.id);\n },\n [unpinConversationMutation],\n );\n\n const muteConversation = useCallback(\n async (id: string): Promise<boolean> => {\n const { data } = await muteConversationMutation({\n variables: { conversationId: id },\n });\n return Boolean(data?.muteConversation?.id);\n },\n [muteConversationMutation],\n );\n\n const unmuteConversation = useCallback(\n async (id: string): Promise<boolean> => {\n const { data } = await unmuteConversationMutation({\n variables: { conversationId: id },\n });\n return Boolean(data?.unmuteConversation?.id);\n },\n [unmuteConversationMutation],\n );\n\n const leaveConversation = useCallback(\n async (id: string): Promise<boolean> => {\n const { data } = await leaveConversationMutation({\n variables: { conversationId: id },\n refetchQueries: [\"GetConversations\"],\n awaitRefetchQueries: true,\n });\n return data?.leaveConversation.success ?? false;\n },\n [leaveConversationMutation],\n );\n\n return {\n archiveConversation,\n unarchiveConversation,\n pinConversation,\n unpinConversation,\n muteConversation,\n unmuteConversation,\n leaveConversation,\n loading:\n archiveState.loading ||\n unarchiveState.loading ||\n pinState.loading ||\n unpinState.loading ||\n muteState.loading ||\n unmuteState.loading ||\n leaveState.loading,\n error:\n (archiveState.error as Error | undefined) ??\n (unarchiveState.error as Error | undefined) ??\n (pinState.error as Error | undefined) ??\n (unpinState.error as Error | undefined) ??\n (muteState.error as Error | undefined) ??\n (unmuteState.error as Error | undefined) ??\n (leaveState.error as Error | undefined) ??\n null,\n };\n}\n\ninterface UseUpdateConversationOptions {\n updateConversation: (input: {\n id: string;\n title?: string;\n description?: string;\n avatarUrl?: string;\n settings?: Record<string, unknown>;\n }) => Promise<Conversation | null>;\n loading: boolean;\n error: Error | null;\n}\n\nexport function useUpdateConversation(): UseUpdateConversationOptions {\n const [updateConversationMutation, mutationState] =\n useUpdateConversationByHookMutation();\n\n const updateConversation = useCallback(\n async (input: {\n id: string;\n title?: string;\n description?: string;\n avatarUrl?: string;\n settings?: Record<string, unknown>;\n }): Promise<Conversation | null> => {\n try {\n const { data } = await updateConversationMutation({\n variables: { input },\n });\n const typedData = data as\n | {\n updateConversation?: {\n conversation?: Record<string, unknown> | null;\n };\n }\n | undefined;\n const conversation = typedData?.updateConversation?.conversation;\n return conversation\n ? mapConversation(conversation as Record<string, unknown>)\n : null;\n } catch (err) {\n throw err instanceof Error\n ? err\n : new Error(\"Failed to update conversation\");\n }\n },\n [updateConversationMutation],\n );\n\n return {\n updateConversation,\n loading: mutationState.loading,\n error: mutationState.error ?? null,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useConversationQueries.js","names":[],"sources":["../../../src/conversations/hooks/useConversationQueries.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useState } from \"react\";\nimport { type ApolloCache, type FetchResult } from \"@apollo/client\";\nimport {\n type GetConversationQuery,\n type GetOrCreateDirectConversationQuery,\n type GetWorkspaceDirectoryQuery,\n type MarkConversationAsReadMutation,\n useArchiveConversationMutation,\n useCreateConversationMutation,\n useGetConversationQuery,\n useGetConversationsQuery,\n useGetOrCreateDirectConversationLazyQuery,\n useGetWorkspaceDirectoryQuery,\n useMarkConversationAsReadMutation,\n useMuteConversationMutation,\n useLeaveConversationMutation,\n usePinConversationMutation,\n useSearchConversationsQuery,\n useUnarchiveConversationMutation,\n useUnmuteConversationMutation,\n useUnpinConversationMutation,\n useUpdateConversationByHookMutation,\n} from \"../../generated/wspace-operations\";\nimport type * as WspaceTypes from \"../../generated/wspace-types\";\nimport type {\n Conversation,\n ConversationState,\n ConversationType,\n ConversationVisibility,\n} from \"../types\";\nimport { useConversations } from \"../providers/ConversationsProvider\";\nimport { mapConversation } from \"../utils/mappers\";\nimport { applyMarkConversationAsReadToCache } from \"../utils/readReceiptCache\";\n\ninterface UseGetConversationsOptions {\n first?: number;\n after?: string;\n types?: ConversationType[];\n /**\n * Conversation lifecycle states to include. When omitted the backend\n * returns ACTIVE conversations by default (filtering is union, not\n * exclusion, so `[\"ACTIVE\"]` and `undefined` are interchangeable for the\n * default inbox). Pass `[\"ARCHIVED\"]` to drive the archived tab.\n */\n states?: ConversationState[];\n hasUnread?: boolean;\n isPinned?: boolean;\n}\n\ninterface UseGetConversationsResult {\n conversations: Conversation[];\n loading: boolean;\n error: Error | null;\n hasNextPage: boolean;\n fetchMore: () => void;\n refetch: () => void;\n}\n\n/**\n * Hook: Fetch a paginated list of conversations.\n */\nexport function useGetConversations(\n options?: UseGetConversationsOptions,\n): UseGetConversationsResult {\n const { workspaceId } = useConversations();\n const queryResult = useGetConversationsQuery({\n variables: {\n first: options?.first ?? 25,\n after: options?.after,\n filter: {\n types: options?.types as WspaceTypes.InputMaybe<\n WspaceTypes.ConversationType[]\n >,\n states: options?.states as WspaceTypes.InputMaybe<\n WspaceTypes.ConversationState[]\n >,\n hasUnread: options?.hasUnread,\n isPinned: options?.isPinned,\n },\n },\n skip: !workspaceId,\n });\n\n const conversations = useMemo(\n () =>\n queryResult.data?.conversations.edges.map(\n (edge: { node: Record<string, unknown> }) => mapConversation(edge.node),\n ) ?? [],\n [queryResult.data],\n );\n\n const hasNextPage =\n queryResult.data?.conversations.pageInfo.hasNextPage ?? false;\n\n return {\n conversations,\n loading: queryResult.loading,\n error: queryResult.error ?? null,\n hasNextPage,\n fetchMore: () => {\n const endCursor = queryResult.data?.conversations.pageInfo.endCursor;\n if (!endCursor || !hasNextPage) return;\n void queryResult.fetchMore({\n variables: {\n first: options?.first ?? 25,\n after: endCursor,\n filter: {\n types: options?.types as WspaceTypes.InputMaybe<\n WspaceTypes.ConversationType[]\n >,\n states: options?.states as WspaceTypes.InputMaybe<\n WspaceTypes.ConversationState[]\n >,\n hasUnread: options?.hasUnread,\n isPinned: options?.isPinned,\n },\n },\n });\n },\n refetch: () => {\n void queryResult.refetch();\n },\n };\n}\n\ninterface UseGetConversationResult {\n conversation: Conversation | null;\n members: ConversationMember[];\n membersTotalCount: number;\n loading: boolean;\n error: Error | null;\n refetch: () => void;\n}\n\nexport type ConversationMember = NonNullable<\n GetConversationQuery[\"conversation\"]\n>[\"members\"][\"edges\"][number][\"node\"];\ntype ConversationMemberEdge = NonNullable<\n GetConversationQuery[\"conversation\"]\n>[\"members\"][\"edges\"][number];\n\n/**\n * Hook: Fetch a single conversation by ID.\n */\nexport function useGetConversation(\n conversationId: string,\n): UseGetConversationResult {\n const { workspaceId } = useConversations();\n const queryResult = useGetConversationQuery({\n variables: { id: conversationId },\n skip: !workspaceId || !conversationId,\n });\n\n return {\n conversation: queryResult.data?.conversation\n ? mapConversation(queryResult.data.conversation)\n : null,\n members:\n queryResult.data?.conversation?.members.edges\n .map((edge: ConversationMemberEdge) => edge.node)\n .filter(\n (member: ConversationMember | null): member is ConversationMember =>\n Boolean(member),\n ) ?? [],\n membersTotalCount: queryResult.data?.conversation?.members.totalCount ?? 0,\n loading: queryResult.loading,\n error: queryResult.error ?? null,\n refetch: () => {\n void queryResult.refetch();\n },\n };\n}\n\ninterface UseSearchConversationsResult {\n conversations: Conversation[];\n loading: boolean;\n error: Error | null;\n}\n\n/**\n * Hook: Search conversations by query string.\n */\nexport function useSearchConversations(\n query: string,\n): UseSearchConversationsResult {\n const { workspaceId } = useConversations();\n const trimmedQuery = query.trim();\n const queryResult = useSearchConversationsQuery({\n variables: { query: trimmedQuery, first: 25 },\n skip: !workspaceId || trimmedQuery.length < 2,\n });\n\n return {\n conversations:\n queryResult.data?.searchConversations.edges.map(\n (edge: { node: Record<string, unknown> }) => mapConversation(edge.node),\n ) ?? [],\n loading: queryResult.loading,\n error: queryResult.error ?? null,\n };\n}\n\n/**\n * A workspace member surfaced by the directory, resolved to a friendly\n * identity. `id` is the member's user id (used for `participantIds`).\n */\nexport type WorkspaceDirectoryMember =\n GetWorkspaceDirectoryQuery[\"workspaceDirectory\"][\"edges\"][number];\n\ninterface UseWorkspaceDirectoryResult {\n members: WorkspaceDirectoryMember[];\n totalCount: number;\n loading: boolean;\n error: Error | null;\n}\n\nconst DIRECTORY_PAGE_SIZE = 25;\nconst DIRECTORY_SEARCH_DEBOUNCE_MS = 250;\n\n/**\n * Hook: Search the current workspace's member directory (server-side,\n * debounced). Backed by the conversations `workspaceDirectory` query, which is\n * a thin, identity-resolved wrapper over wspace-workspace-svc. An empty search\n * lists the first page of members so the picker shows suggestions before the\n * user types.\n */\nexport function useWorkspaceDirectory(\n search: string,\n): UseWorkspaceDirectoryResult {\n const { workspaceId } = useConversations();\n const [debouncedSearch, setDebouncedSearch] = useState(search.trim());\n\n useEffect(() => {\n const trimmed = search.trim();\n const handle = setTimeout(() => {\n setDebouncedSearch(trimmed);\n }, DIRECTORY_SEARCH_DEBOUNCE_MS);\n return () => clearTimeout(handle);\n }, [search]);\n\n const queryResult = useGetWorkspaceDirectoryQuery({\n variables: {\n filter: debouncedSearch ? { search: debouncedSearch } : undefined,\n pagination: { limit: DIRECTORY_PAGE_SIZE, offset: 0 },\n },\n skip: !workspaceId,\n fetchPolicy: \"cache-and-network\",\n });\n\n const members = useMemo(\n () => queryResult.data?.workspaceDirectory.edges ?? [],\n [queryResult.data],\n );\n\n return {\n members,\n totalCount: queryResult.data?.workspaceDirectory.totalCount ?? 0,\n loading: queryResult.loading,\n error: queryResult.error ?? null,\n };\n}\n\ninterface CreateConversationInput {\n type: ConversationType;\n title?: string;\n description?: string;\n participantIds: string[];\n visibility?: ConversationVisibility;\n}\n\ninterface UseCreateConversationResult {\n createConversation: (\n input: CreateConversationInput,\n ) => Promise<Conversation | null>;\n loading: boolean;\n error: Error | null;\n}\n\n/**\n * Hook: Create a new conversation.\n */\nexport function useCreateConversation(): UseCreateConversationResult {\n const { workspaceId } = useConversations();\n const [createConversationMutation, mutationState] =\n useCreateConversationMutation();\n\n const createConversation = useCallback(\n async (input: CreateConversationInput): Promise<Conversation | null> => {\n if (!workspaceId) {\n throw new Error(\n \"Workspace context is required to create a conversation\",\n );\n }\n\n try {\n const { data, error } = await createConversationMutation({\n variables: {\n input: {\n workspaceId,\n type: input.type as WspaceTypes.ConversationType,\n title: input.title,\n description: input.description,\n participantIds: input.participantIds,\n },\n },\n });\n if (error) throw error;\n\n const conversation = data?.createConversation.conversation;\n return conversation ? mapConversation(conversation) : null;\n } catch (err) {\n throw err instanceof Error\n ? err\n : new Error(\"Failed to create conversation\");\n }\n },\n [createConversationMutation, workspaceId],\n );\n\n return {\n createConversation,\n loading: mutationState.loading,\n error: mutationState.error ?? null,\n };\n}\n\ninterface UseStartDirectConversationResult {\n /**\n * Open (or create) the 1-to-1 conversation with `userId`. Reuses the\n * existing thread if one is already bound to the (viewer, recipient) pair;\n * mints a new one otherwise. See BOFF-8227.\n */\n startDirectConversation: (userId: string) => Promise<Conversation | null>;\n loading: boolean;\n error: Error | null;\n}\n\n/**\n * Hook: open or create the 1-to-1 conversation with another workspace member.\n *\n * Wraps the `getOrCreateDirectConversation` GraphQL query so the *only* place\n * a DM is ever created for a given pair is the backend's exact-pair lookup.\n * The frontend must NOT call `useCreateConversation` for DMs — that path\n * always mints a new row, which is what produced duplicate DM threads in the\n * inbox (BOFF-8227).\n *\n * The returned conversation id is the id of either the freshly-created row OR\n * the pre-existing thread that was reused — callers can treat it as the\n * authoritative detail-page id regardless of which branch ran.\n */\nexport function useStartDirectConversation(): UseStartDirectConversationResult {\n const [getOrCreateDirectConversation, queryState] =\n useGetOrCreateDirectConversationLazyQuery();\n\n const startDirectConversation = useCallback(\n async (userId: string): Promise<Conversation | null> => {\n try {\n const { data, error } = await getOrCreateDirectConversation({\n variables: { userId },\n });\n if (error) throw error;\n\n const rawConversation = (\n data as GetOrCreateDirectConversationQuery | undefined | null\n )?.getOrCreateDirectConversation;\n return rawConversation\n ? mapConversation(\n rawConversation as unknown as Record<string, unknown>,\n )\n : null;\n } catch (err) {\n throw err instanceof Error\n ? err\n : new Error(\"Failed to open direct conversation\");\n }\n },\n [getOrCreateDirectConversation],\n );\n\n return {\n startDirectConversation,\n loading: queryState.loading,\n error: queryState.error ?? null,\n };\n}\n\ninterface UseMarkConversationAsReadResult {\n markConversationAsRead: (input: {\n conversationId: string;\n messageId?: string;\n }) => Promise<boolean>;\n loading: boolean;\n error: Error | null;\n}\n\n/**\n * Hook: Mark a conversation as read for the current viewer.\n *\n * Pass the newest message the viewer has seen as `messageId`; when omitted\n * the backend marks the whole conversation read. On success the cache update\n * zeroes the viewer-scoped `unreadCount`/`mentionCount` fields on the cached\n * Conversation entity — the list badge reads those top-level fields, which\n * membership normalization alone would leave stale — and drops the\n * conversation from any cached `hasUnread: true` list (see\n * `applyMarkConversationAsReadToCache`).\n */\nexport function useMarkConversationAsRead(): UseMarkConversationAsReadResult {\n const [markConversationAsReadMutation, mutationState] =\n useMarkConversationAsReadMutation();\n\n const markConversationAsRead = useCallback(\n async (input: {\n conversationId: string;\n messageId?: string;\n }): Promise<boolean> => {\n try {\n const { data, error } = await markConversationAsReadMutation({\n variables: {\n conversationId: input.conversationId,\n messageId: input.messageId,\n },\n update: (\n cache: ApolloCache,\n result: FetchResult<MarkConversationAsReadMutation>,\n ) => {\n const membership = result.data?.markConversationAsRead;\n if (!membership) return;\n applyMarkConversationAsReadToCache(cache, membership);\n },\n });\n if (error) throw error;\n return Boolean(data?.markConversationAsRead);\n } catch (err) {\n throw err instanceof Error\n ? err\n : new Error(\"Failed to mark conversation as read\");\n }\n },\n [markConversationAsReadMutation],\n );\n\n return {\n markConversationAsRead,\n loading: mutationState.loading,\n error: mutationState.error ?? null,\n };\n}\n\ninterface UseUpdateConversationResult {\n archiveConversation: (id: string) => Promise<boolean>;\n unarchiveConversation: (id: string) => Promise<boolean>;\n pinConversation: (id: string) => Promise<boolean>;\n unpinConversation: (id: string) => Promise<boolean>;\n muteConversation: (id: string) => Promise<boolean>;\n unmuteConversation: (id: string) => Promise<boolean>;\n leaveConversation: (id: string) => Promise<boolean>;\n loading: boolean;\n error: Error | null;\n}\n\n/**\n * Hook: Conversation mutation operations.\n */\nexport function useConversationActions(): UseUpdateConversationResult {\n const [archiveConversationMutation, archiveState] =\n useArchiveConversationMutation();\n const [unarchiveConversationMutation, unarchiveState] =\n useUnarchiveConversationMutation();\n const [pinConversationMutation, pinState] = usePinConversationMutation();\n const [unpinConversationMutation, unpinState] =\n useUnpinConversationMutation();\n const [muteConversationMutation, muteState] = useMuteConversationMutation();\n const [unmuteConversationMutation, unmuteState] =\n useUnmuteConversationMutation();\n const [leaveConversationMutation, leaveState] =\n useLeaveConversationMutation();\n\n const archiveConversation = useCallback(\n async (id: string): Promise<boolean> => {\n // refetch GetConversations so the row drops out of the inbox (and the\n // Archived tab gains it) without waiting for a manual refresh.\n const { data } = await archiveConversationMutation({\n variables: { id },\n refetchQueries: [\"GetConversations\"],\n awaitRefetchQueries: true,\n });\n return data?.archiveConversation.id === id;\n },\n [archiveConversationMutation],\n );\n\n const unarchiveConversation = useCallback(\n async (id: string): Promise<boolean> => {\n const { data } = await unarchiveConversationMutation({\n variables: { id },\n refetchQueries: [\"GetConversations\"],\n awaitRefetchQueries: true,\n });\n return data?.unarchiveConversation.id === id;\n },\n [unarchiveConversationMutation],\n );\n\n const pinConversation = useCallback(\n async (id: string): Promise<boolean> => {\n const { data } = await pinConversationMutation({\n variables: { conversationId: id },\n });\n return Boolean(data?.pinConversation?.id);\n },\n [pinConversationMutation],\n );\n\n const unpinConversation = useCallback(\n async (id: string): Promise<boolean> => {\n const { data } = await unpinConversationMutation({\n variables: { conversationId: id },\n });\n return Boolean(data?.unpinConversation?.id);\n },\n [unpinConversationMutation],\n );\n\n const muteConversation = useCallback(\n async (id: string): Promise<boolean> => {\n const { data } = await muteConversationMutation({\n variables: { conversationId: id },\n });\n return Boolean(data?.muteConversation?.id);\n },\n [muteConversationMutation],\n );\n\n const unmuteConversation = useCallback(\n async (id: string): Promise<boolean> => {\n const { data } = await unmuteConversationMutation({\n variables: { conversationId: id },\n });\n return Boolean(data?.unmuteConversation?.id);\n },\n [unmuteConversationMutation],\n );\n\n const leaveConversation = useCallback(\n async (id: string): Promise<boolean> => {\n const { data } = await leaveConversationMutation({\n variables: { conversationId: id },\n refetchQueries: [\"GetConversations\"],\n awaitRefetchQueries: true,\n });\n return data?.leaveConversation.success ?? false;\n },\n [leaveConversationMutation],\n );\n\n return {\n archiveConversation,\n unarchiveConversation,\n pinConversation,\n unpinConversation,\n muteConversation,\n unmuteConversation,\n leaveConversation,\n loading:\n archiveState.loading ||\n unarchiveState.loading ||\n pinState.loading ||\n unpinState.loading ||\n muteState.loading ||\n unmuteState.loading ||\n leaveState.loading,\n error:\n (archiveState.error as Error | undefined) ??\n (unarchiveState.error as Error | undefined) ??\n (pinState.error as Error | undefined) ??\n (unpinState.error as Error | undefined) ??\n (muteState.error as Error | undefined) ??\n (unmuteState.error as Error | undefined) ??\n (leaveState.error as Error | undefined) ??\n null,\n };\n}\n\ninterface UseUpdateConversationOptions {\n updateConversation: (input: {\n id: string;\n title?: string;\n description?: string;\n avatarUrl?: string;\n settings?: Record<string, unknown>;\n }) => Promise<Conversation | null>;\n loading: boolean;\n error: Error | null;\n}\n\nexport function useUpdateConversation(): UseUpdateConversationOptions {\n const [updateConversationMutation, mutationState] =\n useUpdateConversationByHookMutation();\n\n const updateConversation = useCallback(\n async (input: {\n id: string;\n title?: string;\n description?: string;\n avatarUrl?: string;\n settings?: Record<string, unknown>;\n }): Promise<Conversation | null> => {\n try {\n const { data } = await updateConversationMutation({\n variables: { input },\n });\n const typedData = data as\n | {\n updateConversation?: {\n conversation?: Record<string, unknown> | null;\n };\n }\n | undefined;\n const conversation = typedData?.updateConversation?.conversation;\n return conversation\n ? mapConversation(conversation as Record<string, unknown>)\n : null;\n } catch (err) {\n throw err instanceof Error\n ? err\n : new Error(\"Failed to update conversation\");\n }\n },\n [updateConversationMutation],\n );\n\n return {\n updateConversation,\n loading: mutationState.loading,\n error: mutationState.error ?? null,\n };\n}\n"],"mappings":"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"}
|