@planningcenter/chat-react-native 4.2.1-qa-staging.5 → 4.3.0-rc.0

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 (170) hide show
  1. package/build/components/conversation/message_form.d.ts +1 -2
  2. package/build/components/conversation/message_form.d.ts.map +1 -1
  3. package/build/components/conversation/message_form.js +51 -54
  4. package/build/components/conversation/message_form.js.map +1 -1
  5. package/build/components/conversation/message_list.d.ts +2 -3
  6. package/build/components/conversation/message_list.d.ts.map +1 -1
  7. package/build/components/conversation/message_list.js +66 -152
  8. package/build/components/conversation/message_list.js.map +1 -1
  9. package/build/contexts/api_provider.d.ts.map +1 -1
  10. package/build/contexts/api_provider.js +1 -2
  11. package/build/contexts/api_provider.js.map +1 -1
  12. package/build/contexts/conversation_context.d.ts +1 -3
  13. package/build/contexts/conversation_context.d.ts.map +1 -1
  14. package/build/contexts/conversation_context.js +18 -23
  15. package/build/contexts/conversation_context.js.map +1 -1
  16. package/build/hooks/paginator_meta.d.ts +1 -3
  17. package/build/hooks/paginator_meta.d.ts.map +1 -1
  18. package/build/hooks/paginator_meta.js +8 -25
  19. package/build/hooks/paginator_meta.js.map +1 -1
  20. package/build/hooks/use_api.d.ts.map +1 -1
  21. package/build/hooks/use_api.js +8 -5
  22. package/build/hooks/use_api.js.map +1 -1
  23. package/build/hooks/use_conversation_messages.d.ts +2 -9
  24. package/build/hooks/use_conversation_messages.d.ts.map +1 -1
  25. package/build/hooks/use_conversation_messages.js +45 -84
  26. package/build/hooks/use_conversation_messages.js.map +1 -1
  27. package/build/hooks/use_conversation_messages_jolt_events.d.ts +1 -17
  28. package/build/hooks/use_conversation_messages_jolt_events.d.ts.map +1 -1
  29. package/build/hooks/use_conversation_messages_jolt_events.js +80 -89
  30. package/build/hooks/use_conversation_messages_jolt_events.js.map +1 -1
  31. package/build/hooks/use_mark_latest_message_read.d.ts +1 -2
  32. package/build/hooks/use_mark_latest_message_read.d.ts.map +1 -1
  33. package/build/hooks/use_mark_latest_message_read.js +3 -4
  34. package/build/hooks/use_mark_latest_message_read.js.map +1 -1
  35. package/build/hooks/use_message_create_or_update.d.ts.map +1 -1
  36. package/build/hooks/use_message_create_or_update.js +80 -91
  37. package/build/hooks/use_message_create_or_update.js.map +1 -1
  38. package/build/hooks/use_message_reaction_toggle.d.ts.map +1 -1
  39. package/build/hooks/use_message_reaction_toggle.js +40 -45
  40. package/build/hooks/use_message_reaction_toggle.js.map +1 -1
  41. package/build/hooks/use_suspense_api.d.ts.map +1 -1
  42. package/build/hooks/use_suspense_api.js +8 -5
  43. package/build/hooks/use_suspense_api.js.map +1 -1
  44. package/build/screens/conversation_new/components/services_form.js +8 -9
  45. package/build/screens/conversation_new/components/services_form.js.map +1 -1
  46. package/build/screens/conversation_screen.d.ts +0 -1
  47. package/build/screens/conversation_screen.d.ts.map +1 -1
  48. package/build/screens/conversation_screen.js +254 -481
  49. package/build/screens/conversation_screen.js.map +1 -1
  50. package/build/screens/message_actions_screen.d.ts.map +1 -1
  51. package/build/screens/message_actions_screen.js +26 -21
  52. package/build/screens/message_actions_screen.js.map +1 -1
  53. package/build/screens/message_report_screen.d.ts.map +1 -1
  54. package/build/screens/message_report_screen.js +44 -17
  55. package/build/screens/message_report_screen.js.map +1 -1
  56. package/build/screens/reactions_screen.d.ts.map +1 -1
  57. package/build/screens/reactions_screen.js +88 -70
  58. package/build/screens/reactions_screen.js.map +1 -1
  59. package/build/types/api_primitives.d.ts +0 -2
  60. package/build/types/api_primitives.d.ts.map +1 -1
  61. package/build/types/api_primitives.js.map +1 -1
  62. package/build/utils/cache/messages_cache.d.ts +0 -1
  63. package/build/utils/cache/messages_cache.d.ts.map +1 -1
  64. package/build/utils/cache/messages_cache.js +0 -3
  65. package/build/utils/cache/messages_cache.js.map +1 -1
  66. package/build/utils/cache/optimistically_update_message.d.ts +1 -2
  67. package/build/utils/cache/optimistically_update_message.d.ts.map +1 -1
  68. package/build/utils/cache/optimistically_update_message.js +4 -4
  69. package/build/utils/cache/optimistically_update_message.js.map +1 -1
  70. package/build/utils/client/types.d.ts +0 -1
  71. package/build/utils/client/types.d.ts.map +1 -1
  72. package/build/utils/client/types.js.map +1 -1
  73. package/build/utils/generate_placeholder_ulid.d.ts +6 -0
  74. package/build/utils/generate_placeholder_ulid.d.ts.map +1 -1
  75. package/build/utils/generate_placeholder_ulid.js +2 -2
  76. package/build/utils/generate_placeholder_ulid.js.map +1 -1
  77. package/build/utils/request/get_messages.d.ts +6 -18
  78. package/build/utils/request/get_messages.d.ts.map +1 -1
  79. package/build/utils/request/get_messages.js +12 -10
  80. package/build/utils/request/get_messages.js.map +1 -1
  81. package/package.json +3 -3
  82. package/src/__tests__/hooks/paginator_meta.test.ts +2 -73
  83. package/src/components/conversation/__tests__/message_list.test.tsx +0 -42
  84. package/src/components/conversation/message_form.tsx +0 -3
  85. package/src/components/conversation/message_list.tsx +4 -58
  86. package/src/contexts/api_provider.tsx +1 -2
  87. package/src/contexts/conversation_context.tsx +1 -6
  88. package/src/hooks/paginator_meta.ts +5 -29
  89. package/src/hooks/use_api.ts +5 -4
  90. package/src/hooks/use_conversation_messages.ts +11 -52
  91. package/src/hooks/use_conversation_messages_jolt_events.ts +43 -62
  92. package/src/hooks/use_mark_latest_message_read.ts +3 -4
  93. package/src/hooks/use_message_create_or_update.ts +8 -8
  94. package/src/hooks/use_message_reaction_toggle.ts +1 -3
  95. package/src/hooks/use_suspense_api.ts +5 -4
  96. package/src/screens/conversation_new/components/services_form.tsx +5 -5
  97. package/src/screens/conversation_screen.tsx +14 -123
  98. package/src/screens/message_actions_screen.tsx +5 -3
  99. package/src/screens/message_report_screen.tsx +3 -2
  100. package/src/screens/reactions_screen.tsx +4 -2
  101. package/src/types/api_primitives.ts +0 -2
  102. package/src/utils/cache/__tests__/messages_cache.test.ts +1 -14
  103. package/src/utils/cache/messages_cache.ts +0 -3
  104. package/src/utils/cache/optimistically_update_message.ts +5 -2
  105. package/src/utils/client/types.ts +0 -1
  106. package/src/utils/generate_placeholder_ulid.ts +2 -2
  107. package/src/utils/request/get_messages.ts +14 -12
  108. package/build/components/conversation/anchor_not_found_boundary.d.ts +0 -18
  109. package/build/components/conversation/anchor_not_found_boundary.d.ts.map +0 -1
  110. package/build/components/conversation/anchor_not_found_boundary.js +0 -20
  111. package/build/components/conversation/anchor_not_found_boundary.js.map +0 -1
  112. package/build/hooks/use_conversation_message_lookup.d.ts +0 -7
  113. package/build/hooks/use_conversation_message_lookup.d.ts.map +0 -1
  114. package/build/hooks/use_conversation_message_lookup.js +0 -52
  115. package/build/hooks/use_conversation_message_lookup.js.map +0 -1
  116. package/build/hooks/use_fetch_newer_messages.d.ts +0 -9
  117. package/build/hooks/use_fetch_newer_messages.d.ts.map +0 -1
  118. package/build/hooks/use_fetch_newer_messages.js +0 -69
  119. package/build/hooks/use_fetch_newer_messages.js.map +0 -1
  120. package/build/hooks/use_fetch_newer_on_foreground.d.ts +0 -8
  121. package/build/hooks/use_fetch_newer_on_foreground.d.ts.map +0 -1
  122. package/build/hooks/use_fetch_newer_on_foreground.js +0 -42
  123. package/build/hooks/use_fetch_newer_on_foreground.js.map +0 -1
  124. package/build/hooks/use_held_back_messages.d.ts +0 -11
  125. package/build/hooks/use_held_back_messages.d.ts.map +0 -1
  126. package/build/hooks/use_held_back_messages.js +0 -24
  127. package/build/hooks/use_held_back_messages.js.map +0 -1
  128. package/build/utils/cache/find_message_in_caches.d.ts +0 -4
  129. package/build/utils/cache/find_message_in_caches.d.ts.map +0 -1
  130. package/build/utils/cache/find_message_in_caches.js +0 -16
  131. package/build/utils/cache/find_message_in_caches.js.map +0 -1
  132. package/build/utils/cache/invalidate_refetchable_queries.d.ts +0 -3
  133. package/build/utils/cache/invalidate_refetchable_queries.d.ts.map +0 -1
  134. package/build/utils/cache/invalidate_refetchable_queries.js +0 -8
  135. package/build/utils/cache/invalidate_refetchable_queries.js.map +0 -1
  136. package/build/utils/cache/prepend_newer_page.d.ts +0 -7
  137. package/build/utils/cache/prepend_newer_page.d.ts.map +0 -1
  138. package/build/utils/cache/prepend_newer_page.js +0 -15
  139. package/build/utils/cache/prepend_newer_page.js.map +0 -1
  140. package/build/utils/gated_message_cache.d.ts +0 -7
  141. package/build/utils/gated_message_cache.d.ts.map +0 -1
  142. package/build/utils/gated_message_cache.js +0 -13
  143. package/build/utils/gated_message_cache.js.map +0 -1
  144. package/build/utils/ulid.d.ts +0 -3
  145. package/build/utils/ulid.d.ts.map +0 -1
  146. package/build/utils/ulid.js +0 -7
  147. package/build/utils/ulid.js.map +0 -1
  148. package/src/__tests__/hooks/use_conversation_messages.test.ts +0 -22
  149. package/src/__tests__/hooks/use_conversation_messages_jolt_events.test.ts +0 -33
  150. package/src/__tests__/screens/conversation_new/components/services_form.test.tsx +0 -91
  151. package/src/__tests__/utils/gated_message_cache.test.ts +0 -26
  152. package/src/__tests__/utils/request/get_messages.test.ts +0 -24
  153. package/src/__tests__/utils/ulid.test.ts +0 -15
  154. package/src/components/conversation/__tests__/anchor_not_found_boundary.test.tsx +0 -51
  155. package/src/components/conversation/anchor_not_found_boundary.tsx +0 -21
  156. package/src/hooks/__tests__/use_fetch_newer_messages.test.tsx +0 -95
  157. package/src/hooks/__tests__/use_held_back_messages.test.tsx +0 -60
  158. package/src/hooks/__tests__/use_message_reaction_toggle.test.tsx +0 -73
  159. package/src/hooks/use_conversation_message_lookup.ts +0 -29
  160. package/src/hooks/use_fetch_newer_messages.ts +0 -52
  161. package/src/hooks/use_fetch_newer_on_foreground.ts +0 -25
  162. package/src/hooks/use_held_back_messages.ts +0 -33
  163. package/src/utils/cache/__tests__/find_message_in_caches.test.ts +0 -37
  164. package/src/utils/cache/__tests__/invalidate_refetchable_queries.test.ts +0 -23
  165. package/src/utils/cache/__tests__/prepend_newer_page.test.ts +0 -34
  166. package/src/utils/cache/find_message_in_caches.ts +0 -20
  167. package/src/utils/cache/invalidate_refetchable_queries.ts +0 -9
  168. package/src/utils/cache/prepend_newer_page.ts +0 -19
  169. package/src/utils/gated_message_cache.ts +0 -19
  170. package/src/utils/ulid.ts +0 -11
@@ -2,52 +2,21 @@ import { useMemo } from 'react'
2
2
  import { MessageResource } from '../types'
3
3
  import { replaceEqualById } from '../utils/replace_equal_by_id'
4
4
  import { getMessagesQueryKey, getMessagesRequestArgs } from '../utils/request/get_messages'
5
- import { ResponseError } from '../utils/response_error'
6
- import { getNewerPageParam } from './paginator_meta'
7
5
  import { SuspensePaginatorOptions, useSuspensePaginator } from './use_suspense_api'
8
6
 
9
- type Options = {
10
- conversation_id: number
11
- reply_root_id?: string | null
12
- anchor?: string | null
13
- }
14
-
15
- export const anchoredRetry = (failureCount: number, error: unknown) => {
16
- const status = error instanceof ResponseError ? error.status : undefined
17
- if (status === 404 || status === 429) return false
18
-
19
- return failureCount < 3
20
- }
21
-
22
7
  export const useConversationMessages = (
23
- { conversation_id, reply_root_id, anchor }: Options,
8
+ { conversation_id, reply_root_id }: { conversation_id: number; reply_root_id?: string | null },
24
9
  opts?: SuspensePaginatorOptions
25
10
  ) => {
26
- const {
27
- data,
28
- refetch,
29
- isRefetching,
30
- fetchNextPage,
31
- hasNextPage,
32
- fetchPreviousPage,
33
- hasPreviousPage,
34
- isFetchingPreviousPage,
35
- } = useSuspensePaginator<MessageResource>(
36
- getMessagesRequestArgs({ conversation_id, reply_root_id, anchor }),
37
- {
38
- structuralSharing: replaceEqualById,
39
- ...(anchor
40
- ? {
41
- getPreviousPageParam: (page, _pages, pageParam) => getNewerPageParam(page, pageParam),
42
- gcTime: 0,
43
- staleTime: Infinity,
44
- retry: anchoredRetry,
45
- }
46
- : {}),
47
- ...opts,
48
- }
49
- )
50
- const queryKey = getMessagesQueryKey({ conversation_id, reply_root_id, anchor })
11
+ const { data, refetch, isRefetching, fetchNextPage, hasNextPage } =
12
+ useSuspensePaginator<MessageResource>(
13
+ getMessagesRequestArgs({ conversation_id, reply_root_id }),
14
+ {
15
+ structuralSharing: replaceEqualById,
16
+ ...opts,
17
+ }
18
+ )
19
+ const queryKey = getMessagesQueryKey({ conversation_id, reply_root_id })
51
20
  const messages = useMemo(
52
21
  () =>
53
22
  data
@@ -60,15 +29,5 @@ export const useConversationMessages = (
60
29
  [data]
61
30
  )
62
31
 
63
- return {
64
- messages,
65
- refetch,
66
- isRefetching,
67
- fetchNextPage,
68
- hasNextPage,
69
- fetchPreviousPage,
70
- hasPreviousPage,
71
- isFetchingPreviousPage,
72
- queryKey,
73
- }
32
+ return { messages, refetch, isRefetching, fetchNextPage, hasNextPage, queryKey }
74
33
  }
@@ -12,69 +12,28 @@ import {
12
12
  updateCacheWithIndividualMessage,
13
13
  updateCacheWithReaction,
14
14
  updateCacheReplyRoots,
15
+ getThreadedMessagesQueryKey,
15
16
  } from '../utils/cache/messages_cache'
16
17
  import { transformMessageEventDataToMessageResource } from '../utils/jolt/transform_message_event_data_to_message_resource'
17
18
  import { completeMessageCreationTracking } from '../utils/performance_tracking'
18
19
  import { getMessageQueryKey } from '../utils/request/get_message'
19
- import { getMessagesQueryKey } from '../utils/request/get_messages'
20
+ import { getMessagesRequestArgs } from '../utils/request/get_messages'
20
21
  import { useApiClient } from './use_api_client'
21
22
  import { useCurrentPerson } from './use_current_person'
22
23
  import { useJoltChannel, useJoltEvent } from './use_jolt'
24
+ import { getRequestQueryKey } from './use_suspense_api'
23
25
  import { TYPING_TIMEOUT_INTERVAL, useTypingStatusCache } from './use_typing_status_cache'
24
26
 
25
- export type MessagesJoltEvent =
26
- | 'message.created'
27
- | 'message.updated'
28
- | 'message.destroyed'
29
- | 'reaction'
30
-
31
- export interface MessagesCache {
32
- queryKey: unknown[]
33
- canUpdate?: (event: MessagesJoltEvent) => boolean
34
- }
35
-
36
- export interface MessagesView {
37
- replyRootId: string | null
38
- cache: MessagesCache
39
- }
40
-
41
- type MessageEventData = { sort_key: string; reply_root_id?: string | null }
42
-
43
27
  interface Props {
44
28
  conversationId: number
45
- replyRootId?: string | null
46
- messageCache?: MessagesCache
47
29
  }
48
30
 
49
- const belongsToView = (replyRootId: string | null, data: MessageEventData) =>
50
- replyRootId === null || data.reply_root_id === replyRootId || data.sort_key === replyRootId
51
-
52
- export const queryKeysForEvent = (
53
- event: MessagesJoltEvent,
54
- view: MessagesView,
55
- data: MessageEventData
56
- ) => {
57
- const accepted = !view.cache.canUpdate || view.cache.canUpdate(event)
58
- return belongsToView(view.replyRootId, data) && accepted ? [view.cache.queryKey] : []
59
- }
60
-
61
- export function useConversationMessagesJoltEvents({
62
- conversationId,
63
- replyRootId = null,
64
- messageCache,
65
- }: Props) {
31
+ export function useConversationMessagesJoltEvents({ conversationId }: Props) {
66
32
  const queryClient = useQueryClient()
67
33
  const currentPerson = useCurrentPerson()
68
34
  const joltChannel = useJoltChannel(`chat.conversations.${conversationId}`)
69
- const view: MessagesView = {
70
- replyRootId,
71
- cache: messageCache ?? {
72
- queryKey: getMessagesQueryKey({
73
- conversation_id: conversationId,
74
- reply_root_id: replyRootId,
75
- }),
76
- },
77
- }
35
+ const messagesRequestArgs = getMessagesRequestArgs({ conversation_id: conversationId })
36
+ const messagesQueryKey = getRequestQueryKey(messagesRequestArgs)
78
37
  const apiClient = useApiClient()
79
38
 
80
39
  const { addTypingEvent, removeTypingEventById, removeAllTypingEventsByAuthorId } =
@@ -95,12 +54,21 @@ export function useConversationMessagesJoltEvents({
95
54
  }
96
55
  }
97
56
 
98
- const targetQueryKeys = queryKeysForEvent(e.event, view, data)
99
- targetQueryKeys.forEach(queryKey =>
100
- updateCacheWithMessage(queryClient, queryKey, message, e.event)
101
- )
102
- if (isReplyRootUpdate) {
103
- targetQueryKeys.forEach(queryKey => updateCacheReplyRoots(queryClient, queryKey, message))
57
+ // Update the main conversation cache
58
+ updateCacheWithMessage(queryClient, messagesQueryKey, message, e.event)
59
+
60
+ // If message has a reply_root_id, also update the threaded cache
61
+ if (data.reply_root_id) {
62
+ const threadedMessagesQueryKey = getThreadedMessagesQueryKey(
63
+ conversationId,
64
+ data.reply_root_id
65
+ )
66
+ updateCacheWithMessage(queryClient, threadedMessagesQueryKey, message, e.event)
67
+
68
+ if (isReplyRootUpdate) {
69
+ updateCacheReplyRoots(queryClient, messagesQueryKey, message)
70
+ updateCacheReplyRoots(queryClient, threadedMessagesQueryKey, message)
71
+ }
104
72
  }
105
73
 
106
74
  // Update individual message query cache so reply shadow messages update immediately
@@ -120,21 +88,34 @@ export function useConversationMessagesJoltEvents({
120
88
  currentPersonId: currentPerson.id,
121
89
  })
122
90
 
123
- queryKeysForEvent('message.destroyed', view, data).forEach(queryKey =>
124
- queryClient.setQueryData<QueryData>(queryKey, prev =>
91
+ // Update the main conversation cache
92
+ queryClient.setQueryData<QueryData>(messagesQueryKey, prev =>
93
+ deleteRecordInPagesData({ data: prev, record: message })
94
+ )
95
+
96
+ // If message has a reply_root_id, also update the threaded cache
97
+ if (data.reply_root_id) {
98
+ const threadedMessagesQueryKey = getThreadedMessagesQueryKey(
99
+ conversationId,
100
+ data.reply_root_id
101
+ )
102
+ queryClient.setQueryData<QueryData>(threadedMessagesQueryKey, prev =>
125
103
  deleteRecordInPagesData({ data: prev, record: message })
126
104
  )
127
- )
105
+ }
128
106
  }
129
107
 
130
108
  const handleReactionJoltEvent = async (e: JoltReactionEvent) => {
131
- const data = {
132
- sort_key: e.data.data.message_sort_key,
133
- reply_root_id: e.data.data.reply_root_id,
109
+ // Update the main conversation cache and capture the reply_root_id if present
110
+ updateCacheWithReaction(queryClient, messagesQueryKey, e, currentPerson.id)
111
+
112
+ const replyRootId = e.data.data.reply_root_id
113
+
114
+ // If the message has a reply_root_id, also update the threaded cache
115
+ if (replyRootId) {
116
+ const threadedMessagesQueryKey = getThreadedMessagesQueryKey(conversationId, replyRootId)
117
+ updateCacheWithReaction(queryClient, threadedMessagesQueryKey, e, currentPerson.id)
134
118
  }
135
- queryKeysForEvent('reaction', view, data).forEach(queryKey =>
136
- updateCacheWithReaction(queryClient, queryKey, e, currentPerson.id)
137
- )
138
119
  }
139
120
 
140
121
  const handleTypingEvent = async (e: JoltTypingEvent) => {
@@ -7,10 +7,9 @@ import { useConversationsMarkRead } from './use_conversations_actions'
7
7
  interface Props {
8
8
  conversation: ConversationResource
9
9
  messages: MessageResource[]
10
- enabled?: boolean
11
10
  }
12
11
 
13
- export function useMarkLatestMessageRead({ conversation, enabled = true }: Props) {
12
+ export function useMarkLatestMessageRead({ conversation }: Props) {
14
13
  const firedOnce = useRef<boolean>(false)
15
14
  const { markRead } = useConversationsMarkRead({ conversation })
16
15
  const debouncedMarkRead = useMemo(
@@ -25,10 +24,10 @@ export function useMarkLatestMessageRead({ conversation, enabled = true }: Props
25
24
  const isActive = appState === 'active'
26
25
 
27
26
  useEffect(() => {
28
- if (!enabled || !isActive || !shouldMarkRead) return
27
+ if (!isActive || !shouldMarkRead) return
29
28
 
30
29
  firedOnce.current = true
31
30
 
32
31
  debouncedMarkRead(true)
33
- }, [debouncedMarkRead, enabled, isActive, shouldMarkRead, unreadReaction])
32
+ }, [debouncedMarkRead, isActive, shouldMarkRead, unreadReaction])
34
33
  }
@@ -1,6 +1,5 @@
1
1
  import { InfiniteData, useMutation } from '@tanstack/react-query'
2
2
  import { chatQueryClient } from '../contexts/api_provider'
3
- import { useConversationContext } from '../contexts/conversation_context'
4
3
  import { ApiCollection, ApiResource, MessageResource } from '../types'
5
4
  import { DenormalizedAttachmentResourceForCreate } from '../types/resources/denormalized_attachment_resource_for_create'
6
5
  import {
@@ -36,12 +35,6 @@ export function useMessageCreateOrUpdate({ conversationId, message, replyRootId
36
35
  const isEditing = !isNewMessage(message)
37
36
  const apiClient = useApiClient()
38
37
  const currentPerson = useCurrentPerson()
39
- const { messagesQueryKey } = useConversationContext()
40
- const liveQueryKey = getMessagesQueryKey({
41
- conversation_id: conversationId,
42
- reply_root_id: replyRootId,
43
- })
44
- const queryKey = isEditing ? (messagesQueryKey ?? liveQueryKey) : liveQueryKey
45
38
 
46
39
  const mutation = useMutation({
47
40
  mutationFn: ({ text, attachments, idempotentKey }: ResolvedMessageMutationVariables) => {
@@ -86,7 +79,6 @@ export function useMessageCreateOrUpdate({ conversationId, message, replyRootId
86
79
  message,
87
80
  text,
88
81
  replyRootId,
89
- queryKey,
90
82
  })
91
83
 
92
84
  return { message: optimisticMessage }
@@ -110,6 +102,10 @@ export function useMessageCreateOrUpdate({ conversationId, message, replyRootId
110
102
 
111
103
  // Add error to the optimistic message from the cache on error
112
104
  if (optimisticMessage) {
105
+ const queryKey = getMessagesQueryKey({
106
+ conversation_id: conversationId,
107
+ reply_root_id: replyRootId,
108
+ })
113
109
  chatQueryClient.setQueryData(
114
110
  queryKey,
115
111
  (data: InfiniteData<ApiCollection<MessageResource>> | undefined) =>
@@ -129,6 +125,10 @@ export function useMessageCreateOrUpdate({ conversationId, message, replyRootId
129
125
  const { message: optimisticMessage } = context || {}
130
126
  const updatedMessage = result.data
131
127
  type QueryData = InfiniteData<ApiCollection<MessageResource>>
128
+ const queryKey = getMessagesQueryKey({
129
+ conversation_id: conversationId,
130
+ reply_root_id: replyRootId,
131
+ })
132
132
 
133
133
  // First remove the optimistic message if it exists
134
134
  if (optimisticMessage) {
@@ -1,7 +1,6 @@
1
1
  import { InfiniteData, useMutation, useQueryClient } from '@tanstack/react-query'
2
2
  import { useCallback } from 'react'
3
3
  import { Alert } from 'react-native'
4
- import { useConversationContext } from '../contexts/conversation_context'
5
4
  import { ApiCollection, ApiResource, MessageResource } from '../types'
6
5
  import { ReactionCountResource } from '../types/resources/reaction'
7
6
  import { updateRecordInPagesData, REACTION_EMOJIS } from '../utils'
@@ -31,8 +30,7 @@ export function useMessageReactionToggle({
31
30
  }: UseMessageReactionToggleProps) {
32
31
  const apiClient = useApiClient()
33
32
  const queryClient = useQueryClient()
34
- const { messagesQueryKey } = useConversationContext()
35
- const queryKey = messagesQueryKey ?? getMessagesQueryKey({ conversation_id })
33
+ const queryKey = getMessagesQueryKey({ conversation_id })
36
34
  const message_id = message.id
37
35
 
38
36
  const toggleReaction = useCallback(
@@ -10,7 +10,7 @@ import { FailedResponse } from '../types/api_primitives'
10
10
  import { Log } from '../utils'
11
11
  import { GetRequest, RequestData } from '../utils/client'
12
12
  import { throwResponseError } from '../utils/response_error'
13
- import { getNextPageParamFromMeta, mergePageWhere } from './paginator_meta'
13
+ import { getNextPageParamFromMeta } from './paginator_meta'
14
14
  import { App, useApiClient } from './use_api_client'
15
15
 
16
16
  interface SuspenseGetOptions extends GetRequest {
@@ -65,11 +65,12 @@ export const useSuspensePaginator = <T extends ResourceObject>(
65
65
  queryFn: ({ pageParam }) => {
66
66
  Log.info('useSuspenseGet', { pageParam })
67
67
 
68
- const where = mergePageWhere(args.data.where, pageParam?.where)
68
+ const pageParmWhere = pageParam?.where || {}
69
+ const argsWhere = args.data.where || {}
70
+ const where = { ...argsWhere, ...pageParmWhere }
69
71
  const app = args.app || 'chat'
70
72
  const offset = pageParam?.offset || args.data.offset
71
- const order = pageParam?.order || args.data.order
72
- const data = { ...args.data, where, offset, order }
73
+ const data = { ...args.data, where, offset }
73
74
 
74
75
  return apiClient[app]
75
76
  .get<ApiCollection<T>>({
@@ -45,7 +45,6 @@ export const ServicesForm = ({
45
45
  const customAvatarsEnabled = featureEnabled(availableFeatures.custom_conversation_avatars)
46
46
  const safetyLockEnabled = featureEnabled(availableFeatures.conversation_safety_lock)
47
47
  const [selectedPlanId, setSelectedPlanId] = useState<number | undefined>(initialPlanId)
48
- const [filterByPlan, setFilterByPlan] = useState<boolean>(!!initialPlanId)
49
48
  const initialState = useMemo(() => uniq(initialTeamIds) || [], [initialTeamIds]) // Uniq here because services can send duplicates in the teams_i_lead response.
50
49
  const [selectedTeamIds, setSelectedTeamIds] = useState<number[]>(initialState)
51
50
 
@@ -53,7 +52,6 @@ export const ServicesForm = ({
53
52
  useEffect(() => {
54
53
  setSelectedTeamIds(initialState)
55
54
  setSelectedPlanId(initialPlanId)
56
- setFilterByPlan(!!initialPlanId)
57
55
  }, [initialState, initialPlanId])
58
56
 
59
57
  const removeSelection = useCallback(
@@ -63,7 +61,9 @@ export const ServicesForm = ({
63
61
  [selectedTeamIds]
64
62
  )
65
63
 
66
- const activePlanId = filterByPlan ? selectedPlanId : undefined
64
+ const [filerByPlan, setFilterByPlan] = useState(false)
65
+
66
+ const activePlanId = filerByPlan ? selectedPlanId : undefined
67
67
 
68
68
  const { payload, isLoading: isLoadingPayload } = useServicesChatConversationPayload({
69
69
  teamIds: selectedTeamIds,
@@ -79,7 +79,7 @@ export const ServicesForm = ({
79
79
 
80
80
  const { members, isError: isMemberError } = useTeamMembersForNewConversation({
81
81
  teamIds: selectedTeamIds,
82
- planId: activePlanId,
82
+ planId: selectedPlanId,
83
83
  })
84
84
  const adultMembers = useMemo(() => members.filter(member => !member.child), [members])
85
85
 
@@ -118,7 +118,7 @@ export const ServicesForm = ({
118
118
  removeSelection={removeSelection}
119
119
  selectedPlanId={selectedPlanId}
120
120
  setSelectedPlanId={setSelectedPlanId}
121
- filterByPlan={filterByPlan}
121
+ filterByPlan={filerByPlan}
122
122
  setFilterByPlan={setFilterByPlan}
123
123
  members={members}
124
124
  isMemberError={isMemberError}
@@ -7,11 +7,10 @@ import {
7
7
  useTheme as useNavigationTheme,
8
8
  useRoute,
9
9
  } from '@react-navigation/native'
10
- import React, { startTransition, useCallback, useEffect, useRef, useState } from 'react'
10
+ import React, { useCallback, useEffect, useRef, useState } from 'react'
11
11
  import { FlatList, Platform, StyleSheet, useWindowDimensions, View } from 'react-native'
12
12
  import { useSafeAreaInsets } from 'react-native-safe-area-context'
13
13
  import { Badge, Icon } from '../components'
14
- import { AnchorNotFoundBoundary } from '../components/conversation/anchor_not_found_boundary'
15
14
  import { ConversationStartBanner } from '../components/conversation/conversation_start_banner'
16
15
  import { JumpToBottomButton } from '../components/conversation/jump_to_bottom_button'
17
16
  import { MessageForm } from '../components/conversation/message_form'
@@ -31,9 +30,6 @@ import { useConversation } from '../hooks/use_conversation'
31
30
  import { useConversationJoltEvents } from '../hooks/use_conversation_jolt_events'
32
31
  import { useConversationMessages } from '../hooks/use_conversation_messages'
33
32
  import { useConversationMessagesJoltEvents } from '../hooks/use_conversation_messages_jolt_events'
34
- import { useFetchNewerMessages } from '../hooks/use_fetch_newer_messages'
35
- import { useFetchNewerOnForeground } from '../hooks/use_fetch_newer_on_foreground'
36
- import { useHeldBackMessages } from '../hooks/use_held_back_messages'
37
33
  import { useMarkLatestMessageRead } from '../hooks/use_mark_latest_message_read'
38
34
  import {
39
35
  analyticsEvents,
@@ -45,10 +41,6 @@ import { useVideoModerationJoltEvents } from '../hooks/use_video_moderation_jolt
45
41
  import { ConversationResource } from '../types/resources/conversation'
46
42
  import { ConversationBadgeResource } from '../types/resources/conversation_badge'
47
43
  import { MessageResource } from '../types/resources/message'
48
- import { newestPersistedMessageId } from '../utils/cache/messages_cache'
49
- import { gatedMessageCache } from '../utils/gated_message_cache'
50
- import { getMessagesQueryKey } from '../utils/request/get_messages'
51
- import { decodeUlidTime } from '../utils/ulid'
52
44
 
53
45
  export type ConversationRouteProps = {
54
46
  conversation_id: number
@@ -57,7 +49,6 @@ export type ConversationRouteProps = {
57
49
  chat_group_graph_id?: string
58
50
  clear_input?: boolean
59
51
  editing_message_id?: number | null
60
- initial_message_id?: string | null
61
52
  title?: string
62
53
  subtitle?: string
63
54
  badge?: ConversationBadgeResource
@@ -68,16 +59,9 @@ export type ConversationRouteProps = {
68
59
  export type ConversationScreenProps = StaticScreenProps<ConversationRouteProps>
69
60
 
70
61
  export function ConversationScreen({ route }: ConversationScreenProps) {
71
- const { conversation_id, reply_root_id, initial_message_id } = route.params
62
+ const { conversation_id, reply_root_id } = route.params
72
63
 
73
64
  const { data: conversation } = useConversation({ conversation_id })
74
- const [anchor, setAnchor] = useState(() =>
75
- isBeforeLatestMessage(initial_message_id, conversation?.lastMessageCreatedAt)
76
- ? initial_message_id
77
- : null
78
- )
79
- const exitAnchoredMode = useCallback(() => startTransition(() => setAnchor(null)), [])
80
- const dropAnchor = useCallback(() => setAnchor(null), [])
81
65
 
82
66
  usePublishProductAnalyticsEvent(analyticsEvents.conversation_show_opened, {
83
67
  reply_root_id,
@@ -88,83 +72,27 @@ export function ConversationScreen({ route }: ConversationScreenProps) {
88
72
  <ConversationContextProvider
89
73
  conversationId={conversation_id}
90
74
  currentPageReplyRootId={reply_root_id ?? null}
91
- messagesQueryKey={getMessagesQueryKey({ conversation_id, reply_root_id, anchor })}
92
75
  >
93
- {anchor ? (
94
- <AnchorNotFoundBoundary onAnchorNotFound={dropAnchor}>
95
- <ConversationScreenContent
96
- route={route}
97
- anchor={anchor}
98
- onExitAnchoredMode={exitAnchoredMode}
99
- />
100
- </AnchorNotFoundBoundary>
101
- ) : (
102
- <ConversationScreenContent
103
- route={route}
104
- anchor={null}
105
- onExitAnchoredMode={exitAnchoredMode}
106
- />
107
- )}
76
+ <ConversationScreenContent route={route} />
108
77
  </ConversationContextProvider>
109
78
  )
110
79
  }
111
80
 
112
- interface ConversationScreenContentProps extends ConversationScreenProps {
113
- anchor: string | null | undefined
114
- onExitAnchoredMode: () => void
115
- }
116
-
117
- function ConversationScreenContent({
118
- route,
119
- anchor,
120
- onExitAnchoredMode,
121
- }: ConversationScreenContentProps) {
81
+ function ConversationScreenContent({ route }: ConversationScreenProps) {
122
82
  const styles = useStyles()
123
83
  const navigation = useNavigation()
124
84
  const { conversation_id, editing_message_id, reply_root_id, reply_root_author_name } =
125
85
  route.params
126
86
  const { data: conversation } = useConversation(route.params)
127
- const anchored = !!anchor
128
- const {
129
- messages,
130
- fetchNextPage,
131
- hasNextPage,
132
- fetchPreviousPage,
133
- hasPreviousPage,
134
- isFetchingPreviousPage,
135
- queryKey,
136
- } = useConversationMessages({ conversation_id, reply_root_id, anchor })
137
- const behindLatest = anchored && hasPreviousPage
138
- const caughtUp = !hasPreviousPage
139
- const [showJumpToBottomButton, setShowJumpToBottomButton] = useState(false)
140
- const readingHistory = showJumpToBottomButton
141
- const newestMessageId = newestPersistedMessageId(messages)
142
- const fetchNewer = useFetchNewerMessages({
87
+ const { messages, fetchNextPage, hasNextPage } = useConversationMessages({
143
88
  conversation_id,
144
89
  reply_root_id,
145
- queryKey,
146
- onTooFarBehind: anchored ? onExitAnchoredMode : undefined,
147
- })
148
- const { onSkippedNewMessage } = useHeldBackMessages({
149
- fetchNewer,
150
- newestMessageId,
151
- readingHistory,
152
- caughtUp,
153
90
  })
154
91
  useConversationJoltEvents({ conversationId: conversation_id })
155
- useConversationMessagesJoltEvents({
156
- conversationId: conversation_id,
157
- replyRootId: reply_root_id ?? null,
158
- messageCache: gatedMessageCache({
159
- queryKey,
160
- acceptNewMessages: caughtUp && !readingHistory,
161
- onSkippedNewMessage,
162
- }),
163
- })
92
+ useConversationMessagesJoltEvents({ conversationId: conversation_id })
164
93
  const { markHandledByMessageForm } = useVideoModerationJoltEvents()
165
94
  useEnsureConversationsRouteExists()
166
- useMarkLatestMessageRead({ conversation, messages, enabled: !behindLatest })
167
- useFetchNewerOnForeground({ fetchNewer, newestMessageId, enabled: anchored && caughtUp })
95
+ useMarkLatestMessageRead({ conversation, messages })
168
96
  const noMessages = messages.length === 0
169
97
 
170
98
  const { repliesDisabled, memberAbility, badges, title } = conversation
@@ -179,8 +107,12 @@ function ConversationScreenContent({
179
107
  const muted = conversation.conversationMembership?.muted ?? conversation.muted
180
108
 
181
109
  const listRef = useRef<FlatList>(null)
182
- const [hasDragged, setHasDragged] = useState(false)
183
- const holdListPosition = behindLatest || readingHistory
110
+ const [showJumpToBottomButton, setShowJumpToBottomButton] = useState(false)
111
+
112
+ const trackScroll = (event: any) => {
113
+ const offsetY = event.nativeEvent.contentOffset.y
114
+ setShowJumpToBottomButton(offsetY > 200)
115
+ }
184
116
 
185
117
  const { restOffset, onLayout, onContentSizeChange, onMomentumScrollEnd, onScrollEndDrag } =
186
118
  useRestAwayFromDismissEdge({
@@ -194,28 +126,6 @@ function ConversationScreenContent({
194
126
  })
195
127
  }, [restOffset])
196
128
 
197
- const exitAnchoredMode = useCallback(() => {
198
- onExitAnchoredMode()
199
- handleReturnToBottom()
200
- }, [handleReturnToBottom, onExitAnchoredMode])
201
- const jumpToLatest = useCallback(() => {
202
- setShowJumpToBottomButton(false)
203
- if (anchored) exitAnchoredMode()
204
- else handleReturnToBottom()
205
- }, [anchored, exitAnchoredMode, handleReturnToBottom])
206
-
207
- const trackScroll = (event: any) => {
208
- const offsetY = event.nativeEvent.contentOffset.y
209
- setShowJumpToBottomButton(offsetY > 200)
210
- }
211
-
212
- const loadNewerMessages = () => {
213
- if (!hasPreviousPage || isFetchingPreviousPage) return
214
-
215
- setHasDragged(false)
216
- fetchPreviousPage()
217
- }
218
-
219
129
  useEffect(() => {
220
130
  if (reply_root_id) {
221
131
  navigation.setParams({
@@ -264,7 +174,6 @@ function ConversationScreenContent({
264
174
  conversationId={conversation_id}
265
175
  messages={messages}
266
176
  replyRootId={reply_root_id}
267
- anchorMessageId={anchor}
268
177
  hasNextPage={hasNextPage}
269
178
  onEndReached={() => fetchNextPage()}
270
179
  onScroll={trackScroll}
@@ -272,18 +181,9 @@ function ConversationScreenContent({
272
181
  onContentSizeChange={onContentSizeChange}
273
182
  onScrollEndDrag={onScrollEndDrag}
274
183
  onMomentumScrollEnd={onMomentumScrollEnd}
275
- onScrollBeginDrag={anchored ? () => setHasDragged(true) : undefined}
276
- onStartReached={anchored && hasDragged ? loadNewerMessages : undefined}
277
- onStartReachedThreshold={anchored ? 0.5 : undefined}
278
- maintainVisibleContentPosition={
279
- holdListPosition ? { minIndexForVisible: 0 } : undefined
280
- }
281
184
  />
282
185
  )}
283
- <JumpToBottomButton
284
- onPress={jumpToLatest}
285
- visible={behindLatest || showJumpToBottomButton}
286
- />
186
+ <JumpToBottomButton onPress={handleReturnToBottom} visible={showJumpToBottomButton} />
287
187
  {!noMessages && <TypingIndicator />}
288
188
  {showLeaderDisabledReplyBanner && <LeaderMessagesDisabledBanner />}
289
189
  <ConversationBottomBar
@@ -293,7 +193,6 @@ function ConversationScreenContent({
293
193
  replyRootId={reply_root_id}
294
194
  currentlyEditingMessage={currentlyEditingMessage}
295
195
  onFlaggedInMessageForm={markHandledByMessageForm}
296
- onBeforeSubmit={jumpToLatest}
297
196
  />
298
197
  </KeyboardView>
299
198
  </View>
@@ -307,7 +206,6 @@ interface ConversationBottomBarProps {
307
206
  replyRootId: string | null | undefined
308
207
  currentlyEditingMessage: MessageResource | undefined
309
208
  onFlaggedInMessageForm?: (id: string) => void
310
- onBeforeSubmit?: () => void
311
209
  }
312
210
 
313
211
  function ConversationBottomBar({
@@ -317,7 +215,6 @@ function ConversationBottomBar({
317
215
  replyRootId,
318
216
  currentlyEditingMessage,
319
217
  onFlaggedInMessageForm,
320
- onBeforeSubmit,
321
218
  }: ConversationBottomBarProps) {
322
219
  const messageLink = useMessageAvailableMembersAction(conversation)
323
220
 
@@ -336,7 +233,6 @@ function ConversationBottomBar({
336
233
  replyRootId={replyRootId}
337
234
  currentlyEditingMessage={currentlyEditingMessage}
338
235
  onFlaggedInMessageForm={onFlaggedInMessageForm}
339
- onBeforeSubmit={onBeforeSubmit}
340
236
  key={
341
237
  currentlyEditingMessage
342
238
  ? `edit-message-form-${currentlyEditingMessage.id}`
@@ -351,11 +247,6 @@ function ConversationBottomBar({
351
247
  )
352
248
  }
353
249
 
354
- const isBeforeLatestMessage = (anchor?: string | null, latestMessageCreatedAt?: string) =>
355
- !!anchor &&
356
- !!latestMessageCreatedAt &&
357
- decodeUlidTime(anchor) < Date.parse(latestMessageCreatedAt)
358
-
359
250
  interface ConversationScreenTitleProps extends HeaderTitleProps {
360
251
  conversation_id: number
361
252
  badge?: ConversationBadgeResource
@@ -9,7 +9,6 @@ import { useReactionStyles } from '../components/conversation/message_reaction'
9
9
  import FormSheet, { getFormSheetScreenOptions } from '../components/primitive/form_sheet'
10
10
  import { useCreateAndroidRippleColor, useFontScale, useTheme } from '../hooks'
11
11
  import { useApiClient } from '../hooks/use_api_client'
12
- import { useConversationMessageLookup } from '../hooks/use_conversation_message_lookup'
13
12
  import { useConversationMessages } from '../hooks/use_conversation_messages'
14
13
  import { availableFeatures, useFeatures } from '../hooks/use_features'
15
14
  import { useMessageReactionToggle } from '../hooks/use_message_reaction_toggle'
@@ -42,8 +41,11 @@ export function MessageActionsScreen({ route }: MessageActionsScreenProps) {
42
41
  isSystemMessage,
43
42
  } = route.params
44
43
 
45
- const { refetch } = useConversationMessages({ conversation_id }, { refetchOnMount: false })
46
- const message = useConversationMessageLookup({ conversation_id, message_id })
44
+ const { messages, refetch } = useConversationMessages(
45
+ { conversation_id },
46
+ { refetchOnMount: false }
47
+ )
48
+ const message = messages.find(m => m.id === message_id)
47
49
 
48
50
  if (!message) return null
49
51