@planningcenter/chat-react-native 3.39.0-rc.0 → 3.39.0-rc.10

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 (205) hide show
  1. package/build/components/conversation/messages_disabled_banners.d.ts +6 -1
  2. package/build/components/conversation/messages_disabled_banners.d.ts.map +1 -1
  3. package/build/components/conversation/messages_disabled_banners.js +8 -2
  4. package/build/components/conversation/messages_disabled_banners.js.map +1 -1
  5. package/build/contexts/api_provider.js +2 -1
  6. package/build/contexts/api_provider.js.map +1 -1
  7. package/build/contexts/conversations_context.d.ts.map +1 -1
  8. package/build/contexts/conversations_context.js +6 -2
  9. package/build/contexts/conversations_context.js.map +1 -1
  10. package/build/hooks/direct_messages/use_direct_message_affiliations.d.ts +5 -0
  11. package/build/hooks/direct_messages/use_direct_message_affiliations.d.ts.map +1 -0
  12. package/build/hooks/direct_messages/use_direct_message_affiliations.js +20 -0
  13. package/build/hooks/direct_messages/use_direct_message_affiliations.js.map +1 -0
  14. package/build/hooks/direct_messages/use_direct_message_candidates.d.ts +4 -0
  15. package/build/hooks/direct_messages/use_direct_message_candidates.d.ts.map +1 -0
  16. package/build/hooks/direct_messages/use_direct_message_candidates.js +28 -0
  17. package/build/hooks/direct_messages/use_direct_message_candidates.js.map +1 -0
  18. package/build/hooks/direct_messages/use_direct_message_conversation_validate.d.ts +11 -0
  19. package/build/hooks/direct_messages/use_direct_message_conversation_validate.d.ts.map +1 -0
  20. package/build/hooks/direct_messages/use_direct_message_conversation_validate.js +25 -0
  21. package/build/hooks/direct_messages/use_direct_message_conversation_validate.js.map +1 -0
  22. package/build/hooks/direct_messages/use_find_or_create_direct_message_conversation.d.ts +13 -0
  23. package/build/hooks/direct_messages/use_find_or_create_direct_message_conversation.d.ts.map +1 -0
  24. package/build/hooks/direct_messages/use_find_or_create_direct_message_conversation.js +51 -0
  25. package/build/hooks/direct_messages/use_find_or_create_direct_message_conversation.js.map +1 -0
  26. package/build/hooks/groups/use_group_chat_conversation_payload.d.ts.map +1 -1
  27. package/build/hooks/groups/use_group_chat_conversation_payload.js +2 -1
  28. package/build/hooks/groups/use_group_chat_conversation_payload.js.map +1 -1
  29. package/build/hooks/index.d.ts +1 -0
  30. package/build/hooks/index.d.ts.map +1 -1
  31. package/build/hooks/index.js +1 -0
  32. package/build/hooks/index.js.map +1 -1
  33. package/build/hooks/services/use_services_chat_conversation_payload.d.ts.map +1 -1
  34. package/build/hooks/services/use_services_chat_conversation_payload.js +2 -1
  35. package/build/hooks/services/use_services_chat_conversation_payload.js.map +1 -1
  36. package/build/hooks/use_api.d.ts +5 -1
  37. package/build/hooks/use_api.d.ts.map +1 -1
  38. package/build/hooks/use_api.js +2 -1
  39. package/build/hooks/use_api.js.map +1 -1
  40. package/build/hooks/use_chat_configuration.d.ts.map +1 -1
  41. package/build/hooks/use_chat_configuration.js +2 -1
  42. package/build/hooks/use_chat_configuration.js.map +1 -1
  43. package/build/hooks/use_chat_permissions.d.ts.map +1 -1
  44. package/build/hooks/use_chat_permissions.js +4 -1
  45. package/build/hooks/use_chat_permissions.js.map +1 -1
  46. package/build/hooks/use_conversation.d.ts.map +1 -1
  47. package/build/hooks/use_conversation.js +1 -0
  48. package/build/hooks/use_conversation.js.map +1 -1
  49. package/build/hooks/use_conversation_jolt_events.d.ts.map +1 -1
  50. package/build/hooks/use_conversation_jolt_events.js +2 -2
  51. package/build/hooks/use_conversation_jolt_events.js.map +1 -1
  52. package/build/hooks/use_current_person.d.ts.map +1 -1
  53. package/build/hooks/use_current_person.js +4 -2
  54. package/build/hooks/use_current_person.js.map +1 -1
  55. package/build/hooks/use_features.d.ts.map +1 -1
  56. package/build/hooks/use_features.js +2 -1
  57. package/build/hooks/use_features.js.map +1 -1
  58. package/build/hooks/use_groups.d.ts.map +1 -1
  59. package/build/hooks/use_groups.js +2 -1
  60. package/build/hooks/use_groups.js.map +1 -1
  61. package/build/hooks/use_groups_groups.d.ts.map +1 -1
  62. package/build/hooks/use_groups_groups.js +2 -1
  63. package/build/hooks/use_groups_groups.js.map +1 -1
  64. package/build/hooks/use_has_direct_messages.d.ts +2 -0
  65. package/build/hooks/use_has_direct_messages.d.ts.map +1 -0
  66. package/build/hooks/use_has_direct_messages.js +17 -0
  67. package/build/hooks/use_has_direct_messages.js.map +1 -0
  68. package/build/hooks/use_jolt.d.ts.map +1 -1
  69. package/build/hooks/use_jolt.js +2 -1
  70. package/build/hooks/use_jolt.js.map +1 -1
  71. package/build/hooks/use_organization.d.ts.map +1 -1
  72. package/build/hooks/use_organization.js +4 -1
  73. package/build/hooks/use_organization.js.map +1 -1
  74. package/build/hooks/use_person_settings.d.ts +6 -0
  75. package/build/hooks/use_person_settings.d.ts.map +1 -0
  76. package/build/hooks/use_person_settings.js +26 -0
  77. package/build/hooks/use_person_settings.js.map +1 -0
  78. package/build/navigation/index.d.ts +13 -0
  79. package/build/navigation/index.d.ts.map +1 -1
  80. package/build/navigation/index.js +9 -0
  81. package/build/navigation/index.js.map +1 -1
  82. package/build/screens/conversation_details_screen.d.ts.map +1 -1
  83. package/build/screens/conversation_details_screen.js +2 -1
  84. package/build/screens/conversation_details_screen.js.map +1 -1
  85. package/build/screens/conversation_filters/components/conversation_filters.d.ts.map +1 -1
  86. package/build/screens/conversation_filters/components/conversation_filters.js +20 -5
  87. package/build/screens/conversation_filters/components/conversation_filters.js.map +1 -1
  88. package/build/screens/conversation_filters/components/rows.d.ts +2 -1
  89. package/build/screens/conversation_filters/components/rows.d.ts.map +1 -1
  90. package/build/screens/conversation_filters/components/rows.js +2 -2
  91. package/build/screens/conversation_filters/components/rows.js.map +1 -1
  92. package/build/screens/conversation_filters/context/conversation_filter_context.d.ts.map +1 -1
  93. package/build/screens/conversation_filters/context/conversation_filter_context.js +4 -0
  94. package/build/screens/conversation_filters/context/conversation_filter_context.js.map +1 -1
  95. package/build/screens/conversation_filters/filter_types.d.ts +1 -0
  96. package/build/screens/conversation_filters/filter_types.d.ts.map +1 -1
  97. package/build/screens/conversation_filters/filter_types.js +1 -0
  98. package/build/screens/conversation_filters/filter_types.js.map +1 -1
  99. package/build/screens/conversation_filters/screen_props.d.ts +1 -0
  100. package/build/screens/conversation_filters/screen_props.d.ts.map +1 -1
  101. package/build/screens/conversation_filters/screen_props.js.map +1 -1
  102. package/build/screens/conversation_screen.d.ts.map +1 -1
  103. package/build/screens/conversation_screen.js +1 -1
  104. package/build/screens/conversation_screen.js.map +1 -1
  105. package/build/screens/conversation_select_recipients/conversation_select_direct_message_recipients_screen.d.ts +1 -2
  106. package/build/screens/conversation_select_recipients/conversation_select_direct_message_recipients_screen.d.ts.map +1 -1
  107. package/build/screens/conversation_select_recipients/conversation_select_direct_message_recipients_screen.js +319 -10
  108. package/build/screens/conversation_select_recipients/conversation_select_direct_message_recipients_screen.js.map +1 -1
  109. package/build/screens/conversations/components/list_header_component.d.ts.map +1 -1
  110. package/build/screens/conversations/components/list_header_component.js +35 -17
  111. package/build/screens/conversations/components/list_header_component.js.map +1 -1
  112. package/build/screens/conversations/conversations_screen.d.ts +1 -0
  113. package/build/screens/conversations/conversations_screen.d.ts.map +1 -1
  114. package/build/screens/conversations/conversations_screen.js.map +1 -1
  115. package/build/screens/get_help_screen.d.ts.map +1 -1
  116. package/build/screens/get_help_screen.js +2 -18
  117. package/build/screens/get_help_screen.js.map +1 -1
  118. package/build/screens/index.d.ts +1 -0
  119. package/build/screens/index.d.ts.map +1 -1
  120. package/build/screens/index.js +1 -0
  121. package/build/screens/index.js.map +1 -1
  122. package/build/screens/settings_screen.d.ts +5 -0
  123. package/build/screens/settings_screen.d.ts.map +1 -0
  124. package/build/screens/settings_screen.js +56 -0
  125. package/build/screens/settings_screen.js.map +1 -0
  126. package/build/types/jolt_events/conversation_events.d.ts +2 -0
  127. package/build/types/jolt_events/conversation_events.d.ts.map +1 -1
  128. package/build/types/jolt_events/conversation_events.js.map +1 -1
  129. package/build/types/resources/conversation.d.ts +2 -0
  130. package/build/types/resources/conversation.d.ts.map +1 -1
  131. package/build/types/resources/conversation.js.map +1 -1
  132. package/build/types/resources/direct_message_affiliations.d.ts +8 -0
  133. package/build/types/resources/direct_message_affiliations.d.ts.map +1 -0
  134. package/build/types/resources/direct_message_affiliations.js +2 -0
  135. package/build/types/resources/direct_message_affiliations.js.map +1 -0
  136. package/build/types/resources/direct_message_candidate.d.ts +12 -0
  137. package/build/types/resources/direct_message_candidate.d.ts.map +1 -0
  138. package/build/types/resources/direct_message_candidate.js +2 -0
  139. package/build/types/resources/direct_message_candidate.js.map +1 -0
  140. package/build/types/resources/index.d.ts +2 -0
  141. package/build/types/resources/index.d.ts.map +1 -1
  142. package/build/types/resources/index.js +2 -0
  143. package/build/types/resources/index.js.map +1 -1
  144. package/build/types/resources/organization.d.ts +1 -0
  145. package/build/types/resources/organization.d.ts.map +1 -1
  146. package/build/types/resources/organization.js.map +1 -1
  147. package/build/types/resources/person.d.ts +1 -0
  148. package/build/types/resources/person.d.ts.map +1 -1
  149. package/build/types/resources/person.js.map +1 -1
  150. package/build/utils/request/conversation.d.ts +2 -1
  151. package/build/utils/request/conversation.d.ts.map +1 -1
  152. package/build/utils/request/conversation.js +15 -6
  153. package/build/utils/request/conversation.js.map +1 -1
  154. package/build/utils/time.d.ts +8 -0
  155. package/build/utils/time.d.ts.map +1 -0
  156. package/build/utils/time.js +15 -0
  157. package/build/utils/time.js.map +1 -0
  158. package/package.json +2 -2
  159. package/src/components/conversation/messages_disabled_banners.tsx +16 -2
  160. package/src/contexts/api_provider.tsx +4 -1
  161. package/src/contexts/conversations_context.tsx +6 -2
  162. package/src/hooks/direct_messages/use_direct_message_affiliations.ts +39 -0
  163. package/src/hooks/direct_messages/use_direct_message_candidates.ts +31 -0
  164. package/src/hooks/direct_messages/use_direct_message_conversation_validate.ts +35 -0
  165. package/src/hooks/direct_messages/use_find_or_create_direct_message_conversation.ts +66 -0
  166. package/src/hooks/groups/use_group_chat_conversation_payload.ts +2 -1
  167. package/src/hooks/index.ts +1 -0
  168. package/src/hooks/services/use_services_chat_conversation_payload.ts +2 -1
  169. package/src/hooks/use_api.ts +10 -1
  170. package/src/hooks/use_chat_configuration.ts +2 -1
  171. package/src/hooks/use_chat_permissions.ts +4 -1
  172. package/src/hooks/use_conversation.ts +1 -0
  173. package/src/hooks/use_conversation_jolt_events.ts +3 -2
  174. package/src/hooks/use_current_person.ts +4 -2
  175. package/src/hooks/use_features.ts +2 -1
  176. package/src/hooks/use_groups.ts +16 -12
  177. package/src/hooks/use_groups_groups.ts +19 -15
  178. package/src/hooks/use_has_direct_messages.ts +21 -0
  179. package/src/hooks/use_jolt.ts +2 -1
  180. package/src/hooks/use_organization.ts +4 -1
  181. package/src/hooks/use_person_settings.ts +29 -0
  182. package/src/navigation/index.tsx +11 -0
  183. package/src/screens/conversation_details_screen.tsx +2 -1
  184. package/src/screens/conversation_filters/components/conversation_filters.tsx +25 -5
  185. package/src/screens/conversation_filters/components/rows.tsx +16 -3
  186. package/src/screens/conversation_filters/context/conversation_filter_context.tsx +4 -0
  187. package/src/screens/conversation_filters/filter_types.ts +1 -0
  188. package/src/screens/conversation_filters/screen_props.ts +1 -0
  189. package/src/screens/conversation_screen.tsx +2 -1
  190. package/src/screens/conversation_select_recipients/conversation_select_direct_message_recipients_screen.tsx +513 -10
  191. package/src/screens/conversations/components/list_header_component.tsx +84 -34
  192. package/src/screens/conversations/conversations_screen.tsx +1 -0
  193. package/src/screens/get_help_screen.tsx +2 -29
  194. package/src/screens/index.ts +1 -0
  195. package/src/screens/settings_screen.tsx +67 -0
  196. package/src/types/jolt_events/conversation_events.ts +2 -0
  197. package/src/types/resources/conversation.ts +3 -0
  198. package/src/types/resources/direct_message_affiliations.ts +8 -0
  199. package/src/types/resources/direct_message_candidate.ts +12 -0
  200. package/src/types/resources/index.ts +2 -0
  201. package/src/types/resources/organization.ts +1 -0
  202. package/src/types/resources/person.ts +1 -0
  203. package/src/utils/__tests__/conversation.test.ts +14 -0
  204. package/src/utils/request/conversation.ts +13 -5
  205. package/src/utils/time.ts +17 -0
@@ -95,7 +95,10 @@ function useSessionChanged(value: Pick<ChatContextValue, 'token' | 'env'>): bool
95
95
  const isInitialLogin = !prevToken && !!newToken
96
96
  if (isInitialLogin) return false
97
97
 
98
- return Boolean(prevToken && newToken !== prevToken) || Boolean(prevEnv && newEnv !== prevEnv)
98
+ const tokenChanged = Boolean(
99
+ prevToken?.access_token && newToken?.access_token !== prevToken?.access_token
100
+ )
101
+ return tokenChanged || Boolean(prevEnv && newEnv !== prevEnv)
99
102
  }
100
103
 
101
104
  function usePrevious<T>(value: T): T {
@@ -39,9 +39,13 @@ export const ConversationsContextProvider = ({
39
39
  args = {},
40
40
  }: PropsWithChildren<{ args: ConversationFiltersParams }>) => {
41
41
  const [activeConversationId, setActiveConversationId] = useState<number | undefined>()
42
- const { chat_group_graph_id, group_source_app_name } = args
42
+ const { chat_group_graph_id, group_source_app_name, show_direct_messages } = args
43
43
 
44
- const query = useConversations({ chat_group_graph_id, group_source_app_name })
44
+ const query = useConversations({
45
+ chat_group_graph_id,
46
+ group_source_app_name,
47
+ show_direct_messages,
48
+ })
45
49
 
46
50
  const value = useMemo(
47
51
  () => ({
@@ -0,0 +1,39 @@
1
+ import { useQuery } from '@tanstack/react-query'
2
+ import { useMemo } from 'react'
3
+ import { ApiResource } from '../../types'
4
+ import { DirectMessageAffiliationsResource } from '../../types/resources/direct_message_affiliations'
5
+ import { useApiClient } from '../use_api_client'
6
+
7
+ export function useDirectMessageAffiliations() {
8
+ const apiClient = useApiClient()
9
+
10
+ const { data } = useQuery({
11
+ queryKey: ['chat', '/me/direct_message_affiliations'],
12
+ queryFn: () =>
13
+ apiClient.chat.get({
14
+ url: '/me/direct_message_affiliations',
15
+ data: {
16
+ fields: { DirectMessageAffiliations: ['groups', 'teams'] },
17
+ },
18
+ }) as Promise<ApiResource<DirectMessageAffiliationsResource>>,
19
+ staleTime: 5 * 60 * 1000,
20
+ })
21
+
22
+ const groups = useMemo(
23
+ () =>
24
+ new Map<number, string>(
25
+ Object.entries(data?.data?.groups ?? {}).map(([id, name]) => [+id, name])
26
+ ),
27
+ [data]
28
+ )
29
+
30
+ const teams = useMemo(
31
+ () =>
32
+ new Map<number, string>(
33
+ Object.entries(data?.data?.teams ?? {}).map(([id, name]) => [+id, name])
34
+ ),
35
+ [data]
36
+ )
37
+
38
+ return { groups, teams }
39
+ }
@@ -0,0 +1,31 @@
1
+ import { useQuery } from '@tanstack/react-query'
2
+ import { ApiCollection } from '../../types'
3
+ import { DirectMessageCandidateResource } from '../../types/resources/direct_message_candidate'
4
+ import { useApiClient } from '../use_api_client'
5
+
6
+ export function useDirectMessageCandidates(term: string) {
7
+ const apiClient = useApiClient()
8
+ return useQuery({
9
+ queryKey: ['chat', '/me/direct_message_candidates', term],
10
+ queryFn: () =>
11
+ apiClient.chat.get({
12
+ url: '/me/direct_message_candidates',
13
+ data: {
14
+ q: term,
15
+ fields: {
16
+ DirectMessageCandidate: [
17
+ 'name',
18
+ 'avatar',
19
+ 'groups',
20
+ 'teams',
21
+ 'under_18',
22
+ 'direct_messages_disabled',
23
+ ],
24
+ },
25
+ },
26
+ }) as Promise<ApiCollection<DirectMessageCandidateResource>>,
27
+ enabled: term.length >= 1,
28
+ staleTime: 1000 * 60,
29
+ throwOnError: true,
30
+ })
31
+ }
@@ -0,0 +1,35 @@
1
+ import { useQuery } from '@tanstack/react-query'
2
+ import { ApiResource, ConversationValidateResource, ConversationWarning } from '../../types'
3
+ import { useApiClient } from '../use_api_client'
4
+
5
+ interface Props {
6
+ recipientIds: string[]
7
+ enabled?: boolean
8
+ }
9
+
10
+ export function useDirectMessageConversationValidate({ recipientIds, enabled = true }: Props) {
11
+ const apiClient = useApiClient()
12
+
13
+ const { data, isLoading } = useQuery({
14
+ queryKey: ['chat', '/me/conversation_validate', { recipient_ids: recipientIds }],
15
+ queryFn: () =>
16
+ apiClient.chat.post<ApiResource<ConversationValidateResource>>({
17
+ url: '/me/conversation_validate',
18
+ data: {
19
+ data: {
20
+ type: 'ConversationValidate',
21
+ attributes: { recipient_ids: recipientIds },
22
+ },
23
+ },
24
+ }),
25
+ enabled: enabled && recipientIds.length > 0,
26
+ retry: false,
27
+ })
28
+
29
+ const warnings: ConversationWarning[] = data?.data.warnings ?? []
30
+
31
+ return {
32
+ warnings,
33
+ validationPending: enabled && recipientIds.length > 0 && isLoading,
34
+ }
35
+ }
@@ -0,0 +1,66 @@
1
+ import { useQuery } from '@tanstack/react-query'
2
+ import { useState, useCallback } from 'react'
3
+ import { ApiCollection, ApiResource, ConversationResource } from '../../types'
4
+ import { ApiClient, useApiClient } from '../use_api_client'
5
+
6
+ const findDirectMessageConversation = (apiClient: ApiClient, participantIds: string[]) =>
7
+ apiClient.chat.get({
8
+ url: '/me/conversations',
9
+ data: {
10
+ fields: { Conversation: ['title'] },
11
+ filter: 'with_exact_direct_message_participants',
12
+ participant_ids: participantIds.join(','),
13
+ },
14
+ }) as Promise<ApiCollection<ConversationResource>>
15
+
16
+ const createDirectMessageConversation = (apiClient: ApiClient, ids: string[]) =>
17
+ apiClient.chat.post({
18
+ url: '/me/direct_messages',
19
+ data: {
20
+ data: {
21
+ type: 'DirectMessage',
22
+ attributes: { recipients: ids },
23
+ },
24
+ },
25
+ }) as Promise<ApiResource<ConversationResource>>
26
+
27
+ interface Props {
28
+ recipientIds: string[]
29
+ onSuccess: (conversationId: ConversationResource['id']) => void
30
+ }
31
+
32
+ export function useFindOrCreateDirectMessageConversation({ recipientIds, onSuccess }: Props) {
33
+ const apiClient = useApiClient()
34
+ const [isFinding, setIsFinding] = useState(false)
35
+ const [isError, setIsError] = useState(false)
36
+
37
+ const { data } = useQuery({
38
+ queryKey: ['chat', '/me/conversations', 'with_exact_direct_message_participants', recipientIds],
39
+ queryFn: () => findDirectMessageConversation(apiClient, recipientIds),
40
+ enabled: recipientIds.length > 0,
41
+ })
42
+
43
+ const directMessageExists = (data?.data.length ?? 0) > 0
44
+
45
+ const findOrCreateConversation = useCallback(
46
+ async (participantIds: string[]) => {
47
+ setIsFinding(true)
48
+ setIsError(false)
49
+ try {
50
+ const findResponse = await findDirectMessageConversation(apiClient, participantIds)
51
+ if (findResponse.data.length > 0) {
52
+ onSuccess(findResponse.data[0].id)
53
+ } else {
54
+ const createResponse = await createDirectMessageConversation(apiClient, participantIds)
55
+ onSuccess(createResponse.data.id)
56
+ }
57
+ } catch {
58
+ setIsError(true)
59
+ setIsFinding(false)
60
+ }
61
+ },
62
+ [apiClient, onSuccess]
63
+ )
64
+
65
+ return { findOrCreateConversation, directMessageExists, isFinding, isError }
66
+ }
@@ -1,8 +1,9 @@
1
1
  import { useQuery } from '@tanstack/react-query'
2
2
  import { ApiResource, ResourceObject } from '../../types'
3
+ import { toMs } from '../../utils/time'
3
4
  import { useApiClient } from '../use_api_client'
4
5
 
5
- const STALE_TIME_MS = 25 * 60 * 1000 // under Groups' 30-min server-side expiry
6
+ const STALE_TIME_MS = toMs(25, 'minutes') // under Groups' 30-min server-side expiry
6
7
 
7
8
  interface Props {
8
9
  groupId: number
@@ -10,6 +10,7 @@ export * from './use_current_person'
10
10
  export * from './use_deleting_ids'
11
11
  export * from './use_direct_messages_enabled'
12
12
  export * from './use_font_scale'
13
+ export * from './use_has_direct_messages'
13
14
  export * from './use_groups_groups'
14
15
  export * from './use_groups'
15
16
  export * from './use_interaction_ghost_color'
@@ -1,8 +1,9 @@
1
1
  import { useQuery } from '@tanstack/react-query'
2
+ import { toMs } from '../../utils/time'
2
3
  import { useApiClient } from '../use_api_client'
3
4
  import { getServicesChatConversationPayload } from './use_find_or_create_services_conversation'
4
5
 
5
- const STALE_TIME_MS = 55 * 60 * 1000 // under Services' 1-hour server-side expiry
6
+ const STALE_TIME_MS = toMs(55, 'minutes') // under Services' 1-hour server-side expiry
6
7
 
7
8
  interface Props {
8
9
  teamIds: number[]
@@ -17,7 +17,15 @@ interface ApiGetOptions extends GetRequest {
17
17
  enabled?: boolean
18
18
  }
19
19
 
20
- export const useApiGet = <T extends ResourceObject | ResourceObject[]>(args: ApiGetOptions) => {
20
+ type UseApiGetOptions = {
21
+ staleTime?: number
22
+ gcTime?: number
23
+ }
24
+
25
+ export const useApiGet = <T extends ResourceObject | ResourceObject[]>(
26
+ args: ApiGetOptions,
27
+ opts?: UseApiGetOptions
28
+ ) => {
21
29
  type Resource = ApiResource<T>
22
30
  const { session } = useContext(ChatContext)
23
31
  const apiClient = useApiClient()
@@ -32,6 +40,7 @@ export const useApiGet = <T extends ResourceObject | ResourceObject[]>(args: Api
32
40
  return apiClient[app].get({ url, data: d, headers }) as Promise<Resource>
33
41
  },
34
42
  enabled,
43
+ ...opts,
35
44
  })
36
45
 
37
46
  return { ...data, ...query }
@@ -5,6 +5,7 @@ import {
5
5
  getChatConfigurationRequestArgs,
6
6
  getChatConfigurationQueryKey,
7
7
  } from '../utils/request/get_chat_configuration'
8
+ import { ONE_HOUR } from '../utils/time'
8
9
  import {
9
10
  FALLBACK_ALLOWED_FILE_EXTENSIONS,
10
11
  FALLBACK_ALLOWED_MIME_TYPES,
@@ -28,7 +29,7 @@ export function useChatConfiguration() {
28
29
  .get<ApiResource<ChatConfigurationResource>>(requestArgs)
29
30
  .catch(() => stableFallbackConfiguration)
30
31
  },
31
- staleTime: 1000 * 60 * 60, // 1 hour — this rarely changes
32
+ staleTime: ONE_HOUR,
32
33
  })
33
34
 
34
35
  const attrs = data.data
@@ -1,4 +1,5 @@
1
1
  import { AppGrantsResource } from '../types'
2
+ import { FIVE_MINUTES } from '../utils/time'
2
3
  import { useApiGet } from './use_api'
3
4
  import { App } from './use_api_client'
4
5
 
@@ -13,7 +14,9 @@ export const appGrantsRequestArgs = {
13
14
  }
14
15
 
15
16
  export function useAppGrants() {
16
- return useApiGet<AppGrantsResource[]>(appGrantsRequestArgs)
17
+ return useApiGet<AppGrantsResource[]>(appGrantsRequestArgs, {
18
+ staleTime: FIVE_MINUTES,
19
+ })
17
20
  }
18
21
 
19
22
  export function useCanCreateConversations(): boolean {
@@ -29,6 +29,7 @@ export const getConversationRequestArgs = ({ conversation_id }: { conversation_i
29
29
  'custom_avatar_color',
30
30
  'custom_avatar_image_url',
31
31
  'disabled',
32
+ 'disabled_reason',
32
33
  'member_ability',
33
34
  'muted',
34
35
  'replies_disabled',
@@ -26,7 +26,8 @@ export function useConversationJoltEvents({ conversationId }: Props) {
26
26
 
27
27
  const handleUpdatedConversation = useCallback(
28
28
  (e: JoltConversationEvent) => {
29
- const { last_message_idempotent_key, last_message_author_id, disabled } = e.data.data
29
+ const { last_message_idempotent_key, last_message_author_id, disabled, disabled_reason } =
30
+ e.data.data
30
31
 
31
32
  if (last_message_idempotent_key && last_message_author_id === currentPersonId) {
32
33
  completeMessageCreationConversationTracking({
@@ -37,7 +38,7 @@ export function useConversationJoltEvents({ conversationId }: Props) {
37
38
 
38
39
  queryClient.setQueryData<ApiResource<ConversationResource>>(queryKey, prev => {
39
40
  if (!prev?.data) return prev
40
- return { ...prev, data: { ...prev.data, disabled } }
41
+ return { ...prev, data: { ...prev.data, disabled, disabledReason: disabled_reason } }
41
42
  })
42
43
 
43
44
  queryClient.invalidateQueries({ queryKey })
@@ -1,6 +1,7 @@
1
1
  import { useQueryClient } from '@tanstack/react-query'
2
2
  import { CurrentPersonResource } from '../types'
3
3
  import { ApiResource } from '../types/api_primitives'
4
+ import { FIVE_MINUTES, THIRTY_MINUTES } from '../utils/time'
4
5
  import { getRequestQueryKey, useSuspenseGet } from './use_suspense_api'
5
6
 
6
7
  export const currentPersonRequestArgs = {
@@ -15,6 +16,7 @@ export const currentPersonRequestArgs = {
15
16
  'unread_count',
16
17
  'pco_chat_enabled',
17
18
  'age_qualification_status',
19
+ 'direct_messages_disabled',
18
20
  ],
19
21
  },
20
22
  },
@@ -24,8 +26,8 @@ export const currentPersonQueryKey = getRequestQueryKey(currentPersonRequestArgs
24
26
 
25
27
  export const useCurrentPerson = () => {
26
28
  const { data: person } = useSuspenseGet<CurrentPersonResource>(currentPersonRequestArgs, {
27
- staleTime: 5 * 60 * 1000, // 5 minutes - data stays fresh for 5 minutes
28
- gcTime: 30 * 60 * 1000, // 30 minutes - data stays in cache for 30 minutes when not in use
29
+ staleTime: FIVE_MINUTES,
30
+ gcTime: THIRTY_MINUTES,
29
31
  })
30
32
 
31
33
  return person
@@ -3,6 +3,7 @@ import { useCallback } from 'react'
3
3
  import { ApiCollection } from '../types'
4
4
  import type { FeatureResource } from '../types/resources/feature_resource'
5
5
  import { getFeaturesRequestArgs, getFeaturesQueryKey } from '../utils/request/get_features'
6
+ import { FIVE_MINUTES } from '../utils/time'
6
7
  import { useApiClient } from './use_api_client'
7
8
 
8
9
  type FeatureName = (typeof availableFeatures)[keyof typeof availableFeatures]
@@ -18,7 +19,7 @@ export function useFeatures() {
18
19
  .get<ApiCollection<FeatureResource>>(requestArgs)
19
20
  .catch(() => stableEmptyFeatures)
20
21
  },
21
- staleTime: 1000 * 60 * 5, // 5 minutes
22
+ staleTime: FIVE_MINUTES,
22
23
  })
23
24
 
24
25
  const features = data.data
@@ -1,5 +1,6 @@
1
1
  import { AppName } from '../types/resources/app_name'
2
2
  import { GroupResource, SourceType } from '../types/resources/group_resource'
3
+ import { FIVE_MINUTES } from '../utils/time'
3
4
  import { useApiPaginator } from './use_api'
4
5
 
5
6
  type UseGroupsProps = {
@@ -8,20 +9,23 @@ type UseGroupsProps = {
8
9
  }
9
10
 
10
11
  export const useGroups = ({ source_app_name, source_type }: UseGroupsProps) => {
11
- return useApiPaginator<GroupResource>({
12
- url: '/me/groups',
13
- data: {
14
- fields: {
15
- Group: [],
12
+ return useApiPaginator<GroupResource>(
13
+ {
14
+ url: '/me/groups',
15
+ data: {
16
+ fields: {
17
+ Group: [],
18
+ },
19
+ where: {
20
+ source_app_name,
21
+ source_type,
22
+ },
23
+ perPage: 100,
16
24
  },
17
- where: {
18
- source_app_name,
19
- source_type,
20
- },
21
- perPage: 100,
25
+ app: 'chat',
22
26
  },
23
- app: 'chat',
24
- })
27
+ { staleTime: FIVE_MINUTES }
28
+ )
25
29
  }
26
30
 
27
31
  export const useCanDisplayGroups = (props: UseGroupsProps) => {
@@ -1,24 +1,28 @@
1
1
  import { GroupsGroupResource } from '../types'
2
+ import { FIVE_MINUTES } from '../utils/time'
2
3
  import { useApiPaginator } from './use_api'
3
4
 
4
5
  export const useGroupsGroups = () => {
5
- return useApiPaginator<GroupsGroupResource>({
6
- url: '/me/groups',
7
- data: {
8
- perPage: 100,
9
- fields: {
10
- Group: [
11
- 'can_create_conversation',
12
- 'can_create_conversation_reason_code',
13
- 'name',
14
- 'header_image',
15
- 'memberships_count',
16
- ],
6
+ return useApiPaginator<GroupsGroupResource>(
7
+ {
8
+ url: '/me/groups',
9
+ data: {
10
+ perPage: 100,
11
+ fields: {
12
+ Group: [
13
+ 'can_create_conversation',
14
+ 'can_create_conversation_reason_code',
15
+ 'name',
16
+ 'header_image',
17
+ 'memberships_count',
18
+ ],
19
+ },
20
+ order: 'name',
17
21
  },
18
- order: 'name',
22
+ app: 'groups',
19
23
  },
20
- app: 'groups',
21
- })
24
+ { staleTime: FIVE_MINUTES }
25
+ )
22
26
  }
23
27
 
24
28
  export const useCanDisplayGroupsGroups = () => {
@@ -0,0 +1,21 @@
1
+ import { useQuery } from '@tanstack/react-query'
2
+ import { ApiCollection, ConversationResource } from '../types'
3
+ import { useApiClient } from './use_api_client'
4
+
5
+ export function useHasDirectMessages(): boolean {
6
+ const apiClient = useApiClient()
7
+
8
+ const { data, isFetched } = useQuery<ApiCollection<ConversationResource>>({
9
+ queryKey: ['chat', '/me/conversations', 'direct_message', 'has_any'],
10
+ queryFn: () =>
11
+ apiClient.chat.get({
12
+ url: '/me/conversations',
13
+ data: { filter: 'direct_message', perPage: 1, fields: { Conversation: ['id'] } },
14
+ }),
15
+ staleTime: 1000 * 60 * 5,
16
+ })
17
+
18
+ if (!isFetched) return false
19
+
20
+ return (data?.data.length ?? 0) > 0
21
+ }
@@ -13,6 +13,7 @@ import { useChatContext } from '../contexts/chat_context'
13
13
  import { ApiResource } from '../types'
14
14
  import type { JoltSubscriptionPattern } from '../types/jolt_events'
15
15
  import { Client, Uri } from '../utils'
16
+ import { FIVE_SECONDS } from '../utils/time'
16
17
 
17
18
  interface JoltResponse {
18
19
  type: 'JoltToken'
@@ -49,7 +50,7 @@ export const useJoltClient = (): JoltClient | undefined => {
49
50
  },
50
51
  })
51
52
  },
52
- staleTime: 5000, // 5 seconds
53
+ staleTime: FIVE_SECONDS,
53
54
  })
54
55
  }
55
56
 
@@ -1,4 +1,5 @@
1
1
  import { OrganizationResource } from '../types'
2
+ import { ONE_HOUR } from '../utils/time'
2
3
  import { App } from './use_api_client'
3
4
  import { useSuspenseGet } from './use_suspense_api'
4
5
 
@@ -13,5 +14,7 @@ export const organizationRequestArgs = {
13
14
  }
14
15
 
15
16
  export function useOrganization() {
16
- return useSuspenseGet<OrganizationResource>(organizationRequestArgs)
17
+ return useSuspenseGet<OrganizationResource>(organizationRequestArgs, {
18
+ staleTime: ONE_HOUR,
19
+ })
17
20
  }
@@ -0,0 +1,29 @@
1
+ import { useMutation } from '@tanstack/react-query'
2
+ import { throwResponseError } from '../utils/response_error'
3
+ import { useApiClient } from './use_api_client'
4
+ import { useCurrentPerson, useCurrentPersonCache } from './use_current_person'
5
+
6
+ export function usePersonSettings() {
7
+ const apiClient = useApiClient()
8
+ const person = useCurrentPerson()
9
+ const { update, invalidate } = useCurrentPersonCache()
10
+
11
+ const directMessagesDisabled = person?.directMessagesDisabled ?? false
12
+
13
+ const { mutate: setDirectMessagesDisabled, isPending: isMutating } = useMutation({
14
+ mutationFn: (disabled: boolean) => {
15
+ const action = disabled ? 'disable_direct_messages' : 'enable_direct_messages'
16
+ return apiClient.chat
17
+ .post({ url: `/me/${action}`, data: { data: { type: 'Person', attributes: {} } } })
18
+ .catch(throwResponseError)
19
+ },
20
+ onMutate: disabled => {
21
+ update({ directMessagesDisabled: disabled })
22
+ },
23
+ onError: () => {
24
+ invalidate()
25
+ },
26
+ })
27
+
28
+ return { directMessagesDisabled, setDirectMessagesDisabled, isMutating }
29
+ }
@@ -77,6 +77,7 @@ import { NotificationSettingsScreen } from '../screens/notification_settings_scr
77
77
  import { PreferredAppSelectionScreen } from '../screens/preferred_app_selection_screen'
78
78
  import { ReactionsScreen, ReactionsScreenOptions } from '../screens/reactions_screen'
79
79
  import { SendGiphyScreen, SendGiphyScreenOptions } from '../screens/send_giphy_screen'
80
+ import { SettingsScreen } from '../screens/settings_screen'
80
81
  import {
81
82
  SystemMessagePeopleScreen,
82
83
  SystemMessagePeopleScreenOptions,
@@ -280,6 +281,16 @@ export const ChatStack = createNativeStackNavigator({
280
281
  screen: AvatarPickerScreen,
281
282
  options: AvatarPickerScreenOptions,
282
283
  },
284
+ ChatSettings: {
285
+ screen: SettingsScreen,
286
+ options: ({ navigation }) => ({
287
+ title: 'Chat settings',
288
+ headerBackVisible: false,
289
+ headerRight: (props: NativeStackHeaderRightProps) => (
290
+ <HeaderTextButton {...props} onPress={navigation.goBack} title="Done" />
291
+ ),
292
+ }),
293
+ },
283
294
  NotificationSettings: {
284
295
  screen: NotificationSettingsScreen,
285
296
  options: ({ navigation }) => ({
@@ -122,6 +122,7 @@ export function ConversationDetailsScreen({ route }: ConversationDetailsScreenPr
122
122
  const canDelete = conversation.memberAbility?.canDelete || false
123
123
  const isLeader = conversation.memberAbility?.leader || false
124
124
  const isDisabled = conversation.disabled ?? false
125
+ const showAdultRequirementNotice = isDisabled && conversation.disabledReason === 'safety_check'
125
126
 
126
127
  const {
127
128
  data: members,
@@ -350,7 +351,7 @@ export function ConversationDetailsScreen({ route }: ConversationDetailsScreenPr
350
351
  sectionInnerStyle: styles.sectionInnerGenderNotice,
351
352
  },
352
353
  {
353
- type: isDisabled ? SectionTypes.view : SectionTypes.hidden,
354
+ type: showAdultRequirementNotice ? SectionTypes.view : SectionTypes.hidden,
354
355
  data: {
355
356
  children: <AdultRequirementNotice />,
356
357
  },
@@ -2,7 +2,12 @@ import React, { useContext, useMemo } from 'react'
2
2
  import { StyleSheet, View, ViewStyle } from 'react-native'
3
3
  import { FlatList } from 'react-native-gesture-handler'
4
4
  import { Heading } from '../../../components'
5
- import { useAtFontScaleBreakpoint, useTheme } from '../../../hooks'
5
+ import {
6
+ useAtFontScaleBreakpoint,
7
+ useDirectMessagesEnabled,
8
+ useHasDirectMessages,
9
+ useTheme,
10
+ } from '../../../hooks'
6
11
  import { FilterContext, useFilterContext } from '../context/conversation_filter_context'
7
12
  import { FilterTypes } from '../filter_types'
8
13
  import { useGroupsToFilter, useTeamsToFilter } from '../hooks/filters'
@@ -52,7 +57,11 @@ interface DataItem<T, TName extends SectionTypes> {
52
57
  export const ConversationFilters = () => {
53
58
  const styles = useStyles()
54
59
  const { setScrollOffset, params } = useContext(FilterContext)
55
- const { chat_group_graph_id, group_source_app_name = '' } = params
60
+ const { chat_group_graph_id, group_source_app_name = '', show_direct_messages } = params
61
+
62
+ const directMessagesEnabled = useDirectMessagesEnabled()
63
+ const hasDirectMessages = useHasDirectMessages()
64
+ const canFilterByDMs = directMessagesEnabled && hasDirectMessages
56
65
 
57
66
  const activeFilter: FilterTypes = useMemo(() => {
58
67
  if (chat_group_graph_id) {
@@ -61,10 +70,12 @@ export const ConversationFilters = () => {
61
70
  return FilterTypes.Groups
62
71
  } else if (/services/i.test(group_source_app_name)) {
63
72
  return FilterTypes.Teams
73
+ } else if (show_direct_messages) {
74
+ return FilterTypes.DirectMessages
64
75
  }
65
76
 
66
77
  return FilterTypes.All
67
- }, [chat_group_graph_id, group_source_app_name])
78
+ }, [chat_group_graph_id, group_source_app_name, show_direct_messages])
68
79
 
69
80
  const { groups = [] } = useGroupsToFilter()
70
81
  const { teams = [] } = useTeamsToFilter()
@@ -91,14 +102,15 @@ export const ConversationFilters = () => {
91
102
  const teamItemData = selectActiveWithFirst(5)(teamItems)
92
103
 
93
104
  const hideAppFilters = groupItems.length < 1 || teamItems.length < 1
105
+ const showGeneralFilters = !hideAppFilters || canFilterByDMs
94
106
 
95
107
  const listData: SectionListData = [
96
108
  {
97
- type: hideAppFilters ? SectionTypes.hidden : SectionTypes.header,
109
+ type: showGeneralFilters ? SectionTypes.header : SectionTypes.hidden,
98
110
  data: { title: 'General Filters' },
99
111
  },
100
112
  {
101
- type: hideAppFilters ? SectionTypes.hidden : SectionTypes.filter,
113
+ type: showGeneralFilters ? SectionTypes.filter : SectionTypes.hidden,
102
114
  data: { filter: FilterTypes.All, isActive: activeFilter === FilterTypes.All },
103
115
  },
104
116
  {
@@ -117,6 +129,14 @@ export const ConversationFilters = () => {
117
129
  isActive: activeFilter === FilterTypes.Teams,
118
130
  },
119
131
  },
132
+ {
133
+ type: canFilterByDMs ? SectionTypes.filter : SectionTypes.hidden,
134
+ data: {
135
+ filter: FilterTypes.DirectMessages,
136
+ show_direct_messages: true,
137
+ isActive: activeFilter === FilterTypes.DirectMessages,
138
+ },
139
+ },
120
140
  {
121
141
  type: groupItems.length ? SectionTypes.header : SectionTypes.hidden,
122
142
  data: { title: 'Groups', style: styles.extraHeaderScalableSpace },