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

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 (118) hide show
  1. package/build/components/conversation/message_list.d.ts +10 -0
  2. package/build/components/conversation/message_list.d.ts.map +1 -0
  3. package/build/components/conversation/message_list.js +13 -0
  4. package/build/components/conversation/message_list.js.map +1 -0
  5. package/build/components/conversations/conversations.d.ts.map +1 -1
  6. package/build/components/conversations/conversations.js +6 -16
  7. package/build/components/conversations/conversations.js.map +1 -1
  8. package/build/components/conversations/conversations_blank_state.d.ts +8 -0
  9. package/build/components/conversations/conversations_blank_state.d.ts.map +1 -0
  10. package/build/components/conversations/conversations_blank_state.js +25 -0
  11. package/build/components/conversations/conversations_blank_state.js.map +1 -0
  12. package/build/components/display/conversation_avatar.d.ts +2 -1
  13. package/build/components/display/conversation_avatar.d.ts.map +1 -1
  14. package/build/components/display/conversation_avatar.js +6 -5
  15. package/build/components/display/conversation_avatar.js.map +1 -1
  16. package/build/components/display/emoji_avatar.d.ts +3 -1
  17. package/build/components/display/emoji_avatar.d.ts.map +1 -1
  18. package/build/components/display/emoji_avatar.js +2 -2
  19. package/build/components/display/emoji_avatar.js.map +1 -1
  20. package/build/components/display/icon_avatar.d.ts +3 -1
  21. package/build/components/display/icon_avatar.d.ts.map +1 -1
  22. package/build/components/display/icon_avatar.js +2 -2
  23. package/build/components/display/icon_avatar.js.map +1 -1
  24. package/build/hooks/groups/use_group_chat_conversation_payload.d.ts.map +1 -1
  25. package/build/hooks/groups/use_group_chat_conversation_payload.js +1 -0
  26. package/build/hooks/groups/use_group_chat_conversation_payload.js.map +1 -1
  27. package/build/hooks/index.d.ts +1 -0
  28. package/build/hooks/index.d.ts.map +1 -1
  29. package/build/hooks/index.js +1 -0
  30. package/build/hooks/index.js.map +1 -1
  31. package/build/hooks/use_preview_avatar_diameter.d.ts +2 -0
  32. package/build/hooks/use_preview_avatar_diameter.d.ts.map +1 -0
  33. package/build/hooks/use_preview_avatar_diameter.js +11 -0
  34. package/build/hooks/use_preview_avatar_diameter.js.map +1 -0
  35. package/build/jest.js +1 -1
  36. package/build/jest.js.map +1 -1
  37. package/build/screens/age_check/age_check_underage_screen.js +1 -1
  38. package/build/screens/age_check/age_check_underage_screen.js.map +1 -1
  39. package/build/screens/avatar_picker/avatar_picker_screen.d.ts.map +1 -1
  40. package/build/screens/avatar_picker/avatar_picker_screen.js +11 -9
  41. package/build/screens/avatar_picker/avatar_picker_screen.js.map +1 -1
  42. package/build/screens/avatar_picker/avatar_preview.d.ts.map +1 -1
  43. package/build/screens/avatar_picker/avatar_preview.js +13 -5
  44. package/build/screens/avatar_picker/avatar_preview.js.map +1 -1
  45. package/build/screens/avatar_picker/emoji_tab.d.ts.map +1 -1
  46. package/build/screens/avatar_picker/emoji_tab.js +3 -7
  47. package/build/screens/avatar_picker/emoji_tab.js.map +1 -1
  48. package/build/screens/avatar_picker/upload_tab.d.ts.map +1 -1
  49. package/build/screens/avatar_picker/upload_tab.js +2 -1
  50. package/build/screens/avatar_picker/upload_tab.js.map +1 -1
  51. package/build/screens/conversation_details_screen.d.ts.map +1 -1
  52. package/build/screens/conversation_details_screen.js +5 -2
  53. package/build/screens/conversation_details_screen.js.map +1 -1
  54. package/build/screens/conversation_filter_recipients/components/header_row.d.ts.map +1 -1
  55. package/build/screens/conversation_filter_recipients/components/header_row.js +3 -2
  56. package/build/screens/conversation_filter_recipients/components/header_row.js.map +1 -1
  57. package/build/screens/conversation_filter_recipients/hooks/use_flattened_array_of_service_types_with_teams.d.ts.map +1 -1
  58. package/build/screens/conversation_filter_recipients/hooks/use_flattened_array_of_service_types_with_teams.js +47 -18
  59. package/build/screens/conversation_filter_recipients/hooks/use_flattened_array_of_service_types_with_teams.js.map +1 -1
  60. package/build/screens/conversation_filter_recipients/hooks/use_service_types_with_teams.d.ts +2 -1
  61. package/build/screens/conversation_filter_recipients/hooks/use_service_types_with_teams.d.ts.map +1 -1
  62. package/build/screens/conversation_filter_recipients/hooks/use_service_types_with_teams.js +23 -26
  63. package/build/screens/conversation_filter_recipients/hooks/use_service_types_with_teams.js.map +1 -1
  64. package/build/screens/conversation_filter_recipients/types.d.ts +1 -1
  65. package/build/screens/conversation_filter_recipients/types.d.ts.map +1 -1
  66. package/build/screens/conversation_filter_recipients/types.js.map +1 -1
  67. package/build/screens/conversation_screen.d.ts.map +1 -1
  68. package/build/screens/conversation_screen.js +3 -7
  69. package/build/screens/conversation_screen.js.map +1 -1
  70. package/build/screens/conversation_select_recipients/components/recipient_link_row.d.ts +1 -1
  71. package/build/screens/conversation_select_recipients/components/recipient_link_row.d.ts.map +1 -1
  72. package/build/screens/conversation_select_recipients/components/recipient_link_row.js +3 -3
  73. package/build/screens/conversation_select_recipients/components/recipient_link_row.js.map +1 -1
  74. package/build/screens/conversation_select_recipients/components/team_recipient_row.d.ts.map +1 -1
  75. package/build/screens/conversation_select_recipients/components/team_recipient_row.js +1 -1
  76. package/build/screens/conversation_select_recipients/components/team_recipient_row.js.map +1 -1
  77. package/build/screens/team_conversation_screen.d.ts.map +1 -1
  78. package/build/screens/team_conversation_screen.js +24 -1
  79. package/build/screens/team_conversation_screen.js.map +1 -1
  80. package/build/utils/client/client.d.ts +1 -1
  81. package/build/utils/client/client.d.ts.map +1 -1
  82. package/build/utils/client/client.js +7 -6
  83. package/build/utils/client/client.js.map +1 -1
  84. package/build/utils/client/instrumented_fetch.js +3 -5
  85. package/build/utils/client/instrumented_fetch.js.map +1 -1
  86. package/package.json +4 -4
  87. package/src/__tests__/hooks/use_group_chat_conversation_payload.test.tsx +50 -0
  88. package/src/__tests__/jest.ts +1 -1
  89. package/src/__tests__/utils/client.ts +32 -0
  90. package/src/components/conversation/__tests__/message_list.test.tsx +14 -0
  91. package/src/components/conversation/message_list.tsx +42 -0
  92. package/src/components/conversations/conversations.tsx +9 -16
  93. package/src/components/conversations/conversations_blank_state.tsx +42 -0
  94. package/src/components/display/conversation_avatar.tsx +7 -5
  95. package/src/components/display/emoji_avatar.tsx +10 -2
  96. package/src/components/display/icon_avatar.tsx +10 -2
  97. package/src/hooks/groups/use_group_chat_conversation_payload.ts +1 -0
  98. package/src/hooks/index.ts +1 -0
  99. package/src/hooks/use_preview_avatar_diameter.ts +12 -0
  100. package/src/jest.ts +1 -1
  101. package/src/screens/age_check/age_check_underage_screen.tsx +1 -1
  102. package/src/screens/avatar_picker/avatar_picker_screen.tsx +25 -9
  103. package/src/screens/avatar_picker/avatar_preview.tsx +14 -5
  104. package/src/screens/avatar_picker/emoji_tab.tsx +3 -6
  105. package/src/screens/avatar_picker/upload_tab.tsx +2 -0
  106. package/src/screens/conversation_details_screen.tsx +10 -1
  107. package/src/screens/conversation_filter_recipients/components/header_row.tsx +3 -2
  108. package/src/screens/conversation_filter_recipients/hooks/__tests__/use_service_types_with_teams.test.ts +108 -0
  109. package/src/screens/conversation_filter_recipients/hooks/use_flattened_array_of_service_types_with_teams.tsx +46 -19
  110. package/src/screens/conversation_filter_recipients/hooks/use_service_types_with_teams.ts +31 -29
  111. package/src/screens/conversation_filter_recipients/types.tsx +1 -1
  112. package/src/screens/conversation_screen.tsx +5 -14
  113. package/src/screens/conversation_select_recipients/components/recipient_link_row.tsx +6 -4
  114. package/src/screens/conversation_select_recipients/components/team_recipient_row.tsx +2 -1
  115. package/src/screens/team_conversation_screen.tsx +33 -1
  116. package/src/utils/client/__tests__/instrumented_fetch.test.ts +9 -5
  117. package/src/utils/client/client.ts +9 -7
  118. package/src/utils/client/instrumented_fetch.ts +3 -6
@@ -11,7 +11,7 @@ interface RecipientLinkRowProps {
11
11
  accessibilityHint: string
12
12
  imageUri?: string
13
13
  title: string
14
- subtitle: string
14
+ subtitle?: string
15
15
  }
16
16
 
17
17
  export const RecipientLinkRow = ({
@@ -40,9 +40,11 @@ export const RecipientLinkRow = ({
40
40
  <Text style={styles.title} numberOfLines={2}>
41
41
  {title}
42
42
  </Text>
43
- <Text variant="tertiary" numberOfLines={1}>
44
- {subtitle}
45
- </Text>
43
+ {subtitle && (
44
+ <Text variant="tertiary" numberOfLines={1}>
45
+ {subtitle}
46
+ </Text>
47
+ )}
46
48
  </View>
47
49
  {Platform.OS === 'ios' && (
48
50
  <Icon name="general.rightChevron" size={16} style={styles.icon} />
@@ -9,7 +9,8 @@ interface TeamRecipientRowProps {
9
9
 
10
10
  export const TeamRecipientRow = ({ serviceType, onPress }: TeamRecipientRowProps) => {
11
11
  const serviceTypeAccessibilityLabel = `Select ${pluralize(serviceType.teams.length, 'team')} for ${serviceType.name}`
12
- const teamNames = serviceType.teams.map(team => team.name).join(', ')
12
+ const teamNames =
13
+ serviceType.id > 0 ? serviceType.teams.map(team => team.name).join(', ') : undefined
13
14
 
14
15
  return (
15
16
  <RecipientLinkRow
@@ -2,8 +2,10 @@ import { StackActions, StaticScreenProps, useNavigation } from '@react-navigatio
2
2
  import { useQuery, useQueryClient } from '@tanstack/react-query'
3
3
  import { useEffect } from 'react'
4
4
  import { DefaultLoading } from '../components/page/loading'
5
+ import BlankState from '../components/primitive/blank_state_primitive'
5
6
  import { useApiClient } from '../hooks'
6
7
  import { findOrCreateServicesConversation } from '../hooks/services/use_find_or_create_services_conversation'
8
+ import { ResponseError } from '../utils/response_error'
7
9
 
8
10
  export type TeamConversationRouteProps = {
9
11
  plan_id?: number
@@ -16,7 +18,11 @@ export const TeamConversationScreen = ({ route }: TeamConversationScreenProps) =
16
18
  const apiClient = useApiClient()
17
19
  const queryClient = useQueryClient()
18
20
  const navigation = useNavigation()
19
- const { data: conversation } = useQuery({
21
+ const {
22
+ data: conversation,
23
+ isError,
24
+ error,
25
+ } = useQuery({
20
26
  queryKey: ['team-conversation', route.params.team_ids, route.params.plan_id],
21
27
  queryFn: () =>
22
28
  findOrCreateServicesConversation({
@@ -24,6 +30,7 @@ export const TeamConversationScreen = ({ route }: TeamConversationScreenProps) =
24
30
  teamIds: route.params.team_ids ?? [],
25
31
  planId: route.params.plan_id,
26
32
  }).then(r => r.conversation),
33
+ retry: (failureCount, err) => !(err instanceof ResponseError) && failureCount < 3,
27
34
  })
28
35
 
29
36
  useEffect(() => {
@@ -41,5 +48,30 @@ export const TeamConversationScreen = ({ route }: TeamConversationScreenProps) =
41
48
  }
42
49
  }, [conversation?.id, conversation?.title, navigation, queryClient])
43
50
 
51
+ if (isError) return <TeamConversationError error={error} onGoBack={navigation.goBack} />
52
+
44
53
  return <DefaultLoading />
45
54
  }
55
+
56
+ function TeamConversationError({ error, onGoBack }: { error: Error; onGoBack: () => void }) {
57
+ const detail =
58
+ error instanceof ResponseError
59
+ ? error.errors
60
+ .map(e => e.detail)
61
+ .filter(Boolean)
62
+ .join('\n')
63
+ : ''
64
+
65
+ return (
66
+ <BlankState.Root>
67
+ <BlankState.Imagery name="people.noTextMessage" />
68
+ <BlankState.Content>
69
+ <BlankState.Heading>Can't start this conversation</BlankState.Heading>
70
+ <BlankState.Text>
71
+ {detail || 'Something went wrong while starting this conversation.'}
72
+ </BlankState.Text>
73
+ </BlankState.Content>
74
+ <BlankState.Button title="Go back" onPress={onGoBack} size="md" accessibilityRole="link" />
75
+ </BlankState.Root>
76
+ )
77
+ }
@@ -5,15 +5,18 @@ const buildResponse = (status: number) => new Response('', { status })
5
5
 
6
6
  describe('instrumentedFetch', () => {
7
7
  let reportError: jest.SpyInstance
8
+ let warnSpy: jest.SpyInstance
8
9
  let fetchSpy: jest.SpyInstance
9
10
 
10
11
  beforeEach(() => {
11
12
  reportError = jest.spyOn(Log, 'reportError').mockImplementation(() => {})
13
+ warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {})
12
14
  fetchSpy = jest.spyOn(globalThis, 'fetch')
13
15
  })
14
16
 
15
17
  afterEach(() => {
16
18
  reportError.mockRestore()
19
+ warnSpy.mockRestore()
17
20
  fetchSpy.mockRestore()
18
21
  })
19
22
 
@@ -27,6 +30,7 @@ describe('instrumentedFetch', () => {
27
30
 
28
31
  expect(result).toBe(response)
29
32
  expect(reportError).not.toHaveBeenCalled()
33
+ expect(warnSpy).not.toHaveBeenCalled()
30
34
  })
31
35
 
32
36
  it.each([400, 422, 500])(
@@ -63,7 +67,7 @@ describe('instrumentedFetch', () => {
63
67
  expect(reportError).not.toHaveBeenCalled()
64
68
  })
65
69
 
66
- it('reports network failures with http.error=network', async () => {
70
+ it('warns on network failures instead of reporting them', async () => {
67
71
  const networkError = new TypeError('Network request failed')
68
72
  fetchSpy.mockRejectedValueOnce(networkError)
69
73
 
@@ -71,10 +75,10 @@ describe('instrumentedFetch', () => {
71
75
  instrumentedFetch('https://api.example.com/conversations/123', { method: 'GET' })
72
76
  ).rejects.toBe(networkError)
73
77
 
74
- expect(reportError).toHaveBeenCalledTimes(1)
75
- const [error, context] = reportError.mock.calls[0]
76
- expect(error.name).toBe('NetworkError')
77
- expect(error.message).toContain('Network failure GET /conversations/:id')
78
+ expect(reportError).not.toHaveBeenCalled()
79
+ expect(warnSpy).toHaveBeenCalledTimes(1)
80
+ const [message, context] = warnSpy.mock.calls[0]
81
+ expect(message).toContain('Network failure GET /conversations/:id')
78
82
  expect(context.tags).toMatchObject({
79
83
  'http.method': 'GET',
80
84
  'http.path': '/conversations/:id',
@@ -105,16 +105,18 @@ export class Client {
105
105
  return makeRequest(requestArgs).catch(this.handleNotOk)
106
106
  }
107
107
 
108
- handleNotOk = async (response: Response) => {
108
+ handleNotOk = async (response: Response | Error) => {
109
+ if (!(response instanceof Response)) return Promise.reject(response)
110
+
111
+ const errorData = await this.parseErrorResponse(response)
112
+ const notOkResponse = response.clone() as FailedResponse
113
+ notOkResponse.errors = errorData.errors || []
114
+
109
115
  if (response.status === 401) {
110
- const errorData = await this.parseErrorResponse(response)
111
- this.onUnauthorizedResponse?.({
112
- ...response,
113
- errors: errorData.errors || [],
114
- } as FailedResponse)
116
+ this.onUnauthorizedResponse?.(notOkResponse)
115
117
  }
116
118
 
117
- return Promise.reject(response)
119
+ return Promise.reject(notOkResponse)
118
120
  }
119
121
 
120
122
  parseErrorResponse = async (response: Response): Promise<Partial<FailedResponse>> => {
@@ -14,7 +14,7 @@ export async function instrumentedFetch(url: string, init: RequestInit): Promise
14
14
  if (!response.ok) reportHttpError(response, method, url)
15
15
  return response
16
16
  } catch (networkError) {
17
- reportNetworkError(networkError as Error, method, url)
17
+ warnNetworkError(networkError as Error, method, url)
18
18
  throw networkError
19
19
  }
20
20
  }
@@ -38,12 +38,9 @@ function reportHttpError(response: Response, method: string, url: string) {
38
38
  })
39
39
  }
40
40
 
41
- function reportNetworkError(networkError: Error, method: string, url: string) {
41
+ function warnNetworkError(networkError: Error, method: string, url: string) {
42
42
  const path = templatePath(url)
43
- const error = new Error(`Network failure ${method} ${path}: ${networkError.message}`)
44
- error.name = 'NetworkError'
45
-
46
- Log.reportError(error, {
43
+ console.warn(`Network failure ${method} ${path}: ${networkError.message}`, {
47
44
  scope: 'http',
48
45
  tags: {
49
46
  ...SHARED_TAGS,