@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.
- package/build/components/conversation/anchor_not_found_boundary.d.ts +18 -0
- package/build/components/conversation/anchor_not_found_boundary.d.ts.map +1 -0
- package/build/components/conversation/anchor_not_found_boundary.js +20 -0
- package/build/components/conversation/anchor_not_found_boundary.js.map +1 -0
- package/build/components/conversation/message_form.d.ts +2 -1
- package/build/components/conversation/message_form.d.ts.map +1 -1
- package/build/components/conversation/message_form.js +54 -51
- package/build/components/conversation/message_form.js.map +1 -1
- package/build/components/conversation/message_list.d.ts +3 -2
- package/build/components/conversation/message_list.d.ts.map +1 -1
- package/build/components/conversation/message_list.js +152 -66
- package/build/components/conversation/message_list.js.map +1 -1
- package/build/hooks/index.d.ts +1 -0
- package/build/hooks/index.d.ts.map +1 -1
- package/build/hooks/index.js +1 -0
- package/build/hooks/index.js.map +1 -1
- package/build/hooks/paginator_meta.d.ts +3 -1
- package/build/hooks/paginator_meta.d.ts.map +1 -1
- package/build/hooks/paginator_meta.js +25 -8
- package/build/hooks/paginator_meta.js.map +1 -1
- package/build/hooks/use_api.d.ts.map +1 -1
- package/build/hooks/use_api.js +5 -8
- package/build/hooks/use_api.js.map +1 -1
- package/build/hooks/use_conversation_messages.d.ts +8 -2
- package/build/hooks/use_conversation_messages.d.ts.map +1 -1
- package/build/hooks/use_conversation_messages.js +77 -45
- package/build/hooks/use_conversation_messages.js.map +1 -1
- package/build/hooks/use_conversation_messages_jolt_events.d.ts +9 -1
- package/build/hooks/use_conversation_messages_jolt_events.d.ts.map +1 -1
- package/build/hooks/use_conversation_messages_jolt_events.js +88 -73
- package/build/hooks/use_conversation_messages_jolt_events.js.map +1 -1
- package/build/hooks/use_direct_message_eligibility.d.ts +14 -0
- package/build/hooks/use_direct_message_eligibility.d.ts.map +1 -0
- package/build/hooks/use_direct_message_eligibility.js +87 -0
- package/build/hooks/use_direct_message_eligibility.js.map +1 -0
- package/build/hooks/use_fetch_newer_messages.d.ts +8 -0
- package/build/hooks/use_fetch_newer_messages.d.ts.map +1 -0
- package/build/hooks/use_fetch_newer_messages.js +56 -0
- package/build/hooks/use_fetch_newer_messages.js.map +1 -0
- package/build/hooks/use_fetch_newer_on_foreground.d.ts +8 -0
- package/build/hooks/use_fetch_newer_on_foreground.d.ts.map +1 -0
- package/build/hooks/use_fetch_newer_on_foreground.js +41 -0
- package/build/hooks/use_fetch_newer_on_foreground.js.map +1 -0
- package/build/hooks/use_mark_latest_message_read.d.ts +2 -1
- package/build/hooks/use_mark_latest_message_read.d.ts.map +1 -1
- package/build/hooks/use_mark_latest_message_read.js +4 -3
- package/build/hooks/use_mark_latest_message_read.js.map +1 -1
- package/build/hooks/use_suspense_api.d.ts.map +1 -1
- package/build/hooks/use_suspense_api.js +5 -8
- package/build/hooks/use_suspense_api.js.map +1 -1
- package/build/screens/conversation_new/components/services_form.js +9 -8
- package/build/screens/conversation_new/components/services_form.js.map +1 -1
- package/build/screens/conversation_screen.d.ts +1 -0
- package/build/screens/conversation_screen.d.ts.map +1 -1
- package/build/screens/conversation_screen.js +241 -355
- package/build/screens/conversation_screen.js.map +1 -1
- package/build/types/api_primitives.d.ts +2 -0
- package/build/types/api_primitives.d.ts.map +1 -1
- package/build/types/api_primitives.js.map +1 -1
- package/build/types/resources/member.d.ts +1 -0
- package/build/types/resources/member.d.ts.map +1 -1
- package/build/types/resources/member.js.map +1 -1
- package/build/utils/cache/prepend_newer_page.d.ts +7 -0
- package/build/utils/cache/prepend_newer_page.d.ts.map +1 -0
- package/build/utils/cache/prepend_newer_page.js +14 -0
- package/build/utils/cache/prepend_newer_page.js.map +1 -0
- package/build/utils/client/types.d.ts +1 -0
- package/build/utils/client/types.d.ts.map +1 -1
- package/build/utils/client/types.js.map +1 -1
- package/build/utils/gated_message_cache.d.ts +7 -0
- package/build/utils/gated_message_cache.d.ts.map +1 -0
- package/build/utils/gated_message_cache.js +13 -0
- package/build/utils/gated_message_cache.js.map +1 -0
- package/build/utils/generate_placeholder_ulid.d.ts +0 -6
- package/build/utils/generate_placeholder_ulid.d.ts.map +1 -1
- package/build/utils/generate_placeholder_ulid.js +2 -2
- package/build/utils/generate_placeholder_ulid.js.map +1 -1
- package/build/utils/group_messages.js +4 -1
- package/build/utils/group_messages.js.map +1 -1
- package/build/utils/request/get_messages.d.ts +18 -6
- package/build/utils/request/get_messages.d.ts.map +1 -1
- package/build/utils/request/get_messages.js +10 -12
- package/build/utils/request/get_messages.js.map +1 -1
- package/build/utils/ulid.d.ts +3 -0
- package/build/utils/ulid.d.ts.map +1 -0
- package/build/utils/ulid.js +7 -0
- package/build/utils/ulid.js.map +1 -0
- package/package.json +3 -3
- package/src/__tests__/hooks/paginator_meta.test.ts +73 -2
- package/src/__tests__/hooks/use_conversation_messages_jolt_events.test.ts +36 -0
- package/src/__tests__/hooks/use_direct_message_eligibility.test.tsx +83 -0
- package/src/__tests__/screens/conversation_new/components/services_form.test.tsx +91 -0
- package/src/__tests__/utils/gated_message_cache.test.ts +26 -0
- package/src/__tests__/utils/request/get_messages.test.ts +24 -0
- package/src/__tests__/utils/ulid.test.ts +15 -0
- package/src/components/conversation/__tests__/anchor_not_found_boundary.test.tsx +51 -0
- package/src/components/conversation/__tests__/message_list.test.tsx +42 -0
- package/src/components/conversation/anchor_not_found_boundary.tsx +21 -0
- package/src/components/conversation/message_form.tsx +4 -0
- package/src/components/conversation/message_list.tsx +58 -4
- package/src/hooks/index.ts +1 -0
- package/src/hooks/paginator_meta.ts +29 -5
- package/src/hooks/use_api.ts +4 -5
- package/src/hooks/use_conversation_messages.ts +43 -11
- package/src/hooks/use_conversation_messages_jolt_events.ts +57 -40
- package/src/hooks/use_direct_message_eligibility.ts +40 -0
- package/src/hooks/use_fetch_newer_messages.ts +37 -0
- package/src/hooks/use_fetch_newer_on_foreground.ts +25 -0
- package/src/hooks/use_mark_latest_message_read.ts +4 -3
- package/src/hooks/use_suspense_api.ts +4 -5
- package/src/screens/conversation_new/components/services_form.tsx +5 -5
- package/src/screens/conversation_screen.tsx +120 -17
- package/src/types/api_primitives.ts +2 -0
- package/src/types/resources/member.ts +1 -0
- package/src/utils/__tests__/group_messages.test.ts +29 -0
- package/src/utils/cache/__tests__/prepend_newer_page.test.ts +35 -0
- package/src/utils/cache/prepend_newer_page.ts +22 -0
- package/src/utils/client/types.ts +1 -0
- package/src/utils/gated_message_cache.ts +19 -0
- package/src/utils/generate_placeholder_ulid.ts +2 -2
- package/src/utils/group_messages.ts +4 -1
- package/src/utils/request/get_messages.ts +12 -14
- 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
|
+
}
|
package/src/hooks/use_api.ts
CHANGED
|
@@ -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
|
|
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
|
|
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 }:
|
|
15
|
+
{ conversation_id, reply_root_id, anchor }: Options,
|
|
9
16
|
opts?: SuspensePaginatorOptions
|
|
10
17
|
) => {
|
|
11
|
-
const {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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 {
|
|
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
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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={
|
|
121
|
+
filterByPlan={filterByPlan}
|
|
122
122
|
setFilterByPlan={setFilterByPlan}
|
|
123
123
|
members={members}
|
|
124
124
|
isMemberError={isMemberError}
|