@planningcenter/chat-react-native 3.24.4 → 3.25.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.
- package/build/components/conversations/conversation_actions.js +3 -3
- package/build/components/conversations/conversation_actions.js.map +1 -1
- package/build/components/conversations/conversations.d.ts.map +1 -1
- package/build/components/conversations/conversations.js +1 -0
- package/build/components/conversations/conversations.js.map +1 -1
- package/build/hooks/use_conversation.d.ts.map +1 -1
- package/build/hooks/use_conversation.js +13 -1
- package/build/hooks/use_conversation.js.map +1 -1
- package/build/hooks/use_conversation_membership.d.ts +5 -0
- package/build/hooks/use_conversation_membership.d.ts.map +1 -0
- package/build/hooks/use_conversation_membership.js +63 -0
- package/build/hooks/use_conversation_membership.js.map +1 -0
- package/build/hooks/use_conversations_actions.d.ts.map +1 -1
- package/build/hooks/use_conversations_actions.js +4 -0
- package/build/hooks/use_conversations_actions.js.map +1 -1
- package/build/hooks/use_features.d.ts +1 -0
- package/build/hooks/use_features.d.ts.map +1 -1
- package/build/hooks/use_features.js +1 -0
- package/build/hooks/use_features.js.map +1 -1
- package/build/navigation/index.d.ts +10 -0
- package/build/navigation/index.d.ts.map +1 -1
- package/build/navigation/index.js +12 -2
- package/build/navigation/index.js.map +1 -1
- package/build/screens/conversation_details_screen.d.ts.map +1 -1
- package/build/screens/conversation_details_screen.js +50 -16
- package/build/screens/conversation_details_screen.js.map +1 -1
- package/build/screens/conversation_notification_level_select_screen.d.ts +8 -0
- package/build/screens/conversation_notification_level_select_screen.d.ts.map +1 -0
- package/build/screens/conversation_notification_level_select_screen.js +49 -0
- package/build/screens/conversation_notification_level_select_screen.js.map +1 -0
- package/build/screens/conversation_screen.d.ts +3 -1
- package/build/screens/conversation_screen.d.ts.map +1 -1
- package/build/screens/conversation_screen.js +16 -5
- package/build/screens/conversation_screen.js.map +1 -1
- package/build/screens/group_notification_level_select_screen.d.ts +8 -0
- package/build/screens/group_notification_level_select_screen.d.ts.map +1 -0
- package/build/screens/group_notification_level_select_screen.js +40 -0
- package/build/screens/group_notification_level_select_screen.js.map +1 -0
- package/build/screens/group_notification_settings_screen.d.ts.map +1 -1
- package/build/screens/group_notification_settings_screen.js +30 -17
- package/build/screens/group_notification_settings_screen.js.map +1 -1
- package/build/screens/notification_settings/hooks/groups.d.ts +6 -5
- package/build/screens/notification_settings/hooks/groups.d.ts.map +1 -1
- package/build/screens/notification_settings/hooks/groups.js +63 -36
- package/build/screens/notification_settings/hooks/groups.js.map +1 -1
- package/build/screens/notification_settings_screen.d.ts.map +1 -1
- package/build/screens/notification_settings_screen.js +25 -11
- package/build/screens/notification_settings_screen.js.map +1 -1
- package/build/types/resources/conversation.d.ts +2 -3
- package/build/types/resources/conversation.d.ts.map +1 -1
- package/build/types/resources/conversation.js.map +1 -1
- package/build/types/resources/conversation_membership.d.ts +16 -0
- package/build/types/resources/conversation_membership.d.ts.map +1 -0
- package/build/types/resources/conversation_membership.js +2 -0
- package/build/types/resources/conversation_membership.js.map +1 -0
- package/build/types/resources/group_membership.d.ts +7 -0
- package/build/types/resources/group_membership.d.ts.map +1 -1
- package/build/types/resources/group_membership.js.map +1 -1
- package/build/types/resources/index.d.ts +1 -0
- package/build/types/resources/index.d.ts.map +1 -1
- package/build/types/resources/index.js +1 -0
- package/build/types/resources/index.js.map +1 -1
- package/build/utils/deep_snake_case_keys.d.ts +4 -0
- package/build/utils/deep_snake_case_keys.d.ts.map +1 -0
- package/build/utils/deep_snake_case_keys.js +13 -0
- package/build/utils/deep_snake_case_keys.js.map +1 -0
- package/package.json +2 -2
- package/src/components/conversations/conversation_actions.tsx +3 -3
- package/src/components/conversations/conversations.tsx +1 -0
- package/src/hooks/use_conversation.ts +13 -1
- package/src/hooks/use_conversation_membership.ts +91 -0
- package/src/hooks/use_conversations_actions.ts +4 -0
- package/src/hooks/use_features.ts +1 -0
- package/src/navigation/index.tsx +19 -1
- package/src/screens/conversation_details_screen.tsx +87 -22
- package/src/screens/conversation_notification_level_select_screen.tsx +73 -0
- package/src/screens/conversation_screen.tsx +18 -4
- package/src/screens/group_notification_level_select_screen.tsx +62 -0
- package/src/screens/group_notification_settings_screen.tsx +53 -18
- package/src/screens/notification_settings/hooks/groups.ts +76 -37
- package/src/screens/notification_settings_screen.tsx +24 -21
- package/src/types/resources/conversation.ts +2 -3
- package/src/types/resources/conversation_membership.ts +17 -0
- package/src/types/resources/group_membership.ts +7 -0
- package/src/types/resources/index.ts +1 -0
- package/src/utils/deep_snake_case_keys.ts +16 -0
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
|
1
|
+
import { InfiniteData, useMutation, useQueryClient } from '@tanstack/react-query'
|
|
2
|
+
import { merge } from 'lodash'
|
|
2
3
|
import { getRequestQueryKey, useApiClient } from '../../../hooks'
|
|
3
4
|
import { useSuspenseGet, useSuspensePaginator } from '../../../hooks'
|
|
4
|
-
import { ApiResource } from '../../../types'
|
|
5
|
+
import { ApiCollection, ApiResource } from '../../../types'
|
|
5
6
|
import { GroupMembership, GroupResourceWithMembership } from '../../../types/resources'
|
|
6
7
|
import { throwResponseError } from '../../../utils/response_error'
|
|
8
|
+
import { updateRecordInPagesData } from '../../../utils'
|
|
7
9
|
|
|
8
10
|
export const getGroupsRequestArgs = () => ({
|
|
9
11
|
url: '/me/groups',
|
|
@@ -13,7 +15,11 @@ export const getGroupsRequestArgs = () => ({
|
|
|
13
15
|
filter: 'user_settable_notification_level',
|
|
14
16
|
fields: {
|
|
15
17
|
Group: [],
|
|
16
|
-
GroupMembership: [
|
|
18
|
+
GroupMembership: [
|
|
19
|
+
'notification_level',
|
|
20
|
+
'notification_level_description',
|
|
21
|
+
'notification_level_options',
|
|
22
|
+
],
|
|
17
23
|
},
|
|
18
24
|
order: 'name',
|
|
19
25
|
},
|
|
@@ -25,7 +31,11 @@ export const getGroupRequestArgs = ({ groupId }: { groupId: number | string }) =
|
|
|
25
31
|
include: ['my_group_membership'],
|
|
26
32
|
fields: {
|
|
27
33
|
Group: ['name', 'source_app_name', 'source_type', 'my_group_membership'],
|
|
28
|
-
GroupMembership: [
|
|
34
|
+
GroupMembership: [
|
|
35
|
+
'notification_level',
|
|
36
|
+
'notification_level_description',
|
|
37
|
+
'notification_level_options',
|
|
38
|
+
],
|
|
29
39
|
},
|
|
30
40
|
},
|
|
31
41
|
})
|
|
@@ -39,50 +49,77 @@ export const useGroups = () => {
|
|
|
39
49
|
export const useGroup = ({ groupId }: { groupId: number | string }) => {
|
|
40
50
|
const args = getGroupRequestArgs({ groupId })
|
|
41
51
|
|
|
42
|
-
return useSuspenseGet<GroupResourceWithMembership>(args)
|
|
52
|
+
return useSuspenseGet<GroupResourceWithMembership>(args, { refetchOnMount: false })
|
|
43
53
|
}
|
|
44
54
|
|
|
55
|
+
type GroupsQueryData = InfiniteData<ApiCollection<GroupResourceWithMembership>>
|
|
56
|
+
|
|
45
57
|
export const useGroupMembershipUpdate = ({ groupId }: { groupId: number | string }) => {
|
|
46
58
|
const apiClient = useApiClient()
|
|
47
59
|
const queryClient = useQueryClient()
|
|
48
|
-
const
|
|
49
|
-
const
|
|
60
|
+
const groupRequestArgs = getGroupRequestArgs({ groupId })
|
|
61
|
+
const groupsRequestArgs = getGroupsRequestArgs()
|
|
62
|
+
const groupQueryKey = getRequestQueryKey(groupRequestArgs)
|
|
50
63
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
64
|
+
const enrichGroupMembership = (
|
|
65
|
+
membership: Partial<GroupMembership>
|
|
66
|
+
): Partial<GroupMembership> => {
|
|
67
|
+
const cachedGroup =
|
|
68
|
+
queryClient.getQueryData<ApiResource<GroupResourceWithMembership>>(groupQueryKey)
|
|
69
|
+
const currentOptions = cachedGroup?.data.myGroupMembership?.notificationLevelOptions ?? []
|
|
70
|
+
const { notificationLevel } = membership
|
|
71
|
+
const selectedOption = notificationLevel
|
|
72
|
+
? currentOptions.find(o => o.value === notificationLevel)
|
|
73
|
+
: undefined
|
|
74
|
+
const notificationLevelDescription =
|
|
75
|
+
membership.notificationLevelDescription ?? selectedOption?.description
|
|
56
76
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
77
|
+
return {
|
|
78
|
+
...membership,
|
|
79
|
+
notificationLevelDescription,
|
|
80
|
+
...(notificationLevel && {
|
|
81
|
+
notificationLevelOptions: currentOptions.map(o => ({
|
|
82
|
+
...o,
|
|
83
|
+
enabled: o.value === notificationLevel,
|
|
84
|
+
})),
|
|
85
|
+
}),
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const updateGroupsMembershipCache = (membership: Partial<GroupMembership>) => {
|
|
90
|
+
queryClient.setQueryData<GroupsQueryData>(getRequestQueryKey(groupsRequestArgs), prev =>
|
|
91
|
+
updateRecordInPagesData({
|
|
92
|
+
data: prev,
|
|
93
|
+
record: { id: groupId } as GroupResourceWithMembership,
|
|
94
|
+
processRecord: (_record, current) => merge({}, current, { myGroupMembership: membership }),
|
|
67
95
|
})
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const updateGroupMembershipCache = (membership: Partial<GroupMembership>) => {
|
|
100
|
+
queryClient.setQueryData<ApiResource<GroupResourceWithMembership>>(groupQueryKey, groupData => {
|
|
101
|
+
if (!groupData?.data.myGroupMembership) return groupData
|
|
102
|
+
return merge({}, groupData, { data: { myGroupMembership: membership } })
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return useMutation({
|
|
107
|
+
throwOnError: true,
|
|
108
|
+
onMutate: membership => {
|
|
109
|
+
const enriched = enrichGroupMembership(membership)
|
|
110
|
+
updateGroupsMembershipCache(enriched)
|
|
111
|
+
updateGroupMembershipCache(enriched)
|
|
68
112
|
},
|
|
69
113
|
onSuccess: response => {
|
|
70
|
-
|
|
71
|
-
|
|
114
|
+
const enriched = enrichGroupMembership(response.data)
|
|
115
|
+
updateGroupsMembershipCache(enriched)
|
|
116
|
+
updateGroupMembershipCache(enriched)
|
|
72
117
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
...groupData.data,
|
|
77
|
-
myGroupMembership: {
|
|
78
|
-
...groupData.data.myGroupMembership,
|
|
79
|
-
...response.data,
|
|
80
|
-
},
|
|
81
|
-
},
|
|
82
|
-
}
|
|
83
|
-
})
|
|
118
|
+
// Updating conversations without access to the current filters
|
|
119
|
+
// is a hard itch to scratch so we just need to refetch.
|
|
120
|
+
queryClient.invalidateQueries({ queryKey: ['/me/conversations'] })
|
|
84
121
|
},
|
|
85
|
-
mutationFn: (
|
|
122
|
+
mutationFn: (membership: Partial<GroupMembership>) => {
|
|
86
123
|
return apiClient.chat
|
|
87
124
|
.patch<ApiResource<GroupMembership>>({
|
|
88
125
|
url: `/me/groups/${groupId}/my_group_membership`,
|
|
@@ -90,7 +127,9 @@ export const useGroupMembershipUpdate = ({ groupId }: { groupId: number | string
|
|
|
90
127
|
data: {
|
|
91
128
|
type: 'GroupMembership',
|
|
92
129
|
attributes: {
|
|
93
|
-
|
|
130
|
+
...(membership.notificationLevel !== undefined && {
|
|
131
|
+
notification_level: membership.notificationLevel,
|
|
132
|
+
}),
|
|
94
133
|
},
|
|
95
134
|
},
|
|
96
135
|
},
|
|
@@ -1,27 +1,19 @@
|
|
|
1
1
|
import { PlatformPressable } from '@react-navigation/elements'
|
|
2
2
|
import { StaticScreenProps, useNavigation } from '@react-navigation/native'
|
|
3
|
-
import type { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
|
4
3
|
import React, { useCallback, useEffect, type ReactNode } from 'react'
|
|
5
4
|
import { FlatList, Platform, StyleSheet, View, type ViewProps, type ViewStyle } from 'react-native'
|
|
6
5
|
import { Badge, Heading, Icon, Text } from '../components'
|
|
7
6
|
import { HeaderTextButton } from '../components/display/platform_modal_header_buttons'
|
|
8
7
|
import { useTheme } from '../hooks'
|
|
8
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
9
9
|
import { isDefined } from '../types'
|
|
10
|
-
import type { GroupNotificationSettingsScreenProps } from './group_notification_settings_screen'
|
|
11
10
|
import { useGroups } from './notification_settings/hooks/groups'
|
|
12
11
|
import { useChatTypes } from './preferred_app/hooks/use_chat_types'
|
|
13
|
-
import type { PreferredAppSelectionScreenProps } from './preferred_app_selection_screen'
|
|
14
12
|
|
|
15
13
|
// =========================================
|
|
16
14
|
// ====== Factory Constants & Types ========
|
|
17
15
|
// =========================================
|
|
18
16
|
|
|
19
|
-
type NotificationSettingsStackParamList = {
|
|
20
|
-
NotificationSettings: {}
|
|
21
|
-
PreferredAppSelection: PreferredAppSelectionScreenProps['route']['params']
|
|
22
|
-
GroupNotificationSettings: GroupNotificationSettingsScreenProps['route']['params']
|
|
23
|
-
}
|
|
24
|
-
|
|
25
17
|
enum SectionTypes {
|
|
26
18
|
header,
|
|
27
19
|
hidden,
|
|
@@ -53,7 +45,7 @@ interface DataItem<T, TName extends SectionTypes> {
|
|
|
53
45
|
export type NotificationSettingsScreenProps = StaticScreenProps<{}>
|
|
54
46
|
|
|
55
47
|
export function NotificationSettingsScreen({}: NotificationSettingsScreenProps) {
|
|
56
|
-
const navigation = useNavigation
|
|
48
|
+
const navigation = useNavigation()
|
|
57
49
|
const styles = useStyles()
|
|
58
50
|
const { data: chatTypes } = useChatTypes()
|
|
59
51
|
const { data: groups } = useGroups()
|
|
@@ -109,14 +101,14 @@ export function NotificationSettingsScreen({}: NotificationSettingsScreenProps)
|
|
|
109
101
|
showBottomBorder: true,
|
|
110
102
|
})),
|
|
111
103
|
{
|
|
112
|
-
type: SectionTypes.header,
|
|
104
|
+
type: groups.length === 0 ? SectionTypes.hidden : SectionTypes.header,
|
|
113
105
|
data: {
|
|
114
106
|
title: 'Manage chat settings',
|
|
115
107
|
},
|
|
116
108
|
sectionInnerStyle: styles.sectionInnerHeader,
|
|
117
109
|
},
|
|
118
110
|
{
|
|
119
|
-
type: SectionTypes.view,
|
|
111
|
+
type: groups.length === 0 ? SectionTypes.hidden : SectionTypes.view,
|
|
120
112
|
data: {
|
|
121
113
|
children: (
|
|
122
114
|
<Text variant="tertiary" style={styles.sectionDescription}>
|
|
@@ -130,6 +122,7 @@ export function NotificationSettingsScreen({}: NotificationSettingsScreenProps)
|
|
|
130
122
|
type: SectionTypes.link,
|
|
131
123
|
data: {
|
|
132
124
|
title: group.name,
|
|
125
|
+
subtitle: group.myGroupMembership?.notificationLevelDescription,
|
|
133
126
|
rightLabel: group.sourceType,
|
|
134
127
|
onPress: () =>
|
|
135
128
|
navigation.navigate('GroupNotificationSettings', {
|
|
@@ -206,6 +199,7 @@ export function NotificationSettingsScreen({}: NotificationSettingsScreenProps)
|
|
|
206
199
|
<LinkRow {...item.data} />
|
|
207
200
|
</ListSection>
|
|
208
201
|
)
|
|
202
|
+
case SectionTypes.hidden:
|
|
209
203
|
default:
|
|
210
204
|
return null
|
|
211
205
|
}
|
|
@@ -268,11 +262,12 @@ function SettingRow({ title, style, rightLabel, rightItem, rightItemStyle = {} }
|
|
|
268
262
|
|
|
269
263
|
interface LinkRowProps {
|
|
270
264
|
title: string
|
|
265
|
+
subtitle?: string
|
|
271
266
|
rightLabel: string
|
|
272
267
|
onPress: () => void
|
|
273
268
|
}
|
|
274
269
|
|
|
275
|
-
function LinkRow({ title, rightLabel, onPress }: LinkRowProps) {
|
|
270
|
+
function LinkRow({ title, subtitle, rightLabel, onPress }: LinkRowProps) {
|
|
276
271
|
const styles = useLinkRowStyles()
|
|
277
272
|
const isSourceType = rightLabel === 'Team' || rightLabel === 'Group' || rightLabel === 'PlanTeam'
|
|
278
273
|
|
|
@@ -285,9 +280,12 @@ function LinkRow({ title, rightLabel, onPress }: LinkRowProps) {
|
|
|
285
280
|
accessibilityHint={`Navigate to ${title} settings`}
|
|
286
281
|
>
|
|
287
282
|
<View style={styles.innerContainer}>
|
|
288
|
-
<
|
|
289
|
-
{title}
|
|
290
|
-
|
|
283
|
+
<View style={styles.leftContent}>
|
|
284
|
+
<Text style={styles.title} numberOfLines={2}>
|
|
285
|
+
{title}
|
|
286
|
+
</Text>
|
|
287
|
+
{Boolean(subtitle) && <Text variant="footnote">{subtitle}</Text>}
|
|
288
|
+
</View>
|
|
291
289
|
<View style={styles.rightContent}>
|
|
292
290
|
{isSourceType ? (
|
|
293
291
|
<Badge label={rightLabel} appearance="neutral" variant="meta" />
|
|
@@ -311,13 +309,16 @@ function LinkRow({ title, rightLabel, onPress }: LinkRowProps) {
|
|
|
311
309
|
|
|
312
310
|
const useStyles = ({}: { isStart?: boolean; isEnd?: boolean } = {}) => {
|
|
313
311
|
const { colors } = useTheme()
|
|
312
|
+
const { bottom } = useSafeAreaInsets()
|
|
314
313
|
const headerBottomPadding = 0
|
|
315
314
|
|
|
316
315
|
return StyleSheet.create({
|
|
317
316
|
listContainer: {
|
|
318
317
|
flex: 1,
|
|
319
318
|
},
|
|
320
|
-
contentContainer: {
|
|
319
|
+
contentContainer: {
|
|
320
|
+
paddingBottom: bottom,
|
|
321
|
+
},
|
|
321
322
|
sectionOuterBase: {
|
|
322
323
|
paddingLeft: 16,
|
|
323
324
|
},
|
|
@@ -326,6 +327,7 @@ const useStyles = ({}: { isStart?: boolean; isEnd?: boolean } = {}) => {
|
|
|
326
327
|
paddingVertical: 16,
|
|
327
328
|
},
|
|
328
329
|
sectionInnerHeader: {
|
|
330
|
+
paddingTop: 24,
|
|
329
331
|
paddingBottom: headerBottomPadding,
|
|
330
332
|
},
|
|
331
333
|
sectionInnerBottomBorder: {
|
|
@@ -364,15 +366,16 @@ const useLinkRowStyles = () => {
|
|
|
364
366
|
justifyContent: 'space-between',
|
|
365
367
|
gap: 12,
|
|
366
368
|
},
|
|
369
|
+
leftContent: {
|
|
370
|
+
flex: 1,
|
|
371
|
+
gap: 4,
|
|
372
|
+
},
|
|
367
373
|
rightContent: {
|
|
368
374
|
flexDirection: 'row',
|
|
369
375
|
alignItems: 'center',
|
|
370
376
|
gap: 8,
|
|
371
377
|
},
|
|
372
|
-
title: {
|
|
373
|
-
flexShrink: 1,
|
|
374
|
-
alignSelf: 'center',
|
|
375
|
-
},
|
|
378
|
+
title: {},
|
|
376
379
|
rightLabelText: {
|
|
377
380
|
color: theme.colors.textColorDefaultSecondary,
|
|
378
381
|
},
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AnalyticsMetadataResource } from './analytics_metadata'
|
|
2
2
|
import { ConversationBadgeResource } from './conversation_badge'
|
|
3
|
+
import { ConversationMembershipResource } from './conversation_membership'
|
|
3
4
|
import { GroupResource } from './group_resource'
|
|
4
5
|
import { MemberAbilityResource } from './member_ability'
|
|
5
6
|
|
|
@@ -8,9 +9,7 @@ export interface ConversationResource {
|
|
|
8
9
|
id: number
|
|
9
10
|
badges?: ConversationBadgeResource[]
|
|
10
11
|
analyticsMetadata?: AnalyticsMetadataResource
|
|
11
|
-
conversationMembership?:
|
|
12
|
-
lastReadMessageSortKey: string
|
|
13
|
-
}
|
|
12
|
+
conversationMembership?: Partial<ConversationMembershipResource>
|
|
14
13
|
createdAt: string
|
|
15
14
|
deleted?: boolean
|
|
16
15
|
groups?: GroupResource[]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ResourceObject } from '../api_primitives'
|
|
2
|
+
|
|
3
|
+
export type NotificationLevelValue = 'everything' | 'nothing'
|
|
4
|
+
export type NotificationLevelDescription = string
|
|
5
|
+
|
|
6
|
+
export interface ConversationMembershipResource extends ResourceObject {
|
|
7
|
+
type: 'ConversationMembership'
|
|
8
|
+
lastReadMessageSortKey: string
|
|
9
|
+
muted: boolean
|
|
10
|
+
notificationLevel: NotificationLevelValue
|
|
11
|
+
notificationLevelDescription: NotificationLevelDescription
|
|
12
|
+
notificationLevelOptions: Array<{
|
|
13
|
+
description: NotificationLevelDescription
|
|
14
|
+
enabled: boolean
|
|
15
|
+
value: NotificationLevelValue
|
|
16
|
+
}>
|
|
17
|
+
}
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { ResourceObject } from '../api_primitives'
|
|
2
2
|
|
|
3
|
+
interface GroupNotificationLevelOption {
|
|
4
|
+
value: string
|
|
5
|
+
description: string
|
|
6
|
+
enabled: boolean
|
|
7
|
+
}
|
|
8
|
+
|
|
3
9
|
export interface GroupMembership extends ResourceObject {
|
|
4
10
|
notificationLevel: string
|
|
5
11
|
notificationLevelDescription: string
|
|
12
|
+
notificationLevelOptions: GroupNotificationLevelOption[]
|
|
6
13
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { isArray, isObject, mapKeys, mapValues, snakeCase } from 'lodash'
|
|
2
|
+
|
|
3
|
+
type ObjType = Record<string, unknown> | unknown[] | unknown
|
|
4
|
+
|
|
5
|
+
export function deepSnakeCaseKeys<T extends ObjType>(obj: T): T {
|
|
6
|
+
if (isArray(obj)) {
|
|
7
|
+
return obj.map(deepSnakeCaseKeys) as T
|
|
8
|
+
} else if (isObject(obj)) {
|
|
9
|
+
return mapValues(
|
|
10
|
+
// @ts-ignore This mutates the object, but we don't care about the type here
|
|
11
|
+
mapKeys(obj, (_value: string, key: string) => snakeCase(key)),
|
|
12
|
+
deepSnakeCaseKeys
|
|
13
|
+
) as T
|
|
14
|
+
}
|
|
15
|
+
return obj
|
|
16
|
+
}
|