import { useNavigation } from '@react-navigation/native' import type { NativeStackNavigationOptions, NativeStackScreenProps, } from '@react-navigation/native-stack' import { startsWith } from 'lodash' import React, { useCallback, useState } from 'react' import { View, StyleSheet, TextInput, Linking, ScrollView, TouchableOpacity, Platform, } from 'react-native' import { useSafeAreaInsets } from 'react-native-safe-area-context' import { Button, Spinner, Text, TextInlineButton, ImageAttachmentPreview, Icon, KeyboardScrollView, } from '../components' import { composeOptions, HeaderDismissButton, useHeaderRight, withLeftClose, } from '../components/display/platform_modal_header_buttons' import { VideoAttachmentPreview } from '../components/display/video_attachment_preview' import { DefaultLoading } from '../components/page/loading' import BlankState from '../components/primitive/blank_state_primitive' import { SafeAreaModal } from '../components/safe_area_modal' import { useTheme } from '../hooks' import { useAppName } from '../hooks/use_app_name' import { useReportBugAction } from '../hooks/use_report_bug_action' import { useUploadClient } from '../hooks/use_upload_client' import { ImagePicker, ImagePickerResult, platformFontWeightBold } from '../utils' const MAX_DESCRIPTION_LENGTH = 2000 enum BUG_TYPE_OPTIONS { Chat = 'Chat', FindMyChurch = 'Find my church', LoggingIn = 'Logging in', UsingGroups = 'Using groups (events, resources, members)', CheckingIn = 'Checking in', MakingADonation = 'Making a donation', RegisteringForAnEvent = 'Registering for an event', MyProfileAndSchedule = 'My profile and schedule', Directory = 'Directory', Other = 'Other', } export const BugReportScreenOptions = ({ navigation, }: NativeStackScreenProps): NativeStackNavigationOptions => composeOptions( { presentation: 'modal', title: 'Report a bug' }, withLeftClose({ onPress: () => navigation.goBack() }) ) interface Attachment { id: string name: string type: string } enum QualifiedMobileAppName { chat = 'Chat', // There is no app name for the mobile app churchcenter = 'Church Center App', services = 'Services Mobile', } enum DisplayableAppName { chat = 'Chat', churchcenter = 'Church Center', services = 'Services', } export function BugReportScreen() { const styles = useStyles() const navigation = useNavigation() const name = useAppName() const appName = QualifiedMobileAppName[name] const [bugType, setBugType] = useState(BUG_TYPE_OPTIONS.Chat) const [showBugTypePicker, setShowBugTypePicker] = useState(false) const [whatWereDoing, setWhatWereDoing] = useState('') const [whatExpected, setWhatExpected] = useState('') const uploadApi = useUploadClient() const [uploading, setUploading] = useState(false) const [attachment, setAttachment] = useState(null) const [uploadError, setUploadError] = useState(null) const { mutate: createBugReport, status } = useReportBugAction() const formValid = bugType.trim().length > 0 && whatWereDoing.trim().length > 0 && whatExpected.trim().length > 0 && status === 'idle' && !uploading const [imagePreviewURI, setImagePreviewURI] = useState('') const isImageAttachment = startsWith(attachment?.type, 'image/') const handleSubmit = useCallback(() => { const description = generateBugReportDescription(bugType, whatWereDoing, whatExpected) createBugReport({ description, description_json: { bugType, whatWereDoing, whatExpected, appName: appName, }, attachmentIds: attachment ? [attachment.id] : [], }) }, [attachment, bugType, whatWereDoing, whatExpected, createBugReport, appName]) const handleRemoveAttachment = useCallback(() => { setAttachment(null) setImagePreviewURI('') }, []) function uploadImagePickerResult(result: ImagePickerResult) { if (result.canceled || result.assets.length === 0) { setUploading(false) return } const asset = result.assets[0] uploadApi ?.uploadFile({ uri: asset.uri, name: asset.fileName as string, type: asset.mimeType as string, }) .then(uploadedResource => { setUploading(false) setImagePreviewURI(asset.uri) setAttachment({ id: uploadedResource.id, name: asset.fileName || 'File', type: asset.mimeType as string, }) }) .catch(() => { setUploading(false) setUploadError(`Failed to upload attachment`) }) } function pickImage() { setUploading(true) return ImagePicker.openImageLibraryAsync().then(result => uploadImagePickerResult(result)) } useHeaderRight({ icon: 'general.upArrow', accessibilityLabel: 'Submit', prominent: true, onPress: handleSubmit, disabled: !formValid, }) if (status === 'pending') { return ( ) } if (status === 'success') { return ( Thank you! We appreciate you taking the time to help improve {DisplayableAppName[name]}! We'll take a look at the issue you reported. ) } if (status === 'error') { return ( This is embarrassing, we can't submit your bug report right now. If you still need help or would like to submit this bug report another way please{' '} Linking.openURL('https://support.planningcenteronline.com/hc/en-us/requests/new') } > contact our help center .