@planningcenter/chat-react-native 3.39.0-rc.1 → 3.39.0-rc.11

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 (183) 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/components/primitive/form_sheet.d.ts +1 -0
  6. package/build/components/primitive/form_sheet.d.ts.map +1 -1
  7. package/build/components/primitive/form_sheet.js +17 -7
  8. package/build/components/primitive/form_sheet.js.map +1 -1
  9. package/build/contexts/api_provider.js +2 -1
  10. package/build/contexts/api_provider.js.map +1 -1
  11. package/build/contexts/conversations_context.d.ts.map +1 -1
  12. package/build/contexts/conversations_context.js +6 -2
  13. package/build/contexts/conversations_context.js.map +1 -1
  14. package/build/hooks/direct_messages/use_direct_message_candidates.d.ts.map +1 -1
  15. package/build/hooks/direct_messages/use_direct_message_candidates.js +1 -0
  16. package/build/hooks/direct_messages/use_direct_message_candidates.js.map +1 -1
  17. package/build/hooks/groups/use_group_chat_conversation_payload.d.ts.map +1 -1
  18. package/build/hooks/groups/use_group_chat_conversation_payload.js +2 -1
  19. package/build/hooks/groups/use_group_chat_conversation_payload.js.map +1 -1
  20. package/build/hooks/index.d.ts +1 -0
  21. package/build/hooks/index.d.ts.map +1 -1
  22. package/build/hooks/index.js +1 -0
  23. package/build/hooks/index.js.map +1 -1
  24. package/build/hooks/services/use_services_chat_conversation_payload.d.ts.map +1 -1
  25. package/build/hooks/services/use_services_chat_conversation_payload.js +2 -1
  26. package/build/hooks/services/use_services_chat_conversation_payload.js.map +1 -1
  27. package/build/hooks/use_api.d.ts +5 -1
  28. package/build/hooks/use_api.d.ts.map +1 -1
  29. package/build/hooks/use_api.js +2 -1
  30. package/build/hooks/use_api.js.map +1 -1
  31. package/build/hooks/use_chat_configuration.d.ts.map +1 -1
  32. package/build/hooks/use_chat_configuration.js +2 -1
  33. package/build/hooks/use_chat_configuration.js.map +1 -1
  34. package/build/hooks/use_chat_permissions.d.ts.map +1 -1
  35. package/build/hooks/use_chat_permissions.js +4 -1
  36. package/build/hooks/use_chat_permissions.js.map +1 -1
  37. package/build/hooks/use_conversation.d.ts.map +1 -1
  38. package/build/hooks/use_conversation.js +1 -0
  39. package/build/hooks/use_conversation.js.map +1 -1
  40. package/build/hooks/use_conversation_jolt_events.d.ts.map +1 -1
  41. package/build/hooks/use_conversation_jolt_events.js +2 -2
  42. package/build/hooks/use_conversation_jolt_events.js.map +1 -1
  43. package/build/hooks/use_current_person.d.ts.map +1 -1
  44. package/build/hooks/use_current_person.js +4 -2
  45. package/build/hooks/use_current_person.js.map +1 -1
  46. package/build/hooks/use_features.d.ts.map +1 -1
  47. package/build/hooks/use_features.js +2 -1
  48. package/build/hooks/use_features.js.map +1 -1
  49. package/build/hooks/use_groups.d.ts.map +1 -1
  50. package/build/hooks/use_groups.js +2 -1
  51. package/build/hooks/use_groups.js.map +1 -1
  52. package/build/hooks/use_groups_groups.d.ts.map +1 -1
  53. package/build/hooks/use_groups_groups.js +2 -1
  54. package/build/hooks/use_groups_groups.js.map +1 -1
  55. package/build/hooks/use_has_direct_messages.d.ts +2 -0
  56. package/build/hooks/use_has_direct_messages.d.ts.map +1 -0
  57. package/build/hooks/use_has_direct_messages.js +17 -0
  58. package/build/hooks/use_has_direct_messages.js.map +1 -0
  59. package/build/hooks/use_jolt.d.ts.map +1 -1
  60. package/build/hooks/use_jolt.js +2 -1
  61. package/build/hooks/use_jolt.js.map +1 -1
  62. package/build/hooks/use_organization.d.ts.map +1 -1
  63. package/build/hooks/use_organization.js +4 -1
  64. package/build/hooks/use_organization.js.map +1 -1
  65. package/build/hooks/use_person_settings.d.ts +6 -0
  66. package/build/hooks/use_person_settings.d.ts.map +1 -0
  67. package/build/hooks/use_person_settings.js +26 -0
  68. package/build/hooks/use_person_settings.js.map +1 -0
  69. package/build/navigation/index.d.ts +13 -0
  70. package/build/navigation/index.d.ts.map +1 -1
  71. package/build/navigation/index.js +9 -0
  72. package/build/navigation/index.js.map +1 -1
  73. package/build/screens/conversation_details_screen.d.ts.map +1 -1
  74. package/build/screens/conversation_details_screen.js +2 -1
  75. package/build/screens/conversation_details_screen.js.map +1 -1
  76. package/build/screens/conversation_filters/components/conversation_filters.d.ts.map +1 -1
  77. package/build/screens/conversation_filters/components/conversation_filters.js +20 -5
  78. package/build/screens/conversation_filters/components/conversation_filters.js.map +1 -1
  79. package/build/screens/conversation_filters/components/rows.d.ts +2 -1
  80. package/build/screens/conversation_filters/components/rows.d.ts.map +1 -1
  81. package/build/screens/conversation_filters/components/rows.js +2 -2
  82. package/build/screens/conversation_filters/components/rows.js.map +1 -1
  83. package/build/screens/conversation_filters/context/conversation_filter_context.d.ts.map +1 -1
  84. package/build/screens/conversation_filters/context/conversation_filter_context.js +4 -0
  85. package/build/screens/conversation_filters/context/conversation_filter_context.js.map +1 -1
  86. package/build/screens/conversation_filters/filter_types.d.ts +1 -0
  87. package/build/screens/conversation_filters/filter_types.d.ts.map +1 -1
  88. package/build/screens/conversation_filters/filter_types.js +1 -0
  89. package/build/screens/conversation_filters/filter_types.js.map +1 -1
  90. package/build/screens/conversation_filters/screen_props.d.ts +1 -0
  91. package/build/screens/conversation_filters/screen_props.d.ts.map +1 -1
  92. package/build/screens/conversation_filters/screen_props.js.map +1 -1
  93. package/build/screens/conversation_screen.d.ts.map +1 -1
  94. package/build/screens/conversation_screen.js +1 -1
  95. package/build/screens/conversation_screen.js.map +1 -1
  96. package/build/screens/conversation_select_recipients/conversation_select_direct_message_recipients_screen.d.ts +1 -2
  97. package/build/screens/conversation_select_recipients/conversation_select_direct_message_recipients_screen.d.ts.map +1 -1
  98. package/build/screens/conversation_select_recipients/conversation_select_direct_message_recipients_screen.js +319 -10
  99. package/build/screens/conversation_select_recipients/conversation_select_direct_message_recipients_screen.js.map +1 -1
  100. package/build/screens/conversation_select_type_screen.d.ts.map +1 -1
  101. package/build/screens/conversation_select_type_screen.js +4 -2
  102. package/build/screens/conversation_select_type_screen.js.map +1 -1
  103. package/build/screens/conversations/components/list_header_component.d.ts.map +1 -1
  104. package/build/screens/conversations/components/list_header_component.js +35 -17
  105. package/build/screens/conversations/components/list_header_component.js.map +1 -1
  106. package/build/screens/conversations/conversations_screen.d.ts +1 -0
  107. package/build/screens/conversations/conversations_screen.d.ts.map +1 -1
  108. package/build/screens/conversations/conversations_screen.js.map +1 -1
  109. package/build/screens/get_help_screen.d.ts.map +1 -1
  110. package/build/screens/get_help_screen.js +2 -18
  111. package/build/screens/get_help_screen.js.map +1 -1
  112. package/build/screens/index.d.ts +1 -0
  113. package/build/screens/index.d.ts.map +1 -1
  114. package/build/screens/index.js +1 -0
  115. package/build/screens/index.js.map +1 -1
  116. package/build/screens/settings_screen.d.ts +5 -0
  117. package/build/screens/settings_screen.d.ts.map +1 -0
  118. package/build/screens/settings_screen.js +56 -0
  119. package/build/screens/settings_screen.js.map +1 -0
  120. package/build/types/jolt_events/conversation_events.d.ts +2 -0
  121. package/build/types/jolt_events/conversation_events.d.ts.map +1 -1
  122. package/build/types/jolt_events/conversation_events.js.map +1 -1
  123. package/build/types/resources/conversation.d.ts +2 -0
  124. package/build/types/resources/conversation.d.ts.map +1 -1
  125. package/build/types/resources/conversation.js.map +1 -1
  126. package/build/types/resources/organization.d.ts +1 -0
  127. package/build/types/resources/organization.d.ts.map +1 -1
  128. package/build/types/resources/organization.js.map +1 -1
  129. package/build/types/resources/person.d.ts +1 -0
  130. package/build/types/resources/person.d.ts.map +1 -1
  131. package/build/types/resources/person.js.map +1 -1
  132. package/build/utils/request/conversation.d.ts +2 -1
  133. package/build/utils/request/conversation.d.ts.map +1 -1
  134. package/build/utils/request/conversation.js +15 -6
  135. package/build/utils/request/conversation.js.map +1 -1
  136. package/build/utils/time.d.ts +8 -0
  137. package/build/utils/time.d.ts.map +1 -0
  138. package/build/utils/time.js +15 -0
  139. package/build/utils/time.js.map +1 -0
  140. package/package.json +2 -2
  141. package/src/components/conversation/messages_disabled_banners.tsx +16 -2
  142. package/src/components/primitive/form_sheet.tsx +23 -6
  143. package/src/contexts/api_provider.tsx +4 -1
  144. package/src/contexts/conversations_context.tsx +6 -2
  145. package/src/hooks/direct_messages/use_direct_message_candidates.ts +1 -0
  146. package/src/hooks/groups/use_group_chat_conversation_payload.ts +2 -1
  147. package/src/hooks/index.ts +1 -0
  148. package/src/hooks/services/use_services_chat_conversation_payload.ts +2 -1
  149. package/src/hooks/use_api.ts +10 -1
  150. package/src/hooks/use_chat_configuration.ts +2 -1
  151. package/src/hooks/use_chat_permissions.ts +4 -1
  152. package/src/hooks/use_conversation.ts +1 -0
  153. package/src/hooks/use_conversation_jolt_events.ts +3 -2
  154. package/src/hooks/use_current_person.ts +4 -2
  155. package/src/hooks/use_features.ts +2 -1
  156. package/src/hooks/use_groups.ts +16 -12
  157. package/src/hooks/use_groups_groups.ts +19 -15
  158. package/src/hooks/use_has_direct_messages.ts +21 -0
  159. package/src/hooks/use_jolt.ts +2 -1
  160. package/src/hooks/use_organization.ts +4 -1
  161. package/src/hooks/use_person_settings.ts +29 -0
  162. package/src/navigation/index.tsx +11 -0
  163. package/src/screens/conversation_details_screen.tsx +2 -1
  164. package/src/screens/conversation_filters/components/conversation_filters.tsx +25 -5
  165. package/src/screens/conversation_filters/components/rows.tsx +16 -3
  166. package/src/screens/conversation_filters/context/conversation_filter_context.tsx +4 -0
  167. package/src/screens/conversation_filters/filter_types.ts +1 -0
  168. package/src/screens/conversation_filters/screen_props.ts +1 -0
  169. package/src/screens/conversation_screen.tsx +2 -1
  170. package/src/screens/conversation_select_recipients/conversation_select_direct_message_recipients_screen.tsx +513 -10
  171. package/src/screens/conversation_select_type_screen.tsx +6 -2
  172. package/src/screens/conversations/components/list_header_component.tsx +84 -34
  173. package/src/screens/conversations/conversations_screen.tsx +1 -0
  174. package/src/screens/get_help_screen.tsx +2 -29
  175. package/src/screens/index.ts +1 -0
  176. package/src/screens/settings_screen.tsx +67 -0
  177. package/src/types/jolt_events/conversation_events.ts +2 -0
  178. package/src/types/resources/conversation.ts +3 -0
  179. package/src/types/resources/organization.ts +1 -0
  180. package/src/types/resources/person.ts +1 -0
  181. package/src/utils/__tests__/conversation.test.ts +14 -0
  182. package/src/utils/request/conversation.ts +13 -5
  183. package/src/utils/time.ts +17 -0
@@ -242,6 +242,7 @@ interface FormSheetActionProps {
242
242
  accessibilityRole?: AccessibilityRole
243
243
  appearance?: FormSheetActionAppearanceUnion
244
244
  disabled?: boolean
245
+ disabledReason?: string
245
246
  }
246
247
 
247
248
  function FormSheetAction({
@@ -253,14 +254,17 @@ function FormSheetAction({
253
254
  accessibilityRole = 'button',
254
255
  appearance = 'neutral',
255
256
  disabled = false,
257
+ disabledReason,
256
258
  }: FormSheetActionProps) {
257
- const styles = useStyles({ appearance })
259
+ const styles = useStyles({ appearance, disabled })
258
260
 
259
261
  return (
260
262
  <PlatformPressable
261
263
  onPress={onPress}
262
264
  accessibilityLabel={accessibilityLabel}
263
- accessibilityHint={accessibilityHint}
265
+ accessibilityHint={
266
+ accessibilityHint ?? (disabled && disabledReason ? disabledReason : undefined)
267
+ }
264
268
  accessibilityRole={accessibilityRole}
265
269
  style={styles.actionPressable}
266
270
  disabled={disabled}
@@ -268,7 +272,12 @@ function FormSheetAction({
268
272
  {iconName && (
269
273
  <Icon name={iconName} size={16} accessibilityElementsHidden style={styles.actionIcon} />
270
274
  )}
271
- <Text style={styles.actionTitle}>{title}</Text>
275
+ <View style={styles.actionTextBlock}>
276
+ <Text style={styles.actionTitle}>{title}</Text>
277
+ {disabled && disabledReason && (
278
+ <Text style={styles.actionDisabledReason}>{disabledReason}</Text>
279
+ )}
280
+ </View>
272
281
  </PlatformPressable>
273
282
  )
274
283
  }
@@ -281,9 +290,10 @@ FormSheetAction.displayName = 'FormSheet.Action'
281
290
 
282
291
  interface Styles {
283
292
  appearance?: FormSheetActionAppearanceUnion
293
+ disabled?: boolean
284
294
  }
285
295
 
286
- const useStyles = ({ appearance = 'neutral' }: Styles = {}) => {
296
+ const useStyles = ({ appearance = 'neutral', disabled = false }: Styles = {}) => {
287
297
  const { colors } = useTheme()
288
298
  const { height } = useWindowDimensions()
289
299
  const { bottom, top } = useSafeAreaInsets()
@@ -368,10 +378,17 @@ const useStyles = ({ appearance = 'neutral' }: Styles = {}) => {
368
378
  gap: 8,
369
379
  },
370
380
  actionIcon: {
371
- color: appearanceColorsMap[appearance].iconColor,
381
+ color: disabled ? colors.textColorDefaultDisabled : appearanceColorsMap[appearance].iconColor,
382
+ },
383
+ actionTextBlock: {
384
+ flex: 1,
372
385
  },
373
386
  actionTitle: {
374
- color: appearanceColorsMap[appearance].textColor,
387
+ color: disabled ? colors.textColorDefaultDisabled : appearanceColorsMap[appearance].textColor,
388
+ },
389
+ actionDisabledReason: {
390
+ color: colors.textColorDefaultDisabled,
391
+ fontSize: 12,
375
392
  },
376
393
  })
377
394
  }
@@ -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
  () => ({
@@ -26,5 +26,6 @@ export function useDirectMessageCandidates(term: string) {
26
26
  }) as Promise<ApiCollection<DirectMessageCandidateResource>>,
27
27
  enabled: term.length >= 1,
28
28
  staleTime: 1000 * 60,
29
+ throwOnError: true,
29
30
  })
30
31
  }
@@ -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 },
@@ -17,13 +17,26 @@ import { FilterTypes } from '../filter_types'
17
17
  import { FilteredGroup } from '../hooks/filters'
18
18
  import { ConversationFilterStackParamList } from '../screen_props'
19
19
 
20
- export type FilterProps = { group_source_app_name?: string; isActive: boolean; filter: FilterTypes }
20
+ export type FilterProps = {
21
+ group_source_app_name?: string
22
+ show_direct_messages?: boolean
23
+ isActive: boolean
24
+ filter: FilterTypes
25
+ }
21
26
 
22
- export const FilterRow = ({ group_source_app_name, isActive, filter }: FilterProps) => {
27
+ export const FilterRow = ({
28
+ group_source_app_name,
29
+ show_direct_messages,
30
+ isActive,
31
+ filter,
32
+ }: FilterProps) => {
23
33
  const { setAppFilter } = useContext(FilterContext)
24
34
 
25
35
  return (
26
- <PressableRow onPress={() => setAppFilter({ group_source_app_name })} isActive={isActive}>
36
+ <PressableRow
37
+ onPress={() => setAppFilter({ group_source_app_name, show_direct_messages })}
38
+ isActive={isActive}
39
+ >
27
40
  <Text>{filter}</Text>
28
41
  </PressableRow>
29
42
  )
@@ -21,6 +21,7 @@ export const FilterContext = createContext<{
21
21
  params: {
22
22
  chat_group_graph_id: undefined,
23
23
  group_source_app_name: undefined,
24
+ show_direct_messages: undefined,
24
25
  },
25
26
  scrollOffset: 0,
26
27
  setScrollOffset: () => {},
@@ -55,6 +56,7 @@ export const FilterProvider = ({ children }: PropsWithChildren) => {
55
56
  StackActions.popTo('Conversations', {
56
57
  chat_group_graph_id: undefined,
57
58
  group_source_app_name: undefined,
59
+ show_direct_messages: undefined,
58
60
  })
59
61
  )
60
62
  },
@@ -62,12 +64,14 @@ export const FilterProvider = ({ children }: PropsWithChildren) => {
62
64
  navigation.setParams({
63
65
  chat_group_graph_id: params.chat_group_graph_id,
64
66
  group_source_app_name: undefined,
67
+ show_direct_messages: undefined,
65
68
  })
66
69
  },
67
70
  setAppFilter: (params: Omit<ConversationFiltersParams, 'chat_group_graph_id'>) => {
68
71
  navigation.setParams({
69
72
  chat_group_graph_id: undefined,
70
73
  group_source_app_name: params.group_source_app_name,
74
+ show_direct_messages: params.show_direct_messages,
71
75
  })
72
76
  },
73
77
  applyFilters: (params: ConversationFiltersParams) => {
@@ -1,5 +1,6 @@
1
1
  export enum FilterTypes {
2
2
  All = 'All conversations',
3
+ DirectMessages = 'Direct messages',
3
4
  Groups = 'All my groups',
4
5
  Teams = 'All my teams and plans',
5
6
  More = 'More',
@@ -4,6 +4,7 @@ import { GraphId } from '../../types/resources/group_resource'
4
4
  export type ConversationFiltersParams = {
5
5
  chat_group_graph_id?: GraphId
6
6
  group_source_app_name?: string
7
+ show_direct_messages?: boolean
7
8
  }
8
9
 
9
10
  export type ConversationFiltersScreenProps = StaticScreenProps<ConversationFiltersParams>
@@ -239,7 +239,8 @@ function ConversationBottomBar({
239
239
  replyRootId,
240
240
  currentlyEditingMessage,
241
241
  }: ConversationBottomBarProps) {
242
- if (conversation.disabled) return <ConversationDisabledBanner />
242
+ if (conversation.disabled)
243
+ return <ConversationDisabledBanner disabledReason={conversation.disabledReason} />
243
244
  if (!canReply) return <MemberMessagesDisabledBanner />
244
245
  return (
245
246
  <MessageForm.Root