@planningcenter/chat-react-native 2.2.2-rc.1 → 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 (95) 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/hooks/index.d.ts +2 -0
  11. package/build/hooks/index.d.ts.map +1 -1
  12. package/build/hooks/index.js +2 -0
  13. package/build/hooks/index.js.map +1 -1
  14. package/build/hooks/use_api.d.ts +383 -0
  15. package/build/hooks/use_api.d.ts.map +1 -0
  16. package/build/hooks/use_api.js +40 -0
  17. package/build/hooks/use_api.js.map +1 -0
  18. package/build/hooks/use_api_client.d.ts +5 -3
  19. package/build/hooks/use_api_client.d.ts.map +1 -1
  20. package/build/hooks/use_api_client.js +1 -1
  21. package/build/hooks/use_api_client.js.map +1 -1
  22. package/build/hooks/use_chat_permissions.d.ts +172 -0
  23. package/build/hooks/use_chat_permissions.d.ts.map +1 -0
  24. package/build/hooks/use_chat_permissions.js +17 -0
  25. package/build/hooks/use_chat_permissions.js.map +1 -0
  26. package/build/hooks/use_conversation.d.ts.map +1 -1
  27. package/build/hooks/use_conversation.js +6 -5
  28. package/build/hooks/use_conversation.js.map +1 -1
  29. package/build/hooks/use_suspense_api.d.ts +13 -4
  30. package/build/hooks/use_suspense_api.d.ts.map +1 -1
  31. package/build/hooks/use_suspense_api.js +1 -0
  32. package/build/hooks/use_suspense_api.js.map +1 -1
  33. package/build/index.d.ts +1 -1
  34. package/build/index.d.ts.map +1 -1
  35. package/build/index.js +1 -1
  36. package/build/index.js.map +1 -1
  37. package/build/navigation/index.d.ts +123 -2
  38. package/build/navigation/index.d.ts.map +1 -1
  39. package/build/navigation/index.js +40 -4
  40. package/build/navigation/index.js.map +1 -1
  41. package/build/navigation/screenLayout.js +1 -1
  42. package/build/navigation/screenLayout.js.map +1 -1
  43. package/build/screens/create/conversation_create_screen.d.ts +9 -0
  44. package/build/screens/create/conversation_create_screen.d.ts.map +1 -0
  45. package/build/screens/create/conversation_create_screen.js +123 -0
  46. package/build/screens/create/conversation_create_screen.js.map +1 -0
  47. package/build/screens/create/conversation_filter_recipients_screen.d.ts +8 -0
  48. package/build/screens/create/conversation_filter_recipients_screen.d.ts.map +1 -0
  49. package/build/screens/create/conversation_filter_recipients_screen.js +52 -0
  50. package/build/screens/create/conversation_filter_recipients_screen.js.map +1 -0
  51. package/build/screens/create/conversation_select_recipients_screen.d.ts +8 -0
  52. package/build/screens/create/conversation_select_recipients_screen.d.ts.map +1 -0
  53. package/build/screens/create/conversation_select_recipients_screen.js +105 -0
  54. package/build/screens/create/conversation_select_recipients_screen.js.map +1 -0
  55. package/build/types/resources/app_grant.d.ts +6 -0
  56. package/build/types/resources/app_grant.d.ts.map +1 -0
  57. package/build/types/resources/app_grant.js +2 -0
  58. package/build/types/resources/app_grant.js.map +1 -0
  59. package/build/types/resources/groups/groups_group_resource.d.ts +12 -0
  60. package/build/types/resources/groups/groups_group_resource.d.ts.map +1 -0
  61. package/build/types/resources/groups/groups_group_resource.js +2 -0
  62. package/build/types/resources/groups/groups_group_resource.js.map +1 -0
  63. package/build/types/resources/groups/index.d.ts +2 -0
  64. package/build/types/resources/groups/index.d.ts.map +1 -0
  65. package/build/types/resources/groups/index.js +2 -0
  66. package/build/types/resources/groups/index.js.map +1 -0
  67. package/build/types/resources/index.d.ts +2 -0
  68. package/build/types/resources/index.d.ts.map +1 -1
  69. package/build/types/resources/index.js +2 -0
  70. package/build/types/resources/index.js.map +1 -1
  71. package/package.json +2 -2
  72. package/src/components/conversations.tsx +8 -0
  73. package/src/components/display/action_button.tsx +62 -0
  74. package/src/contexts/api_provider.tsx +2 -2
  75. package/src/hooks/index.ts +2 -0
  76. package/src/hooks/use_api.ts +80 -0
  77. package/src/hooks/use_api_client.ts +13 -15
  78. package/src/hooks/use_chat_permissions.ts +20 -0
  79. package/src/hooks/use_conversation.ts +6 -5
  80. package/src/hooks/use_suspense_api.ts +16 -4
  81. package/src/index.tsx +1 -1
  82. package/src/navigation/index.tsx +46 -7
  83. package/src/navigation/screenLayout.tsx +1 -1
  84. package/src/screens/create/conversation_create_screen.tsx +148 -0
  85. package/src/screens/create/conversation_filter_recipients_screen.tsx +79 -0
  86. package/src/screens/create/conversation_select_recipients_screen.tsx +136 -0
  87. package/src/types/resources/app_grant.ts +6 -0
  88. package/src/types/resources/groups/groups_group_resource.ts +12 -0
  89. package/src/types/resources/groups/index.ts +1 -0
  90. package/src/types/resources/index.ts +2 -0
  91. package/build/contexts/index.d.ts +0 -3
  92. package/build/contexts/index.d.ts.map +0 -1
  93. package/build/contexts/index.js +0 -3
  94. package/build/contexts/index.js.map +0 -1
  95. package/src/contexts/index.ts +0 -2
@@ -7,11 +7,14 @@ import { useConversationsJoltEvents } from '../hooks/use_conversation_jolt_event
7
7
  import { useConversations } from '../hooks/use_conversations'
8
8
  import { formatDatePreview } from '../utils/date'
9
9
  import { AvatarGroup, Badge, Heading, Text, TextButton } from './display'
10
+ import { ActionButton } from './display/action_button'
11
+ import { useCanCreateConversations } from '../hooks/use_chat_permissions'
10
12
 
11
13
  export const Conversations = () => {
12
14
  const styles = useStyles()
13
15
 
14
16
  const { conversations, fetchNextPage, refetch, isRefetching } = useConversations()
17
+ const canCreateConversations = useCanCreateConversations()
15
18
 
16
19
  // TODO: Filter using the API
17
20
  const nonEmptyConversations = conversations.filter(c => c.lastMessageTextPreview) || []
@@ -74,6 +77,11 @@ export const Conversations = () => {
74
77
  )}
75
78
  onEndReached={() => fetchNextPage()}
76
79
  />
80
+ <ActionButton
81
+ visible={canCreateConversations}
82
+ title="New conversation"
83
+ onPress={() => navigation.navigate('Create')}
84
+ />
77
85
  </View>
78
86
  )
79
87
  }
@@ -0,0 +1,62 @@
1
+ import React, { useState } from 'react'
2
+ import { Animated, LayoutAnimation, StyleSheet } from 'react-native'
3
+ import { Button } from './button'
4
+ import { useEffect } from 'react'
5
+ import { useSafeAreaInsets } from 'react-native-safe-area-context'
6
+ import { useTheme } from '../../hooks'
7
+ import { Text } from './text'
8
+
9
+ export const ActionButton = ({
10
+ visible = true,
11
+ onPress,
12
+ title,
13
+ infoText,
14
+ }: {
15
+ visible?: boolean
16
+ onPress: () => void
17
+ title: string
18
+ infoText?: string
19
+ }) => {
20
+ const styles = useStyles()
21
+ const [show, setShow] = useState(visible)
22
+
23
+ useEffect(() => {
24
+ if (show === visible) return
25
+
26
+ setShow(visible)
27
+ LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
28
+ }, [show, visible])
29
+
30
+ if (!visible) return null
31
+
32
+ return (
33
+ <Animated.View style={styles.container}>
34
+ {Boolean(infoText) && (
35
+ <Text style={styles.infoText} variant="tertiary">
36
+ {infoText}
37
+ </Text>
38
+ )}
39
+ <Button variant="fill" size="lg" onPress={onPress} title={title} />
40
+ </Animated.View>
41
+ )
42
+ }
43
+
44
+ const useStyles = () => {
45
+ const { bottom } = useSafeAreaInsets()
46
+ const { colors } = useTheme()
47
+
48
+ return StyleSheet.create({
49
+ container: {
50
+ paddingVertical: 16,
51
+ paddingHorizontal: 24,
52
+ paddingBottom: bottom,
53
+ borderTopWidth: 1,
54
+ borderTopColor: colors.fillColorNeutral060,
55
+ gap: 16,
56
+ },
57
+ infoText: {
58
+ textAlign: 'center',
59
+ color: colors.textColorDefaultSecondary,
60
+ },
61
+ })
62
+ }
@@ -12,9 +12,9 @@ const defaultQueryFn = ({ queryKey }: { queryKey: QueryKey }) => {
12
12
  throw new Error('No token present')
13
13
  }
14
14
 
15
- const [url, data, headers] = queryKey as RequestQueryKey
15
+ const [url, data, headers, app = 'chat'] = queryKey as RequestQueryKey
16
16
 
17
- return apiClient.chat.get({ url, data, headers })
17
+ return apiClient[app].get({ url, data, headers })
18
18
  }
19
19
 
20
20
  export const queryClient = new QueryClient({
@@ -4,3 +4,5 @@ export * from './use_suspense_api'
4
4
  export * from './use_current_person'
5
5
  export * from './use_font_scale'
6
6
  export * from './use_create_android_ripple_color'
7
+ export * from './use_chat_permissions'
8
+ export * from './use_api_client'
@@ -0,0 +1,80 @@
1
+ import {
2
+ AnyUseSuspenseInfiniteQueryOptions,
3
+ InfiniteData,
4
+ useInfiniteQuery,
5
+ useQuery,
6
+ } from '@tanstack/react-query'
7
+ import { ApiCollection, ApiResource, ResourceObject } from '../types'
8
+ import { GetRequest, RequestData } from '../utils/client/types'
9
+ import { useApiClient } from './use_api_client'
10
+ import { getRequestQueryKey } from './use_suspense_api'
11
+
12
+ interface SuspenseGetOptions extends GetRequest {
13
+ app?: 'chat' | 'groups'
14
+ }
15
+
16
+ export const useApiGet = <T extends ResourceObject | ResourceObject[]>(
17
+ args: SuspenseGetOptions
18
+ ) => {
19
+ type Resource = ApiResource<T>
20
+
21
+ const { data, ...query } = useQuery<Resource, Response>({
22
+ queryKey: getRequestQueryKey(args),
23
+ })
24
+
25
+ return { ...data, ...query }
26
+ }
27
+
28
+ type NextMeta = Partial<{
29
+ offset: string
30
+ idLt: string
31
+ }>
32
+
33
+ export type SuspensePaginatorOptions = Omit<
34
+ AnyUseSuspenseInfiniteQueryOptions,
35
+ 'getNextPageParam' | 'initialPageParam' | 'queryFn' | 'queryKey'
36
+ >
37
+
38
+ export const useApiPaginator = <T extends ResourceObject>(
39
+ args: SuspenseGetOptions,
40
+ opts?: SuspensePaginatorOptions
41
+ ) => {
42
+ const apiClient = useApiClient()
43
+ const query = useInfiniteQuery<
44
+ ApiCollection<T>,
45
+ Response,
46
+ InfiniteData<ApiCollection<T>>,
47
+ any,
48
+ Partial<RequestData> | undefined
49
+ >({
50
+ queryKey: getRequestQueryKey(args),
51
+ queryFn: ({ pageParam }) => {
52
+ const pageParmWhere = pageParam?.where || {}
53
+ const argsWhere = args.data.where || {}
54
+ const where = { ...argsWhere, ...pageParmWhere }
55
+
56
+ const offset = pageParam?.offset || args.data.offset
57
+ const data = { ...args.data, where, offset }
58
+
59
+ return apiClient.chat.get({
60
+ url: args.url,
61
+ data,
62
+ })
63
+ },
64
+ initialPageParam: {} as Partial<RequestData>,
65
+ getNextPageParam: lastPage => {
66
+ const next: NextMeta = lastPage.meta?.next || {}
67
+ const { offset, idLt } = next
68
+
69
+ if (idLt) return { where: { id_lt: idLt } }
70
+ if (offset) return { offset: Number(offset) }
71
+
72
+ return undefined
73
+ },
74
+ ...(opts || {}),
75
+ })
76
+
77
+ const data: T[] = query.data?.pages.flatMap(page => page.data) || []
78
+
79
+ return { ...query, data }
80
+ }
@@ -2,26 +2,24 @@ import { useContext, useMemo } from 'react'
2
2
  import { ChatContext } from '../contexts/chat_context'
3
3
  import { Client } from '../utils/client'
4
4
 
5
- type App = 'chat' | 'groups'
6
- const apps: App[] = ['chat', 'groups']
7
- export type ApiClient = Record<App, Client>
5
+ type App = 'chat' | 'groups' | 'services'
6
+ const apps: App[] = ['chat', 'groups', 'services']
7
+
8
+ export type ApiClient = { [_K in App]: Client }
8
9
 
9
10
  export const useApiClient = () => {
10
11
  const { session, onTokenExpired } = useContext(ChatContext)
11
12
  const api = useMemo(
12
13
  () =>
13
- apps.reduce(
14
- (acc, app) => {
15
- acc[app] = new Client({
16
- app,
17
- session,
18
- version: '2018-11-01',
19
- onTokenExpired,
20
- })
21
- return acc
22
- },
23
- {} as Record<App, Client>
24
- ),
14
+ apps.reduce((acc, app) => {
15
+ acc[app] = new Client({
16
+ app,
17
+ session,
18
+ version: '2018-11-01',
19
+ onTokenExpired,
20
+ })
21
+ return acc
22
+ }, {} as ApiClient),
25
23
  [session, onTokenExpired]
26
24
  )
27
25
 
@@ -0,0 +1,20 @@
1
+ import { AppGrantsResource } from '../types'
2
+ import { useApiGet } from './use_api'
3
+
4
+ export function useAppGrants() {
5
+ return useApiGet<AppGrantsResource[]>({
6
+ url: '/me/app_grants',
7
+ data: {
8
+ fields: {
9
+ AppGrant: ['create_conversations', 'app_name'],
10
+ },
11
+ },
12
+ app: 'chat',
13
+ })
14
+ }
15
+
16
+ export function useCanCreateConversations(): boolean {
17
+ const { data: appGrants = [] } = useAppGrants()
18
+
19
+ return appGrants.some(appGrant => appGrant.createConversations)
20
+ }
@@ -1,10 +1,9 @@
1
- import { useMutation } from '@tanstack/react-query'
1
+ import { useMutation, useQueryClient } from '@tanstack/react-query'
2
+ import { useState } from 'react'
2
3
  import { ApiResource, ConversationResource } from '../types'
3
- import { getRequestQueryKey, useSuspenseGet } from './use_suspense_api'
4
- import { queryClient } from '../contexts'
5
- import { useApiClient } from './use_api_client'
6
4
  import { transformGetToPost } from '../utils/client/request_helpers'
7
- import { useState } from 'react'
5
+ import { useApiClient } from './use_api_client'
6
+ import { getRequestQueryKey, useSuspenseGet } from './use_suspense_api'
8
7
 
9
8
  export const getConversationRequestArgs = ({ conversation_id }: { conversation_id: string }) => ({
10
9
  url: `/me/conversations/${conversation_id}`,
@@ -39,6 +38,7 @@ export const useConversation = ({ conversation_id }) => {
39
38
 
40
39
  export const useConversationMute = ({ conversation_id }: { conversation_id: string }) => {
41
40
  const apiClient = useApiClient()
41
+ const queryClient = useQueryClient()
42
42
  const requestArgs = getConversationRequestArgs({ conversation_id })
43
43
  const queryKey = getRequestQueryKey(requestArgs)
44
44
  const { data: conversation } = useConversation({ conversation_id })
@@ -82,6 +82,7 @@ export const useConversationMute = ({ conversation_id }: { conversation_id: stri
82
82
 
83
83
  export const useConversationUpdate = ({ conversation_id }: { conversation_id: string }) => {
84
84
  const apiClient = useApiClient()
85
+ const queryClient = useQueryClient()
85
86
  const requestArgs = getConversationRequestArgs({ conversation_id })
86
87
  const queryKey = getRequestQueryKey(requestArgs)
87
88
 
@@ -8,7 +8,13 @@ import { ApiCollection, ApiResource, ResourceObject } from '../types'
8
8
  import { GetRequest, RequestData } from '../utils/client/types'
9
9
  import { useApiClient } from './use_api_client'
10
10
 
11
- export const useSuspenseGet = <T extends ResourceObject | ResourceObject[]>(args: GetRequest) => {
11
+ interface SuspenseGetOptions extends GetRequest {
12
+ app?: 'chat' | 'groups'
13
+ }
14
+
15
+ export const useSuspenseGet = <T extends ResourceObject | ResourceObject[]>(
16
+ args: SuspenseGetOptions
17
+ ) => {
12
18
  type Resource = ApiResource<T>
13
19
 
14
20
  const { data, ...query } = useSuspenseQuery<Resource, Response>({
@@ -29,7 +35,7 @@ export type SuspensePaginatorOptions = Omit<
29
35
  >
30
36
 
31
37
  export const useSuspensePaginator = <T extends ResourceObject>(
32
- args: GetRequest,
38
+ args: SuspenseGetOptions,
33
39
  opts?: SuspensePaginatorOptions
34
40
  ) => {
35
41
  const apiClient = useApiClient()
@@ -72,9 +78,15 @@ export const useSuspensePaginator = <T extends ResourceObject>(
72
78
  return { ...query, data }
73
79
  }
74
80
 
75
- export type RequestQueryKey = [GetRequest['url'], GetRequest['data'], GetRequest['headers']]
76
- export const getRequestQueryKey = (args: GetRequest): RequestQueryKey => [
81
+ export type RequestQueryKey = [
82
+ SuspenseGetOptions['url'],
83
+ SuspenseGetOptions['data'],
84
+ SuspenseGetOptions['headers'],
85
+ SuspenseGetOptions['app'],
86
+ ]
87
+ export const getRequestQueryKey = (args: SuspenseGetOptions): RequestQueryKey => [
77
88
  args.url,
78
89
  args.data,
79
90
  args.headers,
91
+ args.app || 'chat',
80
92
  ]
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
+ }