@planningcenter/chat-react-native 3.37.0-rc.4 → 3.38.0-rc.0

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 (29) hide show
  1. package/build/components/conversation/message_form.d.ts.map +1 -1
  2. package/build/components/conversation/message_form.js +19 -7
  3. package/build/components/conversation/message_form.js.map +1 -1
  4. package/build/hooks/use_attachment_uploader.d.ts.map +1 -1
  5. package/build/hooks/use_attachment_uploader.js +9 -0
  6. package/build/hooks/use_attachment_uploader.js.map +1 -1
  7. package/build/hooks/use_features.d.ts +1 -0
  8. package/build/hooks/use_features.d.ts.map +1 -1
  9. package/build/hooks/use_features.js +1 -0
  10. package/build/hooks/use_features.js.map +1 -1
  11. package/build/hooks/use_upload_client.d.ts +4 -0
  12. package/build/hooks/use_upload_client.d.ts.map +1 -1
  13. package/build/hooks/use_upload_client.js +13 -1
  14. package/build/hooks/use_upload_client.js.map +1 -1
  15. package/build/types/jolt_events/attachment_events.d.ts +14 -0
  16. package/build/types/jolt_events/attachment_events.d.ts.map +1 -0
  17. package/build/types/jolt_events/attachment_events.js +2 -0
  18. package/build/types/jolt_events/attachment_events.js.map +1 -0
  19. package/build/types/jolt_events/index.d.ts +3 -1
  20. package/build/types/jolt_events/index.d.ts.map +1 -1
  21. package/build/types/jolt_events/index.js.map +1 -1
  22. package/package.json +2 -2
  23. package/src/__tests__/hooks/use_attachment_uploader.test.tsx +36 -0
  24. package/src/components/conversation/message_form.tsx +21 -7
  25. package/src/hooks/use_attachment_uploader.ts +14 -1
  26. package/src/hooks/use_features.ts +1 -0
  27. package/src/hooks/use_upload_client.ts +19 -1
  28. package/src/types/jolt_events/attachment_events.ts +14 -0
  29. package/src/types/jolt_events/index.ts +3 -0
@@ -1 +1 @@
1
- {"version":3,"file":"message_form.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/message_form.tsx"],"names":[],"mappings":"AACA,OAAO,KAAuD,MAAM,OAAO,CAAA;AAC3E,OAAO,EAQL,SAAS,EAEV,MAAM,cAAc,CAAA;AAiBrB,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AA2BnE,eAAO,MAAM,WAAW;;;;;;CAOvB,CAAA;AAED,UAAU,qBAAsB,SAAQ,SAAS;IAC/C,YAAY,EAAE,oBAAoB,CAAA;IAClC,uBAAuB,CAAC,EAAE,eAAe,GAAG,IAAI,CAAA;IAChD,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,wBAAwB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACzC;AAmCD,iBAAS,eAAe,CAAC,EACvB,YAAY,EACZ,uBAAuB,EACvB,QAAQ,EACR,WAAW,EACX,wBAAwB,GACzB,EAAE,qBAAqB,qBA0KvB;AA4HD,iBAAS,gBAAgB,sBAwDxB;AAcD,iBAAS,oBAAoB,sBAgD5B;AAED,iBAAS,2BAA2B,6BAuHnC;AAED,iBAAS,mBAAmB,6BAkC3B"}
1
+ {"version":3,"file":"message_form.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/message_form.tsx"],"names":[],"mappings":"AACA,OAAO,KAAuD,MAAM,OAAO,CAAA;AAC3E,OAAO,EAQL,SAAS,EAEV,MAAM,cAAc,CAAA;AAiBrB,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AA4BnE,eAAO,MAAM,WAAW;;;;;;CAOvB,CAAA;AAED,UAAU,qBAAsB,SAAQ,SAAS;IAC/C,YAAY,EAAE,oBAAoB,CAAA;IAClC,uBAAuB,CAAC,EAAE,eAAe,GAAG,IAAI,CAAA;IAChD,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,wBAAwB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACzC;AAmCD,iBAAS,eAAe,CAAC,EACvB,YAAY,EACZ,uBAAuB,EACvB,QAAQ,EACR,WAAW,EACX,wBAAwB,GACzB,EAAE,qBAAqB,qBA0KvB;AAyID,iBAAS,gBAAgB,sBAwDxB;AAcD,iBAAS,oBAAoB,sBAgD5B;AAED,iBAAS,2BAA2B,6BAuHnC;AAED,iBAAS,mBAAmB,6BAkC3B"}
@@ -218,20 +218,32 @@ function MessageFormAttachments() {
218
218
  })}
219
219
  </ScrollView>);
220
220
  }
221
+ function getFileType(attachments) {
222
+ const kinds = new Set(attachments.map(({ file }) => {
223
+ if (file.type.startsWith('image/'))
224
+ return 'image';
225
+ if (file.type.startsWith('video/'))
226
+ return 'video';
227
+ return 'file';
228
+ }));
229
+ return kinds.size === 1 ? [...kinds][0] : 'file';
230
+ }
221
231
  function FlaggedContentBanner() {
222
232
  const styles = useMessageFormStyles();
223
233
  const { attachmentUploader } = React.useContext(MessageFormContext);
224
- const flaggedCount = attachmentUploader?.flaggedAttachmentCount || 0;
234
+ const flaggedAttachments = attachmentUploader?.attachments.filter(a => a.flagged) ?? [];
235
+ const flaggedCount = flaggedAttachments.length;
225
236
  if (flaggedCount === 0)
226
237
  return null;
227
238
  const isSingular = flaggedCount === 1;
228
- const buttonTitle = isSingular ? 'Remove flagged image' : 'Remove flagged images';
239
+ const fileType = getFileType(flaggedAttachments);
240
+ const buttonTitle = isSingular ? `Remove flagged ${fileType}` : `Remove flagged ${fileType}s`;
229
241
  return (<View style={styles.flaggedBannerContainer}>
230
242
  <BannerPrimitive.Root appearance="error">
231
243
  <BannerPrimitive.StaticLayout>
232
244
  <BannerPrimitive.StatusIcon />
233
245
  <BannerPrimitive.Content>
234
- <BannerMessage isSingular={isSingular}/>
246
+ <BannerMessage isSingular={isSingular} fileType={fileType}/>
235
247
  <View style={styles.flaggedBannerButtonRow}>
236
248
  <Button title={buttonTitle} size="sm" appearance="danger" onPress={() => attachmentUploader?.removeFlaggedAttachments()} accessibilityLabel={buttonTitle}/>
237
249
  </View>
@@ -240,17 +252,17 @@ function FlaggedContentBanner() {
240
252
  </BannerPrimitive.Root>
241
253
  </View>);
242
254
  }
243
- function BannerMessage({ isSingular }) {
255
+ function BannerMessage({ isSingular, fileType }) {
244
256
  const styles = useMessageFormStyles();
245
257
  const contentGuidelinesLink = (<Text style={styles.flaggedBannerLink} onPress={() => Linking.openURL('https://www.planningcenter.com/terms#:~:text=4.%20Acceptable%20Use%20of%20Planning%20Center')} accessibilityRole="link">
246
258
  content guidelines
247
259
  </Text>);
248
260
  return isSingular ? (<Text style={styles.flaggedBannerText}>
249
- An uploaded image was flagged because it doesn't meet our {contentGuidelinesLink}. Please
261
+ An uploaded {fileType} was flagged because it doesn't meet our {contentGuidelinesLink}. Please
250
262
  remove before proceeding.
251
263
  </Text>) : (<Text style={styles.flaggedBannerText}>
252
- Some uploaded images were flagged because they don't meet our {contentGuidelinesLink}. Please
253
- remove them before proceeding.
264
+ Some uploaded {fileType}s were flagged because they don't meet our {contentGuidelinesLink}.
265
+ Please remove them before proceeding.
254
266
  </Text>);
255
267
  }
256
268
  function MessageFormInput() {
@@ -1 +1 @@
1
- {"version":3,"file":"message_form.js","sourceRoot":"","sources":["../../../src/components/conversation/message_form.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useNavigation, useTheme as useNavigationTheme, useRoute } from '@react-navigation/native'\nimport React, { useCallback, useContext, useEffect, useState } from 'react'\nimport {\n Linking,\n Platform,\n Pressable,\n ScrollView,\n StyleSheet,\n TextInput,\n View,\n ViewProps,\n Keyboard,\n} from 'react-native'\nimport LinearGradient from 'react-native-linear-gradient'\nimport { Heading, Icon, IconButton, IconString, Text, TextButton } from '../../components'\nimport { ChatContext } from '../../contexts/chat_context'\nimport {\n useCreateAndroidRippleColor,\n useTheme,\n useInteractionGhostBackgroundColor,\n useScalableNumberOfLines,\n useFontScale,\n} from '../../hooks'\nimport { useAttachmentUploader } from '../../hooks/use_attachment_uploader'\nimport { useBroadcastTypingStatus } from '../../hooks/use_broadcast_typing_status'\nimport { useChatConfiguration } from '../../hooks/use_chat_configuration'\nimport { useMessageCreateOrUpdate } from '../../hooks/use_message_create_or_update'\nimport { useMessageDraft } from '../../hooks/use_message_draft'\nimport { ConversationScreenProps } from '../../screens/conversation_screen'\nimport { ConversationResource, MessageResource } from '../../types'\nimport {\n DenormalizedAttachmentResourceForCreate,\n DenormalizedMessageAttachmentResourceForCreate,\n} from '../../types/resources/denormalized_attachment_resource_for_create'\nimport {\n MAX_FONT_SIZE_MULTIPLIER_LANDMARK,\n platformFontWeightMedium,\n platformPressedOpacityStyle,\n} from '../../utils'\nimport { pickAttachmentPreviewKind } from '../../utils/attachment_kind'\nimport { canSubmitMessage } from '../../utils/can_submit_message'\nimport { isInputEditable } from '../../utils/is_input_editable'\nimport {\n DocumentPicker,\n DocumentPickerResult,\n Haptic,\n ImagePicker,\n ImagePickerResult,\n} from '../../utils/native_adapters'\nimport { tokens } from '../../vendor/tapestry/tokens'\nimport { Button } from '../display/button'\nimport BannerPrimitive from '../primitive/banner_primitive'\nimport { MessageFormAttachmentFile } from './message_form/message_form_attachment_file'\nimport { MessageFormAttachmentImage } from './message_form/message_form_attachment_image'\nimport { MessageFormAttachmentVideo } from './message_form/message_form_attachment_video'\n\nexport const MessageForm = {\n Root: MessageFormRoot,\n\n TextInput: MessageFormInput,\n SubmitButton: MessageFormSubmitBtn,\n AttachmentPicker: MessageFormAttachmentPicker,\n Commands: MessageFormCommands,\n}\n\ninterface MessagesFormRootProps extends ViewProps {\n conversation: ConversationResource\n currentlyEditingMessage?: MessageResource | null\n replyRootId?: string | null\n replyRootAuthorFirstName?: string | null\n}\n\nconst MessageFormContext = React.createContext<{\n text: string\n setText: (text: string) => void\n onSubmit: () => void\n disabled: boolean\n inputEditable: boolean\n canGiphy: boolean\n usingGiphy: boolean\n setUsingGiphy: (usingGiphy: boolean) => void\n attachmentUploader?: ReturnType<typeof useAttachmentUploader>\n currentlyEditingMessage?: MessageResource | null\n reset: () => void\n replyRootId?: string | null\n replyRootAuthorFirstName?: string | null\n}>({\n text: '',\n setText: (_text: string) => {},\n onSubmit: () => {},\n disabled: false,\n inputEditable: true,\n canGiphy: false,\n usingGiphy: false,\n setUsingGiphy: (_usingGiphy: boolean) => {},\n currentlyEditingMessage: null,\n reset: () => {},\n replyRootId: undefined,\n replyRootAuthorFirstName: undefined,\n})\n\nconst GIPHY_MAX_LENGTH = 50\nconst MAX_MESSAGE_LENGTH = 5000\nconst PLACEHOLDER_FONT_SCALE_THRESHOLD = 1.7\n\nfunction MessageFormRoot({\n conversation,\n currentlyEditingMessage,\n children,\n replyRootId,\n replyRootAuthorFirstName,\n}: MessagesFormRootProps) {\n const { giphyApiKey } = useContext(ChatContext)\n const canGiphy = !!giphyApiKey && !currentlyEditingMessage\n const styles = useMessageFormStyles()\n const { draft, saveDraft, clearDraft } = useMessageDraft()\n const [text, setText] = React.useState(() => {\n // Initialize from draft only when not editing a message\n return currentlyEditingMessage ? '' : draft?.text || ''\n })\n const [usingGiphy, setUsingGiphy] = useState(false)\n const navigation = useNavigation()\n const route = useRoute() as ConversationScreenProps['route']\n const {\n status,\n isPending,\n reset: resetMutation,\n mutateAsync,\n } = useMessageCreateOrUpdate({\n conversationId: conversation.id,\n message: currentlyEditingMessage || undefined,\n replyRootId,\n })\n const attachmentUploader = useAttachmentUploader({\n conversationId: conversation.id,\n draftAttachments: currentlyEditingMessage ? undefined : draft?.attachments,\n })\n const resetAttachmentUploader = attachmentUploader.reset\n\n const reset = useCallback(() => {\n resetAttachmentUploader()\n resetMutation()\n setText('')\n setUsingGiphy(false)\n if (!currentlyEditingMessage) {\n clearDraft()\n }\n navigation.setParams({\n editing_message_id: null,\n })\n }, [resetAttachmentUploader, resetMutation, navigation, currentlyEditingMessage, clearDraft])\n\n useEffect(() => {\n if (canGiphy && !usingGiphy && text.startsWith('/giphy ')) {\n setUsingGiphy(true)\n setText('')\n }\n }, [canGiphy, text, usingGiphy])\n\n useEffect(() => {\n switch (status) {\n case 'success':\n reset()\n break\n }\n }, [reset, status])\n\n useEffect(() => {\n if (route.params.clear_input) {\n reset()\n navigation.setParams({ ...route.params, clear_input: false })\n }\n }, [reset, navigation, route.params])\n\n // Save draft when text or attachments change (debounced)\n useEffect(() => {\n // Don't save drafts when editing a message\n if (currentlyEditingMessage) return\n\n const timeoutId = setTimeout(() => {\n saveDraft(text, attachmentUploader.attachments)\n }, 500)\n\n return () => clearTimeout(timeoutId)\n }, [text, attachmentUploader.attachments, saveDraft, currentlyEditingMessage])\n\n const canSubmit = canSubmitMessage({\n isPending,\n conversationDisabled: conversation.disabled ?? false,\n pendingUploads: !!attachmentUploader?.pendingUploads,\n hasFlaggedAttachments: !!attachmentUploader?.flaggedAttachmentCount,\n hasText: text.length > 0,\n hasSendableAttachments: !!attachmentUploader?.attachmentIds?.length,\n })\n const disabled = !canSubmit\n const inputEditable = isInputEditable({\n isPending,\n conversationDisabled: conversation.disabled ?? false,\n })\n\n /*\n Opening a FormSheet on Android while the keyboard is visible breaks the FormSheet's height & position.\n This is a workaround to ensure we don't open the FormSheet while the keyboard is visible.\n */\n const navigateToSendGiphyAfterKeyboardHides = useCallback(() => {\n if (!Keyboard.isVisible() || Platform.OS === 'ios') {\n navigation.navigate('SendGiphy', {\n conversation_id: conversation.id,\n search_term: text,\n })\n } else {\n const keyboardListener = Keyboard.addListener('keyboardDidHide', () => {\n navigation.navigate('SendGiphy', {\n conversation_id: conversation.id,\n search_term: text,\n })\n keyboardListener?.remove()\n })\n }\n }, [conversation.id, navigation, text])\n\n const handleSubmit = () => {\n if (!canSubmit) return\n\n if (canGiphy && usingGiphy) {\n TextInput.State.blurTextInput(TextInput.State.currentlyFocusedInput())\n Haptic.impactLight()\n navigateToSendGiphyAfterKeyboardHides()\n } else {\n let attachmentsForSubmit: DenormalizedAttachmentResourceForCreate[] =\n attachmentUploader.attachments\n .filter(a => a.status === 'success' && a.id)\n .map(\n (attachment): DenormalizedMessageAttachmentResourceForCreate => ({\n type: 'MessageAttachment',\n id: attachment.id as string,\n file: attachment.file,\n })\n )\n if (currentlyEditingMessage) {\n mutateAsync({ text })\n reset()\n } else {\n mutateAsync({ text, attachments: attachmentsForSubmit })\n reset()\n }\n }\n }\n\n useEffect(() => {\n if (currentlyEditingMessage) {\n setText(currentlyEditingMessage.text || '')\n }\n }, [currentlyEditingMessage])\n\n return (\n <MessageFormContext.Provider\n value={{\n text,\n setText,\n onSubmit: handleSubmit,\n disabled,\n inputEditable,\n canGiphy,\n usingGiphy,\n setUsingGiphy,\n attachmentUploader,\n currentlyEditingMessage,\n reset,\n replyRootId,\n replyRootAuthorFirstName,\n }}\n >\n <View style={styles.container}>\n <EditingIndicator />\n <ReplyIndicator />\n <FlaggedContentBanner />\n <View style={styles.textInputContainer}>{children}</View>\n </View>\n </MessageFormContext.Provider>\n )\n}\n\nfunction MessageFormAttachments() {\n const styles = useMessageFormStyles()\n const { attachmentUploader } = React.useContext(MessageFormContext)\n const numberOfAttachments = attachmentUploader?.attachments?.length || 0\n const attachments = attachmentUploader?.attachments || []\n\n if (numberOfAttachments === 0) {\n return null\n }\n\n return (\n <ScrollView\n horizontal\n showsHorizontalScrollIndicator={false}\n contentContainerStyle={styles.messageFormAttachments}\n >\n {attachments.map(attachment => {\n const kind = pickAttachmentPreviewKind(attachment.file.type, attachment.file.name)\n const remove = () => attachmentUploader?.removeAttachment(attachment)\n\n if (kind === 'video') {\n return (\n <MessageFormAttachmentVideo\n key={attachment.file.uri}\n name={attachment.file.name}\n status={attachment.status}\n removeAttachment={remove}\n />\n )\n }\n\n if (kind === 'file') {\n return (\n <MessageFormAttachmentFile\n key={attachment.file.uri}\n name={attachment.file.name}\n contentType={attachment.file.type}\n status={attachment.status}\n removeAttachment={remove}\n />\n )\n }\n\n return (\n <MessageFormAttachmentImage\n key={attachment.file.uri}\n uri={attachment.file.uri}\n alt={attachment.file.name}\n status={attachment.status}\n width={attachment.file.width}\n height={attachment.file.height}\n removeAttachment={remove}\n />\n )\n })}\n </ScrollView>\n )\n}\n\nfunction FlaggedContentBanner() {\n const styles = useMessageFormStyles()\n const { attachmentUploader } = React.useContext(MessageFormContext)\n const flaggedCount = attachmentUploader?.flaggedAttachmentCount || 0\n\n if (flaggedCount === 0) return null\n\n const isSingular = flaggedCount === 1\n const buttonTitle = isSingular ? 'Remove flagged image' : 'Remove flagged images'\n\n return (\n <View style={styles.flaggedBannerContainer}>\n <BannerPrimitive.Root appearance=\"error\">\n <BannerPrimitive.StaticLayout>\n <BannerPrimitive.StatusIcon />\n <BannerPrimitive.Content>\n <BannerMessage isSingular={isSingular} />\n <View style={styles.flaggedBannerButtonRow}>\n <Button\n title={buttonTitle}\n size=\"sm\"\n appearance=\"danger\"\n onPress={() => attachmentUploader?.removeFlaggedAttachments()}\n accessibilityLabel={buttonTitle}\n />\n </View>\n </BannerPrimitive.Content>\n </BannerPrimitive.StaticLayout>\n </BannerPrimitive.Root>\n </View>\n )\n}\n\nfunction BannerMessage({ isSingular }: { isSingular: boolean }) {\n const styles = useMessageFormStyles()\n\n const contentGuidelinesLink = (\n <Text\n style={styles.flaggedBannerLink}\n onPress={() =>\n Linking.openURL(\n 'https://www.planningcenter.com/terms#:~:text=4.%20Acceptable%20Use%20of%20Planning%20Center'\n )\n }\n accessibilityRole=\"link\"\n >\n content guidelines\n </Text>\n )\n\n return isSingular ? (\n <Text style={styles.flaggedBannerText}>\n An uploaded image was flagged because it doesn't meet our {contentGuidelinesLink}. Please\n remove before proceeding.\n </Text>\n ) : (\n <Text style={styles.flaggedBannerText}>\n Some uploaded images were flagged because they don't meet our {contentGuidelinesLink}. Please\n remove them before proceeding.\n </Text>\n )\n}\n\nfunction MessageFormInput() {\n const styles = useMessageFormStyles()\n const theme = useTheme()\n const fontScale = useFontScale()\n const { text, setText, onSubmit, inputEditable, usingGiphy, attachmentUploader, replyRootId } =\n React.useContext(MessageFormContext)\n const attachmentError = attachmentUploader?.errorMessage\n\n const broadcastTypingStatus = useBroadcastTypingStatus()\n\n const handleTextChange = (newText: string) => {\n setText(newText)\n\n if (newText.length > 0) {\n broadcastTypingStatus()\n }\n }\n\n const compactPlaceholder = fontScale >= PLACEHOLDER_FONT_SCALE_THRESHOLD\n let placeholder = compactPlaceholder ? 'Message' : 'Send a message'\n if (replyRootId) {\n placeholder = compactPlaceholder ? 'Reply' : 'Send a reply'\n }\n if (usingGiphy) {\n placeholder = compactPlaceholder ? 'Search' : 'Search GIFs'\n }\n\n return (\n <View style={styles.textInputBoundary}>\n <MessageFormAttachments />\n {attachmentError ? <Text style={styles.inputErrorMessage}>{attachmentError}</Text> : null}\n <View style={styles.textInputRow}>\n {usingGiphy ? (\n <View style={styles.giphyBadge}>\n <Icon name=\"general.bolt\" style={styles.giphyBadgeIcon} />\n <Text variant=\"tertiary\" style={styles.giphyBadgeText}>\n /giphy\n </Text>\n </View>\n ) : null}\n\n <TextInput\n style={[styles.textInput, usingGiphy && styles.textInputWithGiphy]}\n multiline={true}\n placeholder={placeholder}\n placeholderTextColor={theme.colors.textColorDefaultPlaceholder}\n onChangeText={handleTextChange}\n value={text}\n maxLength={usingGiphy ? GIPHY_MAX_LENGTH : MAX_MESSAGE_LENGTH}\n onSubmitEditing={onSubmit}\n submitBehavior={usingGiphy ? 'submit' : 'newline'}\n editable={inputEditable}\n />\n </View>\n </View>\n )\n}\n\nconst SUBMIT_ACCESSIBILITY_LABEL_MAP = {\n giphy: 'Search Giphy',\n editing: 'Save changes',\n send: 'Send message',\n} as const\n\nconst SUBMIT_ICON_NAME_MAP = {\n giphy: 'general.search',\n editing: 'general.check',\n send: 'general.upArrow',\n} as const\n\nfunction MessageFormSubmitBtn() {\n const { onSubmit, disabled, usingGiphy, currentlyEditingMessage } =\n React.useContext(MessageFormContext)\n const styles = useMessageFormStyles()\n const { colors } = useTheme()\n\n const formStateKey = usingGiphy ? 'giphy' : currentlyEditingMessage ? 'editing' : 'send'\n const colorKey = disabled ? 'disabled' : 'interaction'\n const interactionStart = colors.buttonStart || colors.interaction\n const interactionEnd = colors.buttonEnd || colors.interaction\n const colorMap = {\n disabled: [\n colors.fillColorButtonNeutralSolidDisabled,\n colors.fillColorButtonNeutralSolidDisabled,\n ],\n interaction: [interactionStart, interactionEnd],\n }\n\n const androidRippleColor = useCreateAndroidRippleColor({ color: colorMap[colorKey][0] })\n const gradientColors = colorMap[colorKey] as [string, string]\n const iconStyles = [styles.submitIcon, disabled && styles.submitIconDisabled]\n\n return (\n <Pressable\n disabled={disabled}\n onPress={onSubmit}\n accessibilityRole=\"button\"\n accessibilityLabel={SUBMIT_ACCESSIBILITY_LABEL_MAP[formStateKey]}\n style={({ pressed }) => [\n styles.submitPressableWrapper,\n pressed && platformPressedOpacityStyle,\n ]}\n android_ripple={{ color: androidRippleColor, borderless: false, foreground: true }}\n >\n <LinearGradient\n start={{ x: 0.1, y: 0.1 }}\n end={{ x: 0.9, y: 0.9 }}\n colors={gradientColors}\n style={styles.submitGradientWrapper}\n >\n <Icon\n name={SUBMIT_ICON_NAME_MAP[formStateKey]}\n style={iconStyles}\n accessibilityElementsHidden={true}\n />\n </LinearGradient>\n </Pressable>\n )\n}\n\nfunction MessageFormAttachmentPicker() {\n const styles = useMessageFormStyles()\n const { usingGiphy, attachmentUploader, currentlyEditingMessage } =\n React.useContext(MessageFormContext)\n const { allowedMimeTypes } = useChatConfiguration()\n const [isOpen, setIsOpen] = useState(false)\n\n function uploadImagePickerResult(result: ImagePickerResult) {\n if (result.canceled) {\n return\n }\n\n const filteredAssets = result.assets\n .filter(asset => {\n return asset.fileSize && asset.mimeType && asset.uri\n })\n .map(asset => {\n return {\n uri: asset.uri,\n name: asset.fileName || asset.uri.split('/').pop() || 'attachment',\n type: asset.mimeType as string,\n size: asset.fileSize as number,\n height: asset.height,\n width: asset.width,\n }\n })\n\n attachmentUploader?.handleFilesAttached(filteredAssets)\n }\n\n function uploadDocumentPickerResult(result: DocumentPickerResult) {\n if (result.canceled) return\n\n const filteredAssets = result.assets\n .filter(asset => asset.size != null && asset.uri)\n .map(asset => ({\n uri: asset.uri,\n name: asset.name,\n type: asset.mimeType || 'application/octet-stream',\n size: asset.size as number,\n }))\n\n attachmentUploader?.handleFilesAttached(filteredAssets)\n }\n\n const openCamera = async () => {\n setIsOpen(false)\n let result = await ImagePicker.openCameraAsync()\n if (!result.canceled) {\n uploadImagePickerResult(result)\n }\n }\n\n const pickImage = async () => {\n setIsOpen(false)\n let result = await ImagePicker.openImageLibraryAsync()\n if (!result.canceled) {\n uploadImagePickerResult(result)\n }\n }\n\n const pickFile = async () => {\n setIsOpen(false)\n let result = await DocumentPicker.openAsync({ mimeTypes: allowedMimeTypes })\n if (!result.canceled) {\n uploadDocumentPickerResult(result)\n }\n }\n\n if (usingGiphy || currentlyEditingMessage) {\n return null\n }\n\n return (\n <View style={styles.attachmentPicker}>\n {isOpen && (\n <View style={styles.attachmentPickerButtonsContainer}>\n <IconButton\n accessibilityLabel=\"Take a photo\"\n accessibilityHint=\"Opens your camera\"\n size=\"lg\"\n appearance=\"neutral\"\n name=\"chat.camera\"\n onPress={openCamera}\n style={styles.attachmentPickerButton}\n />\n <IconButton\n accessibilityLabel=\"Choose a photo\"\n accessibilityHint=\"Opens your photo gallery\"\n size=\"lg\"\n appearance=\"neutral\"\n name=\"churchCenter.photosIos\"\n onPress={pickImage}\n style={styles.attachmentPickerButton}\n />\n {DocumentPicker.configured && (\n <IconButton\n accessibilityLabel=\"Attach a file\"\n accessibilityHint=\"Opens your files to attach documents\"\n size=\"lg\"\n appearance=\"neutral\"\n name=\"general.blankFile\"\n onPress={pickFile}\n style={styles.attachmentPickerButton}\n />\n )}\n </View>\n )}\n <IconButton\n accessibilityLabel=\"File Menu\"\n accessibilityHint=\"Opens options to attach or take a photo\"\n size=\"lg\"\n appearance=\"neutral\"\n name=\"general.paperclip\"\n onPress={() => setIsOpen(!isOpen)}\n style={styles.formButton}\n />\n </View>\n )\n}\n\nfunction MessageFormCommands() {\n const { text, canGiphy, usingGiphy, setUsingGiphy } = React.useContext(MessageFormContext)\n const styles = useMessageFormStyles()\n\n if (!canGiphy) {\n return null\n }\n\n if (usingGiphy) {\n return (\n <IconButton\n accessibilityLabel=\"Exit Giphy Search\"\n accessibilityHint=\"Changes input back to a text field for sending messages\"\n size=\"lg\"\n appearance=\"neutral\"\n name=\"general.xCircle\"\n onPress={() => setUsingGiphy(false)}\n style={styles.formButton}\n />\n )\n }\n\n return (\n <IconButton\n accessibilityLabel=\"Search Giphy\"\n accessibilityHint=\"Changes input into a search field for finding Giphys\"\n size=\"lg\"\n appearance=\"neutral\"\n name={'general.bolt'}\n onPress={() => setUsingGiphy(true)}\n disabled={text.length > GIPHY_MAX_LENGTH}\n style={styles.formButton}\n />\n )\n}\n\nfunction EditingIndicator() {\n const { currentlyEditingMessage, reset } = React.useContext(MessageFormContext)\n\n if (!currentlyEditingMessage) {\n return null\n }\n\n return (\n <FormIndicatorRow\n title=\"Editing message\"\n buttonText=\"Cancel\"\n accessibilityHint=\"Exit message editing mode without saving\"\n iconName=\"accounts.editor\"\n onPress={() => reset()}\n />\n )\n}\n\nfunction ReplyIndicator() {\n const { replyRootId, replyRootAuthorFirstName } = React.useContext(MessageFormContext)\n const navigation = useNavigation()\n const title = replyRootAuthorFirstName ? `Reply to ${replyRootAuthorFirstName}` : 'Reply'\n\n if (!replyRootId) return null\n\n return (\n <FormIndicatorRow\n title={title}\n buttonText=\"Exit replies\"\n accessibilityHint=\"Return to the main conversation\"\n iconName=\"registrations.undo\"\n onPress={() => navigation.goBack()}\n />\n )\n}\n\ninterface FormIndicatorRowProps {\n title: string\n buttonText: string\n accessibilityHint: string\n onPress: () => void\n iconName: IconString\n}\n\nfunction FormIndicatorRow({\n title,\n buttonText,\n accessibilityHint,\n iconName,\n onPress,\n}: FormIndicatorRowProps) {\n const styles = useMessageFormStyles()\n const scalableNumberOfLines = useScalableNumberOfLines(1)\n\n return (\n <View style={styles.formIndicatorRow}>\n <View style={styles.formIndicatorRowTitleContainer}>\n <Icon name={iconName} size={16} style={styles.formIndicatorRowIcon} />\n <Heading\n variant=\"h4\"\n style={styles.formIndicatorRowTitle}\n numberOfLines={scalableNumberOfLines}\n maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER_LANDMARK}\n >\n {title}\n </Heading>\n </View>\n <TextButton\n onPress={onPress}\n accessibilityHint={accessibilityHint}\n maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER_LANDMARK}\n >\n {buttonText}\n </TextButton>\n </View>\n )\n}\n\nconst useMessageFormStyles = () => {\n const theme = useTheme()\n const navigationTheme = useNavigationTheme()\n const buttonSize = 38\n\n const giphyBadgeBackgroundColor = useInteractionGhostBackgroundColor()\n\n return StyleSheet.create({\n container: {\n borderColor: theme.colors.borderColorDefaultDim,\n borderTopWidth: 1,\n padding: 12,\n },\n textInputContainer: {\n backgroundColor: navigationTheme.colors.card,\n flexDirection: 'row',\n alignItems: 'flex-end',\n gap: 16,\n },\n textInputBoundary: {\n overflow: 'hidden',\n borderRadius: 24,\n borderWidth: 1,\n borderColor: theme.colors.fillColorNeutral050Base,\n flex: 1,\n gap: 8,\n },\n textInputRow: {\n flexDirection: 'row',\n alignItems: 'center',\n gap: 8,\n },\n textInput: {\n paddingBottom: 8,\n // TODO: Remove iOS's extra top padding and use `textAlignVertical: \"center\"` for both platforms when services-react-native upgrades to v0.74.5 or greater.\n paddingTop: Platform.select({\n ios: 9, // Extra padding compensates for `textAlignVertical` not working in Services iOS.\n default: 8,\n }),\n textAlignVertical: Platform.select({\n ios: 'top', // Services iOS doesn't support `textAlignVertical` due to its lower React Native version of 0.72.14. (React Native 0.74.5+ seems to support this style.)\n default: 'center', // centers the text vertically for multiline TextInput.\n }),\n paddingHorizontal: 16,\n color: theme.colors.textColorDefaultPrimary,\n justifyContent: 'center',\n flex: 1,\n fontSize: 16,\n maxHeight: 200,\n },\n textInputWithGiphy: {\n paddingLeft: 0,\n },\n giphyBadge: {\n backgroundColor: giphyBadgeBackgroundColor,\n borderRadius: 24,\n paddingVertical: 4,\n paddingHorizontal: 12,\n marginLeft: 6,\n flexDirection: 'row',\n alignItems: 'center',\n gap: 4,\n },\n giphyBadgeText: {\n color: theme.colors.interaction,\n marginBottom: 1,\n },\n giphyBadgeIcon: {\n color: theme.colors.interaction,\n },\n formButton: {\n height: buttonSize,\n },\n submitPressableWrapper: {\n borderRadius: buttonSize,\n overflow: 'hidden',\n },\n submitGradientWrapper: {\n flexDirection: 'row',\n justifyContent: 'center',\n alignItems: 'center',\n borderRadius: buttonSize,\n height: buttonSize,\n width: buttonSize,\n },\n submitIcon: {\n fontSize: 16,\n color: tokens.colorNeutral100White,\n },\n submitIconDisabled: {\n color: theme.colors.iconColorDefaultDisabled,\n },\n attachmentPicker: {\n position: 'relative',\n },\n attachmentPickerButtonsContainer: {\n position: 'absolute',\n left: 0,\n bottom: 40,\n zIndex: 10,\n gap: 12,\n },\n attachmentPickerButton: {\n backgroundColor: theme.colors.fillColorNeutral070,\n borderRadius: 20,\n height: buttonSize,\n width: buttonSize,\n elevation: 3,\n ...Platform.select({\n ios: {\n borderWidth: 1,\n borderColor: theme.colors.borderColorDefaultBase,\n },\n default: null,\n }),\n },\n messageFormAttachments: {\n overflow: 'hidden',\n paddingHorizontal: 12,\n paddingTop: 12,\n gap: 8,\n },\n inputErrorMessage: {\n color: theme.colors.statusErrorText,\n fontSize: 14,\n paddingHorizontal: 16,\n paddingVertical: 4,\n },\n flaggedBannerContainer: {\n paddingBottom: 12,\n },\n flaggedBannerText: {\n color: theme.colors.textColorDefaultPrimary,\n fontSize: 14,\n },\n flaggedBannerLink: {\n color: theme.colors.textColorDefaultPrimary,\n fontSize: 14,\n textDecorationLine: 'underline' as const,\n },\n flaggedBannerButtonRow: {\n flexDirection: 'row',\n justifyContent: 'flex-end',\n marginTop: 4,\n },\n formIndicatorRow: {\n flexDirection: 'row',\n alignItems: 'center',\n justifyContent: 'space-between',\n gap: 8,\n paddingBottom: 12,\n },\n formIndicatorRowTitleContainer: {\n flexDirection: 'row',\n alignItems: 'center',\n gap: 8,\n flex: 1,\n },\n formIndicatorRowIcon: {\n color: theme.colors.iconColorDefaultSecondary,\n },\n formIndicatorRowTitle: {\n fontWeight: platformFontWeightMedium,\n textTransform: 'none',\n flexShrink: 1,\n },\n })\n}\n"]}
1
+ {"version":3,"file":"message_form.js","sourceRoot":"","sources":["../../../src/components/conversation/message_form.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useNavigation, useTheme as useNavigationTheme, useRoute } from '@react-navigation/native'\nimport React, { useCallback, useContext, useEffect, useState } from 'react'\nimport {\n Linking,\n Platform,\n Pressable,\n ScrollView,\n StyleSheet,\n TextInput,\n View,\n ViewProps,\n Keyboard,\n} from 'react-native'\nimport LinearGradient from 'react-native-linear-gradient'\nimport { Heading, Icon, IconButton, IconString, Text, TextButton } from '../../components'\nimport { ChatContext } from '../../contexts/chat_context'\nimport {\n useCreateAndroidRippleColor,\n useTheme,\n useInteractionGhostBackgroundColor,\n useScalableNumberOfLines,\n useFontScale,\n} from '../../hooks'\nimport { useAttachmentUploader } from '../../hooks/use_attachment_uploader'\nimport { useBroadcastTypingStatus } from '../../hooks/use_broadcast_typing_status'\nimport { useChatConfiguration } from '../../hooks/use_chat_configuration'\nimport { useMessageCreateOrUpdate } from '../../hooks/use_message_create_or_update'\nimport { useMessageDraft } from '../../hooks/use_message_draft'\nimport { ConversationScreenProps } from '../../screens/conversation_screen'\nimport { ConversationResource, MessageResource } from '../../types'\nimport {\n DenormalizedAttachmentResourceForCreate,\n DenormalizedMessageAttachmentResourceForCreate,\n FileAttachment,\n} from '../../types/resources/denormalized_attachment_resource_for_create'\nimport {\n MAX_FONT_SIZE_MULTIPLIER_LANDMARK,\n platformFontWeightMedium,\n platformPressedOpacityStyle,\n} from '../../utils'\nimport { pickAttachmentPreviewKind } from '../../utils/attachment_kind'\nimport { canSubmitMessage } from '../../utils/can_submit_message'\nimport { isInputEditable } from '../../utils/is_input_editable'\nimport {\n DocumentPicker,\n DocumentPickerResult,\n Haptic,\n ImagePicker,\n ImagePickerResult,\n} from '../../utils/native_adapters'\nimport { tokens } from '../../vendor/tapestry/tokens'\nimport { Button } from '../display/button'\nimport BannerPrimitive from '../primitive/banner_primitive'\nimport { MessageFormAttachmentFile } from './message_form/message_form_attachment_file'\nimport { MessageFormAttachmentImage } from './message_form/message_form_attachment_image'\nimport { MessageFormAttachmentVideo } from './message_form/message_form_attachment_video'\n\nexport const MessageForm = {\n Root: MessageFormRoot,\n\n TextInput: MessageFormInput,\n SubmitButton: MessageFormSubmitBtn,\n AttachmentPicker: MessageFormAttachmentPicker,\n Commands: MessageFormCommands,\n}\n\ninterface MessagesFormRootProps extends ViewProps {\n conversation: ConversationResource\n currentlyEditingMessage?: MessageResource | null\n replyRootId?: string | null\n replyRootAuthorFirstName?: string | null\n}\n\nconst MessageFormContext = React.createContext<{\n text: string\n setText: (text: string) => void\n onSubmit: () => void\n disabled: boolean\n inputEditable: boolean\n canGiphy: boolean\n usingGiphy: boolean\n setUsingGiphy: (usingGiphy: boolean) => void\n attachmentUploader?: ReturnType<typeof useAttachmentUploader>\n currentlyEditingMessage?: MessageResource | null\n reset: () => void\n replyRootId?: string | null\n replyRootAuthorFirstName?: string | null\n}>({\n text: '',\n setText: (_text: string) => {},\n onSubmit: () => {},\n disabled: false,\n inputEditable: true,\n canGiphy: false,\n usingGiphy: false,\n setUsingGiphy: (_usingGiphy: boolean) => {},\n currentlyEditingMessage: null,\n reset: () => {},\n replyRootId: undefined,\n replyRootAuthorFirstName: undefined,\n})\n\nconst GIPHY_MAX_LENGTH = 50\nconst MAX_MESSAGE_LENGTH = 5000\nconst PLACEHOLDER_FONT_SCALE_THRESHOLD = 1.7\n\nfunction MessageFormRoot({\n conversation,\n currentlyEditingMessage,\n children,\n replyRootId,\n replyRootAuthorFirstName,\n}: MessagesFormRootProps) {\n const { giphyApiKey } = useContext(ChatContext)\n const canGiphy = !!giphyApiKey && !currentlyEditingMessage\n const styles = useMessageFormStyles()\n const { draft, saveDraft, clearDraft } = useMessageDraft()\n const [text, setText] = React.useState(() => {\n // Initialize from draft only when not editing a message\n return currentlyEditingMessage ? '' : draft?.text || ''\n })\n const [usingGiphy, setUsingGiphy] = useState(false)\n const navigation = useNavigation()\n const route = useRoute() as ConversationScreenProps['route']\n const {\n status,\n isPending,\n reset: resetMutation,\n mutateAsync,\n } = useMessageCreateOrUpdate({\n conversationId: conversation.id,\n message: currentlyEditingMessage || undefined,\n replyRootId,\n })\n const attachmentUploader = useAttachmentUploader({\n conversationId: conversation.id,\n draftAttachments: currentlyEditingMessage ? undefined : draft?.attachments,\n })\n const resetAttachmentUploader = attachmentUploader.reset\n\n const reset = useCallback(() => {\n resetAttachmentUploader()\n resetMutation()\n setText('')\n setUsingGiphy(false)\n if (!currentlyEditingMessage) {\n clearDraft()\n }\n navigation.setParams({\n editing_message_id: null,\n })\n }, [resetAttachmentUploader, resetMutation, navigation, currentlyEditingMessage, clearDraft])\n\n useEffect(() => {\n if (canGiphy && !usingGiphy && text.startsWith('/giphy ')) {\n setUsingGiphy(true)\n setText('')\n }\n }, [canGiphy, text, usingGiphy])\n\n useEffect(() => {\n switch (status) {\n case 'success':\n reset()\n break\n }\n }, [reset, status])\n\n useEffect(() => {\n if (route.params.clear_input) {\n reset()\n navigation.setParams({ ...route.params, clear_input: false })\n }\n }, [reset, navigation, route.params])\n\n // Save draft when text or attachments change (debounced)\n useEffect(() => {\n // Don't save drafts when editing a message\n if (currentlyEditingMessage) return\n\n const timeoutId = setTimeout(() => {\n saveDraft(text, attachmentUploader.attachments)\n }, 500)\n\n return () => clearTimeout(timeoutId)\n }, [text, attachmentUploader.attachments, saveDraft, currentlyEditingMessage])\n\n const canSubmit = canSubmitMessage({\n isPending,\n conversationDisabled: conversation.disabled ?? false,\n pendingUploads: !!attachmentUploader?.pendingUploads,\n hasFlaggedAttachments: !!attachmentUploader?.flaggedAttachmentCount,\n hasText: text.length > 0,\n hasSendableAttachments: !!attachmentUploader?.attachmentIds?.length,\n })\n const disabled = !canSubmit\n const inputEditable = isInputEditable({\n isPending,\n conversationDisabled: conversation.disabled ?? false,\n })\n\n /*\n Opening a FormSheet on Android while the keyboard is visible breaks the FormSheet's height & position.\n This is a workaround to ensure we don't open the FormSheet while the keyboard is visible.\n */\n const navigateToSendGiphyAfterKeyboardHides = useCallback(() => {\n if (!Keyboard.isVisible() || Platform.OS === 'ios') {\n navigation.navigate('SendGiphy', {\n conversation_id: conversation.id,\n search_term: text,\n })\n } else {\n const keyboardListener = Keyboard.addListener('keyboardDidHide', () => {\n navigation.navigate('SendGiphy', {\n conversation_id: conversation.id,\n search_term: text,\n })\n keyboardListener?.remove()\n })\n }\n }, [conversation.id, navigation, text])\n\n const handleSubmit = () => {\n if (!canSubmit) return\n\n if (canGiphy && usingGiphy) {\n TextInput.State.blurTextInput(TextInput.State.currentlyFocusedInput())\n Haptic.impactLight()\n navigateToSendGiphyAfterKeyboardHides()\n } else {\n let attachmentsForSubmit: DenormalizedAttachmentResourceForCreate[] =\n attachmentUploader.attachments\n .filter(a => a.status === 'success' && a.id)\n .map(\n (attachment): DenormalizedMessageAttachmentResourceForCreate => ({\n type: 'MessageAttachment',\n id: attachment.id as string,\n file: attachment.file,\n })\n )\n if (currentlyEditingMessage) {\n mutateAsync({ text })\n reset()\n } else {\n mutateAsync({ text, attachments: attachmentsForSubmit })\n reset()\n }\n }\n }\n\n useEffect(() => {\n if (currentlyEditingMessage) {\n setText(currentlyEditingMessage.text || '')\n }\n }, [currentlyEditingMessage])\n\n return (\n <MessageFormContext.Provider\n value={{\n text,\n setText,\n onSubmit: handleSubmit,\n disabled,\n inputEditable,\n canGiphy,\n usingGiphy,\n setUsingGiphy,\n attachmentUploader,\n currentlyEditingMessage,\n reset,\n replyRootId,\n replyRootAuthorFirstName,\n }}\n >\n <View style={styles.container}>\n <EditingIndicator />\n <ReplyIndicator />\n <FlaggedContentBanner />\n <View style={styles.textInputContainer}>{children}</View>\n </View>\n </MessageFormContext.Provider>\n )\n}\n\nfunction MessageFormAttachments() {\n const styles = useMessageFormStyles()\n const { attachmentUploader } = React.useContext(MessageFormContext)\n const numberOfAttachments = attachmentUploader?.attachments?.length || 0\n const attachments = attachmentUploader?.attachments || []\n\n if (numberOfAttachments === 0) {\n return null\n }\n\n return (\n <ScrollView\n horizontal\n showsHorizontalScrollIndicator={false}\n contentContainerStyle={styles.messageFormAttachments}\n >\n {attachments.map(attachment => {\n const kind = pickAttachmentPreviewKind(attachment.file.type, attachment.file.name)\n const remove = () => attachmentUploader?.removeAttachment(attachment)\n\n if (kind === 'video') {\n return (\n <MessageFormAttachmentVideo\n key={attachment.file.uri}\n name={attachment.file.name}\n status={attachment.status}\n removeAttachment={remove}\n />\n )\n }\n\n if (kind === 'file') {\n return (\n <MessageFormAttachmentFile\n key={attachment.file.uri}\n name={attachment.file.name}\n contentType={attachment.file.type}\n status={attachment.status}\n removeAttachment={remove}\n />\n )\n }\n\n return (\n <MessageFormAttachmentImage\n key={attachment.file.uri}\n uri={attachment.file.uri}\n alt={attachment.file.name}\n status={attachment.status}\n width={attachment.file.width}\n height={attachment.file.height}\n removeAttachment={remove}\n />\n )\n })}\n </ScrollView>\n )\n}\n\nfunction getFileType(attachments: FileAttachment[]): 'image' | 'video' | 'file' {\n const kinds = new Set(\n attachments.map(({ file }) => {\n if (file.type.startsWith('image/')) return 'image'\n if (file.type.startsWith('video/')) return 'video'\n return 'file'\n })\n )\n return kinds.size === 1 ? [...kinds][0] : 'file'\n}\n\nfunction FlaggedContentBanner() {\n const styles = useMessageFormStyles()\n const { attachmentUploader } = React.useContext(MessageFormContext)\n const flaggedAttachments = attachmentUploader?.attachments.filter(a => a.flagged) ?? []\n const flaggedCount = flaggedAttachments.length\n\n if (flaggedCount === 0) return null\n\n const isSingular = flaggedCount === 1\n const fileType = getFileType(flaggedAttachments)\n const buttonTitle = isSingular ? `Remove flagged ${fileType}` : `Remove flagged ${fileType}s`\n\n return (\n <View style={styles.flaggedBannerContainer}>\n <BannerPrimitive.Root appearance=\"error\">\n <BannerPrimitive.StaticLayout>\n <BannerPrimitive.StatusIcon />\n <BannerPrimitive.Content>\n <BannerMessage isSingular={isSingular} fileType={fileType} />\n <View style={styles.flaggedBannerButtonRow}>\n <Button\n title={buttonTitle}\n size=\"sm\"\n appearance=\"danger\"\n onPress={() => attachmentUploader?.removeFlaggedAttachments()}\n accessibilityLabel={buttonTitle}\n />\n </View>\n </BannerPrimitive.Content>\n </BannerPrimitive.StaticLayout>\n </BannerPrimitive.Root>\n </View>\n )\n}\n\nfunction BannerMessage({ isSingular, fileType }: { isSingular: boolean; fileType: string }) {\n const styles = useMessageFormStyles()\n\n const contentGuidelinesLink = (\n <Text\n style={styles.flaggedBannerLink}\n onPress={() =>\n Linking.openURL(\n 'https://www.planningcenter.com/terms#:~:text=4.%20Acceptable%20Use%20of%20Planning%20Center'\n )\n }\n accessibilityRole=\"link\"\n >\n content guidelines\n </Text>\n )\n\n return isSingular ? (\n <Text style={styles.flaggedBannerText}>\n An uploaded {fileType} was flagged because it doesn't meet our {contentGuidelinesLink}. Please\n remove before proceeding.\n </Text>\n ) : (\n <Text style={styles.flaggedBannerText}>\n Some uploaded {fileType}s were flagged because they don't meet our {contentGuidelinesLink}.\n Please remove them before proceeding.\n </Text>\n )\n}\n\nfunction MessageFormInput() {\n const styles = useMessageFormStyles()\n const theme = useTheme()\n const fontScale = useFontScale()\n const { text, setText, onSubmit, inputEditable, usingGiphy, attachmentUploader, replyRootId } =\n React.useContext(MessageFormContext)\n const attachmentError = attachmentUploader?.errorMessage\n\n const broadcastTypingStatus = useBroadcastTypingStatus()\n\n const handleTextChange = (newText: string) => {\n setText(newText)\n\n if (newText.length > 0) {\n broadcastTypingStatus()\n }\n }\n\n const compactPlaceholder = fontScale >= PLACEHOLDER_FONT_SCALE_THRESHOLD\n let placeholder = compactPlaceholder ? 'Message' : 'Send a message'\n if (replyRootId) {\n placeholder = compactPlaceholder ? 'Reply' : 'Send a reply'\n }\n if (usingGiphy) {\n placeholder = compactPlaceholder ? 'Search' : 'Search GIFs'\n }\n\n return (\n <View style={styles.textInputBoundary}>\n <MessageFormAttachments />\n {attachmentError ? <Text style={styles.inputErrorMessage}>{attachmentError}</Text> : null}\n <View style={styles.textInputRow}>\n {usingGiphy ? (\n <View style={styles.giphyBadge}>\n <Icon name=\"general.bolt\" style={styles.giphyBadgeIcon} />\n <Text variant=\"tertiary\" style={styles.giphyBadgeText}>\n /giphy\n </Text>\n </View>\n ) : null}\n\n <TextInput\n style={[styles.textInput, usingGiphy && styles.textInputWithGiphy]}\n multiline={true}\n placeholder={placeholder}\n placeholderTextColor={theme.colors.textColorDefaultPlaceholder}\n onChangeText={handleTextChange}\n value={text}\n maxLength={usingGiphy ? GIPHY_MAX_LENGTH : MAX_MESSAGE_LENGTH}\n onSubmitEditing={onSubmit}\n submitBehavior={usingGiphy ? 'submit' : 'newline'}\n editable={inputEditable}\n />\n </View>\n </View>\n )\n}\n\nconst SUBMIT_ACCESSIBILITY_LABEL_MAP = {\n giphy: 'Search Giphy',\n editing: 'Save changes',\n send: 'Send message',\n} as const\n\nconst SUBMIT_ICON_NAME_MAP = {\n giphy: 'general.search',\n editing: 'general.check',\n send: 'general.upArrow',\n} as const\n\nfunction MessageFormSubmitBtn() {\n const { onSubmit, disabled, usingGiphy, currentlyEditingMessage } =\n React.useContext(MessageFormContext)\n const styles = useMessageFormStyles()\n const { colors } = useTheme()\n\n const formStateKey = usingGiphy ? 'giphy' : currentlyEditingMessage ? 'editing' : 'send'\n const colorKey = disabled ? 'disabled' : 'interaction'\n const interactionStart = colors.buttonStart || colors.interaction\n const interactionEnd = colors.buttonEnd || colors.interaction\n const colorMap = {\n disabled: [\n colors.fillColorButtonNeutralSolidDisabled,\n colors.fillColorButtonNeutralSolidDisabled,\n ],\n interaction: [interactionStart, interactionEnd],\n }\n\n const androidRippleColor = useCreateAndroidRippleColor({ color: colorMap[colorKey][0] })\n const gradientColors = colorMap[colorKey] as [string, string]\n const iconStyles = [styles.submitIcon, disabled && styles.submitIconDisabled]\n\n return (\n <Pressable\n disabled={disabled}\n onPress={onSubmit}\n accessibilityRole=\"button\"\n accessibilityLabel={SUBMIT_ACCESSIBILITY_LABEL_MAP[formStateKey]}\n style={({ pressed }) => [\n styles.submitPressableWrapper,\n pressed && platformPressedOpacityStyle,\n ]}\n android_ripple={{ color: androidRippleColor, borderless: false, foreground: true }}\n >\n <LinearGradient\n start={{ x: 0.1, y: 0.1 }}\n end={{ x: 0.9, y: 0.9 }}\n colors={gradientColors}\n style={styles.submitGradientWrapper}\n >\n <Icon\n name={SUBMIT_ICON_NAME_MAP[formStateKey]}\n style={iconStyles}\n accessibilityElementsHidden={true}\n />\n </LinearGradient>\n </Pressable>\n )\n}\n\nfunction MessageFormAttachmentPicker() {\n const styles = useMessageFormStyles()\n const { usingGiphy, attachmentUploader, currentlyEditingMessage } =\n React.useContext(MessageFormContext)\n const { allowedMimeTypes } = useChatConfiguration()\n const [isOpen, setIsOpen] = useState(false)\n\n function uploadImagePickerResult(result: ImagePickerResult) {\n if (result.canceled) {\n return\n }\n\n const filteredAssets = result.assets\n .filter(asset => {\n return asset.fileSize && asset.mimeType && asset.uri\n })\n .map(asset => {\n return {\n uri: asset.uri,\n name: asset.fileName || asset.uri.split('/').pop() || 'attachment',\n type: asset.mimeType as string,\n size: asset.fileSize as number,\n height: asset.height,\n width: asset.width,\n }\n })\n\n attachmentUploader?.handleFilesAttached(filteredAssets)\n }\n\n function uploadDocumentPickerResult(result: DocumentPickerResult) {\n if (result.canceled) return\n\n const filteredAssets = result.assets\n .filter(asset => asset.size != null && asset.uri)\n .map(asset => ({\n uri: asset.uri,\n name: asset.name,\n type: asset.mimeType || 'application/octet-stream',\n size: asset.size as number,\n }))\n\n attachmentUploader?.handleFilesAttached(filteredAssets)\n }\n\n const openCamera = async () => {\n setIsOpen(false)\n let result = await ImagePicker.openCameraAsync()\n if (!result.canceled) {\n uploadImagePickerResult(result)\n }\n }\n\n const pickImage = async () => {\n setIsOpen(false)\n let result = await ImagePicker.openImageLibraryAsync()\n if (!result.canceled) {\n uploadImagePickerResult(result)\n }\n }\n\n const pickFile = async () => {\n setIsOpen(false)\n let result = await DocumentPicker.openAsync({ mimeTypes: allowedMimeTypes })\n if (!result.canceled) {\n uploadDocumentPickerResult(result)\n }\n }\n\n if (usingGiphy || currentlyEditingMessage) {\n return null\n }\n\n return (\n <View style={styles.attachmentPicker}>\n {isOpen && (\n <View style={styles.attachmentPickerButtonsContainer}>\n <IconButton\n accessibilityLabel=\"Take a photo\"\n accessibilityHint=\"Opens your camera\"\n size=\"lg\"\n appearance=\"neutral\"\n name=\"chat.camera\"\n onPress={openCamera}\n style={styles.attachmentPickerButton}\n />\n <IconButton\n accessibilityLabel=\"Choose a photo\"\n accessibilityHint=\"Opens your photo gallery\"\n size=\"lg\"\n appearance=\"neutral\"\n name=\"churchCenter.photosIos\"\n onPress={pickImage}\n style={styles.attachmentPickerButton}\n />\n {DocumentPicker.configured && (\n <IconButton\n accessibilityLabel=\"Attach a file\"\n accessibilityHint=\"Opens your files to attach documents\"\n size=\"lg\"\n appearance=\"neutral\"\n name=\"general.blankFile\"\n onPress={pickFile}\n style={styles.attachmentPickerButton}\n />\n )}\n </View>\n )}\n <IconButton\n accessibilityLabel=\"File Menu\"\n accessibilityHint=\"Opens options to attach or take a photo\"\n size=\"lg\"\n appearance=\"neutral\"\n name=\"general.paperclip\"\n onPress={() => setIsOpen(!isOpen)}\n style={styles.formButton}\n />\n </View>\n )\n}\n\nfunction MessageFormCommands() {\n const { text, canGiphy, usingGiphy, setUsingGiphy } = React.useContext(MessageFormContext)\n const styles = useMessageFormStyles()\n\n if (!canGiphy) {\n return null\n }\n\n if (usingGiphy) {\n return (\n <IconButton\n accessibilityLabel=\"Exit Giphy Search\"\n accessibilityHint=\"Changes input back to a text field for sending messages\"\n size=\"lg\"\n appearance=\"neutral\"\n name=\"general.xCircle\"\n onPress={() => setUsingGiphy(false)}\n style={styles.formButton}\n />\n )\n }\n\n return (\n <IconButton\n accessibilityLabel=\"Search Giphy\"\n accessibilityHint=\"Changes input into a search field for finding Giphys\"\n size=\"lg\"\n appearance=\"neutral\"\n name={'general.bolt'}\n onPress={() => setUsingGiphy(true)}\n disabled={text.length > GIPHY_MAX_LENGTH}\n style={styles.formButton}\n />\n )\n}\n\nfunction EditingIndicator() {\n const { currentlyEditingMessage, reset } = React.useContext(MessageFormContext)\n\n if (!currentlyEditingMessage) {\n return null\n }\n\n return (\n <FormIndicatorRow\n title=\"Editing message\"\n buttonText=\"Cancel\"\n accessibilityHint=\"Exit message editing mode without saving\"\n iconName=\"accounts.editor\"\n onPress={() => reset()}\n />\n )\n}\n\nfunction ReplyIndicator() {\n const { replyRootId, replyRootAuthorFirstName } = React.useContext(MessageFormContext)\n const navigation = useNavigation()\n const title = replyRootAuthorFirstName ? `Reply to ${replyRootAuthorFirstName}` : 'Reply'\n\n if (!replyRootId) return null\n\n return (\n <FormIndicatorRow\n title={title}\n buttonText=\"Exit replies\"\n accessibilityHint=\"Return to the main conversation\"\n iconName=\"registrations.undo\"\n onPress={() => navigation.goBack()}\n />\n )\n}\n\ninterface FormIndicatorRowProps {\n title: string\n buttonText: string\n accessibilityHint: string\n onPress: () => void\n iconName: IconString\n}\n\nfunction FormIndicatorRow({\n title,\n buttonText,\n accessibilityHint,\n iconName,\n onPress,\n}: FormIndicatorRowProps) {\n const styles = useMessageFormStyles()\n const scalableNumberOfLines = useScalableNumberOfLines(1)\n\n return (\n <View style={styles.formIndicatorRow}>\n <View style={styles.formIndicatorRowTitleContainer}>\n <Icon name={iconName} size={16} style={styles.formIndicatorRowIcon} />\n <Heading\n variant=\"h4\"\n style={styles.formIndicatorRowTitle}\n numberOfLines={scalableNumberOfLines}\n maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER_LANDMARK}\n >\n {title}\n </Heading>\n </View>\n <TextButton\n onPress={onPress}\n accessibilityHint={accessibilityHint}\n maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER_LANDMARK}\n >\n {buttonText}\n </TextButton>\n </View>\n )\n}\n\nconst useMessageFormStyles = () => {\n const theme = useTheme()\n const navigationTheme = useNavigationTheme()\n const buttonSize = 38\n\n const giphyBadgeBackgroundColor = useInteractionGhostBackgroundColor()\n\n return StyleSheet.create({\n container: {\n borderColor: theme.colors.borderColorDefaultDim,\n borderTopWidth: 1,\n padding: 12,\n },\n textInputContainer: {\n backgroundColor: navigationTheme.colors.card,\n flexDirection: 'row',\n alignItems: 'flex-end',\n gap: 16,\n },\n textInputBoundary: {\n overflow: 'hidden',\n borderRadius: 24,\n borderWidth: 1,\n borderColor: theme.colors.fillColorNeutral050Base,\n flex: 1,\n gap: 8,\n },\n textInputRow: {\n flexDirection: 'row',\n alignItems: 'center',\n gap: 8,\n },\n textInput: {\n paddingBottom: 8,\n // TODO: Remove iOS's extra top padding and use `textAlignVertical: \"center\"` for both platforms when services-react-native upgrades to v0.74.5 or greater.\n paddingTop: Platform.select({\n ios: 9, // Extra padding compensates for `textAlignVertical` not working in Services iOS.\n default: 8,\n }),\n textAlignVertical: Platform.select({\n ios: 'top', // Services iOS doesn't support `textAlignVertical` due to its lower React Native version of 0.72.14. (React Native 0.74.5+ seems to support this style.)\n default: 'center', // centers the text vertically for multiline TextInput.\n }),\n paddingHorizontal: 16,\n color: theme.colors.textColorDefaultPrimary,\n justifyContent: 'center',\n flex: 1,\n fontSize: 16,\n maxHeight: 200,\n },\n textInputWithGiphy: {\n paddingLeft: 0,\n },\n giphyBadge: {\n backgroundColor: giphyBadgeBackgroundColor,\n borderRadius: 24,\n paddingVertical: 4,\n paddingHorizontal: 12,\n marginLeft: 6,\n flexDirection: 'row',\n alignItems: 'center',\n gap: 4,\n },\n giphyBadgeText: {\n color: theme.colors.interaction,\n marginBottom: 1,\n },\n giphyBadgeIcon: {\n color: theme.colors.interaction,\n },\n formButton: {\n height: buttonSize,\n },\n submitPressableWrapper: {\n borderRadius: buttonSize,\n overflow: 'hidden',\n },\n submitGradientWrapper: {\n flexDirection: 'row',\n justifyContent: 'center',\n alignItems: 'center',\n borderRadius: buttonSize,\n height: buttonSize,\n width: buttonSize,\n },\n submitIcon: {\n fontSize: 16,\n color: tokens.colorNeutral100White,\n },\n submitIconDisabled: {\n color: theme.colors.iconColorDefaultDisabled,\n },\n attachmentPicker: {\n position: 'relative',\n },\n attachmentPickerButtonsContainer: {\n position: 'absolute',\n left: 0,\n bottom: 40,\n zIndex: 10,\n gap: 12,\n },\n attachmentPickerButton: {\n backgroundColor: theme.colors.fillColorNeutral070,\n borderRadius: 20,\n height: buttonSize,\n width: buttonSize,\n elevation: 3,\n ...Platform.select({\n ios: {\n borderWidth: 1,\n borderColor: theme.colors.borderColorDefaultBase,\n },\n default: null,\n }),\n },\n messageFormAttachments: {\n overflow: 'hidden',\n paddingHorizontal: 12,\n paddingTop: 12,\n gap: 8,\n },\n inputErrorMessage: {\n color: theme.colors.statusErrorText,\n fontSize: 14,\n paddingHorizontal: 16,\n paddingVertical: 4,\n },\n flaggedBannerContainer: {\n paddingBottom: 12,\n },\n flaggedBannerText: {\n color: theme.colors.textColorDefaultPrimary,\n fontSize: 14,\n },\n flaggedBannerLink: {\n color: theme.colors.textColorDefaultPrimary,\n fontSize: 14,\n textDecorationLine: 'underline' as const,\n },\n flaggedBannerButtonRow: {\n flexDirection: 'row',\n justifyContent: 'flex-end',\n marginTop: 4,\n },\n formIndicatorRow: {\n flexDirection: 'row',\n alignItems: 'center',\n justifyContent: 'space-between',\n gap: 8,\n paddingBottom: 12,\n },\n formIndicatorRowTitleContainer: {\n flexDirection: 'row',\n alignItems: 'center',\n gap: 8,\n flex: 1,\n },\n formIndicatorRowIcon: {\n color: theme.colors.iconColorDefaultSecondary,\n },\n formIndicatorRowTitle: {\n fontWeight: platformFontWeightMedium,\n textTransform: 'none',\n flexShrink: 1,\n },\n })\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"use_attachment_uploader.d.ts","sourceRoot":"","sources":["../../src/hooks/use_attachment_uploader.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,cAAc,EAEd,oBAAoB,EACrB,MAAM,gEAAgE,CAAA;AAKvE,MAAM,WAAW,SAAS;IACxB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAA;IACpB,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAED,wBAAgB,qBAAqB,CAAC,EACpC,cAAc,EACd,gBAAgB,GACjB,EAAE;IACD,cAAc,EAAE,MAAM,CAAA;IACtB,gBAAgB,CAAC,EAAE,cAAc,EAAE,CAAA;CACpC;;;iCAaW,oBAAoB,EAAE;mCAkHkB,cAAc;;;;;;;EAmCjE"}
1
+ {"version":3,"file":"use_attachment_uploader.d.ts","sourceRoot":"","sources":["../../src/hooks/use_attachment_uploader.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,cAAc,EAEd,oBAAoB,EACrB,MAAM,gEAAgE,CAAA;AAQvE,MAAM,WAAW,SAAS;IACxB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAA;IACpB,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAED,wBAAgB,qBAAqB,CAAC,EACpC,cAAc,EACd,gBAAgB,GACjB,EAAE;IACD,cAAc,EAAE,MAAM,CAAA;IACtB,gBAAgB,CAAC,EAAE,cAAc,EAAE,CAAA;CACpC;;;iCAsBW,oBAAoB,EAAE;mCAkHkB,cAAc;;;;;;;EAmCjE"}
@@ -1,6 +1,9 @@
1
1
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
+ import { useApiGet } from './use_api';
2
3
  import { useApiClient } from './use_api_client';
3
4
  import { useChatConfiguration } from './use_chat_configuration';
5
+ import { currentPersonRequestArgs } from './use_current_person';
6
+ import { useJoltChannel, useJoltEvent } from './use_jolt';
4
7
  import { useUploadClient } from './use_upload_client';
5
8
  export function useAttachmentUploader({ conversationId, draftAttachments, }) {
6
9
  const apiClient = useApiClient();
@@ -12,6 +15,12 @@ export function useAttachmentUploader({ conversationId, draftAttachments, }) {
12
15
  const [lastUploadId, setLastUploadId] = useState();
13
16
  const numberOfAttachments = attachments.length;
14
17
  const [errorMessage, setErrorMessage] = useState(null);
18
+ const { data: currentPerson } = useApiGet(currentPersonRequestArgs);
19
+ const joltChannel = useJoltChannel(`chat.people.${currentPerson?.id}`, Boolean(currentPerson?.id));
20
+ useJoltEvent(joltChannel, 'attachment.flagged', (e) => {
21
+ const flaggedId = e.data.data.attachment_id;
22
+ setAttachments(prev => prev.map(a => (a.id === flaggedId ? { ...a, flagged: true, status: 'error' } : a)));
23
+ });
15
24
  const handleFilesAttached = useCallback((files) => {
16
25
  const fileErrors = {};
17
26
  const validFiles = files.filter(file => {
@@ -1 +1 @@
1
- {"version":3,"file":"use_attachment_uploader.js","sourceRoot":"","sources":["../../src/hooks/use_attachment_uploader.ts"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { ApiResource } from '../types'\nimport {\n FileAttachment,\n FileUploadState,\n NativeAttachmentFile,\n} from '../types/resources/denormalized_attachment_resource_for_create'\nimport { useApiClient } from './use_api_client'\nimport { useChatConfiguration } from './use_chat_configuration'\nimport { useUploadClient } from './use_upload_client'\n\nexport interface FileError {\n file_type?: string[]\n file_size?: boolean\n}\n\nexport function useAttachmentUploader({\n conversationId,\n draftAttachments,\n}: {\n conversationId: number\n draftAttachments?: FileAttachment[]\n}) {\n const apiClient = useApiClient()\n const uploadApi = useUploadClient()\n const { allowedFileExtensions, maxFileSizeInBytes, maxAttachmentsPerMessage } =\n useChatConfiguration()\n const maxFileSizeInMb = Number((maxFileSizeInBytes / (1024 * 1024)).toFixed(1))\n const [attachments, setAttachments] = useState<FileAttachment[]>(() => draftAttachments || [])\n const uploadState = useRef<FileUploadState>({})\n const [lastUploadId, setLastUploadId] = useState<string>()\n const numberOfAttachments = attachments.length\n const [errorMessage, setErrorMessage] = useState<string | null>(null)\n\n const handleFilesAttached = useCallback(\n (files: NativeAttachmentFile[]) => {\n const fileErrors = {} as FileError\n\n const validFiles = files.filter(file => {\n const extension = file.name.toLowerCase().split('.').pop() as string\n const isValidExtension = allowedFileExtensions.includes(`.${extension}`)\n const isValidFileSize = file.size <= maxFileSizeInBytes\n\n if (!isValidExtension) {\n fileErrors.file_type ||= []\n fileErrors.file_type.push(extension)\n }\n if (!isValidFileSize) {\n fileErrors.file_size = true\n }\n\n return isValidFileSize && isValidExtension\n })\n\n const errorMessages: string[] = []\n if (fileErrors.file_type) {\n errorMessages.push(\n `The following file types are not supported: ${fileErrors.file_type.join(', ')}`\n )\n }\n if (fileErrors.file_size) {\n errorMessages.push(`File size exceeds ${maxFileSizeInMb} MB`)\n }\n if (numberOfAttachments + validFiles.length > maxAttachmentsPerMessage) {\n errorMessages.push(`You can't attach more than ${maxAttachmentsPerMessage} files at once.`)\n }\n if (errorMessages.length > 0) {\n setErrorMessage(errorMessages.join('\\n'))\n return\n }\n\n const newAttachments: FileAttachment[] = validFiles.map(file => ({\n file,\n status: 'uploading',\n uploadedAt: Date.now(),\n }))\n\n if (newAttachments && newAttachments.length > 0) {\n setAttachments(prevAttachments => [...prevAttachments, ...newAttachments])\n\n newAttachments.forEach(attachment => {\n uploadApi\n .uploadFile(attachment.file)\n .then(({ id: uploadedFileId }) =>\n apiClient.chat.post<ApiResource<MessageAttachmentResource>>({\n url: `/me/conversations/${conversationId}/message_attachments`,\n data: {\n data: {\n type: 'MessageAttachment',\n attributes: { uploaded_file_id: uploadedFileId },\n },\n },\n })\n )\n .then(({ data: { id: messageAttachmentId } }) => {\n uploadState.current[attachment.file.name] = {\n status: 'success',\n id: messageAttachmentId,\n }\n setLastUploadId(messageAttachmentId)\n })\n .catch(async err => {\n const isFlagged = err?.code === 'image_flagged'\n uploadState.current[attachment.file.name] = {\n status: 'error',\n flagged: isFlagged,\n }\n if (!isFlagged) {\n // Dev builds surface the raw server detail to shorten the\n // feedback loop on backend errors (e.g. AWS SSO re-auth).\n // Production users always see the generic message.\n const serverDetail = __DEV__ ? await extractDevOnlyServerErrorDetail(err) : null\n setErrorMessage(serverDetail ?? 'This file could not be uploaded.')\n }\n setLastUploadId(attachment.file.name)\n })\n })\n }\n },\n [\n numberOfAttachments,\n uploadApi,\n apiClient.chat,\n conversationId,\n allowedFileExtensions,\n maxFileSizeInBytes,\n maxFileSizeInMb,\n maxAttachmentsPerMessage,\n ]\n )\n\n useEffect(() => {\n if (!lastUploadId) return\n\n setLastUploadId(undefined)\n setAttachments(\n attachments.map(attachment => {\n // Don't risk overwriting ids already set\n if (attachment.id) return attachment\n\n const state = uploadState.current[attachment.file.name]\n if (state) {\n return { ...attachment, id: state.id, status: state.status, flagged: state.flagged }\n }\n return attachment\n })\n )\n }, [attachments, lastUploadId])\n\n const removeAttachment = useCallback((attachment: FileAttachment) => {\n setAttachments(prevAttachments =>\n prevAttachments.filter(a => a.file.uri !== attachment.file.uri)\n )\n }, [])\n\n const removeFlaggedAttachments = useCallback(() => {\n setAttachments(prevAttachments => prevAttachments.filter(a => !a.flagged))\n }, [])\n\n const reset = useCallback(() => {\n setAttachments([])\n setErrorMessage(null)\n }, [])\n\n const pendingUploads = attachments.filter(a => a.status === 'uploading').length > 0\n const flaggedAttachmentCount = attachments.filter(a => a.flagged).length\n\n const attachmentIds = useMemo(\n () => attachments.filter(a => a.status === 'success' && a.id).map(a => a.id as string),\n [attachments]\n )\n\n return {\n attachments,\n attachmentIds,\n handleFilesAttached,\n removeAttachment,\n removeFlaggedAttachments,\n reset,\n pendingUploads,\n errorMessage,\n flaggedAttachmentCount,\n remainingAttachable: Math.max(0, maxAttachmentsPerMessage - numberOfAttachments),\n }\n}\n\nasync function extractDevOnlyServerErrorDetail(err: unknown): Promise<string | null> {\n const response = err as Response | null\n if (!response?.clone) return null\n try {\n const body = await response.clone().json()\n const detail = body?.errors?.[0]?.detail ?? body?.detail\n return typeof detail === 'string' ? detail : null\n } catch {\n return null\n }\n}\n\ninterface MessageAttachmentResource {\n type: 'MessageAttachment'\n id: string\n uploadedFileId: string\n filename: string\n messageSortKey: string\n metadata: Record<string, unknown>\n checksum: string\n contentType: string\n byteSize: number\n}\n"]}
1
+ {"version":3,"file":"use_attachment_uploader.js","sourceRoot":"","sources":["../../src/hooks/use_attachment_uploader.ts"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { ApiResource, CurrentPersonResource } from '../types'\nimport { AttachmentFlaggedEvent } from '../types/jolt_events/attachment_events'\nimport {\n FileAttachment,\n FileUploadState,\n NativeAttachmentFile,\n} from '../types/resources/denormalized_attachment_resource_for_create'\nimport { useApiGet } from './use_api'\nimport { useApiClient } from './use_api_client'\nimport { useChatConfiguration } from './use_chat_configuration'\nimport { currentPersonRequestArgs } from './use_current_person'\nimport { useJoltChannel, useJoltEvent } from './use_jolt'\nimport { useUploadClient } from './use_upload_client'\n\nexport interface FileError {\n file_type?: string[]\n file_size?: boolean\n}\n\nexport function useAttachmentUploader({\n conversationId,\n draftAttachments,\n}: {\n conversationId: number\n draftAttachments?: FileAttachment[]\n}) {\n const apiClient = useApiClient()\n const uploadApi = useUploadClient()\n const { allowedFileExtensions, maxFileSizeInBytes, maxAttachmentsPerMessage } =\n useChatConfiguration()\n const maxFileSizeInMb = Number((maxFileSizeInBytes / (1024 * 1024)).toFixed(1))\n const [attachments, setAttachments] = useState<FileAttachment[]>(() => draftAttachments || [])\n const uploadState = useRef<FileUploadState>({})\n const [lastUploadId, setLastUploadId] = useState<string>()\n const numberOfAttachments = attachments.length\n const [errorMessage, setErrorMessage] = useState<string | null>(null)\n\n const { data: currentPerson } = useApiGet<CurrentPersonResource>(currentPersonRequestArgs)\n const joltChannel = useJoltChannel(`chat.people.${currentPerson?.id}`, Boolean(currentPerson?.id))\n useJoltEvent(joltChannel, 'attachment.flagged', (e: AttachmentFlaggedEvent) => {\n const flaggedId = e.data.data.attachment_id\n setAttachments(prev =>\n prev.map(a => (a.id === flaggedId ? { ...a, flagged: true, status: 'error' } : a))\n )\n })\n\n const handleFilesAttached = useCallback(\n (files: NativeAttachmentFile[]) => {\n const fileErrors = {} as FileError\n\n const validFiles = files.filter(file => {\n const extension = file.name.toLowerCase().split('.').pop() as string\n const isValidExtension = allowedFileExtensions.includes(`.${extension}`)\n const isValidFileSize = file.size <= maxFileSizeInBytes\n\n if (!isValidExtension) {\n fileErrors.file_type ||= []\n fileErrors.file_type.push(extension)\n }\n if (!isValidFileSize) {\n fileErrors.file_size = true\n }\n\n return isValidFileSize && isValidExtension\n })\n\n const errorMessages: string[] = []\n if (fileErrors.file_type) {\n errorMessages.push(\n `The following file types are not supported: ${fileErrors.file_type.join(', ')}`\n )\n }\n if (fileErrors.file_size) {\n errorMessages.push(`File size exceeds ${maxFileSizeInMb} MB`)\n }\n if (numberOfAttachments + validFiles.length > maxAttachmentsPerMessage) {\n errorMessages.push(`You can't attach more than ${maxAttachmentsPerMessage} files at once.`)\n }\n if (errorMessages.length > 0) {\n setErrorMessage(errorMessages.join('\\n'))\n return\n }\n\n const newAttachments: FileAttachment[] = validFiles.map(file => ({\n file,\n status: 'uploading',\n uploadedAt: Date.now(),\n }))\n\n if (newAttachments && newAttachments.length > 0) {\n setAttachments(prevAttachments => [...prevAttachments, ...newAttachments])\n\n newAttachments.forEach(attachment => {\n uploadApi\n .uploadFile(attachment.file)\n .then(({ id: uploadedFileId }) =>\n apiClient.chat.post<ApiResource<MessageAttachmentResource>>({\n url: `/me/conversations/${conversationId}/message_attachments`,\n data: {\n data: {\n type: 'MessageAttachment',\n attributes: { uploaded_file_id: uploadedFileId },\n },\n },\n })\n )\n .then(({ data: { id: messageAttachmentId } }) => {\n uploadState.current[attachment.file.name] = {\n status: 'success',\n id: messageAttachmentId,\n }\n setLastUploadId(messageAttachmentId)\n })\n .catch(async err => {\n const isFlagged = err?.code === 'image_flagged'\n uploadState.current[attachment.file.name] = {\n status: 'error',\n flagged: isFlagged,\n }\n if (!isFlagged) {\n // Dev builds surface the raw server detail to shorten the\n // feedback loop on backend errors (e.g. AWS SSO re-auth).\n // Production users always see the generic message.\n const serverDetail = __DEV__ ? await extractDevOnlyServerErrorDetail(err) : null\n setErrorMessage(serverDetail ?? 'This file could not be uploaded.')\n }\n setLastUploadId(attachment.file.name)\n })\n })\n }\n },\n [\n numberOfAttachments,\n uploadApi,\n apiClient.chat,\n conversationId,\n allowedFileExtensions,\n maxFileSizeInBytes,\n maxFileSizeInMb,\n maxAttachmentsPerMessage,\n ]\n )\n\n useEffect(() => {\n if (!lastUploadId) return\n\n setLastUploadId(undefined)\n setAttachments(\n attachments.map(attachment => {\n // Don't risk overwriting ids already set\n if (attachment.id) return attachment\n\n const state = uploadState.current[attachment.file.name]\n if (state) {\n return { ...attachment, id: state.id, status: state.status, flagged: state.flagged }\n }\n return attachment\n })\n )\n }, [attachments, lastUploadId])\n\n const removeAttachment = useCallback((attachment: FileAttachment) => {\n setAttachments(prevAttachments =>\n prevAttachments.filter(a => a.file.uri !== attachment.file.uri)\n )\n }, [])\n\n const removeFlaggedAttachments = useCallback(() => {\n setAttachments(prevAttachments => prevAttachments.filter(a => !a.flagged))\n }, [])\n\n const reset = useCallback(() => {\n setAttachments([])\n setErrorMessage(null)\n }, [])\n\n const pendingUploads = attachments.filter(a => a.status === 'uploading').length > 0\n const flaggedAttachmentCount = attachments.filter(a => a.flagged).length\n\n const attachmentIds = useMemo(\n () => attachments.filter(a => a.status === 'success' && a.id).map(a => a.id as string),\n [attachments]\n )\n\n return {\n attachments,\n attachmentIds,\n handleFilesAttached,\n removeAttachment,\n removeFlaggedAttachments,\n reset,\n pendingUploads,\n errorMessage,\n flaggedAttachmentCount,\n remainingAttachable: Math.max(0, maxAttachmentsPerMessage - numberOfAttachments),\n }\n}\n\nasync function extractDevOnlyServerErrorDetail(err: unknown): Promise<string | null> {\n const response = err as Response | null\n if (!response?.clone) return null\n try {\n const body = await response.clone().json()\n const detail = body?.errors?.[0]?.detail ?? body?.detail\n return typeof detail === 'string' ? detail : null\n } catch {\n return null\n }\n}\n\ninterface MessageAttachmentResource {\n type: 'MessageAttachment'\n id: string\n uploadedFileId: string\n filename: string\n messageSortKey: string\n metadata: Record<string, unknown>\n checksum: string\n contentType: string\n byteSize: number\n}\n"]}
@@ -11,6 +11,7 @@ export declare const availableFeatures: {
11
11
  readonly custom_conversation_avatars: "ROLLOUT_custom_conversation_avatars";
12
12
  readonly jump_to_unread: "ROLLOUT_jump_to_unread";
13
13
  readonly conversation_safety_lock: "ROLLOUT_conversation_safety_lock";
14
+ readonly video_moderation: "ROLLOUT_MOBILE_video_moderation";
14
15
  };
15
16
  export {};
16
17
  //# sourceMappingURL=use_features.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"use_features.d.ts","sourceRoot":"","sources":["../../src/hooks/use_features.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qCAAqC,CAAA;AAI1E,KAAK,WAAW,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,OAAO,iBAAiB,CAAC,CAAA;AAE7E,wBAAgB,WAAW;;kCAiBT,WAAW;EAS5B;AAED,eAAO,MAAM,iBAAiB;;;;;;;CAO0B,CAAA"}
1
+ {"version":3,"file":"use_features.d.ts","sourceRoot":"","sources":["../../src/hooks/use_features.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qCAAqC,CAAA;AAI1E,KAAK,WAAW,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,OAAO,iBAAiB,CAAC,CAAA;AAE7E,wBAAgB,WAAW;;kCAiBT,WAAW;EAS5B;AAED,eAAO,MAAM,iBAAiB;;;;;;;;CAQ0B,CAAA"}
@@ -28,6 +28,7 @@ export const availableFeatures = {
28
28
  custom_conversation_avatars: 'ROLLOUT_custom_conversation_avatars',
29
29
  jump_to_unread: 'ROLLOUT_jump_to_unread',
30
30
  conversation_safety_lock: 'ROLLOUT_conversation_safety_lock',
31
+ video_moderation: 'ROLLOUT_MOBILE_video_moderation',
31
32
  };
32
33
  const stableEmptyFeatures = {
33
34
  data: [],
@@ -1 +1 @@
1
- {"version":3,"file":"use_features.js","sourceRoot":"","sources":["../../src/hooks/use_features.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,OAAO,CAAA;AAGnC,OAAO,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAA;AAC3F,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAI/C,MAAM,UAAU,WAAW;IACzB,MAAM,SAAS,GAAG,YAAY,EAAE,CAAA;IAChC,MAAM,WAAW,GAAG,sBAAsB,EAAE,CAAA;IAE5C,MAAM,EAAE,IAAI,EAAE,GAAG,gBAAgB,CAAC;QAChC,QAAQ,EAAE,mBAAmB,EAAE;QAC/B,OAAO,EAAE,GAAG,EAAE;YACZ,OAAO,SAAS,CAAC,IAAI;iBAClB,GAAG,CAAiC,WAAW,CAAC;iBAChD,KAAK,CAAC,GAAG,EAAE,CAAC,mBAAmB,CAAC,CAAA;QACrC,CAAC;QACD,SAAS,EAAE,IAAI,GAAG,EAAE,GAAG,CAAC,EAAE,YAAY;KACvC,CAAC,CAAA;IAEF,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAA;IAE1B,MAAM,cAAc,GAAG,WAAW,CAChC,CAAC,WAAwB,EAAE,EAAE,CAC3B,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC,EAC3E,CAAC,QAAQ,CAAC,CACX,CAAA;IAED,OAAO;QACL,QAAQ;QACR,cAAc;KACf,CAAA;AACH,CAAC;AAED,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,6BAA6B,EAAE,uCAAuC;IACtE,iBAAiB,EAAE,kCAAkC;IACrD,yBAAyB,EAAE,8CAA8C;IACzE,2BAA2B,EAAE,qCAAqC;IAClE,cAAc,EAAE,wBAAwB;IACxC,wBAAwB,EAAE,kCAAkC;CACN,CAAA;AAExD,MAAM,mBAAmB,GAAmC;IAC1D,IAAI,EAAE,EAAE;IACR,KAAK,EAAE,EAAE;IACT,IAAI,EAAE;QACJ,KAAK,EAAE,CAAC;QACR,UAAU,EAAE,CAAC;KACd;CACF,CAAA","sourcesContent":["import { useSuspenseQuery } from '@tanstack/react-query'\nimport { useCallback } from 'react'\nimport { ApiCollection } from '../types'\nimport type { FeatureResource } from '../types/resources/feature_resource'\nimport { getFeaturesRequestArgs, getFeaturesQueryKey } from '../utils/request/get_features'\nimport { useApiClient } from './use_api_client'\n\ntype FeatureName = (typeof availableFeatures)[keyof typeof availableFeatures]\n\nexport function useFeatures() {\n const apiClient = useApiClient()\n const requestArgs = getFeaturesRequestArgs()\n\n const { data } = useSuspenseQuery({\n queryKey: getFeaturesQueryKey(),\n queryFn: () => {\n return apiClient.chat\n .get<ApiCollection<FeatureResource>>(requestArgs)\n .catch(() => stableEmptyFeatures)\n },\n staleTime: 1000 * 60 * 5, // 5 minutes\n })\n\n const features = data.data\n\n const featureEnabled = useCallback(\n (featureName: FeatureName) =>\n features.some(feature => feature.name === featureName && feature.enabled),\n [features]\n )\n\n return {\n features,\n featureEnabled,\n }\n}\n\nexport const availableFeatures = {\n gender_specific_conversations: 'ROLLOUT_gender_specific_conversations',\n message_reporting: 'ROLLOUT_MOBILE_message_reporting',\n granular_notifications_ui: 'ROLLOUT_granular_notification_preferences_ui',\n custom_conversation_avatars: 'ROLLOUT_custom_conversation_avatars',\n jump_to_unread: 'ROLLOUT_jump_to_unread',\n conversation_safety_lock: 'ROLLOUT_conversation_safety_lock',\n} as const satisfies Record<string, `ROLLOUT_${string}`>\n\nconst stableEmptyFeatures: ApiCollection<FeatureResource> = {\n data: [],\n links: {},\n meta: {\n count: 0,\n totalCount: 0,\n },\n}\n"]}
1
+ {"version":3,"file":"use_features.js","sourceRoot":"","sources":["../../src/hooks/use_features.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,OAAO,CAAA;AAGnC,OAAO,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAA;AAC3F,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAI/C,MAAM,UAAU,WAAW;IACzB,MAAM,SAAS,GAAG,YAAY,EAAE,CAAA;IAChC,MAAM,WAAW,GAAG,sBAAsB,EAAE,CAAA;IAE5C,MAAM,EAAE,IAAI,EAAE,GAAG,gBAAgB,CAAC;QAChC,QAAQ,EAAE,mBAAmB,EAAE;QAC/B,OAAO,EAAE,GAAG,EAAE;YACZ,OAAO,SAAS,CAAC,IAAI;iBAClB,GAAG,CAAiC,WAAW,CAAC;iBAChD,KAAK,CAAC,GAAG,EAAE,CAAC,mBAAmB,CAAC,CAAA;QACrC,CAAC;QACD,SAAS,EAAE,IAAI,GAAG,EAAE,GAAG,CAAC,EAAE,YAAY;KACvC,CAAC,CAAA;IAEF,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAA;IAE1B,MAAM,cAAc,GAAG,WAAW,CAChC,CAAC,WAAwB,EAAE,EAAE,CAC3B,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC,EAC3E,CAAC,QAAQ,CAAC,CACX,CAAA;IAED,OAAO;QACL,QAAQ;QACR,cAAc;KACf,CAAA;AACH,CAAC;AAED,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,6BAA6B,EAAE,uCAAuC;IACtE,iBAAiB,EAAE,kCAAkC;IACrD,yBAAyB,EAAE,8CAA8C;IACzE,2BAA2B,EAAE,qCAAqC;IAClE,cAAc,EAAE,wBAAwB;IACxC,wBAAwB,EAAE,kCAAkC;IAC5D,gBAAgB,EAAE,iCAAiC;CACG,CAAA;AAExD,MAAM,mBAAmB,GAAmC;IAC1D,IAAI,EAAE,EAAE;IACR,KAAK,EAAE,EAAE;IACT,IAAI,EAAE;QACJ,KAAK,EAAE,CAAC;QACR,UAAU,EAAE,CAAC;KACd;CACF,CAAA","sourcesContent":["import { useSuspenseQuery } from '@tanstack/react-query'\nimport { useCallback } from 'react'\nimport { ApiCollection } from '../types'\nimport type { FeatureResource } from '../types/resources/feature_resource'\nimport { getFeaturesRequestArgs, getFeaturesQueryKey } from '../utils/request/get_features'\nimport { useApiClient } from './use_api_client'\n\ntype FeatureName = (typeof availableFeatures)[keyof typeof availableFeatures]\n\nexport function useFeatures() {\n const apiClient = useApiClient()\n const requestArgs = getFeaturesRequestArgs()\n\n const { data } = useSuspenseQuery({\n queryKey: getFeaturesQueryKey(),\n queryFn: () => {\n return apiClient.chat\n .get<ApiCollection<FeatureResource>>(requestArgs)\n .catch(() => stableEmptyFeatures)\n },\n staleTime: 1000 * 60 * 5, // 5 minutes\n })\n\n const features = data.data\n\n const featureEnabled = useCallback(\n (featureName: FeatureName) =>\n features.some(feature => feature.name === featureName && feature.enabled),\n [features]\n )\n\n return {\n features,\n featureEnabled,\n }\n}\n\nexport const availableFeatures = {\n gender_specific_conversations: 'ROLLOUT_gender_specific_conversations',\n message_reporting: 'ROLLOUT_MOBILE_message_reporting',\n granular_notifications_ui: 'ROLLOUT_granular_notification_preferences_ui',\n custom_conversation_avatars: 'ROLLOUT_custom_conversation_avatars',\n jump_to_unread: 'ROLLOUT_jump_to_unread',\n conversation_safety_lock: 'ROLLOUT_conversation_safety_lock',\n video_moderation: 'ROLLOUT_MOBILE_video_moderation',\n} as const satisfies Record<string, `ROLLOUT_${string}`>\n\nconst stableEmptyFeatures: ApiCollection<FeatureResource> = {\n data: [],\n links: {},\n meta: {\n count: 0,\n totalCount: 0,\n },\n}\n"]}
@@ -6,6 +6,10 @@ export interface FileForUploadClient {
6
6
  }
7
7
  export declare const useUploadClient: () => UploadClient;
8
8
  declare class UploadClient extends Client {
9
+ private videoModerationEnabled;
10
+ constructor({ videoModerationEnabled, ...rest }: ConstructorParameters<typeof Client>[0] & {
11
+ videoModerationEnabled: boolean;
12
+ });
9
13
  uploadFile(file: FileForUploadClient): Promise<UploadedResource>;
10
14
  }
11
15
  interface UploadedResource {
@@ -1 +1 @@
1
- {"version":3,"file":"use_upload_client.d.ts","sourceRoot":"","sources":["../../src/hooks/use_upload_client.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AAGjC,MAAM,WAAW,mBAAmB;IAClC,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;CACb;AAED,eAAO,MAAM,eAAe,oBAiB3B,CAAA;AAED,cAAM,YAAa,SAAQ,MAAM;IACzB,UAAU,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,gBAAgB,CAAC;CAiCvE;AAED,UAAU,gBAAgB;IACxB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE;QACV,eAAe,EAAE,MAAM,CAAA;QACvB,SAAS,EAAE,MAAM,CAAA;QACjB,GAAG,EAAE,MAAM,CAAA;QACX,UAAU,EAAE,MAAM,CAAA;QAClB,UAAU,EAAE,MAAM,CAAA;QAClB,SAAS,EAAE,MAAM,CAAA;QACjB,IAAI,EAAE,MAAM,CAAA;QACZ,YAAY,EAAE,MAAM,CAAA;QACpB,SAAS,EAAE,MAAM,CAAA;QACjB,QAAQ,EAAE,MAAM,CAAA;KACjB,CAAA;CACF"}
1
+ {"version":3,"file":"use_upload_client.d.ts","sourceRoot":"","sources":["../../src/hooks/use_upload_client.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AAIjC,MAAM,WAAW,mBAAmB;IAClC,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;CACb;AAED,eAAO,MAAM,eAAe,oBAoB3B,CAAA;AAED,cAAM,YAAa,SAAQ,MAAM;IAC/B,OAAO,CAAC,sBAAsB,CAAS;gBAE3B,EACV,sBAAsB,EACtB,GAAG,IAAI,EACR,EAAE,qBAAqB,CAAC,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG;QAAE,sBAAsB,EAAE,OAAO,CAAA;KAAE;IAK1E,UAAU,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,gBAAgB,CAAC;CAqCvE;AAED,UAAU,gBAAgB;IACxB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE;QACV,eAAe,EAAE,MAAM,CAAA;QACvB,SAAS,EAAE,MAAM,CAAA;QACjB,GAAG,EAAE,MAAM,CAAA;QACX,UAAU,EAAE,MAAM,CAAA;QAClB,UAAU,EAAE,MAAM,CAAA;QAClB,SAAS,EAAE,MAAM,CAAA;QACjB,IAAI,EAAE,MAAM,CAAA;QACZ,YAAY,EAAE,MAAM,CAAA;QACpB,SAAS,EAAE,MAAM,CAAA;QACjB,QAAQ,EAAE,MAAM,CAAA;KACjB,CAAA;CACF"}
@@ -2,18 +2,27 @@ import { useContext, useMemo } from 'react';
2
2
  import { ChatContext } from '../contexts/chat_context';
3
3
  import { Client } from '../utils';
4
4
  import { UploadUri } from '../utils/upload_uri';
5
+ import { availableFeatures, useFeatures } from './use_features';
5
6
  export const useUploadClient = () => {
6
7
  const { session, onUnauthorizedResponse } = useContext(ChatContext);
8
+ const { featureEnabled } = useFeatures();
9
+ const videoModerationEnabled = featureEnabled(availableFeatures.video_moderation);
7
10
  const uri = useMemo(() => new UploadUri({ session }), [session]);
8
11
  const api = useMemo(() => new UploadClient({
9
12
  version: '2025-05-16', // not really needed, but required by the Client constructor
10
13
  root: uri.baseUrl,
11
14
  defaultHeaders: uri.headers,
12
15
  onUnauthorizedResponse,
13
- }), [uri, onUnauthorizedResponse]);
16
+ videoModerationEnabled,
17
+ }), [uri, onUnauthorizedResponse, videoModerationEnabled]);
14
18
  return api;
15
19
  };
16
20
  class UploadClient extends Client {
21
+ videoModerationEnabled;
22
+ constructor({ videoModerationEnabled, ...rest }) {
23
+ super(rest);
24
+ this.videoModerationEnabled = videoModerationEnabled;
25
+ }
17
26
  async uploadFile(file) {
18
27
  const formData = new FormData();
19
28
  formData.append('file', {
@@ -23,6 +32,9 @@ class UploadClient extends Client {
23
32
  });
24
33
  const headers = { ...this.headers };
25
34
  delete headers['Content-Type'];
35
+ if (this.videoModerationEnabled && file.type.startsWith('video/')) {
36
+ headers['X-PCO-Moderate-Video'] = 'true';
37
+ }
26
38
  const response = await fetch(`${this.root}/v2/files`, {
27
39
  method: 'POST',
28
40
  headers,
@@ -1 +1 @@
1
- {"version":3,"file":"use_upload_client.js","sourceRoot":"","sources":["../../src/hooks/use_upload_client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AACtD,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AACjC,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAQ/C,MAAM,CAAC,MAAM,eAAe,GAAG,GAAG,EAAE;IAClC,MAAM,EAAE,OAAO,EAAE,sBAAsB,EAAE,GAAG,UAAU,CAAC,WAAW,CAAC,CAAA;IAEnE,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,IAAI,SAAS,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAA;IAEhE,MAAM,GAAG,GAAG,OAAO,CACjB,GAAG,EAAE,CACH,IAAI,YAAY,CAAC;QACf,OAAO,EAAE,YAAY,EAAE,4DAA4D;QACnF,IAAI,EAAE,GAAG,CAAC,OAAO;QACjB,cAAc,EAAE,GAAG,CAAC,OAAO;QAC3B,sBAAsB;KACvB,CAAC,EACJ,CAAC,GAAG,EAAE,sBAAsB,CAAC,CAC9B,CAAA;IAED,OAAO,GAAG,CAAA;AACZ,CAAC,CAAA;AAED,MAAM,YAAa,SAAQ,MAAM;IAC/B,KAAK,CAAC,UAAU,CAAC,IAAyB;QACxC,MAAM,QAAQ,GAAG,IAAI,QAAQ,EAAE,CAAA;QAC/B,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE;YACtB,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,IAAI,EAAE,IAAI,CAAC,IAAI;SAChB,CAAC,CAAA;QAEF,MAAM,OAAO,GAA2B,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,CAAA;QAC3D,OAAO,OAAO,CAAC,cAAc,CAAC,CAAA;QAE9B,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,IAAI,CAAC,IAAI,WAAW,EAAE;YACpD,MAAM,EAAE,MAAM;YACd,OAAO;YACP,IAAI,EAAE,QAAQ;SACf,CAAC,CAAA;QAEF,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YAC5B,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAA;YACzD,IAAI,SAAS,EAAE,MAAM,KAAK,mBAAmB,EAAE,CAAC;gBAC9C,OAAO,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC,CAAA;YAClD,CAAC;YACD,OAAO,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAA;QACnC,CAAC;QAED,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,OAAO,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAA;QACnC,CAAC;QAED,MAAM,YAAY,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAA;QAE1C,OAAO,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IAC7B,CAAC;CACF","sourcesContent":["import { useContext, useMemo } from 'react'\nimport { ChatContext } from '../contexts/chat_context'\nimport { Client } from '../utils'\nimport { UploadUri } from '../utils/upload_uri'\n\nexport interface FileForUploadClient {\n uri: string\n name: string\n type: string // Should be a MIME type\n}\n\nexport const useUploadClient = () => {\n const { session, onUnauthorizedResponse } = useContext(ChatContext)\n\n const uri = useMemo(() => new UploadUri({ session }), [session])\n\n const api = useMemo(\n () =>\n new UploadClient({\n version: '2025-05-16', // not really needed, but required by the Client constructor\n root: uri.baseUrl,\n defaultHeaders: uri.headers,\n onUnauthorizedResponse,\n }),\n [uri, onUnauthorizedResponse]\n )\n\n return api\n}\n\nclass UploadClient extends Client {\n async uploadFile(file: FileForUploadClient): Promise<UploadedResource> {\n const formData = new FormData()\n formData.append('file', {\n uri: file.uri,\n name: file.name,\n type: file.type,\n })\n\n const headers: RequestInit['headers'] = { ...this.headers }\n delete headers['Content-Type']\n\n const response = await fetch(`${this.root}/v2/files`, {\n method: 'POST',\n headers,\n body: formData,\n })\n\n if (response.status === 403) {\n const errorBody = await response.json().catch(() => null)\n if (errorBody?.detail === 'Image was flagged') {\n return Promise.reject({ code: 'image_flagged' })\n }\n return this.handleNotOk(response)\n }\n\n if (!response.ok) {\n return this.handleNotOk(response)\n }\n\n const jsonResponse = await response.json()\n\n return jsonResponse.data[0]\n }\n}\n\ninterface UploadedResource {\n id: string\n type: string\n attributes: {\n organization_id: string\n person_id: string\n md5: string\n created_at: string\n expires_at: string\n source_ip: string\n name: string\n content_type: string\n file_size: number\n location: string\n }\n}\n"]}
1
+ {"version":3,"file":"use_upload_client.js","sourceRoot":"","sources":["../../src/hooks/use_upload_client.ts"],"names":[],"mappings":"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","sourcesContent":["import { useContext, useMemo } from 'react'\nimport { ChatContext } from '../contexts/chat_context'\nimport { Client } from '../utils'\nimport { UploadUri } from '../utils/upload_uri'\nimport { availableFeatures, useFeatures } from './use_features'\n\nexport interface FileForUploadClient {\n uri: string\n name: string\n type: string // Should be a MIME type\n}\n\nexport const useUploadClient = () => {\n const { session, onUnauthorizedResponse } = useContext(ChatContext)\n const { featureEnabled } = useFeatures()\n const videoModerationEnabled = featureEnabled(availableFeatures.video_moderation)\n\n const uri = useMemo(() => new UploadUri({ session }), [session])\n\n const api = useMemo(\n () =>\n new UploadClient({\n version: '2025-05-16', // not really needed, but required by the Client constructor\n root: uri.baseUrl,\n defaultHeaders: uri.headers,\n onUnauthorizedResponse,\n videoModerationEnabled,\n }),\n [uri, onUnauthorizedResponse, videoModerationEnabled]\n )\n\n return api\n}\n\nclass UploadClient extends Client {\n private videoModerationEnabled: boolean\n\n constructor({\n videoModerationEnabled,\n ...rest\n }: ConstructorParameters<typeof Client>[0] & { videoModerationEnabled: boolean }) {\n super(rest)\n this.videoModerationEnabled = videoModerationEnabled\n }\n\n async uploadFile(file: FileForUploadClient): Promise<UploadedResource> {\n const formData = new FormData()\n formData.append('file', {\n uri: file.uri,\n name: file.name,\n type: file.type,\n })\n\n const headers: RequestInit['headers'] = { ...this.headers }\n delete headers['Content-Type']\n\n if (this.videoModerationEnabled && file.type.startsWith('video/')) {\n headers['X-PCO-Moderate-Video'] = 'true'\n }\n\n const response = await fetch(`${this.root}/v2/files`, {\n method: 'POST',\n headers,\n body: formData,\n })\n\n if (response.status === 403) {\n const errorBody = await response.json().catch(() => null)\n if (errorBody?.detail === 'Image was flagged') {\n return Promise.reject({ code: 'image_flagged' })\n }\n return this.handleNotOk(response)\n }\n\n if (!response.ok) {\n return this.handleNotOk(response)\n }\n\n const jsonResponse = await response.json()\n\n return jsonResponse.data[0]\n }\n}\n\ninterface UploadedResource {\n id: string\n type: string\n attributes: {\n organization_id: string\n person_id: string\n md5: string\n created_at: string\n expires_at: string\n source_ip: string\n name: string\n content_type: string\n file_size: number\n location: string\n }\n}\n"]}
@@ -0,0 +1,14 @@
1
+ import type { CustomMessage } from '@planningcenter/jolt-client/dist/types/JoltConnection';
2
+ interface AttachmentFlaggedEventData extends Record<string, unknown> {
3
+ data: {
4
+ attachment_id: string;
5
+ conversation_id: number;
6
+ content_type: string;
7
+ };
8
+ }
9
+ export interface AttachmentFlaggedEvent extends CustomMessage {
10
+ event: 'attachment.flagged';
11
+ data: AttachmentFlaggedEventData;
12
+ }
13
+ export {};
14
+ //# sourceMappingURL=attachment_events.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attachment_events.d.ts","sourceRoot":"","sources":["../../../src/types/jolt_events/attachment_events.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uDAAuD,CAAA;AAE1F,UAAU,0BAA2B,SAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAClE,IAAI,EAAE;QACJ,aAAa,EAAE,MAAM,CAAA;QACrB,eAAe,EAAE,MAAM,CAAA;QACvB,YAAY,EAAE,MAAM,CAAA;KACrB,CAAA;CACF;AAED,MAAM,WAAW,sBAAuB,SAAQ,aAAa;IAC3D,KAAK,EAAE,oBAAoB,CAAA;IAC3B,IAAI,EAAE,0BAA0B,CAAA;CACjC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=attachment_events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attachment_events.js","sourceRoot":"","sources":["../../../src/types/jolt_events/attachment_events.ts"],"names":[],"mappings":"","sourcesContent":["import type { CustomMessage } from '@planningcenter/jolt-client/dist/types/JoltConnection'\n\ninterface AttachmentFlaggedEventData extends Record<string, unknown> {\n data: {\n attachment_id: string\n conversation_id: number\n content_type: string\n }\n}\n\nexport interface AttachmentFlaggedEvent extends CustomMessage {\n event: 'attachment.flagged'\n data: AttachmentFlaggedEventData\n}\n"]}
@@ -1,3 +1,4 @@
1
+ import type { AttachmentFlaggedEvent } from './attachment_events';
1
2
  import type { ConversationCreatedEvent, ConversationDeletedEvent, ConversationUpdatedEvent, ConversationReadEvent } from './conversation_events';
2
3
  import type { MessageCreatedEvent, MessageDeletedEvent, MessageUpdatedEvent } from './message_events';
3
4
  import type { ReactionCreatedEvent, ReactionDeletedEvent } from './reaction_events';
@@ -6,7 +7,8 @@ export type JoltConversationEvent = ConversationCreatedEvent | ConversationUpdat
6
7
  export type JoltMessageEvent = MessageCreatedEvent | MessageUpdatedEvent | MessageDeletedEvent;
7
8
  export type JoltReactionEvent = ReactionCreatedEvent | ReactionDeletedEvent;
8
9
  export type JoltTypingEvent = TypingBroadcastEvent;
9
- export type CustomJoltEvent = JoltConversationEvent | JoltMessageEvent | JoltReactionEvent | JoltTypingEvent;
10
+ export type JoltAttachmentEvent = AttachmentFlaggedEvent;
11
+ export type CustomJoltEvent = JoltConversationEvent | JoltMessageEvent | JoltReactionEvent | JoltTypingEvent | JoltAttachmentEvent;
10
12
  export type JoltEventName = CustomJoltEvent['event'] | 'STREAM_USER_UPDATED';
11
13
  export type JoltSubscriptionPattern = JoltEventName | 'reaction.*';
12
14
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/types/jolt_events/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,wBAAwB,EACxB,wBAAwB,EACxB,wBAAwB,EACxB,qBAAqB,EACtB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,KAAK,EACV,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACpB,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AACnF,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AAE3D,MAAM,MAAM,qBAAqB,GAC7B,wBAAwB,GACxB,wBAAwB,GACxB,wBAAwB,GACxB,qBAAqB,CAAA;AACzB,MAAM,MAAM,gBAAgB,GAAG,mBAAmB,GAAG,mBAAmB,GAAG,mBAAmB,CAAA;AAC9F,MAAM,MAAM,iBAAiB,GAAG,oBAAoB,GAAG,oBAAoB,CAAA;AAC3E,MAAM,MAAM,eAAe,GAAG,oBAAoB,CAAA;AAElD,MAAM,MAAM,eAAe,GACvB,qBAAqB,GACrB,gBAAgB,GAChB,iBAAiB,GACjB,eAAe,CAAA;AAEnB,MAAM,MAAM,aAAa,GAAG,eAAe,CAAC,OAAO,CAAC,GAAG,qBAAqB,CAAA;AAC5E,MAAM,MAAM,uBAAuB,GAAG,aAAa,GAAG,YAAY,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/types/jolt_events/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAA;AACjE,OAAO,KAAK,EACV,wBAAwB,EACxB,wBAAwB,EACxB,wBAAwB,EACxB,qBAAqB,EACtB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,KAAK,EACV,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACpB,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AACnF,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AAE3D,MAAM,MAAM,qBAAqB,GAC7B,wBAAwB,GACxB,wBAAwB,GACxB,wBAAwB,GACxB,qBAAqB,CAAA;AACzB,MAAM,MAAM,gBAAgB,GAAG,mBAAmB,GAAG,mBAAmB,GAAG,mBAAmB,CAAA;AAC9F,MAAM,MAAM,iBAAiB,GAAG,oBAAoB,GAAG,oBAAoB,CAAA;AAC3E,MAAM,MAAM,eAAe,GAAG,oBAAoB,CAAA;AAClD,MAAM,MAAM,mBAAmB,GAAG,sBAAsB,CAAA;AAExD,MAAM,MAAM,eAAe,GACvB,qBAAqB,GACrB,gBAAgB,GAChB,iBAAiB,GACjB,eAAe,GACf,mBAAmB,CAAA;AAEvB,MAAM,MAAM,aAAa,GAAG,eAAe,CAAC,OAAO,CAAC,GAAG,qBAAqB,CAAA;AAC5E,MAAM,MAAM,uBAAuB,GAAG,aAAa,GAAG,YAAY,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/types/jolt_events/index.ts"],"names":[],"mappings":"","sourcesContent":["import type {\n ConversationCreatedEvent,\n ConversationDeletedEvent,\n ConversationUpdatedEvent,\n ConversationReadEvent,\n} from './conversation_events'\nimport type {\n MessageCreatedEvent,\n MessageDeletedEvent,\n MessageUpdatedEvent,\n} from './message_events'\nimport type { ReactionCreatedEvent, ReactionDeletedEvent } from './reaction_events'\nimport type { TypingBroadcastEvent } from './typing_events'\n\nexport type JoltConversationEvent =\n | ConversationCreatedEvent\n | ConversationUpdatedEvent\n | ConversationDeletedEvent\n | ConversationReadEvent\nexport type JoltMessageEvent = MessageCreatedEvent | MessageUpdatedEvent | MessageDeletedEvent\nexport type JoltReactionEvent = ReactionCreatedEvent | ReactionDeletedEvent\nexport type JoltTypingEvent = TypingBroadcastEvent\n\nexport type CustomJoltEvent =\n | JoltConversationEvent\n | JoltMessageEvent\n | JoltReactionEvent\n | JoltTypingEvent\n\nexport type JoltEventName = CustomJoltEvent['event'] | 'STREAM_USER_UPDATED'\nexport type JoltSubscriptionPattern = JoltEventName | 'reaction.*'\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/types/jolt_events/index.ts"],"names":[],"mappings":"","sourcesContent":["import type { AttachmentFlaggedEvent } from './attachment_events'\nimport type {\n ConversationCreatedEvent,\n ConversationDeletedEvent,\n ConversationUpdatedEvent,\n ConversationReadEvent,\n} from './conversation_events'\nimport type {\n MessageCreatedEvent,\n MessageDeletedEvent,\n MessageUpdatedEvent,\n} from './message_events'\nimport type { ReactionCreatedEvent, ReactionDeletedEvent } from './reaction_events'\nimport type { TypingBroadcastEvent } from './typing_events'\n\nexport type JoltConversationEvent =\n | ConversationCreatedEvent\n | ConversationUpdatedEvent\n | ConversationDeletedEvent\n | ConversationReadEvent\nexport type JoltMessageEvent = MessageCreatedEvent | MessageUpdatedEvent | MessageDeletedEvent\nexport type JoltReactionEvent = ReactionCreatedEvent | ReactionDeletedEvent\nexport type JoltTypingEvent = TypingBroadcastEvent\nexport type JoltAttachmentEvent = AttachmentFlaggedEvent\n\nexport type CustomJoltEvent =\n | JoltConversationEvent\n | JoltMessageEvent\n | JoltReactionEvent\n | JoltTypingEvent\n | JoltAttachmentEvent\n\nexport type JoltEventName = CustomJoltEvent['event'] | 'STREAM_USER_UPDATED'\nexport type JoltSubscriptionPattern = JoltEventName | 'reaction.*'\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@planningcenter/chat-react-native",
3
- "version": "3.37.0-rc.4",
3
+ "version": "3.38.0-rc.0",
4
4
  "description": "",
5
5
  "main": "build/index.js",
6
6
  "react-native": "./src/index.tsx",
@@ -72,5 +72,5 @@
72
72
  "react-native-url-polyfill": "^2.0.0",
73
73
  "typescript": "~5.9.2"
74
74
  },
75
- "gitHead": "47b2675fc5a5c7b4dcca6a62690fe01a4e4151f1"
75
+ "gitHead": "e88a39dc1b59889e7757955b536ae8fc9efe98c6"
76
76
  }
@@ -11,6 +11,16 @@ import { FileAttachment } from '../../types/resources/denormalized_attachment_re
11
11
  jest.mock('../../hooks/use_api_client')
12
12
  jest.mock('../../hooks/use_upload_client')
13
13
  jest.mock('../../hooks/use_chat_configuration')
14
+ jest.mock('../../hooks/use_api', () => ({
15
+ useApiGet: jest.fn().mockReturnValue({ data: { id: 1 } }),
16
+ }))
17
+ const joltEventCallbacks: Record<string, (e: unknown) => void> = {}
18
+ jest.mock('../../hooks/use_jolt', () => ({
19
+ useJoltChannel: jest.fn().mockReturnValue('channel-stub'),
20
+ useJoltEvent: jest.fn((_channel, eventName, callback) => {
21
+ joltEventCallbacks[eventName] = callback
22
+ }),
23
+ }))
14
24
 
15
25
  const mockedUseApiClient = useApiClient as jest.MockedFunction<typeof useApiClient>
16
26
  const mockedUseUploadClient = useUploadClient as jest.MockedFunction<typeof useUploadClient>
@@ -217,4 +227,30 @@ describe('useAttachmentUploader', () => {
217
227
  expect(result.current.flaggedAttachmentCount).toBe(1)
218
228
  })
219
229
  })
230
+
231
+ describe('attachment.flagged jolt event', () => {
232
+ it('flips the matching attachment to flagged + error when the event fires', () => {
233
+ const { result } = renderUploader({
234
+ draftAttachments: [draftAttachment('clip.mp4')],
235
+ })
236
+
237
+ act(() => {
238
+ joltEventCallbacks['attachment.flagged']({
239
+ event: 'attachment.flagged',
240
+ data: {
241
+ data: {
242
+ attachment_id: 'att-clip.mp4',
243
+ conversation_id: 1,
244
+ content_type: 'video/mp4',
245
+ },
246
+ },
247
+ })
248
+ })
249
+
250
+ const flagged = result.current.attachments.find(a => a.id === 'att-clip.mp4')
251
+ expect(flagged?.flagged).toBe(true)
252
+ expect(flagged?.status).toBe('error')
253
+ expect(result.current.flaggedAttachmentCount).toBe(1)
254
+ })
255
+ })
220
256
  })
@@ -31,6 +31,7 @@ import { ConversationResource, MessageResource } from '../../types'
31
31
  import {
32
32
  DenormalizedAttachmentResourceForCreate,
33
33
  DenormalizedMessageAttachmentResourceForCreate,
34
+ FileAttachment,
34
35
  } from '../../types/resources/denormalized_attachment_resource_for_create'
35
36
  import {
36
37
  MAX_FONT_SIZE_MULTIPLIER_LANDMARK,
@@ -340,15 +341,28 @@ function MessageFormAttachments() {
340
341
  )
341
342
  }
342
343
 
344
+ function getFileType(attachments: FileAttachment[]): 'image' | 'video' | 'file' {
345
+ const kinds = new Set(
346
+ attachments.map(({ file }) => {
347
+ if (file.type.startsWith('image/')) return 'image'
348
+ if (file.type.startsWith('video/')) return 'video'
349
+ return 'file'
350
+ })
351
+ )
352
+ return kinds.size === 1 ? [...kinds][0] : 'file'
353
+ }
354
+
343
355
  function FlaggedContentBanner() {
344
356
  const styles = useMessageFormStyles()
345
357
  const { attachmentUploader } = React.useContext(MessageFormContext)
346
- const flaggedCount = attachmentUploader?.flaggedAttachmentCount || 0
358
+ const flaggedAttachments = attachmentUploader?.attachments.filter(a => a.flagged) ?? []
359
+ const flaggedCount = flaggedAttachments.length
347
360
 
348
361
  if (flaggedCount === 0) return null
349
362
 
350
363
  const isSingular = flaggedCount === 1
351
- const buttonTitle = isSingular ? 'Remove flagged image' : 'Remove flagged images'
364
+ const fileType = getFileType(flaggedAttachments)
365
+ const buttonTitle = isSingular ? `Remove flagged ${fileType}` : `Remove flagged ${fileType}s`
352
366
 
353
367
  return (
354
368
  <View style={styles.flaggedBannerContainer}>
@@ -356,7 +370,7 @@ function FlaggedContentBanner() {
356
370
  <BannerPrimitive.StaticLayout>
357
371
  <BannerPrimitive.StatusIcon />
358
372
  <BannerPrimitive.Content>
359
- <BannerMessage isSingular={isSingular} />
373
+ <BannerMessage isSingular={isSingular} fileType={fileType} />
360
374
  <View style={styles.flaggedBannerButtonRow}>
361
375
  <Button
362
376
  title={buttonTitle}
@@ -373,7 +387,7 @@ function FlaggedContentBanner() {
373
387
  )
374
388
  }
375
389
 
376
- function BannerMessage({ isSingular }: { isSingular: boolean }) {
390
+ function BannerMessage({ isSingular, fileType }: { isSingular: boolean; fileType: string }) {
377
391
  const styles = useMessageFormStyles()
378
392
 
379
393
  const contentGuidelinesLink = (
@@ -392,13 +406,13 @@ function BannerMessage({ isSingular }: { isSingular: boolean }) {
392
406
 
393
407
  return isSingular ? (
394
408
  <Text style={styles.flaggedBannerText}>
395
- An uploaded image was flagged because it doesn't meet our {contentGuidelinesLink}. Please
409
+ An uploaded {fileType} was flagged because it doesn't meet our {contentGuidelinesLink}. Please
396
410
  remove before proceeding.
397
411
  </Text>
398
412
  ) : (
399
413
  <Text style={styles.flaggedBannerText}>
400
- Some uploaded images were flagged because they don't meet our {contentGuidelinesLink}. Please
401
- remove them before proceeding.
414
+ Some uploaded {fileType}s were flagged because they don't meet our {contentGuidelinesLink}.
415
+ Please remove them before proceeding.
402
416
  </Text>
403
417
  )
404
418
  }
@@ -1,12 +1,16 @@
1
1
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
2
- import { ApiResource } from '../types'
2
+ import { ApiResource, CurrentPersonResource } from '../types'
3
+ import { AttachmentFlaggedEvent } from '../types/jolt_events/attachment_events'
3
4
  import {
4
5
  FileAttachment,
5
6
  FileUploadState,
6
7
  NativeAttachmentFile,
7
8
  } from '../types/resources/denormalized_attachment_resource_for_create'
9
+ import { useApiGet } from './use_api'
8
10
  import { useApiClient } from './use_api_client'
9
11
  import { useChatConfiguration } from './use_chat_configuration'
12
+ import { currentPersonRequestArgs } from './use_current_person'
13
+ import { useJoltChannel, useJoltEvent } from './use_jolt'
10
14
  import { useUploadClient } from './use_upload_client'
11
15
 
12
16
  export interface FileError {
@@ -32,6 +36,15 @@ export function useAttachmentUploader({
32
36
  const numberOfAttachments = attachments.length
33
37
  const [errorMessage, setErrorMessage] = useState<string | null>(null)
34
38
 
39
+ const { data: currentPerson } = useApiGet<CurrentPersonResource>(currentPersonRequestArgs)
40
+ const joltChannel = useJoltChannel(`chat.people.${currentPerson?.id}`, Boolean(currentPerson?.id))
41
+ useJoltEvent(joltChannel, 'attachment.flagged', (e: AttachmentFlaggedEvent) => {
42
+ const flaggedId = e.data.data.attachment_id
43
+ setAttachments(prev =>
44
+ prev.map(a => (a.id === flaggedId ? { ...a, flagged: true, status: 'error' } : a))
45
+ )
46
+ })
47
+
35
48
  const handleFilesAttached = useCallback(
36
49
  (files: NativeAttachmentFile[]) => {
37
50
  const fileErrors = {} as FileError
@@ -42,6 +42,7 @@ export const availableFeatures = {
42
42
  custom_conversation_avatars: 'ROLLOUT_custom_conversation_avatars',
43
43
  jump_to_unread: 'ROLLOUT_jump_to_unread',
44
44
  conversation_safety_lock: 'ROLLOUT_conversation_safety_lock',
45
+ video_moderation: 'ROLLOUT_MOBILE_video_moderation',
45
46
  } as const satisfies Record<string, `ROLLOUT_${string}`>
46
47
 
47
48
  const stableEmptyFeatures: ApiCollection<FeatureResource> = {
@@ -2,6 +2,7 @@ import { useContext, useMemo } from 'react'
2
2
  import { ChatContext } from '../contexts/chat_context'
3
3
  import { Client } from '../utils'
4
4
  import { UploadUri } from '../utils/upload_uri'
5
+ import { availableFeatures, useFeatures } from './use_features'
5
6
 
6
7
  export interface FileForUploadClient {
7
8
  uri: string
@@ -11,6 +12,8 @@ export interface FileForUploadClient {
11
12
 
12
13
  export const useUploadClient = () => {
13
14
  const { session, onUnauthorizedResponse } = useContext(ChatContext)
15
+ const { featureEnabled } = useFeatures()
16
+ const videoModerationEnabled = featureEnabled(availableFeatures.video_moderation)
14
17
 
15
18
  const uri = useMemo(() => new UploadUri({ session }), [session])
16
19
 
@@ -21,14 +24,25 @@ export const useUploadClient = () => {
21
24
  root: uri.baseUrl,
22
25
  defaultHeaders: uri.headers,
23
26
  onUnauthorizedResponse,
27
+ videoModerationEnabled,
24
28
  }),
25
- [uri, onUnauthorizedResponse]
29
+ [uri, onUnauthorizedResponse, videoModerationEnabled]
26
30
  )
27
31
 
28
32
  return api
29
33
  }
30
34
 
31
35
  class UploadClient extends Client {
36
+ private videoModerationEnabled: boolean
37
+
38
+ constructor({
39
+ videoModerationEnabled,
40
+ ...rest
41
+ }: ConstructorParameters<typeof Client>[0] & { videoModerationEnabled: boolean }) {
42
+ super(rest)
43
+ this.videoModerationEnabled = videoModerationEnabled
44
+ }
45
+
32
46
  async uploadFile(file: FileForUploadClient): Promise<UploadedResource> {
33
47
  const formData = new FormData()
34
48
  formData.append('file', {
@@ -40,6 +54,10 @@ class UploadClient extends Client {
40
54
  const headers: RequestInit['headers'] = { ...this.headers }
41
55
  delete headers['Content-Type']
42
56
 
57
+ if (this.videoModerationEnabled && file.type.startsWith('video/')) {
58
+ headers['X-PCO-Moderate-Video'] = 'true'
59
+ }
60
+
43
61
  const response = await fetch(`${this.root}/v2/files`, {
44
62
  method: 'POST',
45
63
  headers,
@@ -0,0 +1,14 @@
1
+ import type { CustomMessage } from '@planningcenter/jolt-client/dist/types/JoltConnection'
2
+
3
+ interface AttachmentFlaggedEventData extends Record<string, unknown> {
4
+ data: {
5
+ attachment_id: string
6
+ conversation_id: number
7
+ content_type: string
8
+ }
9
+ }
10
+
11
+ export interface AttachmentFlaggedEvent extends CustomMessage {
12
+ event: 'attachment.flagged'
13
+ data: AttachmentFlaggedEventData
14
+ }
@@ -1,3 +1,4 @@
1
+ import type { AttachmentFlaggedEvent } from './attachment_events'
1
2
  import type {
2
3
  ConversationCreatedEvent,
3
4
  ConversationDeletedEvent,
@@ -20,12 +21,14 @@ export type JoltConversationEvent =
20
21
  export type JoltMessageEvent = MessageCreatedEvent | MessageUpdatedEvent | MessageDeletedEvent
21
22
  export type JoltReactionEvent = ReactionCreatedEvent | ReactionDeletedEvent
22
23
  export type JoltTypingEvent = TypingBroadcastEvent
24
+ export type JoltAttachmentEvent = AttachmentFlaggedEvent
23
25
 
24
26
  export type CustomJoltEvent =
25
27
  | JoltConversationEvent
26
28
  | JoltMessageEvent
27
29
  | JoltReactionEvent
28
30
  | JoltTypingEvent
31
+ | JoltAttachmentEvent
29
32
 
30
33
  export type JoltEventName = CustomJoltEvent['event'] | 'STREAM_USER_UPDATED'
31
34
  export type JoltSubscriptionPattern = JoltEventName | 'reaction.*'