@planningcenter/chat-react-native 3.11.0-rc.8 → 3.11.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 (202) hide show
  1. package/build/components/conversation/attachments/image_attachment.d.ts.map +1 -1
  2. package/build/components/conversation/attachments/image_attachment.js +9 -2
  3. package/build/components/conversation/attachments/image_attachment.js.map +1 -1
  4. package/build/components/conversation/message.d.ts +1 -1
  5. package/build/components/conversation/message.d.ts.map +1 -1
  6. package/build/components/conversation/message.js +85 -26
  7. package/build/components/conversation/message.js.map +1 -1
  8. package/build/components/conversation/message_form/message_form_attachment_image.d.ts +1 -1
  9. package/build/components/conversation/message_form/message_form_attachment_image.d.ts.map +1 -1
  10. package/build/components/conversation/message_form/message_form_attachment_image.js.map +1 -1
  11. package/build/components/conversation/message_form/message_form_attachment_video.d.ts +1 -1
  12. package/build/components/conversation/message_form/message_form_attachment_video.d.ts.map +1 -1
  13. package/build/components/conversation/message_form/message_form_attachment_video.js.map +1 -1
  14. package/build/components/conversation/message_form.d.ts.map +1 -1
  15. package/build/components/conversation/message_form.js +12 -10
  16. package/build/components/conversation/message_form.js.map +1 -1
  17. package/build/components/conversations/conversations.d.ts.map +1 -1
  18. package/build/components/conversations/conversations.js +5 -1
  19. package/build/components/conversations/conversations.js.map +1 -1
  20. package/build/components/display/spinner.d.ts +6 -1
  21. package/build/components/display/spinner.d.ts.map +1 -1
  22. package/build/components/display/spinner.js +2 -2
  23. package/build/components/display/spinner.js.map +1 -1
  24. package/build/components/display/text.js +10 -2
  25. package/build/components/display/text.js.map +1 -1
  26. package/build/components/primitive/form_sheet.d.ts +1 -0
  27. package/build/components/primitive/form_sheet.d.ts.map +1 -1
  28. package/build/components/primitive/form_sheet.js +2 -2
  29. package/build/components/primitive/form_sheet.js.map +1 -1
  30. package/build/contexts/api_provider.d.ts +1 -0
  31. package/build/contexts/api_provider.d.ts.map +1 -1
  32. package/build/contexts/api_provider.js +24 -2
  33. package/build/contexts/api_provider.js.map +1 -1
  34. package/build/hooks/groups/use_group_members_for_new_conversation.d.ts +1 -1
  35. package/build/hooks/groups/use_groups_conversation_create.d.ts.map +1 -1
  36. package/build/hooks/groups/use_groups_conversation_create.js +3 -1
  37. package/build/hooks/groups/use_groups_conversation_create.js.map +1 -1
  38. package/build/hooks/services/use_find_or_create_services_conversation.d.ts +2 -0
  39. package/build/hooks/services/use_find_or_create_services_conversation.d.ts.map +1 -1
  40. package/build/hooks/services/use_find_or_create_services_conversation.js +22 -19
  41. package/build/hooks/services/use_find_or_create_services_conversation.js.map +1 -1
  42. package/build/hooks/{use_services_team.d.ts → services/use_services_team.d.ts} +1 -1
  43. package/build/hooks/services/use_services_team.d.ts.map +1 -0
  44. package/build/hooks/{use_services_team.js → services/use_services_team.js} +1 -1
  45. package/build/hooks/services/use_services_team.js.map +1 -0
  46. package/build/hooks/use_attachment_uploader.d.ts +5 -13
  47. package/build/hooks/use_attachment_uploader.d.ts.map +1 -1
  48. package/build/hooks/use_attachment_uploader.js.map +1 -1
  49. package/build/hooks/use_chat_permissions.d.ts +10 -0
  50. package/build/hooks/use_chat_permissions.d.ts.map +1 -1
  51. package/build/hooks/use_chat_permissions.js +10 -9
  52. package/build/hooks/use_chat_permissions.js.map +1 -1
  53. package/build/hooks/use_conversation.d.ts +1 -1
  54. package/build/hooks/use_conversation_messages_jolt_events.d.ts.map +1 -1
  55. package/build/hooks/use_conversation_messages_jolt_events.js +16 -1
  56. package/build/hooks/use_conversation_messages_jolt_events.js.map +1 -1
  57. package/build/hooks/use_giphy.d.ts +1 -1
  58. package/build/hooks/use_giphy.d.ts.map +1 -1
  59. package/build/hooks/use_giphy.js.map +1 -1
  60. package/build/hooks/use_message_create_or_update.d.ts +8 -4
  61. package/build/hooks/use_message_create_or_update.d.ts.map +1 -1
  62. package/build/hooks/use_message_create_or_update.js +58 -4
  63. package/build/hooks/use_message_create_or_update.js.map +1 -1
  64. package/build/hooks/use_read_receipts.d.ts +1 -1
  65. package/build/hooks/use_suspense_api.d.ts +2 -2
  66. package/build/index.d.ts +1 -1
  67. package/build/index.d.ts.map +1 -1
  68. package/build/index.js +1 -1
  69. package/build/index.js.map +1 -1
  70. package/build/navigation/index.d.ts +11 -0
  71. package/build/navigation/index.d.ts.map +1 -1
  72. package/build/navigation/index.js +10 -0
  73. package/build/navigation/index.js.map +1 -1
  74. package/build/screens/conversation_details_screen.js +1 -1
  75. package/build/screens/conversation_details_screen.js.map +1 -1
  76. package/build/screens/conversation_filter_recipients/conversation_filter_recipients_screen.d.ts.map +1 -1
  77. package/build/screens/conversation_filter_recipients/conversation_filter_recipients_screen.js +81 -17
  78. package/build/screens/conversation_filter_recipients/conversation_filter_recipients_screen.js.map +1 -1
  79. package/build/screens/conversation_filter_recipients/hooks/use_service_types_with_teams.d.ts +171 -4
  80. package/build/screens/conversation_filter_recipients/hooks/use_service_types_with_teams.d.ts.map +1 -1
  81. package/build/screens/conversation_filter_recipients/hooks/use_service_types_with_teams.js +49 -8
  82. package/build/screens/conversation_filter_recipients/hooks/use_service_types_with_teams.js.map +1 -1
  83. package/build/screens/conversation_filter_recipients/types.d.ts +7 -0
  84. package/build/screens/conversation_filter_recipients/types.d.ts.map +1 -1
  85. package/build/screens/conversation_filter_recipients/types.js +6 -0
  86. package/build/screens/conversation_filter_recipients/types.js.map +1 -1
  87. package/build/screens/conversation_filters/components/rows.js +1 -1
  88. package/build/screens/conversation_filters/components/rows.js.map +1 -1
  89. package/build/screens/conversation_new/components/groups_form.js +2 -2
  90. package/build/screens/conversation_new/components/groups_form.js.map +1 -1
  91. package/build/screens/conversation_new/components/services_form.d.ts +3 -1
  92. package/build/screens/conversation_new/components/services_form.d.ts.map +1 -1
  93. package/build/screens/conversation_new/components/services_form.js +6 -5
  94. package/build/screens/conversation_new/components/services_form.js.map +1 -1
  95. package/build/screens/conversation_new/conversation_new_screen.d.ts +2 -0
  96. package/build/screens/conversation_new/conversation_new_screen.d.ts.map +1 -1
  97. package/build/screens/conversation_new/conversation_new_screen.js +2 -2
  98. package/build/screens/conversation_new/conversation_new_screen.js.map +1 -1
  99. package/build/screens/conversation_select_recipients/conversation_select_recipients_screen.d.ts.map +1 -1
  100. package/build/screens/conversation_select_recipients/conversation_select_recipients_screen.js +38 -33
  101. package/build/screens/conversation_select_recipients/conversation_select_recipients_screen.js.map +1 -1
  102. package/build/screens/team_conversation_screen.d.ts +8 -0
  103. package/build/screens/team_conversation_screen.d.ts.map +1 -0
  104. package/build/screens/team_conversation_screen.js +28 -0
  105. package/build/screens/team_conversation_screen.js.map +1 -0
  106. package/build/types/resources/denormalized_attachment_resource.d.ts +9 -32
  107. package/build/types/resources/denormalized_attachment_resource.d.ts.map +1 -1
  108. package/build/types/resources/denormalized_attachment_resource.js.map +1 -1
  109. package/build/types/resources/denormalized_attachment_resource_for_create.d.ts +50 -0
  110. package/build/types/resources/denormalized_attachment_resource_for_create.d.ts.map +1 -0
  111. package/build/types/resources/denormalized_attachment_resource_for_create.js +2 -0
  112. package/build/types/resources/denormalized_attachment_resource_for_create.js.map +1 -0
  113. package/build/types/resources/message.d.ts +4 -0
  114. package/build/types/resources/message.d.ts.map +1 -1
  115. package/build/types/resources/message.js.map +1 -1
  116. package/build/types/resources/services/chat_resource.d.ts +52 -0
  117. package/build/types/resources/services/chat_resource.d.ts.map +1 -0
  118. package/build/types/resources/services/chat_resource.js +7 -0
  119. package/build/types/resources/services/chat_resource.js.map +1 -0
  120. package/build/types/resources/services/index.d.ts +1 -0
  121. package/build/types/resources/services/index.d.ts.map +1 -1
  122. package/build/types/resources/services/index.js +1 -0
  123. package/build/types/resources/services/index.js.map +1 -1
  124. package/build/types/resources/services/team_resource.d.ts +9 -41
  125. package/build/types/resources/services/team_resource.d.ts.map +1 -1
  126. package/build/types/resources/services/team_resource.js +0 -5
  127. package/build/types/resources/services/team_resource.js.map +1 -1
  128. package/build/utils/cache/optimistically_create_message.d.ts +10 -0
  129. package/build/utils/cache/optimistically_create_message.d.ts.map +1 -0
  130. package/build/utils/cache/optimistically_create_message.js +43 -0
  131. package/build/utils/cache/optimistically_create_message.js.map +1 -0
  132. package/build/utils/cache/optimistically_update_message.d.ts +7 -0
  133. package/build/utils/cache/optimistically_update_message.d.ts.map +1 -0
  134. package/build/utils/cache/optimistically_update_message.js +21 -0
  135. package/build/utils/cache/optimistically_update_message.js.map +1 -0
  136. package/build/utils/cache/page_mutations.d.ts +6 -3
  137. package/build/utils/cache/page_mutations.d.ts.map +1 -1
  138. package/build/utils/cache/page_mutations.js +4 -4
  139. package/build/utils/cache/page_mutations.js.map +1 -1
  140. package/build/utils/convert_attachments_for_create.d.ts +12 -0
  141. package/build/utils/convert_attachments_for_create.d.ts.map +1 -0
  142. package/build/utils/convert_attachments_for_create.js +70 -0
  143. package/build/utils/convert_attachments_for_create.js.map +1 -0
  144. package/build/utils/generate_placeholder_ulid.d.ts +10 -0
  145. package/build/utils/generate_placeholder_ulid.d.ts.map +1 -0
  146. package/build/utils/generate_placeholder_ulid.js +28 -0
  147. package/build/utils/generate_placeholder_ulid.js.map +1 -0
  148. package/build/utils/index.d.ts +1 -0
  149. package/build/utils/index.d.ts.map +1 -1
  150. package/build/utils/index.js +1 -0
  151. package/build/utils/index.js.map +1 -1
  152. package/build/utils/response_error.d.ts +1 -0
  153. package/build/utils/response_error.d.ts.map +1 -1
  154. package/build/utils/response_error.js +6 -0
  155. package/build/utils/response_error.js.map +1 -1
  156. package/package.json +2 -2
  157. package/src/components/conversation/attachments/image_attachment.tsx +25 -10
  158. package/src/components/conversation/message.tsx +116 -28
  159. package/src/components/conversation/message_form/message_form_attachment_image.tsx +1 -1
  160. package/src/components/conversation/message_form/message_form_attachment_video.tsx +1 -1
  161. package/src/components/conversation/message_form.tsx +16 -13
  162. package/src/components/conversations/conversations.tsx +8 -1
  163. package/src/components/display/spinner.tsx +7 -2
  164. package/src/components/display/text.tsx +10 -2
  165. package/src/components/primitive/form_sheet.tsx +3 -2
  166. package/src/contexts/api_provider.tsx +37 -3
  167. package/src/hooks/groups/use_groups_conversation_create.ts +3 -1
  168. package/src/hooks/services/use_find_or_create_services_conversation.ts +29 -21
  169. package/src/hooks/{use_services_team.ts → services/use_services_team.ts} +2 -2
  170. package/src/hooks/use_attachment_uploader.ts +9 -25
  171. package/src/hooks/use_chat_permissions.ts +12 -9
  172. package/src/hooks/use_conversation_messages_jolt_events.ts +19 -1
  173. package/src/hooks/use_giphy.ts +1 -1
  174. package/src/hooks/use_message_create_or_update.ts +82 -6
  175. package/src/index.tsx +1 -1
  176. package/src/navigation/index.tsx +10 -0
  177. package/src/screens/conversation_details_screen.tsx +1 -1
  178. package/src/screens/conversation_filter_recipients/conversation_filter_recipients_screen.tsx +118 -17
  179. package/src/screens/conversation_filter_recipients/hooks/use_service_types_with_teams.ts +61 -10
  180. package/src/screens/conversation_filter_recipients/types.tsx +8 -0
  181. package/src/screens/conversation_filters/components/rows.tsx +1 -1
  182. package/src/screens/conversation_new/components/groups_form.tsx +2 -2
  183. package/src/screens/conversation_new/components/services_form.tsx +17 -3
  184. package/src/screens/conversation_new/conversation_new_screen.tsx +11 -2
  185. package/src/screens/conversation_select_recipients/conversation_select_recipients_screen.tsx +90 -74
  186. package/src/screens/team_conversation_screen.tsx +46 -0
  187. package/src/types/resources/denormalized_attachment_resource.ts +9 -37
  188. package/src/types/resources/denormalized_attachment_resource_for_create.ts +65 -0
  189. package/src/types/resources/message.ts +6 -0
  190. package/src/types/resources/services/chat_resource.ts +66 -0
  191. package/src/types/resources/services/index.ts +1 -0
  192. package/src/types/resources/services/team_resource.ts +10 -53
  193. package/src/utils/__tests__/convert_attachments_for_create.test.ts +175 -0
  194. package/src/utils/cache/optimistically_create_message.ts +71 -0
  195. package/src/utils/cache/optimistically_update_message.ts +37 -0
  196. package/src/utils/cache/page_mutations.ts +5 -3
  197. package/src/utils/convert_attachments_for_create.ts +92 -0
  198. package/src/utils/generate_placeholder_ulid.ts +32 -0
  199. package/src/utils/index.ts +1 -0
  200. package/src/utils/response_error.ts +8 -0
  201. package/build/hooks/use_services_team.d.ts.map +0 -1
  202. package/build/hooks/use_services_team.js.map +0 -1
@@ -1,24 +1,75 @@
1
- import { useMemo } from 'react'
2
- import { TeamResponseItem } from '../../../types'
3
- import { ServiceTypeWithTeams } from '../types'
4
1
  import { uniqBy } from 'lodash'
5
- import { useTeamsILead } from '../../../hooks/services/use_teams_i_lead'
2
+ import { useMemo } from 'react'
3
+ import { useApiGet } from '../../../hooks/use_api'
4
+ import { ServicesChatResource, TeamResponseItem } from '../../../types'
5
+ import { ServiceTypeWithTeams, TeamFilterTypes } from '../types'
6
6
 
7
- export function useServiceTypesWithTeams() {
8
- const { teamsILead, ...data } = useTeamsILead()
7
+ export function useServiceTypesWithTeams({
8
+ filterType = TeamFilterTypes.TeamsIlead,
9
+ searchQuery,
10
+ }: {
11
+ filterType?: TeamFilterTypes
12
+ searchQuery?: string
13
+ } = {}) {
14
+ const { data, ...rest } = useTeams({ filterType })
9
15
 
10
16
  const decoratedResponse = useMemo(() => {
11
- return decorateTeamResponseItems(teamsILead)
12
- }, [teamsILead])
17
+ return decorateTeamResponseItems(data as TeamResponseItem[], searchQuery)
18
+ }, [data, searchQuery])
13
19
 
14
20
  return {
15
21
  serviceTypes: decoratedResponse,
16
- ...data,
22
+ ...rest,
17
23
  }
18
24
  }
19
25
 
20
- function decorateTeamResponseItems(teamResponseItems: TeamResponseItem[]) {
26
+ const useTeams = ({ filterType }: { filterType: TeamFilterTypes }) => {
27
+ const requestField = useMemo(() => {
28
+ switch (filterType) {
29
+ case TeamFilterTypes.All:
30
+ return 'teams'
31
+ case TeamFilterTypes.MyTeams:
32
+ return 'my_teams'
33
+ default:
34
+ return 'teams_i_lead'
35
+ }
36
+ }, [filterType])
37
+
38
+ const { data, ...rest } = useApiGet<ServicesChatResource>({
39
+ url: '/chat',
40
+ data: {
41
+ fields: {
42
+ Chat: [requestField],
43
+ },
44
+ },
45
+ app: 'services',
46
+ })
47
+
48
+ const result: TeamResponseItem[] = useMemo(() => {
49
+ switch (filterType) {
50
+ case TeamFilterTypes.All:
51
+ return data?.teams || []
52
+ case TeamFilterTypes.MyTeams:
53
+ return data?.myTeams || []
54
+ default:
55
+ return data?.teamsILead || []
56
+ }
57
+ }, [data, filterType])
58
+
59
+ return { data: result || [], ...rest }
60
+ }
61
+
62
+ function decorateTeamResponseItems(teamResponseItems: TeamResponseItem[], searchQuery?: string) {
21
63
  return teamResponseItems
64
+ .filter(item => {
65
+ if (!searchQuery) return true
66
+
67
+ const evalMatch = (str: string) => str.toLowerCase().includes(searchQuery.toLowerCase())
68
+ const teamNameMatch = evalMatch(item.name)
69
+ const serviceTypeNamesMatch = evalMatch(item.serviceTypeNames?.join(',') || '')
70
+
71
+ return teamNameMatch || serviceTypeNamesMatch
72
+ })
22
73
  .map(({ value, serviceTypeName, teamName }) => ({
23
74
  service_type: {
24
75
  id: value.serviceTypeId,
@@ -38,9 +38,17 @@ interface DataItem<T, TName extends SectionTypes> {
38
38
  sectionStyle?: ViewStyle
39
39
  }
40
40
 
41
+ export enum TeamFilterTypes {
42
+ All = 'All teams',
43
+ MyTeams = 'My teams',
44
+ TeamsIlead = 'Teams I lead',
45
+ }
46
+
41
47
  export type ConversationFilterRecipientsParams = {
42
48
  source_app_name?: AppName
43
49
  team_ids?: number[]
50
+ team_filter_type?: TeamFilterTypes
51
+ search_query?: string
44
52
  }
45
53
 
46
54
  export type ConversationFilterRecipientsScreenProps =
@@ -4,7 +4,7 @@ import React, { PropsWithChildren, useContext } from 'react'
4
4
  import { StyleSheet, View, ViewStyle } from 'react-native'
5
5
  import { Heading, Icon, Image, Text, TextButton } from '../../../components'
6
6
  import { useTheme } from '../../../hooks'
7
- import { useServicesTeamsMap } from '../../../hooks/use_services_team'
7
+ import { useServicesTeamsMap } from '../../../hooks/services/use_services_team'
8
8
  import { GroupResource } from '../../../types/resources/group_resource'
9
9
  import { FilterContext } from '../context/conversation_filter_context'
10
10
  import { FilterTypes } from '../filter_types'
@@ -48,7 +48,7 @@ export const GroupsForm = ({ groupId, chat_group_graph_id }: GroupsFormProps) =>
48
48
  [chat_group_graph_id, navigation]
49
49
  )
50
50
 
51
- const { mutate: handleSave } = useGroupsConversationCreate({
51
+ const { mutate: handleSave, isPending } = useGroupsConversationCreate({
52
52
  groupId,
53
53
  title,
54
54
  onSuccess: redirectToConversation,
@@ -70,7 +70,7 @@ export const GroupsForm = ({ groupId, chat_group_graph_id }: GroupsFormProps) =>
70
70
  }
71
71
  />
72
72
  <ActionButton
73
- disabled={!title}
73
+ disabled={!title || isPending}
74
74
  title="Start Conversation"
75
75
  onPress={() => handleSave()}
76
76
  infoText="Conversation will be automatically updated if any members are added or removed from this group."
@@ -12,13 +12,19 @@ import { useTeamMembersForNewConversation } from '../../../hooks/services/use_te
12
12
  import { useFindOrCreateServicesConversation } from '../../../hooks/services/use_find_or_create_services_conversation'
13
13
  import { ConversationResource, MemberResource } from '../../../types'
14
14
  import { tokens } from '../../../vendor/tapestry/tokens'
15
+ import { TeamFilterTypes } from '../../conversation_filter_recipients/types'
15
16
 
16
17
  type ServicesFormProps = {
17
18
  initialTeamIds?: number[]
18
19
  initialPlanId?: number
20
+ teamFilterType?: TeamFilterTypes
19
21
  }
20
22
 
21
- export const ServicesForm = ({ initialTeamIds, initialPlanId }: ServicesFormProps) => {
23
+ export const ServicesForm = ({
24
+ initialTeamIds,
25
+ initialPlanId,
26
+ teamFilterType,
27
+ }: ServicesFormProps) => {
22
28
  const styles = useStyles()
23
29
  const [selectedPlanId, setSelectedPlanId] = useState<number | undefined>(initialPlanId)
24
30
  const initialState = useMemo(() => uniq(initialTeamIds) || [], [initialTeamIds]) // Uniq here because services can send duplicates in the teams_i_lead response.
@@ -39,7 +45,11 @@ export const ServicesForm = ({ initialTeamIds, initialPlanId }: ServicesFormProp
39
45
 
40
46
  const [filerByPlan, setFilterByPlan] = useState(false)
41
47
 
42
- const { members, isError: isMemberError } = useTeamMembersForNewConversation({
48
+ const {
49
+ members,
50
+ isPending,
51
+ isError: isMemberError,
52
+ } = useTeamMembersForNewConversation({
43
53
  teamIds: selectedTeamIds,
44
54
  planId: selectedPlanId,
45
55
  })
@@ -75,11 +85,12 @@ export const ServicesForm = ({ initialTeamIds, initialPlanId }: ServicesFormProp
75
85
  setFilterByPlan={setFilterByPlan}
76
86
  members={members}
77
87
  isMemberError={isMemberError}
88
+ teamFilterType={teamFilterType}
78
89
  />
79
90
  }
80
91
  />
81
92
  <ActionButton
82
- disabled={!selectedTeamIds.length}
93
+ disabled={!selectedTeamIds.length || isPending}
83
94
  title="Start Conversation"
84
95
  onPress={createConversation}
85
96
  infoText="Conversation will be automatically updated if any members are added or removed from included teams."
@@ -97,6 +108,7 @@ interface FormContentProps {
97
108
  setFilterByPlan: (value: boolean) => void
98
109
  members: MemberResource[]
99
110
  isMemberError: boolean
111
+ teamFilterType?: TeamFilterTypes
100
112
  }
101
113
 
102
114
  function FormContent({
@@ -108,6 +120,7 @@ function FormContent({
108
120
  setFilterByPlan,
109
121
  members,
110
122
  isMemberError,
123
+ teamFilterType,
111
124
  }: FormContentProps) {
112
125
  const { teamsILead } = useTeamsILead()
113
126
  const selectedTeamsILead = useMemo(() => {
@@ -144,6 +157,7 @@ function FormContent({
144
157
  params: {
145
158
  source_app_name: 'Services',
146
159
  team_ids: selectedTeamIds,
160
+ team_filter_type: teamFilterType,
147
161
  },
148
162
  })
149
163
  }
@@ -5,10 +5,12 @@ import { ServicesForm } from './components/services_form'
5
5
  import { SourceAppErrorCard } from './components/source_app_error_card'
6
6
  import { AppName } from '../../types/resources/app_name'
7
7
  import { GraphId } from '../../types/resources/group_resource'
8
+ import { TeamFilterTypes } from '../conversation_filter_recipients/types'
8
9
 
9
10
  type ConversationNewScreenProps = StaticScreenProps<{
10
11
  group_id?: number
11
12
  team_ids?: number[]
13
+ team_filter_type?: TeamFilterTypes
12
14
  plan_id?: number
13
15
  source_app_name: AppName
14
16
  chat_group_graph_id?: GraphId
@@ -16,7 +18,8 @@ type ConversationNewScreenProps = StaticScreenProps<{
16
18
  }>
17
19
 
18
20
  export const ConversationNewScreen = ({ route }: ConversationNewScreenProps) => {
19
- const { group_id, team_ids, source_app_name, plan_id, chat_group_graph_id } = route.params
21
+ const { group_id, team_ids, source_app_name, plan_id, chat_group_graph_id, team_filter_type } =
22
+ route.params
20
23
 
21
24
  switch (source_app_name) {
22
25
  case 'Groups':
@@ -26,7 +29,13 @@ export const ConversationNewScreen = ({ route }: ConversationNewScreenProps) =>
26
29
  <SourceAppErrorCard />
27
30
  )
28
31
  case 'Services':
29
- return <ServicesForm initialTeamIds={team_ids} initialPlanId={plan_id} />
32
+ return (
33
+ <ServicesForm
34
+ initialTeamIds={team_ids}
35
+ initialPlanId={plan_id}
36
+ teamFilterType={team_filter_type}
37
+ />
38
+ )
30
39
  default:
31
40
  return <SourceAppErrorCard />
32
41
  }
@@ -12,6 +12,8 @@ import { useServiceTypesWithTeams } from '../conversation_filter_recipients/hook
12
12
  import { ServiceTypeWithTeams } from '../conversation_filter_recipients/types'
13
13
  import { TeamRecipientRow } from './components/team_recipient_row'
14
14
  import { GroupsRecipientRow } from './components/groups_recipient_row'
15
+ import { DefaultLoading } from '../../components/page/loading'
16
+ import { useAppGrants } from '../../hooks'
15
17
 
16
18
  const MAX_VISIBLE_RECIPIENTS = 5
17
19
 
@@ -20,7 +22,13 @@ export const ConversationSelectRecipientsScreen = ({
20
22
  }: ConversationSelectRecipientsScreenProps) => {
21
23
  const styles = useStyles()
22
24
  const navigation = useNavigation()
23
-
25
+ const appGrants = useAppGrants()
26
+ const canCreateGroupsConversations = appGrants.data?.some(
27
+ g => g.createConversations && g.appName === 'Groups'
28
+ )
29
+ const canCreateServicesConversations = appGrants.data?.some(
30
+ g => g.createConversations && g.appName === 'Services'
31
+ )
24
32
  const { data: groups = [], isFetching: isGroupsFetching } = useGroupsGroups()
25
33
  const groupsWithCreatePermission = groups.filter(g => g.canCreateConversation)
26
34
  const visibleGroups = groupsWithCreatePermission.slice(0, MAX_VISIBLE_RECIPIENTS)
@@ -77,83 +85,91 @@ export const ConversationSelectRecipientsScreen = ({
77
85
 
78
86
  return (
79
87
  <ScrollView contentContainerStyle={styles.contentContainer}>
80
- <View style={styles.section}>
81
- <View style={styles.sectionHeader}>
82
- <Heading variant="h3">My groups</Heading>
83
- </View>
84
- <View>
85
- {isGroupsFetching ? null : groupsWithCreatePermission.length === 0 ? (
86
- <BlankState
87
- subtitle="Join a group with chat enabled and permissions to start a new conversation."
88
- style={styles.blankState}
89
- />
90
- ) : (
91
- <>
92
- {visibleGroups.map(group => {
93
- return (
94
- <GroupsRecipientRow
95
- key={group.id}
96
- group={group}
97
- onPress={handleGroupsNavigateToConversationNew}
88
+ {canCreateGroupsConversations && (
89
+ <View style={styles.section}>
90
+ <View style={styles.sectionHeader}>
91
+ <Heading variant="h3">My groups</Heading>
92
+ </View>
93
+ <View>
94
+ {isGroupsFetching ? (
95
+ <DefaultLoading />
96
+ ) : groupsWithCreatePermission.length === 0 ? (
97
+ <BlankState
98
+ subtitle="Join a group with chat enabled and permissions to start a new conversation."
99
+ style={styles.blankState}
100
+ />
101
+ ) : (
102
+ <>
103
+ {visibleGroups.map(group => {
104
+ return (
105
+ <GroupsRecipientRow
106
+ key={group.id}
107
+ group={group}
108
+ onPress={handleGroupsNavigateToConversationNew}
109
+ />
110
+ )
111
+ })}
112
+ {hasMoreGroups && (
113
+ <ViewMoreLinkRow
114
+ onPress={handleGroupsViewMore}
115
+ accessibilityHint="Navigate to a full list of your groups."
98
116
  />
99
- )
100
- })}
101
- {hasMoreGroups && (
102
- <ViewMoreLinkRow
103
- onPress={handleGroupsViewMore}
104
- accessibilityHint="Navigate to a full list of your groups."
105
- />
106
- )}
107
- {showGroupsDisclaimer && <GroupsWithoutChatDisclaimerRow />}
108
- </>
109
- )}
110
- </View>
111
- </View>
112
- <View style={styles.section}>
113
- <View style={styles.sectionHeader}>
114
- <Heading variant="h3">Teams I lead</Heading>
115
- {hasServiceTypes && (
116
- <Button
117
- style={styles.selectTeamsButton}
118
- onPress={() =>
119
- navigation.navigate('New', {
120
- screen: 'ConversationFilterRecipients',
121
- params: {
122
- source_app_name: 'Services',
123
- },
124
- })
125
- }
126
- title="Select teams"
127
- variant="outline"
128
- iconNameLeft="general.search"
129
- size="sm"
130
- />
131
- )}
117
+ )}
118
+ {showGroupsDisclaimer && <GroupsWithoutChatDisclaimerRow />}
119
+ </>
120
+ )}
121
+ </View>
132
122
  </View>
133
- <View>
134
- {isServiceTypesFetching ? null : hasServiceTypes ? (
135
- <>
136
- {visibleServiceTypes.map(serviceType => {
137
- return (
138
- <TeamRecipientRow
139
- key={serviceType.id}
140
- serviceType={serviceType}
141
- onPress={handleTeamsNavigateToConversationNew}
123
+ )}
124
+ {canCreateServicesConversations && (
125
+ <View style={styles.section}>
126
+ <View style={styles.sectionHeader}>
127
+ <Heading variant="h3">Teams I lead</Heading>
128
+ {hasServiceTypes && (
129
+ <Button
130
+ style={styles.selectTeamsButton}
131
+ onPress={() =>
132
+ navigation.navigate('New', {
133
+ screen: 'ConversationFilterRecipients',
134
+ params: {
135
+ source_app_name: 'Services',
136
+ },
137
+ })
138
+ }
139
+ title="Select teams"
140
+ variant="outline"
141
+ iconNameLeft="general.search"
142
+ size="sm"
143
+ />
144
+ )}
145
+ </View>
146
+ <View>
147
+ {isServiceTypesFetching ? (
148
+ <DefaultLoading />
149
+ ) : hasServiceTypes ? (
150
+ <>
151
+ {visibleServiceTypes.map(serviceType => {
152
+ return (
153
+ <TeamRecipientRow
154
+ key={serviceType.id}
155
+ serviceType={serviceType}
156
+ onPress={handleTeamsNavigateToConversationNew}
157
+ />
158
+ )
159
+ })}
160
+ {hasMoreServiceTypes && (
161
+ <ViewMoreLinkRow
162
+ onPress={handleServiceTypesViewMore}
163
+ accessibilityHint="Navigate to a full list of teams you lead."
142
164
  />
143
- )
144
- })}
145
- {hasMoreServiceTypes && (
146
- <ViewMoreLinkRow
147
- onPress={handleServiceTypesViewMore}
148
- accessibilityHint="Navigate to a full list of teams you lead."
149
- />
150
- )}
151
- </>
152
- ) : (
153
- <BlankState subtitle="You don't lead any teams." style={styles.blankState} />
154
- )}
165
+ )}
166
+ </>
167
+ ) : (
168
+ <BlankState subtitle="You don't lead any teams." style={styles.blankState} />
169
+ )}
170
+ </View>
155
171
  </View>
156
- </View>
172
+ )}
157
173
  </ScrollView>
158
174
  )
159
175
  }
@@ -0,0 +1,46 @@
1
+ import { StackActions, StaticScreenProps, useNavigation } from '@react-navigation/native'
2
+ import { useQuery, useQueryClient } from '@tanstack/react-query'
3
+ import { useEffect } from 'react'
4
+ import { useApiClient } from '../hooks'
5
+ import { findOrCreateServicesConversation } from '../hooks/services/use_find_or_create_services_conversation'
6
+ import { DefaultLoading } from '../components/page/loading'
7
+
8
+ export type TeamConversationRouteProps = {
9
+ plan_id?: number
10
+ team_ids?: number[]
11
+ }
12
+
13
+ export type TeamConversationScreenProps = StaticScreenProps<TeamConversationRouteProps>
14
+
15
+ export const TeamConversationScreen = ({ route }: TeamConversationScreenProps) => {
16
+ const apiClient = useApiClient()
17
+ const queryClient = useQueryClient()
18
+ const navigation = useNavigation()
19
+
20
+ const { data: conversation } = useQuery({
21
+ queryKey: ['team-conversation', route.params.team_ids, route.params.plan_id],
22
+ queryFn: () =>
23
+ findOrCreateServicesConversation(
24
+ apiClient,
25
+ route.params.team_ids || [2317674, 5223552],
26
+ route.params.plan_id
27
+ ),
28
+ })
29
+
30
+ useEffect(() => {
31
+ if (!conversation?.id) return
32
+
33
+ navigation.dispatch(
34
+ StackActions.replace('Conversation', {
35
+ conversation_id: conversation.id,
36
+ title: conversation.title,
37
+ })
38
+ )
39
+
40
+ return () => {
41
+ queryClient.removeQueries({ queryKey: ['team-conversation'] })
42
+ }
43
+ }, [conversation?.id, conversation?.title, navigation, queryClient])
44
+
45
+ return <DefaultLoading />
46
+ }
@@ -8,12 +8,7 @@ export type DenormalizedAttachmentResource =
8
8
  | DenormalizedGiphyAttachmentResource
9
9
  | DenormalizedExpandedLinkAttachmentResource
10
10
 
11
- export type DenormalizedAttachmentResourceForCreate =
12
- | DenormalizedMessageAttachmentResourceForCreate
13
- | DenormalizedGiphyAttachmentResourceForCreate
14
- | DenormalizedExpandedLinkAttachmentResource
15
-
16
- interface GenericAttachmentResource {
11
+ export interface GenericAttachmentResource {
17
12
  type: string
18
13
  id?: string
19
14
  [key: string]: unknown
@@ -39,11 +34,6 @@ export interface DenormalizedMessageAttachmentResource extends GenericAttachment
39
34
  }
40
35
  }
41
36
 
42
- export interface DenormalizedMessageAttachmentResourceForCreate extends GenericAttachmentResource {
43
- type: 'MessageAttachment'
44
- id: string
45
- }
46
-
47
37
  export interface DenormalizedGiphyAttachmentResource extends GenericAttachmentResource {
48
38
  type: 'giphy'
49
39
  id: string
@@ -52,35 +42,17 @@ export interface DenormalizedGiphyAttachmentResource extends GenericAttachmentRe
52
42
  titleLink: string
53
43
  thumbUrl: string
54
44
  giphy: {
55
- original: GiphyVariant
56
- fixedHeight: GiphyVariant
57
- fixedHeightStill: GiphyVariant
58
- fixedHeightDownsampled: GiphyVariant
59
- fixedWidth: GiphyVariant
60
- fixedWidthStill: GiphyVariant
61
- fixedWidthDownsampled: GiphyVariant
62
- }
63
- }
64
-
65
- export interface DenormalizedGiphyAttachmentResourceForCreate extends GenericAttachmentResource {
66
- type: 'giphy'
67
- id: string
68
- title: string
69
- original_giphy_title: string
70
- title_link: string
71
- thumb_url: string
72
- giphy: {
73
- original: GiphyVariant
74
- fixed_height: GiphyVariant
75
- fixed_height_still: GiphyVariant
76
- fixed_height_downsampled: GiphyVariant
77
- fixed_width: GiphyVariant
78
- fixed_width_still: GiphyVariant
79
- fixed_width_downsampled: GiphyVariant
45
+ original: GiphyAttachmentVariant
46
+ fixedHeight: GiphyAttachmentVariant
47
+ fixedHeightStill: GiphyAttachmentVariant
48
+ fixedHeightDownsampled: GiphyAttachmentVariant
49
+ fixedWidth: GiphyAttachmentVariant
50
+ fixedWidthStill: GiphyAttachmentVariant
51
+ fixedWidthDownsampled: GiphyAttachmentVariant
80
52
  }
81
53
  }
82
54
 
83
- interface GiphyVariant {
55
+ export interface GiphyAttachmentVariant {
84
56
  url: string
85
57
  width: string
86
58
  height: string
@@ -0,0 +1,65 @@
1
+ import {
2
+ DenormalizedExpandedLinkAttachmentResource,
3
+ GenericAttachmentResource,
4
+ GiphyAttachmentVariant,
5
+ } from './denormalized_attachment_resource'
6
+
7
+ export type DenormalizedAttachmentResourceForCreate =
8
+ | DenormalizedMessageAttachmentResourceForCreate
9
+ | DenormalizedGiphyAttachmentResourceForCreate
10
+ | DenormalizedExpandedLinkAttachmentResource
11
+
12
+ export interface DenormalizedMessageAttachmentResourceForCreate extends GenericAttachmentResource {
13
+ type: 'MessageAttachment'
14
+ id: string
15
+
16
+ // Not needed for upload, but is used to preview image during pending send state
17
+ file: NativeAttachmentFile
18
+ }
19
+
20
+ export interface DenormalizedGiphyAttachmentResourceForCreate extends GenericAttachmentResource {
21
+ type: 'giphy'
22
+ id: string
23
+ title: string
24
+ original_giphy_title: string
25
+ title_link: string
26
+ thumb_url: string
27
+ giphy: {
28
+ original: GiphyAttachmentVariant
29
+ fixed_height: GiphyAttachmentVariant
30
+ fixed_height_still: GiphyAttachmentVariant
31
+ fixed_height_downsampled: GiphyAttachmentVariant
32
+ fixed_width: GiphyAttachmentVariant
33
+ fixed_width_still: GiphyAttachmentVariant
34
+ fixed_width_downsampled: GiphyAttachmentVariant
35
+ }
36
+ }
37
+
38
+ type AttachmentStatus = 'uploading' | 'success' | 'error'
39
+
40
+ export interface NativeAttachmentFile {
41
+ uri: string
42
+ name: string
43
+ type: string // Should be a MIME type
44
+ size: number
45
+ width?: number
46
+ height?: number
47
+ }
48
+
49
+ export interface FileAttachment {
50
+ id?: string
51
+ file: NativeAttachmentFile
52
+ status: AttachmentStatus
53
+ }
54
+
55
+ export interface FileUploadState {
56
+ [fileName: string]: {
57
+ status: AttachmentStatus
58
+ id?: string
59
+ }
60
+ }
61
+
62
+ export interface FileUploadError {
63
+ file_type?: string[]
64
+ file_size?: boolean
65
+ }
@@ -1,4 +1,5 @@
1
1
  import { DenormalizedAttachmentResource } from './denormalized_attachment_resource'
2
+ import { DenormalizedAttachmentResourceForCreate } from './denormalized_attachment_resource_for_create'
2
3
  import type { PersonResource } from './person'
3
4
  import type { ReactionCountResource } from './reaction'
4
5
 
@@ -20,4 +21,9 @@ export interface MessageResource {
20
21
  renderTime?: boolean
21
22
  myLatestInConversation?: boolean
22
23
  lastInGroup?: boolean
24
+
25
+ // Properties for mutation state
26
+ pending?: boolean // Indicates if the message is optimistically created and pending server response
27
+ error?: string // Error message if the message failed to send
28
+ attachmentsForCreate?: DenormalizedAttachmentResourceForCreate[]
23
29
  }