@planningcenter/chat-react-native 4.1.1-qa-staging.4 → 4.2.0-rc.1

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 (38) hide show
  1. package/build/contexts/api_provider.d.ts.map +1 -1
  2. package/build/contexts/api_provider.js +1 -12
  3. package/build/contexts/api_provider.js.map +1 -1
  4. package/build/hooks/use_features.d.ts +0 -16
  5. package/build/hooks/use_features.d.ts.map +1 -1
  6. package/build/hooks/use_features.js +47 -43
  7. package/build/hooks/use_features.js.map +1 -1
  8. package/build/navigation/index.d.ts +2 -2
  9. package/build/navigation/index.d.ts.map +1 -1
  10. package/build/navigation/index.js +4 -4
  11. package/build/navigation/index.js.map +1 -1
  12. package/build/screens/conversation_new/components/services_form.js +8 -9
  13. package/build/screens/conversation_new/components/services_form.js.map +1 -1
  14. package/build/screens/conversation_select_recipients/conversation_select_direct_message_recipients_screen.d.ts.map +1 -1
  15. package/build/screens/conversation_select_recipients/conversation_select_direct_message_recipients_screen.js +2 -1
  16. package/build/screens/conversation_select_recipients/conversation_select_direct_message_recipients_screen.js.map +1 -1
  17. package/build/screens/conversations/conversations_screen.d.ts.map +1 -1
  18. package/build/screens/conversations/conversations_screen.js +16 -28
  19. package/build/screens/conversations/conversations_screen.js.map +1 -1
  20. package/build/utils/time.d.ts +0 -1
  21. package/build/utils/time.d.ts.map +1 -1
  22. package/build/utils/time.js +0 -1
  23. package/build/utils/time.js.map +1 -1
  24. package/package.json +3 -3
  25. package/src/contexts/api_provider.tsx +0 -4
  26. package/src/hooks/use_features.ts +14 -26
  27. package/src/navigation/index.tsx +4 -3
  28. package/src/screens/conversation_new/components/services_form.tsx +5 -5
  29. package/src/screens/conversation_select_recipients/conversation_select_direct_message_recipients_screen.tsx +2 -3
  30. package/src/screens/conversations/conversations_screen.tsx +1 -6
  31. package/src/utils/time.ts +0 -1
  32. package/build/screens/conversations/components/search_list_header_component.d.ts +0 -2
  33. package/build/screens/conversations/components/search_list_header_component.d.ts.map +0 -1
  34. package/build/screens/conversations/components/search_list_header_component.js +0 -496
  35. package/build/screens/conversations/components/search_list_header_component.js.map +0 -1
  36. package/src/__tests__/hooks/use_features.test.ts +0 -61
  37. package/src/__tests__/screens/conversation_new/components/services_form.test.tsx +0 -91
  38. package/src/screens/conversations/components/search_list_header_component.tsx +0 -291
@@ -1,91 +0,0 @@
1
- import { DefaultTheme, ThemeProvider } from '@react-navigation/native'
2
- import { render } from '@testing-library/react-native'
3
- import React from 'react'
4
- import { SafeAreaProvider } from 'react-native-safe-area-context'
5
- import { useFindOrCreateServicesConversation } from '../../../../hooks/services/use_find_or_create_services_conversation'
6
- import { useTeamMembersForNewConversation } from '../../../../hooks/services/use_team_members_for_new_conversation'
7
- import { ServicesForm } from '../../../../screens/conversation_new/components/services_form'
8
-
9
- jest.mock('react-native-reanimated', () => ({
10
- ...jest.requireActual('react-native-reanimated/mock'),
11
- LayoutAnimationConfig: ({ children }: { children: React.ReactNode }) => children,
12
- }))
13
- jest.mock('@react-navigation/native', () => ({
14
- ...jest.requireActual('@react-navigation/native'),
15
- useNavigation: () => ({ navigate: jest.fn(), dispatch: jest.fn(), getParent: () => undefined }),
16
- }))
17
- jest.mock('../../../../hooks/use_api_client', () => ({ useApiClient: () => ({}) }))
18
- jest.mock('../../../../hooks/use_upload_client', () => ({ useUploadClient: () => ({}) }))
19
- jest.mock('../../../../hooks/use_features', () => ({
20
- ...jest.requireActual('../../../../hooks/use_features'),
21
- useFeatures: () => ({ features: [], featureEnabled: () => false }),
22
- }))
23
- jest.mock('../../../../hooks/use_conversation_validate', () => ({
24
- useConversationValidate: () => ({ warnings: [], validationPending: false }),
25
- }))
26
- jest.mock('../../../../hooks/services/use_services_chat_conversation_payload', () => ({
27
- useServicesChatConversationPayload: () => ({ payload: undefined, isLoading: false }),
28
- }))
29
- jest.mock('../../../../hooks/services/use_services_team', () => ({
30
- useServicesTeams: () => [],
31
- }))
32
- jest.mock('../../../../hooks/services/use_team_plans', () => ({
33
- useTeamPlans: () => ({
34
- planOptions: [],
35
- isFetching: false,
36
- isError: false,
37
- findPlan: () => undefined,
38
- }),
39
- }))
40
- jest.mock('../../../../hooks/services/use_team_members_for_new_conversation', () => ({
41
- useTeamMembersForNewConversation: jest.fn(() => ({ members: [], isError: false })),
42
- }))
43
- jest.mock('../../../../hooks/services/use_find_or_create_services_conversation', () => ({
44
- useFindOrCreateServicesConversation: jest.fn(() => ({
45
- mutate: jest.fn(),
46
- isPending: false,
47
- selectionHasConversation: false,
48
- isLoadingConversationCheck: false,
49
- })),
50
- }))
51
-
52
- const mockedFindOrCreate = jest.mocked(useFindOrCreateServicesConversation)
53
- const mockedTeamMembers = jest.mocked(useTeamMembersForNewConversation)
54
-
55
- const latestArgs = <T extends jest.Mock>(mock: T) => mock.mock.calls.at(-1)?.[0]
56
-
57
- const safeAreaMetrics = {
58
- frame: { x: 0, y: 0, width: 390, height: 844 },
59
- insets: { top: 0, right: 0, bottom: 0, left: 0 },
60
- }
61
-
62
- const renderForm = (props: { initialPlanId?: number } = {}) =>
63
- render(
64
- <SafeAreaProvider initialMetrics={safeAreaMetrics}>
65
- <ThemeProvider value={DefaultTheme}>
66
- <ServicesForm initialTeamIds={[1, 2]} {...props} />
67
- </ThemeProvider>
68
- </SafeAreaProvider>
69
- )
70
-
71
- describe('ServicesForm', () => {
72
- beforeEach(() => {
73
- jest.clearAllMocks()
74
- })
75
-
76
- it('filters by plan when opened from a plan', () => {
77
- const { getByRole } = renderForm({ initialPlanId: 42 })
78
-
79
- expect(getByRole('switch').props.value).toBe(true)
80
- expect(latestArgs(mockedFindOrCreate)).toMatchObject({ teamIds: [1, 2], planId: 42 })
81
- expect(latestArgs(mockedTeamMembers)).toMatchObject({ teamIds: [1, 2], planId: 42 })
82
- })
83
-
84
- it('does not filter by plan when opened without a plan', () => {
85
- const { getByRole } = renderForm()
86
-
87
- expect(getByRole('switch').props.value).toBe(false)
88
- expect(latestArgs(mockedFindOrCreate)).toMatchObject({ teamIds: [1, 2], planId: undefined })
89
- expect(latestArgs(mockedTeamMembers)).toMatchObject({ teamIds: [1, 2], planId: undefined })
90
- })
91
- })
@@ -1,291 +0,0 @@
1
- import { RouteProp, useNavigation, useRoute } from '@react-navigation/native'
2
- import React, { useCallback, useMemo } from 'react'
3
- import { Alert, ScrollView, StyleSheet, View } from 'react-native'
4
- import { Heading, TextButton, ToggleButton } from '../../../components'
5
- import {
6
- useAtFontScaleBreakpoint,
7
- useCanCreateConversations,
8
- useCurrentPersonCache,
9
- useDirectMessagesEligible,
10
- useHasDirectMessages,
11
- useNewConversationEntry,
12
- useTheme,
13
- } from '../../../hooks'
14
- import { useMarkAllRead } from '../../../hooks/use_conversations_actions'
15
- import { useCanDisplayGroups } from '../../../hooks/use_groups'
16
- import { useNewConversationFromFilter } from '../../../hooks/use_new_conversation_from_filter'
17
- import { GraphId } from '../../../types/resources/group_resource'
18
- import { MAX_FONT_SIZE_MULTIPLIER_LANDMARK } from '../../../utils'
19
- import { Haptic } from '../../../utils/native_adapters'
20
- import { ConversationFiltersParams } from '../../conversation_filters/screen_props'
21
- import { ConversationsScreenProps } from '../conversations_screen'
22
- import { ChatGroupBadge } from './chat_group_badge'
23
- import { ListHeaderActionButton } from './list_header_action_button'
24
-
25
- const ROW_PADDING_HORIZONTAL = 16
26
-
27
- enum FilterTypes {
28
- All = 'All',
29
- Groups = 'Groups',
30
- Teams = 'Teams',
31
- DMs = 'DMs',
32
- More = 'More',
33
- }
34
-
35
- export const SearchListHeaderComponent = () => {
36
- const navigation = useNavigation()
37
- const canFilterByTeams = useCanDisplayGroups({ source_app_name: 'Services', source_type: 'Team' })
38
- const canFilterByGroups = useCanDisplayGroups({ source_app_name: 'Groups', source_type: 'Group' })
39
- const directMessagesEligible = useDirectMessagesEligible()
40
- const hasDirectMessages = useHasDirectMessages()
41
- const canFilterByDMs = directMessagesEligible && hasDirectMessages
42
- const route = useRoute<RouteProp<ConversationsScreenProps['route']>>()
43
- const {
44
- chat_group_graph_id,
45
- group_source_app_name = '',
46
- show_direct_messages,
47
- } = route.params || {}
48
- const currentPersonCache = useCurrentPersonCache()
49
- const { markAllRead, isPending } = useMarkAllRead()
50
- const canCreateConversations = useCanCreateConversations()
51
- const entryMode = useNewConversationEntry()
52
- const newConversationFromFilter = useNewConversationFromFilter(
53
- chat_group_graph_id as GraphId | undefined
54
- )
55
-
56
- const active: FilterTypes = useMemo(() => {
57
- if (chat_group_graph_id) {
58
- return FilterTypes.More
59
- } else if (/groups/i.test(group_source_app_name)) {
60
- return FilterTypes.Groups
61
- } else if (/services/i.test(group_source_app_name)) {
62
- return FilterTypes.Teams
63
- } else if (show_direct_messages) {
64
- return FilterTypes.DMs
65
- }
66
-
67
- return FilterTypes.All
68
- }, [chat_group_graph_id, group_source_app_name, show_direct_messages])
69
-
70
- const handleNewConversationNavigation = useCallback(() => {
71
- if (newConversationFromFilter || entryMode === 'select_type') {
72
- return navigation.navigate('ConversationSelectType', {
73
- ...route.params,
74
- newConversationFromFilter,
75
- })
76
- }
77
-
78
- if (entryMode === 'groups') {
79
- return navigation.navigate('New', {
80
- screen: 'ConversationSelectGroupRecipients',
81
- params: { ...route.params },
82
- })
83
- }
84
-
85
- if (entryMode === 'teams') {
86
- return navigation.navigate('New', {
87
- screen: 'ConversationSelectTeamsILeadRecipients',
88
- params: { ...route.params },
89
- })
90
- }
91
-
92
- if (entryMode === 'direct_message') {
93
- return navigation.navigate('New', {
94
- screen: 'ConversationSelectDirectMessageRecipients',
95
- params: { ...route.params },
96
- })
97
- }
98
- }, [navigation, route.params, entryMode, newConversationFromFilter])
99
-
100
- const handleSetFilter = useCallback(
101
- (params: ConversationFiltersParams) => {
102
- navigation.setParams({
103
- title: undefined,
104
- chat_group_graph_id: undefined,
105
- group_source_app_name: undefined,
106
- show_direct_messages: undefined,
107
- ...params,
108
- })
109
- },
110
- [navigation]
111
- )
112
-
113
- const handleChatSettings = useCallback(() => {
114
- navigation.navigate('ChatSettings', {})
115
- }, [navigation])
116
-
117
- const handleOpenConversationFilters = useCallback(() => {
118
- navigation.navigate('ConversationFilters', {
119
- chat_group_graph_id,
120
- group_source_app_name,
121
- show_direct_messages,
122
- })
123
- }, [navigation, chat_group_graph_id, group_source_app_name, show_direct_messages])
124
-
125
- const handleMarkAllRead = useCallback(() => {
126
- currentPersonCache.update({ unreadCount: 0 })
127
- Haptic.notificationSuccess()
128
- markAllRead()
129
- }, [currentPersonCache, markAllRead])
130
-
131
- const alertMarkAllRead = useCallback(() => {
132
- if (active === FilterTypes.All) return handleMarkAllRead()
133
-
134
- Alert.alert('Mark all read', 'This includes conversations not shown by the current filter.', [
135
- { text: 'Cancel' },
136
- { text: 'OK', onPress: () => handleMarkAllRead() },
137
- ])
138
- }, [active, handleMarkAllRead])
139
-
140
- const hasSomeSourceFilterCapability = canFilterByTeams || canFilterByGroups
141
- const hasMultiSourceFilterCapabilities = canFilterByTeams && canFilterByGroups
142
- const showFilterRow = hasMultiSourceFilterCapabilities || canFilterByDMs
143
- const shouldShowHeaderFiltersButton = hasSomeSourceFilterCapability && !showFilterRow
144
- const moreFiltersButtonA11yLabel = shouldShowHeaderFiltersButton
145
- ? 'View filters'
146
- : 'View more filters'
147
-
148
- const moreFiltersButtonProps = {
149
- onPress: handleOpenConversationFilters,
150
- active: active === FilterTypes.More,
151
- iconNameRight: 'general.threeReducingHorizontalBars' as const,
152
- accessibilityLabel: moreFiltersButtonA11yLabel,
153
- }
154
-
155
- const styles = useStyles({ shouldShowHeaderFiltersButton })
156
-
157
- return (
158
- <View style={styles.listHeader}>
159
- <View style={styles.headingRow}>
160
- <View style={styles.titleContainer}>
161
- <Heading numberOfLines={1} variant="h2">
162
- Conversations
163
- </Heading>
164
- <TextButton
165
- onPress={alertMarkAllRead}
166
- disabled={isPending}
167
- maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER_LANDMARK}
168
- accessibilityShowsLargeContentViewer
169
- accessibilityLargeContentTitle="Mark all read"
170
- >
171
- Mark all read
172
- </TextButton>
173
- </View>
174
- <View style={styles.actionsContainer}>
175
- <ListHeaderActionButton
176
- name="general.cog"
177
- onPress={handleChatSettings}
178
- accessibilityLabel="Chat settings"
179
- testID="conversations-chat-settings-button"
180
- maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER_LANDMARK}
181
- />
182
- {canCreateConversations && (
183
- <ListHeaderActionButton
184
- name="churchCenter.signups"
185
- onPress={handleNewConversationNavigation}
186
- accessibilityLabel="New conversation"
187
- testID="conversations-new-conversation-button"
188
- maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER_LANDMARK}
189
- />
190
- )}
191
- {shouldShowHeaderFiltersButton && <ToggleButton {...moreFiltersButtonProps} />}
192
- </View>
193
- </View>
194
- {showFilterRow && (
195
- <ScrollView
196
- horizontal
197
- showsHorizontalScrollIndicator={false}
198
- contentContainerStyle={styles.filterRow}
199
- accessibilityRole="toolbar"
200
- >
201
- <ToggleButton
202
- title={FilterTypes.All}
203
- active={active === FilterTypes.All}
204
- onPress={() => handleSetFilter({})}
205
- accessibilityLabel="Show all conversations"
206
- style={styles.filterButton}
207
- />
208
- {canFilterByGroups && (
209
- <ToggleButton
210
- title={FilterTypes.Groups}
211
- active={active === FilterTypes.Groups}
212
- onPress={() => handleSetFilter({ group_source_app_name: 'Groups' })}
213
- accessibilityLabel="Filter to group conversations"
214
- style={styles.filterButton}
215
- />
216
- )}
217
- {canFilterByTeams && (
218
- <ToggleButton
219
- title={FilterTypes.Teams}
220
- active={active === FilterTypes.Teams}
221
- onPress={() => handleSetFilter({ group_source_app_name: 'Services' })}
222
- accessibilityLabel="Filter to team conversations"
223
- style={styles.filterButton}
224
- />
225
- )}
226
- {canFilterByDMs && (
227
- <ToggleButton
228
- title={FilterTypes.DMs}
229
- active={active === FilterTypes.DMs}
230
- onPress={() => handleSetFilter({ show_direct_messages: true })}
231
- accessibilityLabel="Filter to direct message conversations"
232
- style={styles.filterButton}
233
- />
234
- )}
235
- <ToggleButton {...moreFiltersButtonProps} />
236
- </ScrollView>
237
- )}
238
- <ChatGroupBadge rowPaddingHorizontal={ROW_PADDING_HORIZONTAL} />
239
- </View>
240
- )
241
- }
242
-
243
- const useStyles = ({
244
- shouldShowHeaderFiltersButton,
245
- }: {
246
- shouldShowHeaderFiltersButton: boolean
247
- }) => {
248
- const theme = useTheme()
249
- const atFontScaleBreakpoint = useAtFontScaleBreakpoint(
250
- shouldShowHeaderFiltersButton ? 1.2 : undefined
251
- )
252
-
253
- return StyleSheet.create({
254
- listHeader: {
255
- borderBottomWidth: 1,
256
- borderBottomColor: theme.colors.statusErrorBorder,
257
- paddingBottom: 16,
258
- gap: 16,
259
- },
260
- headingRow: {
261
- gap: 12,
262
- flexDirection: atFontScaleBreakpoint ? 'column' : 'row',
263
- alignItems: atFontScaleBreakpoint ? 'flex-start' : 'center',
264
- justifyContent: 'space-between',
265
- paddingBottom: atFontScaleBreakpoint ? 4 : 0,
266
- paddingHorizontal: ROW_PADDING_HORIZONTAL,
267
- },
268
- titleContainer: {
269
- gap: 8,
270
- },
271
- actionsContainer: {
272
- flexDirection: 'row',
273
- gap: 8,
274
- alignItems: 'center',
275
- },
276
- markAllReadRow: {
277
- paddingBottom: 4,
278
- },
279
- filterRow: {
280
- flexDirection: 'row',
281
- justifyContent: 'flex-start',
282
- gap: 8,
283
- paddingHorizontal: ROW_PADDING_HORIZONTAL,
284
- flexGrow: 1,
285
- },
286
- filterButton: {
287
- flex: 1,
288
- minWidth: 65,
289
- },
290
- })
291
- }