@planningcenter/chat-react-native 3.15.0-rc.9 → 3.15.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.
@@ -1,5 +1,13 @@
1
1
  import React, { useCallback, useLayoutEffect, useState } from 'react'
2
- import { View, StyleSheet, TextInput, Linking, ScrollView, TouchableOpacity } from 'react-native'
2
+ import {
3
+ View,
4
+ StyleSheet,
5
+ TextInput,
6
+ Linking,
7
+ ScrollView,
8
+ TouchableOpacity,
9
+ Platform,
10
+ } from 'react-native'
3
11
  import type {
4
12
  NativeStackNavigationOptions,
5
13
  NativeStackScreenProps,
@@ -27,19 +35,23 @@ import { DefaultLoading } from '../components/page/loading'
27
35
  import { startsWith } from 'lodash'
28
36
  import { VideoAttachmentPreview } from '../components/display/video_attachment_preview'
29
37
  import { SafeAreaModal } from '../components/safe_area_modal'
38
+ import { useSafeAreaInsets } from 'react-native-safe-area-context'
39
+ import { useAppName } from '../hooks/use_app_name'
30
40
 
31
41
  const MAX_DESCRIPTION_LENGTH = 2000
32
42
 
33
- const BUG_TYPE_OPTIONS = [
34
- 'Issues sending or receiving messages',
35
- 'Trouble starting a new conversation',
36
- "Notifications aren't working",
37
- 'Incorrect read receipts or unread counts',
38
- 'Problems with file attachments',
39
- "Something isn't displaying properly",
40
- 'App is slow or crashes',
41
- 'Other',
42
- ]
43
+ enum BUG_TYPE_OPTIONS {
44
+ Chat = 'Chat',
45
+ FindMyChurch = 'Find my church',
46
+ LoggingIn = 'Logging in',
47
+ UsingGroups = 'Using groups (events, resources, members)',
48
+ CheckingIn = 'Checking in',
49
+ MakingADonation = 'Making a donation',
50
+ RegisteringForAnEvent = 'Registering for an event',
51
+ MyProfileAndSchedule = 'My profile and schedule',
52
+ Directory = 'Directory',
53
+ Other = 'Other',
54
+ }
43
55
 
44
56
  export const BugReportScreenOptions = ({
45
57
  navigation,
@@ -55,57 +67,49 @@ interface Attachment {
55
67
  type: string
56
68
  }
57
69
 
70
+ enum QualifiedMobileAppName {
71
+ chat = 'Chat', // There is no app name for the mobile app
72
+ churchcenter = 'Church Center App',
73
+ services = 'Services Mobile',
74
+ }
75
+
58
76
  export function BugReportScreen() {
59
77
  const styles = useStyles()
60
78
  const navigation = useNavigation()
61
- const [bugType, setBugType] = useState('')
79
+ const name = useAppName()
80
+ const appName = QualifiedMobileAppName[name]
81
+ const [bugType, setBugType] = useState<BUG_TYPE_OPTIONS>(BUG_TYPE_OPTIONS.Chat)
62
82
  const [showBugTypePicker, setShowBugTypePicker] = useState(false)
63
83
  const [whatWereDoing, setWhatWereDoing] = useState('')
64
84
  const [whatExpected, setWhatExpected] = useState('')
65
- const [stepsToResolve, setStepsToResolve] = useState('')
66
85
  const uploadApi = useUploadClient()
67
86
  const [uploading, setUploading] = useState(false)
68
87
  const [attachment, setAttachment] = useState<Attachment | null>(null)
69
88
  const [uploadError, setUploadError] = useState<string | null>(null)
70
- const mutation = useReportBugAction()
71
- const { mutate, status } = mutation
89
+ const { mutate: createBugReport, status } = useReportBugAction()
72
90
  const formValid =
73
91
  bugType.trim().length > 0 &&
74
92
  whatWereDoing.trim().length > 0 &&
75
93
  whatExpected.trim().length > 0 &&
76
- stepsToResolve.trim().length > 0 &&
77
94
  status === 'idle' &&
78
95
  !uploading
79
96
  const [imagePreviewURI, setImagePreviewURI] = useState<string>('')
80
-
81
97
  const isImageAttachment = startsWith(attachment?.type, 'image/')
82
98
 
83
99
  const handleSubmit = useCallback(() => {
84
- const description = `${whatWereDoing.substring(0, 100)}
85
-
86
- ## What kind of bug did you experience?
87
- ${bugType}
88
-
89
- ## What were you trying to do when you encountered the bug?
90
- ${whatWereDoing}
91
-
92
- ## What did you expect to happen? What actually happened?
93
- ${whatExpected}
100
+ const description = generateBugReportDescription(bugType, whatWereDoing, whatExpected)
94
101
 
95
- ## What steps have you tried to resolve the issue?
96
- ${stepsToResolve}`
97
-
98
- mutate({
102
+ createBugReport({
99
103
  description,
100
- description_json: JSON.stringify({
104
+ description_json: {
101
105
  bugType,
102
106
  whatWereDoing,
103
107
  whatExpected,
104
- stepsToResolve,
105
- }),
108
+ appName: appName,
109
+ },
106
110
  attachmentIds: attachment ? [attachment.id] : [],
107
111
  })
108
- }, [attachment, bugType, whatWereDoing, whatExpected, stepsToResolve, mutate])
112
+ }, [attachment, bugType, whatWereDoing, whatExpected, createBugReport, appName])
109
113
 
110
114
  const handleRemoveAttachment = useCallback(() => {
111
115
  setAttachment(null)
@@ -215,12 +219,10 @@ ${stepsToResolve}`
215
219
 
216
220
  <View style={styles.textInputContainer}>
217
221
  <Text style={styles.fieldLabel}>
218
- What kind of bug did you experience? <Text style={styles.required}>*</Text>
222
+ Where did you experience the issue? <Text style={styles.required}>*</Text>
219
223
  </Text>
220
224
  <TouchableOpacity style={styles.pickerButton} onPress={() => setShowBugTypePicker(true)}>
221
- <Text style={[styles.pickerText, !bugType && styles.pickerPlaceholder]}>
222
- {bugType || 'Select the bug type'}
223
- </Text>
225
+ <Text style={[styles.pickerText, !bugType && styles.pickerPlaceholder]}>{bugType}</Text>
224
226
  <Icon
225
227
  name="general.downChevron"
226
228
  style={styles.pickerArrow}
@@ -242,7 +244,7 @@ ${stepsToResolve}`
242
244
  <View style={styles.modalHeaderSpacer} />
243
245
  </View>
244
246
  <ScrollView style={styles.modalContent}>
245
- {BUG_TYPE_OPTIONS.map(option => (
247
+ {Object.values(BUG_TYPE_OPTIONS).map(option => (
246
248
  <TouchableOpacity
247
249
  key={option}
248
250
  style={styles.modalOption}
@@ -287,7 +289,7 @@ ${stepsToResolve}`
287
289
 
288
290
  <View style={styles.textInputContainer}>
289
291
  <Text style={styles.fieldLabel}>
290
- What did you expect to happen? What actually happened?{' '}
292
+ What did you expect to happen? Please describe what actually happened.{' '}
291
293
  <Text style={styles.required}>*</Text>
292
294
  </Text>
293
295
  <TextInput
@@ -305,25 +307,6 @@ ${stepsToResolve}`
305
307
  )}
306
308
  </View>
307
309
 
308
- <View style={styles.textInputContainer}>
309
- <Text style={styles.fieldLabel}>
310
- What steps have you tried to resolve the issue? <Text style={styles.required}>*</Text>
311
- </Text>
312
- <TextInput
313
- style={styles.textInput}
314
- multiline
315
- placeholder="Description"
316
- value={stepsToResolve}
317
- onChangeText={setStepsToResolve}
318
- maxLength={MAX_DESCRIPTION_LENGTH}
319
- />
320
- {stepsToResolve.length >= MAX_DESCRIPTION_LENGTH - 100 && (
321
- <Text variant="footnote">
322
- {stepsToResolve.length}/{MAX_DESCRIPTION_LENGTH}
323
- </Text>
324
- )}
325
- </View>
326
-
327
310
  <View style={styles.attachmentSection}>
328
311
  <View style={styles.attachmentHeader}>
329
312
  <Text style={styles.attachmentLabel}>
@@ -349,7 +332,7 @@ ${stepsToResolve}`
349
332
  </View>
350
333
  ) : (
351
334
  <Button
352
- title="Attach a screenshot"
335
+ title="Attach a screenshot or recording"
353
336
  accessibilityHint="Opens your device's image gallery"
354
337
  iconNameLeft="general.paperclip"
355
338
  onPress={pickImage}
@@ -359,14 +342,19 @@ ${stepsToResolve}`
359
342
  disabled={uploading || Boolean(attachment)}
360
343
  />
361
344
  )}
362
- </View>
363
-
364
- <View style={styles.footer}>
365
345
  <Text variant="footnote">
366
- We can’t respond to every submission, but we may reach out if we have additional
367
- questions.
346
+ Screenshots and screen recordings help us reproduce and fix issues faster.{' '}
347
+ <TextInlineButton
348
+ accessibilityRole="link"
349
+ variant="footnote"
350
+ onPress={() => Linking.openURL(VIDEO_RECORDING_HELP_URL)}
351
+ >
352
+ How to record your screen
353
+ </TextInlineButton>
368
354
  </Text>
355
+ </View>
369
356
 
357
+ <View style={styles.footer}>
370
358
  <Text variant="footnote">
371
359
  For details on how we process your data and ensure its security, please refer to our{' '}
372
360
  <TextInlineButton
@@ -384,12 +372,36 @@ ${stepsToResolve}`
384
372
  )
385
373
  }
386
374
 
375
+ const VIDEO_RECORDING_HELP_URL = Platform.select({
376
+ android: 'https://support.google.com/android/answer/6241341?hl=en',
377
+ default: 'https://support.apple.com/en-us/HT208721',
378
+ })
379
+
380
+ const generateBugReportDescription = (
381
+ bugType: BUG_TYPE_OPTIONS,
382
+ whatWereDoing: string,
383
+ whatExpected: string
384
+ ) => {
385
+ return `${whatWereDoing.substring(0, 100)}
386
+
387
+ ## What kind of bug did you experience?
388
+ ${bugType}
389
+
390
+ ## What were you trying to do when you encountered the bug?
391
+ ${whatWereDoing}
392
+
393
+ ## What did you expect to happen? What actually happened?
394
+ ${whatExpected}
395
+ `
396
+ }
397
+
387
398
  const useStyles = () => {
399
+ const { bottom } = useSafeAreaInsets()
388
400
  const { colors } = useTheme()
389
401
  return StyleSheet.create({
390
402
  container: {
391
403
  padding: 16,
392
- paddingBottom: 16,
404
+ paddingBottom: 16 + bottom,
393
405
  gap: 24,
394
406
  },
395
407
  fullHeight: {
@@ -1,5 +1,5 @@
1
1
  import { StaticScreenProps, useNavigation } from '@react-navigation/native'
2
- import React from 'react'
2
+ import React, { useCallback } from 'react'
3
3
  import { StyleSheet, View } from 'react-native'
4
4
  import { Conversations, TextButton } from '../../components'
5
5
  import { ActionButton } from '../../components/display/action_button'
@@ -54,9 +54,9 @@ export function ConversationsScreen({ route }: ConversationsScreenProps) {
54
54
  })
55
55
  }
56
56
 
57
- const reportABug = () => {
58
- return navigation.navigate('BugReport')
59
- }
57
+ const handleGetHelp = useCallback(() => {
58
+ navigation.navigate('GetHelp')
59
+ }, [navigation])
60
60
 
61
61
  return (
62
62
  <View style={styles.container}>
@@ -71,12 +71,12 @@ export function ConversationsScreen({ route }: ConversationsScreenProps) {
71
71
  secondaryButton={
72
72
  <TextButton
73
73
  variant="tertiary"
74
- onPress={reportABug}
74
+ onPress={handleGetHelp}
75
75
  maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER_LANDMARK}
76
76
  accessibilityShowsLargeContentViewer
77
- accessibilityLargeContentTitle="Report a bug"
77
+ accessibilityLargeContentTitle="Get help"
78
78
  >
79
- Report a bug
79
+ Get help
80
80
  </TextButton>
81
81
  }
82
82
  />
@@ -0,0 +1,131 @@
1
+ import { useNavigation } from '@react-navigation/native'
2
+ import { NativeStackScreenProps } from '@react-navigation/native-stack'
3
+ import { useCallback } from 'react'
4
+ import { Linking, StyleSheet, View } from 'react-native'
5
+ import { Heading, PressableRow, Text, TextInlineButton } from '../components'
6
+ import { useApiGet, useTheme } from '../hooks'
7
+ import { useAppName } from '../hooks/use_app_name'
8
+ import { ResourceObject } from '../types'
9
+
10
+ type GetHelpScreenProps = NativeStackScreenProps<{}>
11
+
12
+ interface OrganizationResource extends ResourceObject {
13
+ id: number
14
+ name: string
15
+ contactEmail: string
16
+ contactPhoneNumber: string
17
+ churchCenterChatHelpUrl: string
18
+ servicesChatHelpUrl: string
19
+ }
20
+
21
+ const useOrganization = () => {
22
+ const { data: organization } = useApiGet<OrganizationResource>({
23
+ url: '/',
24
+ data: {
25
+ fields: {
26
+ Organization: [
27
+ 'contact_phone_number',
28
+ 'contact_email',
29
+ 'name',
30
+ 'church_center_chat_help_url',
31
+ 'services_chat_help_url',
32
+ ],
33
+ },
34
+ },
35
+ })
36
+
37
+ return organization
38
+ }
39
+
40
+ export const GetHelpScreen = ({}: GetHelpScreenProps) => {
41
+ const styles = useStyles()
42
+ const appName = useAppName()
43
+ const isChurchCenter = appName === 'churchcenter'
44
+ const navigation = useNavigation()
45
+ const organization = useOrganization()
46
+ const contactPresent = organization?.contactEmail || organization?.contactPhoneNumber
47
+ const getHelpLink = isChurchCenter
48
+ ? organization?.churchCenterChatHelpUrl
49
+ : organization?.servicesChatHelpUrl
50
+
51
+ const handleNavigateToBugReport = useCallback(() => {
52
+ navigation.navigate('BugReport')
53
+ }, [navigation])
54
+
55
+ return (
56
+ <View style={[styles.container]}>
57
+ <Heading variant="h2" style={[styles.heading, styles.headingBottomBorder]}>
58
+ How-to articles
59
+ </Heading>
60
+ <PressableRow
61
+ onPress={() => Linking.openURL(getHelpLink || '')}
62
+ text="Get help with chat"
63
+ isActive={true}
64
+ iconPath="general.newWindow"
65
+ />
66
+ {contactPresent && (
67
+ <Heading variant="h2" style={[styles.heading, styles.headingBottomBorder]}>
68
+ Contact {organization?.name}
69
+ </Heading>
70
+ )}
71
+ <ContactRow email={organization?.contactEmail} />
72
+ <ContactRow phone={organization?.contactPhoneNumber} />
73
+ <Heading variant="h2" style={[styles.heading]}>
74
+ Report a bug
75
+ </Heading>
76
+ <View style={styles.text}>
77
+ <Text>
78
+ Having an issue?{' '}
79
+ <TextInlineButton onPress={handleNavigateToBugReport}>
80
+ Submit a bug report
81
+ </TextInlineButton>{' '}
82
+ to the development team.
83
+ </Text>
84
+ </View>
85
+ </View>
86
+ )
87
+ }
88
+
89
+ const ContactRow = ({ email, phone }: { email?: string; phone?: string }) => {
90
+ if (!email && !phone) return null
91
+
92
+ const contact = email || phone
93
+
94
+ return (
95
+ <PressableRow
96
+ onPress={() => {}}
97
+ text={contact || ''}
98
+ isActive={true}
99
+ iconPath={email ? 'services.email' : 'general.phone'}
100
+ />
101
+ )
102
+ }
103
+
104
+ const useStyles = () => {
105
+ const { colors } = useTheme()
106
+
107
+ return StyleSheet.create({
108
+ container: {
109
+ flex: 1,
110
+ },
111
+ text: {
112
+ paddingVertical: 4,
113
+ paddingHorizontal: 16,
114
+ },
115
+ heading: {
116
+ marginLeft: 16,
117
+ paddingTop: 24,
118
+ paddingBottom: 12,
119
+ },
120
+ headingBottomBorder: {
121
+ marginLeft: 16,
122
+ paddingLeft: 0,
123
+ borderBottomWidth: 1,
124
+ borderBottomColor: colors.borderColorDefaultBase,
125
+ },
126
+ headingAux: {
127
+ paddingTop: 24,
128
+ paddingHorizontal: 16,
129
+ },
130
+ })
131
+ }