@planningcenter/chat-react-native 3.15.0-rc.8 → 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.
Files changed (37) hide show
  1. package/build/components/display/icon.d.ts +26 -13
  2. package/build/components/display/icon.d.ts.map +1 -1
  3. package/build/components/display/icon.js +0 -12
  4. package/build/components/display/icon.js.map +1 -1
  5. package/build/hooks/use_app_name.d.ts +3 -0
  6. package/build/hooks/use_app_name.d.ts.map +1 -0
  7. package/build/hooks/use_app_name.js +12 -0
  8. package/build/hooks/use_app_name.js.map +1 -0
  9. package/build/hooks/use_report_bug_action.d.ts +1 -1
  10. package/build/hooks/use_report_bug_action.d.ts.map +1 -1
  11. package/build/hooks/use_report_bug_action.js +1 -9
  12. package/build/hooks/use_report_bug_action.js.map +1 -1
  13. package/build/navigation/index.d.ts +20 -5
  14. package/build/navigation/index.d.ts.map +1 -1
  15. package/build/navigation/index.js +23 -15
  16. package/build/navigation/index.js.map +1 -1
  17. package/build/screens/bug_report_screen.d.ts.map +1 -1
  18. package/build/screens/bug_report_screen.js +62 -57
  19. package/build/screens/bug_report_screen.js.map +1 -1
  20. package/build/screens/conversations/conversations_screen.d.ts.map +1 -1
  21. package/build/screens/conversations/conversations_screen.js +6 -6
  22. package/build/screens/conversations/conversations_screen.js.map +1 -1
  23. package/build/screens/design_system_screen.js +1 -1
  24. package/build/screens/design_system_screen.js.map +1 -1
  25. package/build/screens/get_help_screen.d.ts +5 -0
  26. package/build/screens/get_help_screen.d.ts.map +1 -0
  27. package/build/screens/get_help_screen.js +94 -0
  28. package/build/screens/get_help_screen.js.map +1 -0
  29. package/package.json +2 -2
  30. package/src/components/display/icon.tsx +17 -14
  31. package/src/hooks/use_app_name.ts +17 -0
  32. package/src/hooks/use_report_bug_action.ts +2 -10
  33. package/src/navigation/index.tsx +38 -25
  34. package/src/screens/bug_report_screen.tsx +79 -67
  35. package/src/screens/conversations/conversations_screen.tsx +7 -7
  36. package/src/screens/design_system_screen.tsx +1 -1
  37. package/src/screens/get_help_screen.tsx +131 -0
@@ -5,29 +5,17 @@ import { SvgXml } from 'react-native-svg'
5
5
  import type { XmlProps } from 'react-native-svg'
6
6
  import { useFontScale, useTheme } from '../../hooks'
7
7
 
8
- // @ts-ignore
9
8
  import * as accounts from '@planningcenter/icons/paths/accounts'
10
- // @ts-ignore
11
9
  import * as api from '@planningcenter/icons/paths/api'
12
- // @ts-ignore
13
10
  import * as brand from '@planningcenter/icons/paths/brand'
14
- // @ts-ignore
15
11
  import * as calendar from '@planningcenter/icons/paths/calendar'
16
- // @ts-ignore
17
12
  import * as chat from '@planningcenter/icons/paths/chat'
18
- // @ts-ignore
19
13
  import * as churchCenter from '@planningcenter/icons/paths/church-center'
20
- // @ts-ignore
21
14
  import * as general from '@planningcenter/icons/paths/general'
22
- // @ts-ignore
23
15
  import * as groups from '@planningcenter/icons/paths/groups'
24
- // @ts-ignore
25
16
  import * as logomark from '@planningcenter/icons/paths/logomark'
26
- // @ts-ignore
27
17
  import * as people from '@planningcenter/icons/paths/people'
28
- // @ts-ignore
29
18
  import * as services from '@planningcenter/icons/paths/services'
30
- // @ts-ignore
31
19
  import * as publishing from '@planningcenter/icons/paths/publishing'
32
20
 
33
21
  // =================================
@@ -57,7 +45,22 @@ export type IconStyle = ViewStyle & {
57
45
  }
58
46
 
59
47
  export type IconSetName = keyof typeof ICONS
60
- export type IconString = `${IconSetName}.${(typeof ICONS)[IconSetName]}`
48
+
49
+ type IconName<T extends IconSetName> = keyof (typeof ICONS)[T] & string
50
+
51
+ export type IconString =
52
+ | `accounts.${IconName<'accounts'>}`
53
+ | `api.${IconName<'api'>}`
54
+ | `brand.${IconName<'brand'>}`
55
+ | `calendar.${IconName<'calendar'>}`
56
+ | `chat.${IconName<'chat'>}`
57
+ | `churchCenter.${IconName<'churchCenter'>}`
58
+ | `general.${IconName<'general'>}`
59
+ | `groups.${IconName<'groups'>}`
60
+ | `logomark.${IconName<'logomark'>}`
61
+ | `people.${IconName<'people'>}`
62
+ | `services.${IconName<'services'>}`
63
+ | `publishing.${IconName<'publishing'>}`
61
64
 
62
65
  // =================================
63
66
  // ====== Component ================
@@ -137,7 +140,7 @@ const useGetIconSize = (size?: number, style?: IconStyle, maxFontSizeMultiplier?
137
140
  const getIconPath = (name: IconString): string => {
138
141
  const [setName, iconName] = name.split('.')
139
142
 
140
- return ICONS[setName as IconSetName]?.[iconName]
143
+ return (ICONS[setName as IconSetName] as Record<string, string>)?.[iconName]
141
144
  }
142
145
 
143
146
  // =================================
@@ -0,0 +1,17 @@
1
+ import DeviceInfo from 'react-native-device-info'
2
+
3
+ export type AppName = 'chat' | 'churchcenter' | 'services'
4
+
5
+ export const useAppName = (): AppName => {
6
+ const applicationName = DeviceInfo.getApplicationName()
7
+
8
+ if (/churchcenter/i.test(applicationName)) {
9
+ return 'churchcenter'
10
+ }
11
+
12
+ if (/services/i.test(applicationName)) {
13
+ return 'services'
14
+ }
15
+
16
+ return 'chat'
17
+ }
@@ -1,12 +1,5 @@
1
1
  import { useMutation } from '@tanstack/react-query'
2
2
  import { useApiClient } from './use_api_client'
3
- import DeviceInfo from 'react-native-device-info'
4
- const brand = DeviceInfo.getBrand()
5
- const model = DeviceInfo.getModel()
6
- const systemName = DeviceInfo.getSystemName()
7
- const systemVersion = DeviceInfo.getSystemVersion()
8
- const readableVersion = DeviceInfo.getReadableVersion()
9
- const appName = DeviceInfo.getApplicationName()
10
3
 
11
4
  export const useReportBugAction = () => {
12
5
  const apiClient = useApiClient()
@@ -18,7 +11,7 @@ export const useReportBugAction = () => {
18
11
  attachmentIds,
19
12
  }: {
20
13
  description: string
21
- description_json: string
14
+ description_json: Record<string, any>
22
15
  attachmentIds: string[]
23
16
  }) => {
24
17
  return apiClient.chat.post({
@@ -28,8 +21,7 @@ export const useReportBugAction = () => {
28
21
  type: '',
29
22
  attributes: {
30
23
  description,
31
- description_json,
32
- device_info: `${appName}/${readableVersion} (${brand}, ${model}, ${systemName}, ${systemVersion})`,
24
+ description_json: JSON.stringify(description_json),
33
25
  attachment_ids: attachmentIds,
34
26
  },
35
27
  },
@@ -4,47 +4,50 @@ import {
4
4
  createNativeStackNavigator,
5
5
  NativeStackHeaderRightProps,
6
6
  } from '@react-navigation/native-stack'
7
+ import { CardStyleInterpolators } from '@react-navigation/stack'
7
8
  import React from 'react'
9
+ import { Platform } from 'react-native'
8
10
  import { Icon } from '../components'
11
+ import { HeaderTextButton } from '../components/display/platform_modal_header_buttons'
12
+ import {
13
+ AttachmentActionsScreen,
14
+ AttachmentActionsScreenOptions,
15
+ } from '../screens/attachment_actions/attachment_actions_screen'
16
+ import { BugReportScreen, BugReportScreenOptions } from '../screens/bug_report_screen'
17
+ import {
18
+ MessageReadReceiptsScreen,
19
+ MessageReadReceiptsScreenOptions,
20
+ } from '../screens/conversation/message_read_receipts_screen'
9
21
  import { ConversationDetailsScreen } from '../screens/conversation_details_screen'
22
+ import {
23
+ ConversationFilterReceipientsScreenOptions,
24
+ ConversationFilterRecipientsScreen,
25
+ } from '../screens/conversation_filter_recipients/conversation_filter_recipients_screen'
26
+ import { ConversationFiltersParams } from '../screens/conversation_filters/screen_props'
27
+ import {
28
+ ConversationFiltersScreen,
29
+ ConversationFiltersScreenOptions,
30
+ } from '../screens/conversation_filters_screen'
31
+ import { ConversationNewScreen } from '../screens/conversation_new/conversation_new_screen'
10
32
  import {
11
33
  ConversationRouteProps,
12
34
  ConversationScreen,
13
35
  ConversationScreenTitle,
14
36
  } from '../screens/conversation_screen'
15
- import { ConversationsScreen } from '../screens/conversations/conversations_screen'
16
- import { ConversationNewScreen } from '../screens/conversation_new/conversation_new_screen'
17
- import {
18
- ConversationFilterReceipientsScreenOptions,
19
- ConversationFilterRecipientsScreen,
20
- } from '../screens/conversation_filter_recipients/conversation_filter_recipients_screen'
37
+ import { ConversationSelectGroupRecipientsScreen } from '../screens/conversation_select_recipients/conversation_select_group_recipients_screen'
21
38
  import { ConversationSelectRecipientsScreen } from '../screens/conversation_select_recipients/conversation_select_recipients_screen'
39
+ import { ConversationSelectTeamsILeadRecipientsScreen } from '../screens/conversation_select_recipients/conversation_select_teams_i_lead_recipients_screen'
40
+ import { ConversationsScreen } from '../screens/conversations/conversations_screen'
41
+ import { GetHelpScreen } from '../screens/get_help_screen'
22
42
  import {
23
43
  MessageActionsScreen,
24
44
  MessageActionsScreenOptions,
25
45
  } from '../screens/message_actions_screen'
26
- import { SendGiphyScreen, SendGiphyScreenOptions } from '../screens/send_giphy_screen'
27
46
  import { NotFound } from '../screens/not_found'
28
47
  import { ReactionsScreen, ReactionsScreenOptions } from '../screens/reactions_screen'
29
- import { ScreenLayout } from './screenLayout'
30
- import {
31
- ConversationFiltersScreen,
32
- ConversationFiltersScreenOptions,
33
- } from '../screens/conversation_filters_screen'
34
- import { ConversationFiltersParams } from '../screens/conversation_filters/screen_props'
35
- import { ConversationSelectGroupRecipientsScreen } from '../screens/conversation_select_recipients/conversation_select_group_recipients_screen'
36
- import { ConversationSelectTeamsILeadRecipientsScreen } from '../screens/conversation_select_recipients/conversation_select_teams_i_lead_recipients_screen'
37
- import { AttachmentActionsScreenOptions } from '../screens/attachment_actions/attachment_actions_screen'
38
- import { AttachmentActionsScreen } from '../screens/attachment_actions/attachment_actions_screen'
39
- import { BugReportScreen, BugReportScreenOptions } from '../screens/bug_report_screen'
40
- import {
41
- MessageReadReceiptsScreen,
42
- MessageReadReceiptsScreenOptions,
43
- } from '../screens/conversation/message_read_receipts_screen'
44
- import { Platform } from 'react-native'
45
- import { HeaderTextButton } from '../components/display/platform_modal_header_buttons'
48
+ import { SendGiphyScreen, SendGiphyScreenOptions } from '../screens/send_giphy_screen'
46
49
  import { TeamConversationScreen } from '../screens/team_conversation_screen'
47
- import { CardStyleInterpolators } from '@react-navigation/stack'
50
+ import { ScreenLayout } from './screenLayout'
48
51
 
49
52
  const HEADER_BACK_BUTTON_LAYOUT_RESET_STYLES = {
50
53
  marginLeft: Platform.select({ ios: -8, default: -3 }),
@@ -231,6 +234,16 @@ export const ChatStack = createNativeStackNavigator({
231
234
  screen: BugReportScreen,
232
235
  options: BugReportScreenOptions,
233
236
  },
237
+ GetHelp: {
238
+ screen: GetHelpScreen,
239
+ options: ({ navigation }) => ({
240
+ headerTitle: 'Get help',
241
+ presentation: 'modal',
242
+ headerLeft: props => (
243
+ <HeaderTextButton {...props} onPress={navigation.goBack} title="Close" />
244
+ ),
245
+ }),
246
+ },
234
247
  NotFound: {
235
248
  screen: NotFound,
236
249
  options: {
@@ -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
  />
@@ -863,7 +863,7 @@ function ImageIconsSection({ isLast }: SectionProps) {
863
863
  description="Displays any icon from @planningcenter/icons. Missing icons will fallback to a grey circle. Styling with `fontSize` will allow it to scale with the device's text a11y size."
864
864
  >
865
865
  <Row>
866
- {/* @ts-expect-error - Icon name is not a string */}
866
+ {/* @ts-expect-error - Testing missing icon fallback */}
867
867
  <Icon name="missingIcon" size={20} />
868
868
  <Icon name="general.textMessage" size={20} />
869
869
  <Icon name="general.bell" size={20} color={colors.needsDesignPass} />
@@ -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
+ }