@planningcenter/chat-react-native 2.2.2-rc.0 → 2.3.0-rc.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 (111) hide show
  1. package/build/components/conversations.d.ts.map +1 -1
  2. package/build/components/conversations.js +4 -0
  3. package/build/components/conversations.js.map +1 -1
  4. package/build/components/display/action_button.d.ts +8 -0
  5. package/build/components/display/action_button.d.ts.map +1 -0
  6. package/build/components/display/action_button.js +44 -0
  7. package/build/components/display/action_button.js.map +1 -0
  8. package/build/contexts/api_provider.js +2 -2
  9. package/build/contexts/api_provider.js.map +1 -1
  10. package/build/contexts/chat_context.d.ts +2 -1
  11. package/build/contexts/chat_context.d.ts.map +1 -1
  12. package/build/contexts/chat_context.js +3 -1
  13. package/build/contexts/chat_context.js.map +1 -1
  14. package/build/hooks/index.d.ts +2 -0
  15. package/build/hooks/index.d.ts.map +1 -1
  16. package/build/hooks/index.js +2 -0
  17. package/build/hooks/index.js.map +1 -1
  18. package/build/hooks/use_api.d.ts +383 -0
  19. package/build/hooks/use_api.d.ts.map +1 -0
  20. package/build/hooks/use_api.js +40 -0
  21. package/build/hooks/use_api.js.map +1 -0
  22. package/build/hooks/use_api_client.d.ts +5 -3
  23. package/build/hooks/use_api_client.d.ts.map +1 -1
  24. package/build/hooks/use_api_client.js +1 -1
  25. package/build/hooks/use_api_client.js.map +1 -1
  26. package/build/hooks/use_chat_permissions.d.ts +172 -0
  27. package/build/hooks/use_chat_permissions.d.ts.map +1 -0
  28. package/build/hooks/use_chat_permissions.js +17 -0
  29. package/build/hooks/use_chat_permissions.js.map +1 -0
  30. package/build/hooks/use_conversation.d.ts.map +1 -1
  31. package/build/hooks/use_conversation.js +6 -5
  32. package/build/hooks/use_conversation.js.map +1 -1
  33. package/build/hooks/use_suspense_api.d.ts +13 -4
  34. package/build/hooks/use_suspense_api.d.ts.map +1 -1
  35. package/build/hooks/use_suspense_api.js +1 -0
  36. package/build/hooks/use_suspense_api.js.map +1 -1
  37. package/build/index.d.ts +1 -1
  38. package/build/index.d.ts.map +1 -1
  39. package/build/index.js +1 -1
  40. package/build/index.js.map +1 -1
  41. package/build/navigation/index.d.ts +123 -2
  42. package/build/navigation/index.d.ts.map +1 -1
  43. package/build/navigation/index.js +40 -4
  44. package/build/navigation/index.js.map +1 -1
  45. package/build/navigation/screenLayout.js +1 -1
  46. package/build/navigation/screenLayout.js.map +1 -1
  47. package/build/screens/create/conversation_create_screen.d.ts +9 -0
  48. package/build/screens/create/conversation_create_screen.d.ts.map +1 -0
  49. package/build/screens/create/conversation_create_screen.js +123 -0
  50. package/build/screens/create/conversation_create_screen.js.map +1 -0
  51. package/build/screens/create/conversation_filter_recipients_screen.d.ts +8 -0
  52. package/build/screens/create/conversation_filter_recipients_screen.d.ts.map +1 -0
  53. package/build/screens/create/conversation_filter_recipients_screen.js +52 -0
  54. package/build/screens/create/conversation_filter_recipients_screen.js.map +1 -0
  55. package/build/screens/create/conversation_select_recipients_screen.d.ts +8 -0
  56. package/build/screens/create/conversation_select_recipients_screen.d.ts.map +1 -0
  57. package/build/screens/create/conversation_select_recipients_screen.js +105 -0
  58. package/build/screens/create/conversation_select_recipients_screen.js.map +1 -0
  59. package/build/types/api_primitives.d.ts +15 -0
  60. package/build/types/api_primitives.d.ts.map +1 -1
  61. package/build/types/api_primitives.js.map +1 -1
  62. package/build/types/resources/app_grant.d.ts +6 -0
  63. package/build/types/resources/app_grant.d.ts.map +1 -0
  64. package/build/types/resources/app_grant.js +2 -0
  65. package/build/types/resources/app_grant.js.map +1 -0
  66. package/build/types/resources/groups/groups_group_resource.d.ts +12 -0
  67. package/build/types/resources/groups/groups_group_resource.d.ts.map +1 -0
  68. package/build/types/resources/groups/groups_group_resource.js +2 -0
  69. package/build/types/resources/groups/groups_group_resource.js.map +1 -0
  70. package/build/types/resources/groups/index.d.ts +2 -0
  71. package/build/types/resources/groups/index.d.ts.map +1 -0
  72. package/build/types/resources/groups/index.js +2 -0
  73. package/build/types/resources/groups/index.js.map +1 -0
  74. package/build/types/resources/index.d.ts +2 -0
  75. package/build/types/resources/index.d.ts.map +1 -1
  76. package/build/types/resources/index.js +2 -0
  77. package/build/types/resources/index.js.map +1 -1
  78. package/build/utils/client/client.d.ts +8 -5
  79. package/build/utils/client/client.d.ts.map +1 -1
  80. package/build/utils/client/client.js +22 -7
  81. package/build/utils/client/client.js.map +1 -1
  82. package/package.json +2 -2
  83. package/src/__tests__/client.ts +59 -4
  84. package/src/__tests__/hooks/useTheme.tsx +1 -1
  85. package/src/components/conversations.tsx +8 -0
  86. package/src/components/display/action_button.tsx +62 -0
  87. package/src/contexts/api_provider.tsx +2 -2
  88. package/src/contexts/chat_context.tsx +5 -2
  89. package/src/hooks/index.ts +2 -0
  90. package/src/hooks/use_api.ts +80 -0
  91. package/src/hooks/use_api_client.ts +13 -15
  92. package/src/hooks/use_chat_permissions.ts +20 -0
  93. package/src/hooks/use_conversation.ts +6 -5
  94. package/src/hooks/use_suspense_api.ts +16 -4
  95. package/src/index.tsx +1 -1
  96. package/src/navigation/index.tsx +46 -7
  97. package/src/navigation/screenLayout.tsx +1 -1
  98. package/src/screens/create/conversation_create_screen.tsx +148 -0
  99. package/src/screens/create/conversation_filter_recipients_screen.tsx +79 -0
  100. package/src/screens/create/conversation_select_recipients_screen.tsx +136 -0
  101. package/src/types/api_primitives.ts +12 -0
  102. package/src/types/resources/app_grant.ts +6 -0
  103. package/src/types/resources/groups/groups_group_resource.ts +12 -0
  104. package/src/types/resources/groups/index.ts +1 -0
  105. package/src/types/resources/index.ts +2 -0
  106. package/src/utils/client/client.ts +34 -11
  107. package/build/contexts/index.d.ts +0 -3
  108. package/build/contexts/index.d.ts.map +0 -1
  109. package/build/contexts/index.js +0 -3
  110. package/build/contexts/index.js.map +0 -1
  111. package/src/contexts/index.ts +0 -2
package/src/index.tsx CHANGED
@@ -1,4 +1,4 @@
1
- export { ChatProvider, ChatContext, CreateChatThemeProps } from './contexts'
1
+ export { ChatProvider, ChatContext, CreateChatThemeProps } from './contexts/chat_context'
2
2
  export { DesignSystemScreen } from './screens'
3
3
  export { ChatStack } from './navigation'
4
4
  export {
@@ -1,8 +1,7 @@
1
- import { HeaderBackButton, HeaderButton } from '@react-navigation/elements'
1
+ import { HeaderBackButton } from '@react-navigation/elements'
2
2
  import { StaticParamList } from '@react-navigation/native'
3
3
  import {
4
4
  createNativeStackNavigator,
5
- NativeStackHeaderLeftProps,
6
5
  NativeStackHeaderRightProps,
7
6
  } from '@react-navigation/native-stack'
8
7
  import React from 'react'
@@ -10,6 +9,12 @@ import { Icon } from '../components'
10
9
  import { ConversationDetailsScreen } from '../screens/conversation_details_screen'
11
10
  import { ConversationScreen } from '../screens/conversation_screen'
12
11
  import { ConversationsScreen } from '../screens/conversations_screen'
12
+ import { ConversationCreateScreen } from '../screens/create/conversation_create_screen'
13
+ import {
14
+ ConversationFilterReceipientsScreenOptions,
15
+ ConversationFilterRecipientsScreen,
16
+ } from '../screens/create/conversation_filter_recipients_screen'
17
+ import { ConversationSelectRecipientsScreen } from '../screens/create/conversation_select_recipients_screen'
13
18
  import {
14
19
  MessageActionsScreen,
15
20
  MessageActionsScreenOptions,
@@ -19,6 +24,38 @@ import { ReactionsScreen, ReactionsScreenOptions } from '../screens/reactions_sc
19
24
  import { HeaderRightButton } from './header'
20
25
  import { ScreenLayout } from './screenLayout'
21
26
 
27
+ export const CreateConversationStack = createNativeStackNavigator({
28
+ initialRouteName: 'ConversationSelectRecipients',
29
+ screenOptions: {
30
+ headerBackButtonDisplayMode: 'minimal',
31
+ },
32
+ screenLayout: ScreenLayout,
33
+ screens: {
34
+ ConversationSelectRecipients: {
35
+ screen: ConversationSelectRecipientsScreen,
36
+ options: ({ navigation }) => ({
37
+ title: 'New conversation',
38
+ headerRight: (props: NativeStackHeaderRightProps) => (
39
+ <HeaderRightButton {...props} onPress={navigation.goBack}>
40
+ Cancel
41
+ </HeaderRightButton>
42
+ ),
43
+ }),
44
+ },
45
+ ConversationFilterRecipients: {
46
+ screen: ConversationFilterRecipientsScreen,
47
+ options: ConversationFilterReceipientsScreenOptions,
48
+ },
49
+ ConversationCreate: {
50
+ screen: ConversationCreateScreen,
51
+ options: {
52
+ title: 'New conversation',
53
+ headerLeft: () => null,
54
+ },
55
+ },
56
+ },
57
+ })
58
+
22
59
  export const ChatStack = createNativeStackNavigator({
23
60
  screenOptions: {
24
61
  headerBackButtonDisplayMode: 'minimal',
@@ -29,11 +66,6 @@ export const ChatStack = createNativeStackNavigator({
29
66
  screen: ConversationsScreen,
30
67
  options: ({ route, navigation }) => ({
31
68
  headerTitle: (route.params as { title?: string })?.title ?? 'Chat',
32
- headerLeft: ({ tintColor }: NativeStackHeaderLeftProps) => (
33
- <HeaderButton>
34
- <Icon name="general.threeReducingHorizontalBars" size={18} color={tintColor} />
35
- </HeaderButton>
36
- ),
37
69
  headerRight: (props: NativeStackHeaderRightProps) => (
38
70
  <HeaderBackButton
39
71
  backImage={() => <Icon name="general.x" size={18} color={props.tintColor} />}
@@ -58,6 +90,13 @@ export const ChatStack = createNativeStackNavigator({
58
90
  ),
59
91
  }),
60
92
  },
93
+ Create: {
94
+ screen: CreateConversationStack,
95
+ options: {
96
+ headerShown: false,
97
+ presentation: 'modal',
98
+ },
99
+ },
61
100
  MessageActions: {
62
101
  screen: MessageActionsScreen,
63
102
  // Something about sheetAllowedDetents declared inline breaks TS
@@ -1,7 +1,7 @@
1
1
  import React from 'react'
2
2
  import ErrorBoundary from '../components/page/error_boundary'
3
3
  import { Suspense } from 'react'
4
- import { ApiProvider } from '../contexts'
4
+ import { ApiProvider } from '../contexts/api_provider'
5
5
  import { DefaultLoading } from '../components/page/loading'
6
6
 
7
7
  export function ScreenLayout({ children }: { children: React.ReactElement }) {
@@ -0,0 +1,148 @@
1
+ import { StackActions, StaticScreenProps, useNavigation } from '@react-navigation/native'
2
+ import React, { useCallback, useState } from 'react'
3
+ import { StyleSheet, TextInput, View } from 'react-native'
4
+ import { useSafeAreaInsets } from 'react-native-safe-area-context'
5
+ import { Text } from '../../components'
6
+ import { ActionButton } from '../../components/display/action_button'
7
+ import { useSuspenseGet, useTheme } from '../../hooks'
8
+ import { useApiClient } from '../../hooks/use_api_client'
9
+ import { useMutation } from '@tanstack/react-query'
10
+ import { GroupsGroupResource } from '../../types'
11
+
12
+ type ConversationCreateScreenProps = StaticScreenProps<{
13
+ group_id?: string
14
+ team_ids?: string[]
15
+ }>
16
+
17
+ export const ConversationCreateScreen = ({ route }: ConversationCreateScreenProps) => {
18
+ const styles = useStyles()
19
+ const navigation = useNavigation()
20
+ const [title, setTitle] = useState<string>()
21
+ const apiClient = useApiClient()
22
+ const { data: group } = useSuspenseGet<GroupsGroupResource>({
23
+ url: `/me/groups/${route.params.group_id}`,
24
+ data: {
25
+ fields: {
26
+ Group: [],
27
+ },
28
+ include: ['location'],
29
+ },
30
+ app: 'groups',
31
+ })
32
+ const { mutate: handleSave } = useMutation({
33
+ throwOnError: true,
34
+ onSuccess: result => {
35
+ handleRedirectToConversation({ conversation_id: result.data.id })
36
+ },
37
+ mutationFn: () =>
38
+ apiClient.groups
39
+ .post({
40
+ url: `/me/groups/${route.params.group_id}/chat_conversation_payload`,
41
+ data: {
42
+ data: {
43
+ type: '',
44
+ attributes: {
45
+ title,
46
+ },
47
+ },
48
+ },
49
+ })
50
+ .then(res => res.data.value)
51
+ .then(payload =>
52
+ apiClient.chat.post({
53
+ url: '/me/conversations',
54
+ data: {
55
+ data: {
56
+ type: 'Conversation',
57
+ attributes: {
58
+ payload,
59
+ },
60
+ },
61
+ },
62
+ })
63
+ ),
64
+ })
65
+
66
+ const handleRedirectToConversation = useCallback(
67
+ ({ conversation_id }: { conversation_id: string }) => {
68
+ // exit from the create stack
69
+ navigation.getParent()?.goBack()
70
+ // navigate to the conversation screen
71
+ navigation.dispatch(
72
+ StackActions.push('Conversation', {
73
+ conversation_id,
74
+ })
75
+ )
76
+ },
77
+ [navigation]
78
+ )
79
+
80
+ return (
81
+ <View style={styles.container}>
82
+ <View style={styles.section}>
83
+ <View style={styles.to}>
84
+ <Text>To:</Text>
85
+ <View>
86
+ <Text>{group.name}</Text>
87
+ <Text>{group.membershipsCount} members</Text>
88
+ </View>
89
+ </View>
90
+ <View style={styles.titleContainer}>
91
+ <Text style={styles.titleLabel}>Title</Text>
92
+ <TextInput
93
+ placeholder="Topic of conversation (required)"
94
+ value={title}
95
+ onChangeText={setTitle}
96
+ style={styles.titleInput}
97
+ />
98
+ </View>
99
+ </View>
100
+ <ActionButton title="Start Conversation" onPress={() => handleSave()} />
101
+ </View>
102
+ )
103
+ }
104
+
105
+ const useStyles = () => {
106
+ const { bottom } = useSafeAreaInsets()
107
+ const theme = useTheme()
108
+
109
+ return StyleSheet.create({
110
+ container: {
111
+ flex: 1,
112
+ gap: 8,
113
+ },
114
+ section: {
115
+ padding: 16,
116
+ flex: 1,
117
+ },
118
+ sectionHeader: {
119
+ flexDirection: 'row',
120
+ justifyContent: 'space-between',
121
+ },
122
+ selectTeamsButton: {},
123
+ routeDebug: {
124
+ alignContent: 'center',
125
+ padding: 16,
126
+ paddingBottom: bottom,
127
+ borderTopWidth: 1,
128
+ borderTopColor: theme.colors.fillColorNeutral050Base,
129
+ },
130
+ titleContainer: {
131
+ paddingVertical: 12,
132
+ borderBottomWidth: 1,
133
+ borderBottomColor: theme.colors.fillColorNeutral050Base,
134
+ gap: 8,
135
+ },
136
+ titleInput: {
137
+ fontSize: 18,
138
+ },
139
+ titleLabel: {},
140
+ to: {
141
+ flexDirection: 'row',
142
+ gap: 8,
143
+ paddingVertical: 12,
144
+ borderBottomWidth: 1,
145
+ borderBottomColor: theme.colors.fillColorNeutral050Base,
146
+ },
147
+ })
148
+ }
@@ -0,0 +1,79 @@
1
+ import { StackActions, StaticScreenProps, useNavigation } from '@react-navigation/native'
2
+ import { NativeStackNavigationOptions } from '@react-navigation/native-stack'
3
+ import React from 'react'
4
+ import { StyleSheet, View } from 'react-native'
5
+ import { useSafeAreaInsets } from 'react-native-safe-area-context'
6
+ import { Button } from '../../components'
7
+ import { useTheme } from '../../hooks'
8
+
9
+ export const ConversationFilterReceipientsScreenOptions: NativeStackNavigationOptions = {
10
+ presentation: 'formSheet',
11
+ headerShown: false,
12
+ sheetAllowedDetents: [0.75],
13
+ sheetGrabberVisible: true,
14
+ }
15
+
16
+ type ConversationFilterRecipientsScreenProps = StaticScreenProps<{}>
17
+
18
+ export const ConversationFilterRecipientsScreen = ({}: ConversationFilterRecipientsScreenProps) => {
19
+ const styles = useStyles()
20
+ const navigation = useNavigation()
21
+
22
+ return (
23
+ <View style={styles.container}>
24
+ <View style={styles.section}>
25
+ <Button
26
+ style={styles.selectTeamsButton}
27
+ onPress={() =>
28
+ navigation.dispatch(
29
+ StackActions.popTo('ConversationSelectRecipients', {
30
+ chat_group_graph_id: 'TBD',
31
+ })
32
+ )
33
+ }
34
+ title="Redirect to select"
35
+ variant="outline"
36
+ />
37
+
38
+ <Button
39
+ style={styles.selectTeamsButton}
40
+ onPress={() =>
41
+ navigation.dispatch(
42
+ StackActions.popTo('ConversationCreate', {
43
+ chat_group_graph_id: 'TBD',
44
+ })
45
+ )
46
+ }
47
+ title="Redirect to form"
48
+ variant="outline"
49
+ />
50
+ </View>
51
+ </View>
52
+ )
53
+ }
54
+
55
+ const useStyles = () => {
56
+ const { bottom } = useSafeAreaInsets()
57
+ const theme = useTheme()
58
+
59
+ return StyleSheet.create({
60
+ container: {
61
+ flex: 1,
62
+ gap: 8,
63
+ },
64
+ section: {
65
+ padding: 16,
66
+ flexDirection: 'row',
67
+ justifyContent: 'space-between',
68
+ flex: 1,
69
+ },
70
+ selectTeamsButton: {},
71
+ routeDebug: {
72
+ alignContent: 'center',
73
+ padding: 16,
74
+ paddingBottom: bottom,
75
+ borderTopWidth: 1,
76
+ borderTopColor: theme.colors.fillColorNeutral050Base,
77
+ },
78
+ })
79
+ }
@@ -0,0 +1,136 @@
1
+ import { PlatformPressable } from '@react-navigation/elements'
2
+ import { StaticScreenProps, useNavigation } from '@react-navigation/native'
3
+ import React from 'react'
4
+ import { ScrollView, StyleSheet, View } from 'react-native'
5
+ import { useSafeAreaInsets } from 'react-native-safe-area-context'
6
+ import { Button, Heading, Icon, Image, Text } from '../../components'
7
+ import { useSuspenseGet, useTheme } from '../../hooks'
8
+ import { GroupsGroupResource } from '../../types'
9
+
10
+ type ConversationSelectRecipientsScreenProps = StaticScreenProps<{
11
+ chat_group_graph_id?: string
12
+ }>
13
+
14
+ const ASPECT_RATIO = 16 / 9
15
+ const THUMBNAIL_WIDTH = 80
16
+ const THUMBNAIL_HEIGHT = THUMBNAIL_WIDTH / ASPECT_RATIO
17
+
18
+ export const ConversationSelectRecipientsScreen = ({}: ConversationSelectRecipientsScreenProps) => {
19
+ const styles = useStyles()
20
+ const navigation = useNavigation()
21
+ const { data: groups = [] } = useSuspenseGet<GroupsGroupResource[]>({
22
+ url: '/me/groups',
23
+ data: {
24
+ perPage: 100,
25
+ fields: {
26
+ Group: ['can_create_conversation', 'name', 'header_image', 'memberships_count'],
27
+ },
28
+ },
29
+ app: 'groups',
30
+ })
31
+ const groupsWithCreatePermission = groups.filter(g => g.canCreateConversation)
32
+
33
+ const handleNavigateToCreateConversation = (group: GroupsGroupResource) => {
34
+ navigation.navigate('Create', {
35
+ screen: 'ConversationCreate',
36
+ params: {
37
+ group_id: group.id,
38
+ },
39
+ })
40
+ }
41
+
42
+ return (
43
+ <ScrollView style={styles.container}>
44
+ <View style={styles.section}>
45
+ <View style={styles.sectionHeader}>
46
+ <Heading>My groups</Heading>
47
+ </View>
48
+ <View>
49
+ {groupsWithCreatePermission.map(group => (
50
+ <PlatformPressable
51
+ key={group.id}
52
+ style={styles.row}
53
+ onPress={() => handleNavigateToCreateConversation(group)}
54
+ >
55
+ <Image
56
+ source={{ uri: group.headerImage?.thumbnail }}
57
+ resizeMode="cover"
58
+ style={styles.rowImage}
59
+ alt={`Image for ${group.name}`}
60
+ />
61
+ <View>
62
+ <Heading variant="h3">{group.name}</Heading>
63
+ <Text>{group.membershipsCount} members</Text>
64
+ </View>
65
+ <Icon name="general.rightChevron" size={16} style={styles.rowIconRight} />
66
+ </PlatformPressable>
67
+ ))}
68
+ </View>
69
+ </View>
70
+ <View style={styles.section}>
71
+ <View style={styles.sectionHeader}>
72
+ <Heading>Teams I lead</Heading>
73
+ <Button
74
+ style={styles.selectTeamsButton}
75
+ onPress={() =>
76
+ navigation.navigate('Create', {
77
+ screen: 'ConversationFilterRecipients',
78
+ params: {
79
+ conversation_id: '2196252',
80
+ },
81
+ })
82
+ }
83
+ title="Select teams"
84
+ variant="outline"
85
+ iconNameLeft="general.search"
86
+ />
87
+ </View>
88
+ </View>
89
+ </ScrollView>
90
+ )
91
+ }
92
+
93
+ const useStyles = () => {
94
+ const { bottom } = useSafeAreaInsets()
95
+ const theme = useTheme()
96
+
97
+ return StyleSheet.create({
98
+ container: {
99
+ flex: 1,
100
+ gap: 8,
101
+ },
102
+ section: {
103
+ padding: 16,
104
+ flex: 1,
105
+ },
106
+ sectionHeader: {
107
+ flexDirection: 'row',
108
+ justifyContent: 'space-between',
109
+ },
110
+ selectTeamsButton: {},
111
+ row: {
112
+ flexDirection: 'row',
113
+ alignItems: 'center',
114
+ gap: 12,
115
+ paddingVertical: 16,
116
+ borderBottomWidth: 1,
117
+ borderColor: theme.colors.fillColorNeutral050Base,
118
+ },
119
+ rowImage: {
120
+ width: THUMBNAIL_WIDTH,
121
+ height: THUMBNAIL_HEIGHT,
122
+ borderRadius: 4,
123
+ },
124
+ rowIconRight: {
125
+ marginLeft: 'auto',
126
+ color: theme.colors.fillColorNeutral030,
127
+ },
128
+ routeDebug: {
129
+ alignContent: 'center',
130
+ padding: 16,
131
+ paddingBottom: bottom,
132
+ borderTopWidth: 1,
133
+ borderTopColor: theme.colors.fillColorNeutral050Base,
134
+ },
135
+ })
136
+ }
@@ -3,6 +3,14 @@ export interface ResourceObject {
3
3
  type: string
4
4
  }
5
5
 
6
+ export interface ErrorObject {
7
+ source?: { parameter: string }
8
+ detail: string
9
+ meta?: { associated_resources: any[]; resource: string }
10
+ title: string
11
+ status: string
12
+ }
13
+
6
14
  export type ApiResource<Type = ResourceObject> = {
7
15
  data: Type
8
16
  links: Record<string, string>
@@ -15,6 +23,10 @@ export type ApiCollection<Type = ResourceObject> = {
15
23
  meta: CollectionMeta
16
24
  }
17
25
 
26
+ export interface ApiError {
27
+ errors: ErrorObject[]
28
+ }
29
+
18
30
  export interface CollectionMeta {
19
31
  count: number
20
32
  totalCount: number
@@ -0,0 +1,6 @@
1
+ import { ResourceObject } from '../api_primitives'
2
+
3
+ export interface AppGrantsResource extends ResourceObject {
4
+ createConversations: boolean
5
+ appName: string
6
+ }
@@ -0,0 +1,12 @@
1
+ import { ResourceObject } from '../../api_primitives'
2
+
3
+ export interface GroupsGroupResource extends ResourceObject {
4
+ headerImage: {
5
+ thumbnail: string
6
+ medium: string
7
+ original: string
8
+ }
9
+ name: string
10
+ membershipsCount: number
11
+ canCreateConversation: boolean
12
+ }
@@ -0,0 +1 @@
1
+ export * from './groups_group_resource'
@@ -3,3 +3,5 @@ export * from './member'
3
3
  export * from './message'
4
4
  export * from './oauth_token'
5
5
  export * from './person'
6
+ export * from './groups'
7
+ export * from './app_grant'
@@ -1,4 +1,4 @@
1
- import { ApiCollection, ApiResource } from '../../types'
1
+ import { ApiCollection, ApiError, ApiResource } from '../../types'
2
2
  import { Session } from '../session'
3
3
  import { Uri } from '../uri'
4
4
  import {
@@ -15,7 +15,8 @@ import { DeleteRequest, GetRequest, PatchRequest, PostRequest, WalkRequest } fro
15
15
  type ClientArgs = {
16
16
  version: string
17
17
  defaultHeaders?: Record<string, string>
18
- onTokenExpired: () => void
18
+ onForceLogout?: () => void
19
+ onTokenExpired?: () => void
19
20
  session: Session
20
21
  app: string
21
22
  }
@@ -24,13 +25,22 @@ export class Client {
24
25
  version: string = ''
25
26
  defaultHeaders: Record<string, string> = {}
26
27
  uri: Uri
27
- onTokenExpired: () => void
28
-
29
- constructor({ version, defaultHeaders = {}, session, app, onTokenExpired }: ClientArgs) {
28
+ onTokenExpired?: () => void
29
+ onForceLogout?: () => void
30
+
31
+ constructor({
32
+ version,
33
+ defaultHeaders = {},
34
+ session,
35
+ app,
36
+ onTokenExpired,
37
+ onForceLogout,
38
+ }: ClientArgs) {
30
39
  this.version = version
31
40
  this.uri = new Uri({ session, app })
32
41
  this.defaultHeaders = defaultHeaders
33
42
  this.onTokenExpired = onTokenExpired
43
+ this.onForceLogout = onForceLogout
34
44
  }
35
45
 
36
46
  async get<T extends ApiCollection | ApiResource>(args: GetRequest): Promise<T> {
@@ -69,7 +79,7 @@ export class Client {
69
79
 
70
80
  const handler = isWalking ? walkRequest : makeRequest
71
81
 
72
- return throwErrorIfFieldsMissing(handler, requestArgs).catch(this.handleTokenExpired)
82
+ return throwErrorIfFieldsMissing(handler, requestArgs).catch(this.handleNotOk)
73
83
  }
74
84
 
75
85
  async patch(args: PatchRequest) {
@@ -78,7 +88,7 @@ export class Client {
78
88
 
79
89
  const requestArgs: MakeRequestArgs = { data: args.data, url, action: 'PATCH', headers }
80
90
 
81
- return ensureNoQueryParamsInDev(makeRequest, requestArgs).catch(this.handleTokenExpired)
91
+ return ensureNoQueryParamsInDev(makeRequest, requestArgs).catch(this.handleNotOk)
82
92
  }
83
93
 
84
94
  async post(args: PostRequest) {
@@ -87,7 +97,7 @@ export class Client {
87
97
 
88
98
  const requestArgs: MakeRequestArgs = { ...args, data: args.data, url, action: 'POST', headers }
89
99
 
90
- return ensureNoQueryParamsInDev(makeRequest, requestArgs).catch(this.handleTokenExpired)
100
+ return ensureNoQueryParamsInDev(makeRequest, requestArgs).catch(this.handleNotOk)
91
101
  }
92
102
 
93
103
  async delete(args: DeleteRequest) {
@@ -96,17 +106,30 @@ export class Client {
96
106
 
97
107
  const requestArgs: MakeRequestArgs = { url, action: 'DELETE', headers }
98
108
 
99
- return makeRequest(requestArgs).catch(this.handleTokenExpired)
109
+ return makeRequest(requestArgs).catch(this.handleNotOk)
100
110
  }
101
111
 
102
- handleTokenExpired = (response: Response) => {
112
+ handleNotOk = async (response: Response) => {
103
113
  if (response.status === 401) {
104
- this.onTokenExpired()
114
+ const parsed = await this.parseErrorResponse(response)
115
+ const errors = parsed?.errors || []
116
+ const isTokenExpired = errors.some(e => /baboon/i.test(e.detail))
117
+ const isForceLogout = errors.some(e => /capuchin/i.test(e.detail))
118
+ isTokenExpired && this.onTokenExpired?.()
119
+ isForceLogout && this.onForceLogout?.()
105
120
  }
106
121
 
107
122
  return Promise.reject(response)
108
123
  }
109
124
 
125
+ parseErrorResponse = async (response: Response): Promise<ApiError | undefined> => {
126
+ try {
127
+ return (await response.json()) as ApiError
128
+ } catch {
129
+ return undefined
130
+ }
131
+ }
132
+
110
133
  get headers() {
111
134
  return {
112
135
  Accept: 'application/vnd.api+json',
@@ -1,3 +0,0 @@
1
- export * from './api_provider';
2
- export * from './chat_context';
3
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/contexts/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA"}
@@ -1,3 +0,0 @@
1
- export * from './api_provider';
2
- export * from './chat_context';
3
- //# sourceMappingURL=index.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/contexts/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA","sourcesContent":["export * from './api_provider'\nexport * from './chat_context'\n"]}
@@ -1,2 +0,0 @@
1
- export * from './api_provider'
2
- export * from './chat_context'