@planningcenter/chat-react-native 3.39.1-rc.0 → 3.39.1-rc.2

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 (40) hide show
  1. package/build/components/conversation/message_form.d.ts +2 -1
  2. package/build/components/conversation/message_form.d.ts.map +1 -1
  3. package/build/components/conversation/message_form.js +2 -1
  4. package/build/components/conversation/message_form.js.map +1 -1
  5. package/build/hooks/use_attachment_uploader.d.ts +2 -1
  6. package/build/hooks/use_attachment_uploader.d.ts.map +1 -1
  7. package/build/hooks/use_attachment_uploader.js +9 -2
  8. package/build/hooks/use_attachment_uploader.js.map +1 -1
  9. package/build/hooks/use_video_moderation_jolt_events.d.ts +4 -0
  10. package/build/hooks/use_video_moderation_jolt_events.d.ts.map +1 -0
  11. package/build/hooks/use_video_moderation_jolt_events.js +33 -0
  12. package/build/hooks/use_video_moderation_jolt_events.js.map +1 -0
  13. package/build/screens/conversation_screen.d.ts.map +1 -1
  14. package/build/screens/conversation_screen.js +5 -3
  15. package/build/screens/conversation_screen.js.map +1 -1
  16. package/build/screens/notification_settings_screen.d.ts.map +1 -1
  17. package/build/screens/notification_settings_screen.js +2 -1
  18. package/build/screens/notification_settings_screen.js.map +1 -1
  19. package/build/screens/preferred_app_selection_screen.d.ts.map +1 -1
  20. package/build/screens/preferred_app_selection_screen.js +2 -1
  21. package/build/screens/preferred_app_selection_screen.js.map +1 -1
  22. package/build/utils/chat_type_label.d.ts +2 -0
  23. package/build/utils/chat_type_label.d.ts.map +1 -0
  24. package/build/utils/chat_type_label.js +6 -0
  25. package/build/utils/chat_type_label.js.map +1 -0
  26. package/build/utils/index.d.ts +1 -0
  27. package/build/utils/index.d.ts.map +1 -1
  28. package/build/utils/index.js +1 -0
  29. package/build/utils/index.js.map +1 -1
  30. package/package.json +2 -2
  31. package/src/__tests__/hooks/use_attachment_uploader.test.tsx +35 -11
  32. package/src/__tests__/hooks/use_video_moderation_jolt_events.test.tsx +81 -0
  33. package/src/components/conversation/message_form.tsx +3 -0
  34. package/src/hooks/use_attachment_uploader.ts +13 -3
  35. package/src/hooks/use_video_moderation_jolt_events.ts +46 -0
  36. package/src/screens/conversation_screen.tsx +6 -0
  37. package/src/screens/notification_settings_screen.tsx +2 -1
  38. package/src/screens/preferred_app_selection_screen.tsx +2 -1
  39. package/src/utils/chat_type_label.ts +4 -0
  40. package/src/utils/index.ts +1 -0
@@ -21,9 +21,11 @@ export interface FileError {
21
21
  export function useAttachmentUploader({
22
22
  conversationId,
23
23
  draftAttachments,
24
+ onFlaggedInMessageForm,
24
25
  }: {
25
26
  conversationId: number
26
27
  draftAttachments?: FileAttachment[]
28
+ onFlaggedInMessageForm?: (id: string) => void
27
29
  }) {
28
30
  const apiClient = useApiClient()
29
31
  const uploadApi = useUploadClient()
@@ -31,18 +33,26 @@ export function useAttachmentUploader({
31
33
  useChatConfiguration()
32
34
  const maxFileSizeInMb = Number((maxFileSizeInBytes / (1024 * 1024)).toFixed(1))
33
35
  const [attachments, setAttachments] = useState<FileAttachment[]>(() => draftAttachments || [])
36
+ const attachmentsRef = useRef<FileAttachment[]>(draftAttachments ?? [])
34
37
  const uploadState = useRef<FileUploadState>({})
35
38
  const [lastUploadId, setLastUploadId] = useState<string>()
36
39
  const numberOfAttachments = attachments.length
37
40
  const [errorMessage, setErrorMessage] = useState<string | null>(null)
38
41
 
42
+ useEffect(() => {
43
+ attachmentsRef.current = attachments
44
+ }, [attachments])
45
+
39
46
  const { data: currentPerson } = useApiGet<CurrentPersonResource>(currentPersonRequestArgs)
40
47
  const joltChannel = useJoltChannel(`chat.people.${currentPerson?.id}`, Boolean(currentPerson?.id))
41
48
  useJoltEvent(joltChannel, 'attachment.flagged', (e: AttachmentFlaggedEvent) => {
42
49
  const flaggedId = e.data.data.attachment_id
43
- setAttachments(prev =>
44
- prev.map(a => (a.id === flaggedId ? { ...a, flagged: true, status: 'error' } : a))
45
- )
50
+ if (attachmentsRef.current.some(a => a.id === flaggedId)) {
51
+ setAttachments(prev =>
52
+ prev.map(a => (a.id === flaggedId ? { ...a, flagged: true, status: 'error' } : a))
53
+ )
54
+ onFlaggedInMessageForm?.(flaggedId)
55
+ }
46
56
  })
47
57
 
48
58
  const handleFilesAttached = useCallback(
@@ -0,0 +1,46 @@
1
+ import debounce from 'lodash/debounce'
2
+ import { useCallback, useEffect, useMemo, useRef } from 'react'
3
+ import { Alert } from 'react-native'
4
+ import type { CurrentPersonResource } from '../types'
5
+ import type { AttachmentFlaggedEvent } from '../types/jolt_events/attachment_events'
6
+ import { useApiGet } from './use_api'
7
+ import { currentPersonRequestArgs } from './use_current_person'
8
+ import { useJoltChannel, useJoltEvent } from './use_jolt'
9
+
10
+ const DEBOUNCE_MS = 500
11
+
12
+ export function useVideoModerationJoltEvents() {
13
+ const { data: currentPerson } = useApiGet<CurrentPersonResource>(currentPersonRequestArgs)
14
+ const joltChannel = useJoltChannel(`chat.people.${currentPerson?.id}`, Boolean(currentPerson?.id))
15
+ const pendingIdsRef = useRef<Set<string>>(new Set())
16
+
17
+ const flushAlert = useMemo(
18
+ () =>
19
+ debounce(() => {
20
+ const count = pendingIdsRef.current.size
21
+ if (count === 0) return
22
+ pendingIdsRef.current.clear()
23
+ Alert.alert(
24
+ 'Video removed',
25
+ count === 1
26
+ ? "A video you uploaded was flagged because it doesn't meet our content guidelines."
27
+ : "Some videos you uploaded were flagged because they don't meet our content guidelines."
28
+ )
29
+ }, DEBOUNCE_MS),
30
+ []
31
+ )
32
+
33
+ useEffect(() => () => flushAlert.cancel(), [flushAlert])
34
+
35
+ const markHandledByMessageForm = useCallback((id: string) => {
36
+ pendingIdsRef.current.delete(id)
37
+ }, [])
38
+
39
+ useJoltEvent(joltChannel, 'attachment.flagged', (e: AttachmentFlaggedEvent) => {
40
+ if (!e.data.data.content_type.startsWith('video/')) return
41
+ pendingIdsRef.current.add(e.data.data.attachment_id)
42
+ flushAlert()
43
+ })
44
+
45
+ return { markHandledByMessageForm }
46
+ }
@@ -38,6 +38,7 @@ import {
38
38
  normalizeAnalyticsMetadata,
39
39
  usePublishProductAnalyticsEvent,
40
40
  } from '../hooks/use_product_analytics'
41
+ import { useVideoModerationJoltEvents } from '../hooks/use_video_moderation_jolt_events'
41
42
  import { ConversationResource } from '../types/resources/conversation'
42
43
  import { ConversationBadgeResource } from '../types/resources/conversation_badge'
43
44
  import { MessageResource } from '../types/resources/message'
@@ -94,6 +95,7 @@ function ConversationScreenContent({ route }: ConversationScreenProps) {
94
95
  })
95
96
  useConversationJoltEvents({ conversationId: conversation_id })
96
97
  useConversationMessagesJoltEvents({ conversationId: conversation_id })
98
+ const { markHandledByMessageForm } = useVideoModerationJoltEvents()
97
99
  useEnsureConversationsRouteExists()
98
100
  useMarkLatestMessageRead({ conversation, messages })
99
101
  const messagesWithSeparators = groupMessages({
@@ -223,6 +225,7 @@ function ConversationScreenContent({ route }: ConversationScreenProps) {
223
225
  replyRootAuthorFirstName={replyRootAuthorFirstName}
224
226
  replyRootId={reply_root_id}
225
227
  currentlyEditingMessage={currentlyEditingMessage}
228
+ onFlaggedInMessageForm={markHandledByMessageForm}
226
229
  />
227
230
  </KeyboardView>
228
231
  </View>
@@ -235,6 +238,7 @@ interface ConversationBottomBarProps {
235
238
  replyRootAuthorFirstName: string | undefined
236
239
  replyRootId: string | null | undefined
237
240
  currentlyEditingMessage: MessageResource | undefined
241
+ onFlaggedInMessageForm?: (id: string) => void
238
242
  }
239
243
 
240
244
  function ConversationBottomBar({
@@ -243,6 +247,7 @@ function ConversationBottomBar({
243
247
  replyRootAuthorFirstName,
244
248
  replyRootId,
245
249
  currentlyEditingMessage,
250
+ onFlaggedInMessageForm,
246
251
  }: ConversationBottomBarProps) {
247
252
  if (conversation.disabled)
248
253
  return <ConversationDisabledBanner disabledReason={conversation.disabledReason} />
@@ -253,6 +258,7 @@ function ConversationBottomBar({
253
258
  conversation={conversation}
254
259
  replyRootId={replyRootId}
255
260
  currentlyEditingMessage={currentlyEditingMessage}
261
+ onFlaggedInMessageForm={onFlaggedInMessageForm}
256
262
  key={
257
263
  currentlyEditingMessage
258
264
  ? `edit-message-form-${currentlyEditingMessage.id}`
@@ -7,6 +7,7 @@ import { Badge, Heading, Icon, Text } from '../components'
7
7
  import { HeaderTextButton } from '../components/display/platform_modal_header_buttons'
8
8
  import { useTheme } from '../hooks'
9
9
  import { isDefined } from '../types'
10
+ import { chatTypeLabel } from '../utils'
10
11
  import { useGroups } from './notification_settings/hooks/groups'
11
12
  import { useChatTypes } from './preferred_app/hooks/use_chat_types'
12
13
 
@@ -90,7 +91,7 @@ export function NotificationSettingsScreen({}: NotificationSettingsScreenProps)
90
91
  ...chatTypes.map(type => ({
91
92
  type: SectionTypes.link,
92
93
  data: {
93
- title: `${type.title} conversations`,
94
+ title: chatTypeLabel(type.title),
94
95
  rightLabel: type.preferredApp,
95
96
  onPress: () =>
96
97
  navigation.navigate('PreferredAppSelection', {
@@ -6,6 +6,7 @@ import { Image, StyleSheet, View, type ViewStyle } from 'react-native'
6
6
  import { Heading, Icon, Text } from '../components'
7
7
  import { useApiClient, useTheme } from '../hooks'
8
8
  import { useAppName } from '../hooks/use_app_name'
9
+ import { chatTypeLabel } from '../utils'
9
10
  import { throwResponseError } from '../utils/response_error'
10
11
  import { PreferredApp, useChatTypes } from './preferred_app/hooks/use_chat_types'
11
12
 
@@ -41,7 +42,7 @@ export function PreferredAppSelectionScreen({ route }: PreferredAppSelectionScre
41
42
  updateChatType(app)
42
43
  }
43
44
 
44
- const sectionTitle = `${chatType?.title} conversations`
45
+ const sectionTitle = chatType ? chatTypeLabel(chatType.title) : undefined
45
46
 
46
47
  return (
47
48
  <View style={styles.container}>
@@ -0,0 +1,4 @@
1
+ export function chatTypeLabel(title: string): string {
2
+ if (title.toLowerCase() === 'direct message') return 'Direct messages'
3
+ return `${title} conversations`
4
+ }
@@ -1,5 +1,6 @@
1
1
  export * from './assert_keys_are_numbers'
2
2
  export * from './attachment_kind'
3
+ export * from './chat_type_label'
3
4
  export * from './cache'
4
5
  export * from './client'
5
6
  export * from './convert_attachments_for_create'