@planningcenter/chat-react-native 4.2.0 → 4.2.1-qa-staging.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.
Files changed (123) hide show
  1. package/build/components/conversation/anchor_not_found_boundary.d.ts +18 -0
  2. package/build/components/conversation/anchor_not_found_boundary.d.ts.map +1 -0
  3. package/build/components/conversation/anchor_not_found_boundary.js +20 -0
  4. package/build/components/conversation/anchor_not_found_boundary.js.map +1 -0
  5. package/build/components/conversation/message_form.d.ts +2 -1
  6. package/build/components/conversation/message_form.d.ts.map +1 -1
  7. package/build/components/conversation/message_form.js +54 -51
  8. package/build/components/conversation/message_form.js.map +1 -1
  9. package/build/components/conversation/message_list.d.ts +3 -2
  10. package/build/components/conversation/message_list.d.ts.map +1 -1
  11. package/build/components/conversation/message_list.js +152 -66
  12. package/build/components/conversation/message_list.js.map +1 -1
  13. package/build/hooks/index.d.ts +1 -0
  14. package/build/hooks/index.d.ts.map +1 -1
  15. package/build/hooks/index.js +1 -0
  16. package/build/hooks/index.js.map +1 -1
  17. package/build/hooks/paginator_meta.d.ts +3 -1
  18. package/build/hooks/paginator_meta.d.ts.map +1 -1
  19. package/build/hooks/paginator_meta.js +25 -8
  20. package/build/hooks/paginator_meta.js.map +1 -1
  21. package/build/hooks/use_api.d.ts.map +1 -1
  22. package/build/hooks/use_api.js +5 -8
  23. package/build/hooks/use_api.js.map +1 -1
  24. package/build/hooks/use_conversation_messages.d.ts +8 -2
  25. package/build/hooks/use_conversation_messages.d.ts.map +1 -1
  26. package/build/hooks/use_conversation_messages.js +77 -45
  27. package/build/hooks/use_conversation_messages.js.map +1 -1
  28. package/build/hooks/use_conversation_messages_jolt_events.d.ts +9 -1
  29. package/build/hooks/use_conversation_messages_jolt_events.d.ts.map +1 -1
  30. package/build/hooks/use_conversation_messages_jolt_events.js +88 -73
  31. package/build/hooks/use_conversation_messages_jolt_events.js.map +1 -1
  32. package/build/hooks/use_direct_message_eligibility.d.ts +14 -0
  33. package/build/hooks/use_direct_message_eligibility.d.ts.map +1 -0
  34. package/build/hooks/use_direct_message_eligibility.js +87 -0
  35. package/build/hooks/use_direct_message_eligibility.js.map +1 -0
  36. package/build/hooks/use_fetch_newer_messages.d.ts +8 -0
  37. package/build/hooks/use_fetch_newer_messages.d.ts.map +1 -0
  38. package/build/hooks/use_fetch_newer_messages.js +56 -0
  39. package/build/hooks/use_fetch_newer_messages.js.map +1 -0
  40. package/build/hooks/use_fetch_newer_on_foreground.d.ts +8 -0
  41. package/build/hooks/use_fetch_newer_on_foreground.d.ts.map +1 -0
  42. package/build/hooks/use_fetch_newer_on_foreground.js +41 -0
  43. package/build/hooks/use_fetch_newer_on_foreground.js.map +1 -0
  44. package/build/hooks/use_mark_latest_message_read.d.ts +2 -1
  45. package/build/hooks/use_mark_latest_message_read.d.ts.map +1 -1
  46. package/build/hooks/use_mark_latest_message_read.js +4 -3
  47. package/build/hooks/use_mark_latest_message_read.js.map +1 -1
  48. package/build/hooks/use_suspense_api.d.ts.map +1 -1
  49. package/build/hooks/use_suspense_api.js +5 -8
  50. package/build/hooks/use_suspense_api.js.map +1 -1
  51. package/build/screens/conversation_new/components/services_form.js +9 -8
  52. package/build/screens/conversation_new/components/services_form.js.map +1 -1
  53. package/build/screens/conversation_screen.d.ts +1 -0
  54. package/build/screens/conversation_screen.d.ts.map +1 -1
  55. package/build/screens/conversation_screen.js +241 -355
  56. package/build/screens/conversation_screen.js.map +1 -1
  57. package/build/types/api_primitives.d.ts +2 -0
  58. package/build/types/api_primitives.d.ts.map +1 -1
  59. package/build/types/api_primitives.js.map +1 -1
  60. package/build/types/resources/member.d.ts +1 -0
  61. package/build/types/resources/member.d.ts.map +1 -1
  62. package/build/types/resources/member.js.map +1 -1
  63. package/build/utils/cache/prepend_newer_page.d.ts +7 -0
  64. package/build/utils/cache/prepend_newer_page.d.ts.map +1 -0
  65. package/build/utils/cache/prepend_newer_page.js +14 -0
  66. package/build/utils/cache/prepend_newer_page.js.map +1 -0
  67. package/build/utils/client/types.d.ts +1 -0
  68. package/build/utils/client/types.d.ts.map +1 -1
  69. package/build/utils/client/types.js.map +1 -1
  70. package/build/utils/gated_message_cache.d.ts +7 -0
  71. package/build/utils/gated_message_cache.d.ts.map +1 -0
  72. package/build/utils/gated_message_cache.js +13 -0
  73. package/build/utils/gated_message_cache.js.map +1 -0
  74. package/build/utils/generate_placeholder_ulid.d.ts +0 -6
  75. package/build/utils/generate_placeholder_ulid.d.ts.map +1 -1
  76. package/build/utils/generate_placeholder_ulid.js +2 -2
  77. package/build/utils/generate_placeholder_ulid.js.map +1 -1
  78. package/build/utils/group_messages.js +4 -1
  79. package/build/utils/group_messages.js.map +1 -1
  80. package/build/utils/request/get_messages.d.ts +18 -6
  81. package/build/utils/request/get_messages.d.ts.map +1 -1
  82. package/build/utils/request/get_messages.js +10 -12
  83. package/build/utils/request/get_messages.js.map +1 -1
  84. package/build/utils/ulid.d.ts +3 -0
  85. package/build/utils/ulid.d.ts.map +1 -0
  86. package/build/utils/ulid.js +7 -0
  87. package/build/utils/ulid.js.map +1 -0
  88. package/package.json +3 -3
  89. package/src/__tests__/hooks/paginator_meta.test.ts +73 -2
  90. package/src/__tests__/hooks/use_conversation_messages_jolt_events.test.ts +36 -0
  91. package/src/__tests__/hooks/use_direct_message_eligibility.test.tsx +83 -0
  92. package/src/__tests__/screens/conversation_new/components/services_form.test.tsx +91 -0
  93. package/src/__tests__/utils/gated_message_cache.test.ts +26 -0
  94. package/src/__tests__/utils/request/get_messages.test.ts +24 -0
  95. package/src/__tests__/utils/ulid.test.ts +15 -0
  96. package/src/components/conversation/__tests__/anchor_not_found_boundary.test.tsx +51 -0
  97. package/src/components/conversation/__tests__/message_list.test.tsx +42 -0
  98. package/src/components/conversation/anchor_not_found_boundary.tsx +21 -0
  99. package/src/components/conversation/message_form.tsx +4 -0
  100. package/src/components/conversation/message_list.tsx +58 -4
  101. package/src/hooks/index.ts +1 -0
  102. package/src/hooks/paginator_meta.ts +29 -5
  103. package/src/hooks/use_api.ts +4 -5
  104. package/src/hooks/use_conversation_messages.ts +43 -11
  105. package/src/hooks/use_conversation_messages_jolt_events.ts +57 -40
  106. package/src/hooks/use_direct_message_eligibility.ts +40 -0
  107. package/src/hooks/use_fetch_newer_messages.ts +37 -0
  108. package/src/hooks/use_fetch_newer_on_foreground.ts +25 -0
  109. package/src/hooks/use_mark_latest_message_read.ts +4 -3
  110. package/src/hooks/use_suspense_api.ts +4 -5
  111. package/src/screens/conversation_new/components/services_form.tsx +5 -5
  112. package/src/screens/conversation_screen.tsx +120 -17
  113. package/src/types/api_primitives.ts +2 -0
  114. package/src/types/resources/member.ts +1 -0
  115. package/src/utils/__tests__/group_messages.test.ts +29 -0
  116. package/src/utils/cache/__tests__/prepend_newer_page.test.ts +35 -0
  117. package/src/utils/cache/prepend_newer_page.ts +22 -0
  118. package/src/utils/client/types.ts +1 -0
  119. package/src/utils/gated_message_cache.ts +19 -0
  120. package/src/utils/generate_placeholder_ulid.ts +2 -2
  121. package/src/utils/group_messages.ts +4 -1
  122. package/src/utils/request/get_messages.ts +12 -14
  123. package/src/utils/ulid.ts +11 -0
@@ -1,13 +1,37 @@
1
- import { NextPageCursor } from '../types/api_primitives'
1
+ import { ApiCollection, NextPageCursor, ResourceObject } from '../types/api_primitives'
2
2
  import { RequestData } from '../utils/client'
3
3
 
4
4
  export const getNextPageParamFromMeta = (
5
5
  next: NextPageCursor = {}
6
6
  ): Partial<RequestData> | undefined => {
7
- if (next.idLt) return { where: { id_lt: next.idLt } }
8
- if (next.idGt) return { where: { id_gt: next.idGt } }
9
- if (next.idLte) return { where: { id_lte: next.idLte } }
10
- if (next.idGte) return { where: { id_gte: next.idGte } }
7
+ if (next.idLt) return withOrder({ where: { id_lt: next.idLt } }, next.order)
8
+ if (next.idGt) return withOrder({ where: { id_gt: next.idGt } }, next.order)
9
+ if (next.idLte) return withOrder({ where: { id_lte: next.idLte } }, next.order)
10
+ if (next.idGte) return withOrder({ where: { id_gte: next.idGte } }, next.order)
11
11
  if (next.offset) return { offset: Number(next.offset) }
12
12
  return undefined
13
13
  }
14
+
15
+ const withOrder = (param: Partial<RequestData>, order: NextPageCursor['order']) =>
16
+ order ? { ...param, order } : param
17
+
18
+ const BOUND_KEYS = ['id_lt', 'id_lte', 'id_gt', 'id_gte', 'id_around']
19
+
20
+ export const mergePageWhere = (
21
+ argsWhere: RequestData['where'] = {},
22
+ pageWhere: RequestData['where'] = {}
23
+ ): RequestData['where'] => {
24
+ const pageHasCursor = BOUND_KEYS.some(key => key in pageWhere)
25
+ const base = pageHasCursor
26
+ ? Object.fromEntries(Object.entries(argsWhere).filter(([key]) => !BOUND_KEYS.includes(key)))
27
+ : argsWhere
28
+ return { ...base, ...pageWhere }
29
+ }
30
+
31
+ export const getNewerPageParam = (
32
+ page: ApiCollection<ResourceObject>,
33
+ pageParam: Partial<RequestData> | undefined
34
+ ): Partial<RequestData> | undefined => {
35
+ const isAscendingPage = pageParam?.order === 'asc'
36
+ return getNextPageParamFromMeta(isAscendingPage ? page.meta?.next : page.meta?.prev)
37
+ }
@@ -4,7 +4,7 @@ import { ChatContext } from '../contexts/chat_context'
4
4
  import { ApiCollection, ApiResource, ResourceObject } from '../types'
5
5
  import { GetRequest, RequestData } from '../utils/client'
6
6
  import { ResponseError, throwResponseError } from '../utils/response_error'
7
- import { getNextPageParamFromMeta } from './paginator_meta'
7
+ import { getNextPageParamFromMeta, mergePageWhere } from './paginator_meta'
8
8
  import { App, useApiClient } from './use_api_client'
9
9
  import { getRequestQueryKey, RequestQueryKey } from './use_suspense_api'
10
10
 
@@ -61,12 +61,11 @@ export const useApiPaginator = <T extends ResourceObject>(
61
61
  >({
62
62
  queryKey: getRequestQueryKey(args),
63
63
  queryFn: ({ pageParam }) => {
64
- const pageParmWhere = pageParam?.where || {}
65
- const argsWhere = args.data.where || {}
66
- const where = { ...argsWhere, ...pageParmWhere }
64
+ const where = mergePageWhere(args.data.where, pageParam?.where)
67
65
  const app = args.app || 'chat'
68
66
  const offset = pageParam?.offset || args.data.offset
69
- const data = { ...args.data, where, offset }
67
+ const order = pageParam?.order || args.data.order
68
+ const data = { ...args.data, where, offset, order }
70
69
 
71
70
  return apiClient[app].get<ApiCollection<T>>({ url: args.url, data }).catch(throwResponseError)
72
71
  },
@@ -2,21 +2,43 @@ 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 { getNewerPageParam } from './paginator_meta'
5
6
  import { SuspensePaginatorOptions, useSuspensePaginator } from './use_suspense_api'
6
7
 
8
+ type Options = {
9
+ conversation_id: number
10
+ reply_root_id?: string | null
11
+ anchor?: string | null
12
+ }
13
+
7
14
  export const useConversationMessages = (
8
- { conversation_id, reply_root_id }: { conversation_id: number; reply_root_id?: string | null },
15
+ { conversation_id, reply_root_id, anchor }: Options,
9
16
  opts?: SuspensePaginatorOptions
10
17
  ) => {
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 })
18
+ const {
19
+ data,
20
+ refetch,
21
+ isRefetching,
22
+ fetchNextPage,
23
+ hasNextPage,
24
+ fetchPreviousPage,
25
+ hasPreviousPage,
26
+ isFetchingPreviousPage,
27
+ } = useSuspensePaginator<MessageResource>(
28
+ getMessagesRequestArgs({ conversation_id, reply_root_id, anchor }),
29
+ {
30
+ structuralSharing: replaceEqualById,
31
+ ...(anchor
32
+ ? {
33
+ getPreviousPageParam: (page, _pages, pageParam) => getNewerPageParam(page, pageParam),
34
+ gcTime: 0,
35
+ staleTime: Infinity,
36
+ }
37
+ : {}),
38
+ ...opts,
39
+ }
40
+ )
41
+ const queryKey = getMessagesQueryKey({ conversation_id, reply_root_id, anchor })
20
42
  const messages = useMemo(
21
43
  () =>
22
44
  data
@@ -29,5 +51,15 @@ export const useConversationMessages = (
29
51
  [data]
30
52
  )
31
53
 
32
- return { messages, refetch, isRefetching, fetchNextPage, hasNextPage, queryKey }
54
+ return {
55
+ messages,
56
+ refetch,
57
+ isRefetching,
58
+ fetchNextPage,
59
+ hasNextPage,
60
+ fetchPreviousPage,
61
+ hasPreviousPage,
62
+ isFetchingPreviousPage,
63
+ queryKey,
64
+ }
33
65
  }
@@ -1,4 +1,4 @@
1
- import { InfiniteData, useQueryClient } from '@tanstack/react-query'
1
+ import { hashKey, InfiniteData, useQueryClient } from '@tanstack/react-query'
2
2
  import { ApiCollection, MessageResource } from '../types'
3
3
  import { JoltReactionEvent, JoltTypingEvent } from '../types/jolt_events'
4
4
  import {
@@ -24,16 +24,49 @@ import { useJoltChannel, useJoltEvent } from './use_jolt'
24
24
  import { getRequestQueryKey } from './use_suspense_api'
25
25
  import { TYPING_TIMEOUT_INTERVAL, useTypingStatusCache } from './use_typing_status_cache'
26
26
 
27
+ export type MessagesJoltEvent =
28
+ | 'message.created'
29
+ | 'message.updated'
30
+ | 'message.destroyed'
31
+ | 'reaction'
32
+
33
+ export interface MessagesCache {
34
+ queryKey: unknown[]
35
+ canUpdate?: (event: MessagesJoltEvent) => boolean
36
+ }
37
+
27
38
  interface Props {
28
39
  conversationId: number
40
+ messageCaches?: MessagesCache[]
41
+ }
42
+
43
+ export const resolveMessageCaches = (defaults: unknown[][], overrides: MessagesCache[]) => {
44
+ const overrideByHash = new Map(overrides.map(cache => [hashKey(cache.queryKey), cache]))
45
+ const defaultHashes = new Set(defaults.map(hashKey))
46
+ const resolved = defaults.map(queryKey => overrideByHash.get(hashKey(queryKey)) ?? { queryKey })
47
+ const extras = overrides.filter(cache => !defaultHashes.has(hashKey(cache.queryKey)))
48
+ return [...resolved, ...extras]
29
49
  }
30
50
 
31
- export function useConversationMessagesJoltEvents({ conversationId }: Props) {
51
+ export const queryKeysForEvent = (
52
+ event: MessagesJoltEvent,
53
+ defaults: unknown[][],
54
+ overrides: MessagesCache[]
55
+ ) =>
56
+ resolveMessageCaches(defaults, overrides)
57
+ .filter(cache => !cache.canUpdate || cache.canUpdate(event))
58
+ .map(cache => cache.queryKey)
59
+
60
+ export function useConversationMessagesJoltEvents({ conversationId, messageCaches = [] }: Props) {
32
61
  const queryClient = useQueryClient()
33
62
  const currentPerson = useCurrentPerson()
34
63
  const joltChannel = useJoltChannel(`chat.conversations.${conversationId}`)
35
64
  const messagesRequestArgs = getMessagesRequestArgs({ conversation_id: conversationId })
36
65
  const messagesQueryKey = getRequestQueryKey(messagesRequestArgs)
66
+ const cachesTouchedBy = (replyRootId?: string | null): unknown[][] =>
67
+ replyRootId
68
+ ? [messagesQueryKey, getThreadedMessagesQueryKey(conversationId, replyRootId)]
69
+ : [messagesQueryKey]
37
70
  const apiClient = useApiClient()
38
71
 
39
72
  const { addTypingEvent, removeTypingEventById, removeAllTypingEventsByAuthorId } =
@@ -54,21 +87,16 @@ export function useConversationMessagesJoltEvents({ conversationId }: Props) {
54
87
  }
55
88
  }
56
89
 
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
- }
90
+ const targetQueryKeys = queryKeysForEvent(
91
+ e.event,
92
+ cachesTouchedBy(data.reply_root_id),
93
+ messageCaches
94
+ )
95
+ targetQueryKeys.forEach(queryKey =>
96
+ updateCacheWithMessage(queryClient, queryKey, message, e.event)
97
+ )
98
+ if (isReplyRootUpdate) {
99
+ targetQueryKeys.forEach(queryKey => updateCacheReplyRoots(queryClient, queryKey, message))
72
100
  }
73
101
 
74
102
  // Update individual message query cache so reply shadow messages update immediately
@@ -88,34 +116,23 @@ export function useConversationMessagesJoltEvents({ conversationId }: Props) {
88
116
  currentPersonId: currentPerson.id,
89
117
  })
90
118
 
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 =>
119
+ queryKeysForEvent(
120
+ 'message.destroyed',
121
+ cachesTouchedBy(data.reply_root_id),
122
+ messageCaches
123
+ ).forEach(queryKey =>
124
+ queryClient.setQueryData<QueryData>(queryKey, prev =>
103
125
  deleteRecordInPagesData({ data: prev, record: message })
104
126
  )
105
- }
127
+ )
106
128
  }
107
129
 
108
130
  const handleReactionJoltEvent = async (e: JoltReactionEvent) => {
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)
118
- }
131
+ queryKeysForEvent(
132
+ 'reaction',
133
+ cachesTouchedBy(e.data.data.reply_root_id),
134
+ messageCaches
135
+ ).forEach(queryKey => updateCacheWithReaction(queryClient, queryKey, e, currentPerson.id))
119
136
  }
120
137
 
121
138
  const handleTypingEvent = async (e: JoltTypingEvent) => {
@@ -0,0 +1,40 @@
1
+ import { useQuery } from '@tanstack/react-query'
2
+ import { ApiResource, MemberResource } from '../types'
3
+ import { useApiClient } from './use_api_client'
4
+
5
+ export interface DirectMessageEligibility {
6
+ canDirectMessage: boolean
7
+ directMessagesDisabled: boolean
8
+ }
9
+
10
+ export function useDirectMessageEligibility({
11
+ groupId,
12
+ personId,
13
+ enabled = true,
14
+ }: {
15
+ groupId: string
16
+ personId: number
17
+ enabled?: boolean
18
+ }) {
19
+ const apiClient = useApiClient()
20
+ const url = `/me/groups/${groupId}/members/${personId}`
21
+
22
+ const { data, isLoading, isError } = useQuery({
23
+ queryKey: ['chat', url, 'direct_message_eligibility'],
24
+ queryFn: () =>
25
+ apiClient.chat.get({
26
+ url,
27
+ data: { fields: { Member: ['can_direct_message', 'direct_messages_disabled'] } },
28
+ }) as Promise<ApiResource<MemberResource>>,
29
+ enabled,
30
+ retry: false,
31
+ })
32
+
33
+ const member = data?.data
34
+ const eligibility: DirectMessageEligibility | undefined = member && {
35
+ canDirectMessage: member.canDirectMessage ?? false,
36
+ directMessagesDisabled: member.directMessagesDisabled ?? false,
37
+ }
38
+
39
+ return { eligibility, isLoading, isError }
40
+ }
@@ -0,0 +1,37 @@
1
+ import { useQueryClient } from '@tanstack/react-query'
2
+ import { useCallback } from 'react'
3
+ import { ApiCollection, MessageResource } from '../types'
4
+ import { prependNewerPage } from '../utils/cache/prepend_newer_page'
5
+ import { RequestData } from '../utils/client'
6
+ import { getMessagesRequestArgs } from '../utils/request/get_messages'
7
+ import { useApiClient } from './use_api_client'
8
+
9
+ type Options = {
10
+ conversation_id: number
11
+ reply_root_id?: string | null
12
+ queryKey: unknown[]
13
+ }
14
+
15
+ export const useFetchNewerMessages = ({ conversation_id, reply_root_id, queryKey }: Options) => {
16
+ const queryClient = useQueryClient()
17
+ const apiClient = useApiClient()
18
+
19
+ return useCallback(
20
+ (newestMessageId: string) => {
21
+ const { url, data } = getMessagesRequestArgs({ conversation_id, reply_root_id })
22
+ const where = { id_gt: newestMessageId }
23
+ const pageParam: Partial<RequestData> = { where, order: 'asc' }
24
+
25
+ return apiClient.chat
26
+ .get<ApiCollection<MessageResource>>({ url, data: { ...data, where, order: 'asc' } })
27
+ .then(page => {
28
+ queryClient.setQueryData<Parameters<typeof prependNewerPage<MessageResource>>[0]>(
29
+ queryKey,
30
+ prev => prependNewerPage(prev, page, pageParam)
31
+ )
32
+ })
33
+ .catch(() => {})
34
+ },
35
+ [apiClient, conversation_id, queryClient, queryKey, reply_root_id]
36
+ )
37
+ }
@@ -0,0 +1,25 @@
1
+ import { useEffect, useRef } from 'react'
2
+ import { useAppState } from './use_app_state'
3
+
4
+ type Options = {
5
+ fetchNewer: (newestMessageId: string) => void
6
+ newestMessageId?: string
7
+ enabled: boolean
8
+ }
9
+
10
+ export const useFetchNewerOnForeground = ({ fetchNewer, newestMessageId, enabled }: Options) => {
11
+ const appState = useAppState()
12
+ const wasInBackground = useRef(false)
13
+
14
+ useEffect(() => {
15
+ if (appState !== 'active') {
16
+ wasInBackground.current = true
17
+ return
18
+ }
19
+ const returnedToForeground = wasInBackground.current
20
+ wasInBackground.current = false
21
+ if (!returnedToForeground || !enabled || !newestMessageId) return
22
+
23
+ fetchNewer(newestMessageId)
24
+ }, [appState, enabled, fetchNewer, newestMessageId])
25
+ }
@@ -7,9 +7,10 @@ import { useConversationsMarkRead } from './use_conversations_actions'
7
7
  interface Props {
8
8
  conversation: ConversationResource
9
9
  messages: MessageResource[]
10
+ enabled?: boolean
10
11
  }
11
12
 
12
- export function useMarkLatestMessageRead({ conversation }: Props) {
13
+ export function useMarkLatestMessageRead({ conversation, enabled = true }: Props) {
13
14
  const firedOnce = useRef<boolean>(false)
14
15
  const { markRead } = useConversationsMarkRead({ conversation })
15
16
  const debouncedMarkRead = useMemo(
@@ -24,10 +25,10 @@ export function useMarkLatestMessageRead({ conversation }: Props) {
24
25
  const isActive = appState === 'active'
25
26
 
26
27
  useEffect(() => {
27
- if (!isActive || !shouldMarkRead) return
28
+ if (!enabled || !isActive || !shouldMarkRead) return
28
29
 
29
30
  firedOnce.current = true
30
31
 
31
32
  debouncedMarkRead(true)
32
- }, [debouncedMarkRead, isActive, shouldMarkRead, unreadReaction])
33
+ }, [debouncedMarkRead, enabled, isActive, shouldMarkRead, unreadReaction])
33
34
  }
@@ -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 } from './paginator_meta'
13
+ import { getNextPageParamFromMeta, mergePageWhere } from './paginator_meta'
14
14
  import { App, useApiClient } from './use_api_client'
15
15
 
16
16
  interface SuspenseGetOptions extends GetRequest {
@@ -65,12 +65,11 @@ export const useSuspensePaginator = <T extends ResourceObject>(
65
65
  queryFn: ({ pageParam }) => {
66
66
  Log.info('useSuspenseGet', { pageParam })
67
67
 
68
- const pageParmWhere = pageParam?.where || {}
69
- const argsWhere = args.data.where || {}
70
- const where = { ...argsWhere, ...pageParmWhere }
68
+ const where = mergePageWhere(args.data.where, pageParam?.where)
71
69
  const app = args.app || 'chat'
72
70
  const offset = pageParam?.offset || args.data.offset
73
- const data = { ...args.data, where, offset }
71
+ const order = pageParam?.order || args.data.order
72
+ const data = { ...args.data, where, offset, order }
74
73
 
75
74
  return apiClient[app]
76
75
  .get<ApiCollection<T>>({
@@ -45,6 +45,7 @@ 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(!!initialPlanId)
48
49
  const initialState = useMemo(() => uniq(initialTeamIds) || [], [initialTeamIds]) // Uniq here because services can send duplicates in the teams_i_lead response.
49
50
  const [selectedTeamIds, setSelectedTeamIds] = useState<number[]>(initialState)
50
51
 
@@ -52,6 +53,7 @@ export const ServicesForm = ({
52
53
  useEffect(() => {
53
54
  setSelectedTeamIds(initialState)
54
55
  setSelectedPlanId(initialPlanId)
56
+ setFilterByPlan(!!initialPlanId)
55
57
  }, [initialState, initialPlanId])
56
58
 
57
59
  const removeSelection = useCallback(
@@ -61,9 +63,7 @@ export const ServicesForm = ({
61
63
  [selectedTeamIds]
62
64
  )
63
65
 
64
- const [filerByPlan, setFilterByPlan] = useState(false)
65
-
66
- const activePlanId = filerByPlan ? selectedPlanId : undefined
66
+ const activePlanId = filterByPlan ? 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: selectedPlanId,
82
+ planId: activePlanId,
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={filerByPlan}
121
+ filterByPlan={filterByPlan}
122
122
  setFilterByPlan={setFilterByPlan}
123
123
  members={members}
124
124
  isMemberError={isMemberError}