@planningcenter/chat-react-native 3.32.1-rc.0 → 3.33.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 (184) hide show
  1. package/build/components/conversation/message_form.d.ts.map +1 -1
  2. package/build/components/conversation/message_form.js +22 -1
  3. package/build/components/conversation/message_form.js.map +1 -1
  4. package/build/components/display/emoji_avatar.d.ts.map +1 -1
  5. package/build/components/display/emoji_avatar.js +2 -0
  6. package/build/components/display/emoji_avatar.js.map +1 -1
  7. package/build/components/display/icon_avatar.d.ts.map +1 -1
  8. package/build/components/display/icon_avatar.js +2 -0
  9. package/build/components/display/icon_avatar.js.map +1 -1
  10. package/build/components/display/utils/avatar_gradient_colors.d.ts +3 -0
  11. package/build/components/display/utils/avatar_gradient_colors.d.ts.map +1 -1
  12. package/build/components/display/utils/avatar_gradient_colors.js +8 -3
  13. package/build/components/display/utils/avatar_gradient_colors.js.map +1 -1
  14. package/build/components/page/error_boundary.d.ts.map +1 -1
  15. package/build/components/page/error_boundary.js +13 -10
  16. package/build/components/page/error_boundary.js.map +1 -1
  17. package/build/components/primitive/avatar_primitive.d.ts +3 -1
  18. package/build/components/primitive/avatar_primitive.d.ts.map +1 -1
  19. package/build/components/primitive/avatar_primitive.js +10 -2
  20. package/build/components/primitive/avatar_primitive.js.map +1 -1
  21. package/build/contexts/api_provider.d.ts.map +1 -1
  22. package/build/contexts/api_provider.js +2 -0
  23. package/build/contexts/api_provider.js.map +1 -1
  24. package/build/hooks/attachments/fallback_chat_configuration.d.ts +4 -0
  25. package/build/hooks/attachments/fallback_chat_configuration.d.ts.map +1 -0
  26. package/build/hooks/attachments/fallback_chat_configuration.js +59 -0
  27. package/build/hooks/attachments/fallback_chat_configuration.js.map +1 -0
  28. package/build/hooks/groups/use_groups_conversation_create.d.ts.map +1 -1
  29. package/build/hooks/groups/use_groups_conversation_create.js +1 -1
  30. package/build/hooks/groups/use_groups_conversation_create.js.map +1 -1
  31. package/build/hooks/services/use_find_or_create_services_conversation.d.ts +43 -11
  32. package/build/hooks/services/use_find_or_create_services_conversation.d.ts.map +1 -1
  33. package/build/hooks/services/use_find_or_create_services_conversation.js +5 -5
  34. package/build/hooks/services/use_find_or_create_services_conversation.js.map +1 -1
  35. package/build/hooks/use_attachment_uploader.d.ts.map +1 -1
  36. package/build/hooks/use_attachment_uploader.js +39 -14
  37. package/build/hooks/use_attachment_uploader.js.map +1 -1
  38. package/build/hooks/use_chat_configuration.d.ts +6 -0
  39. package/build/hooks/use_chat_configuration.d.ts.map +1 -0
  40. package/build/hooks/use_chat_configuration.js +41 -0
  41. package/build/hooks/use_chat_configuration.js.map +1 -0
  42. package/build/hooks/use_conversation_avatar_update.d.ts +26 -0
  43. package/build/hooks/use_conversation_avatar_update.d.ts.map +1 -0
  44. package/build/hooks/use_conversation_avatar_update.js +130 -0
  45. package/build/hooks/use_conversation_avatar_update.js.map +1 -0
  46. package/build/hooks/use_features.d.ts +1 -0
  47. package/build/hooks/use_features.d.ts.map +1 -1
  48. package/build/hooks/use_features.js +1 -0
  49. package/build/hooks/use_features.js.map +1 -1
  50. package/build/navigation/index.d.ts +16 -0
  51. package/build/navigation/index.d.ts.map +1 -1
  52. package/build/navigation/index.js +9 -0
  53. package/build/navigation/index.js.map +1 -1
  54. package/build/screens/avatar_picker/avatar_picker_screen.d.ts +12 -0
  55. package/build/screens/avatar_picker/avatar_picker_screen.d.ts.map +1 -0
  56. package/build/screens/avatar_picker/avatar_picker_screen.js +193 -0
  57. package/build/screens/avatar_picker/avatar_picker_screen.js.map +1 -0
  58. package/build/screens/avatar_picker/avatar_picker_state.d.ts +38 -0
  59. package/build/screens/avatar_picker/avatar_picker_state.d.ts.map +1 -0
  60. package/build/screens/avatar_picker/avatar_picker_state.js +101 -0
  61. package/build/screens/avatar_picker/avatar_picker_state.js.map +1 -0
  62. package/build/screens/avatar_picker/avatar_preview.d.ts +9 -0
  63. package/build/screens/avatar_picker/avatar_preview.d.ts.map +1 -0
  64. package/build/screens/avatar_picker/avatar_preview.js +39 -0
  65. package/build/screens/avatar_picker/avatar_preview.js.map +1 -0
  66. package/build/screens/avatar_picker/color_picker.d.ts +9 -0
  67. package/build/screens/avatar_picker/color_picker.d.ts.map +1 -0
  68. package/build/screens/avatar_picker/color_picker.js +53 -0
  69. package/build/screens/avatar_picker/color_picker.js.map +1 -0
  70. package/build/screens/avatar_picker/constants.d.ts +3 -0
  71. package/build/screens/avatar_picker/constants.d.ts.map +1 -0
  72. package/build/screens/avatar_picker/constants.js +53 -0
  73. package/build/screens/avatar_picker/constants.js.map +1 -0
  74. package/build/screens/avatar_picker/emoji_tab.d.ts +7 -0
  75. package/build/screens/avatar_picker/emoji_tab.d.ts.map +1 -0
  76. package/build/screens/avatar_picker/emoji_tab.js +55 -0
  77. package/build/screens/avatar_picker/emoji_tab.js.map +1 -0
  78. package/build/screens/avatar_picker/icon_grid.d.ts +8 -0
  79. package/build/screens/avatar_picker/icon_grid.d.ts.map +1 -0
  80. package/build/screens/avatar_picker/icon_grid.js +48 -0
  81. package/build/screens/avatar_picker/icon_grid.js.map +1 -0
  82. package/build/screens/avatar_picker/upload_tab.d.ts +9 -0
  83. package/build/screens/avatar_picker/upload_tab.d.ts.map +1 -0
  84. package/build/screens/avatar_picker/upload_tab.js +39 -0
  85. package/build/screens/avatar_picker/upload_tab.js.map +1 -0
  86. package/build/screens/conversation_details_screen.d.ts.map +1 -1
  87. package/build/screens/conversation_details_screen.js +37 -1
  88. package/build/screens/conversation_details_screen.js.map +1 -1
  89. package/build/screens/conversation_new/components/avatar_selection_row.d.ts +12 -0
  90. package/build/screens/conversation_new/components/avatar_selection_row.d.ts.map +1 -0
  91. package/build/screens/conversation_new/components/avatar_selection_row.js +60 -0
  92. package/build/screens/conversation_new/components/avatar_selection_row.js.map +1 -0
  93. package/build/screens/conversation_new/components/gender_filter_toggle.d.ts.map +1 -1
  94. package/build/screens/conversation_new/components/gender_filter_toggle.js +3 -9
  95. package/build/screens/conversation_new/components/gender_filter_toggle.js.map +1 -1
  96. package/build/screens/conversation_new/components/groups_form.d.ts +3 -1
  97. package/build/screens/conversation_new/components/groups_form.d.ts.map +1 -1
  98. package/build/screens/conversation_new/components/groups_form.js +22 -8
  99. package/build/screens/conversation_new/components/groups_form.js.map +1 -1
  100. package/build/screens/conversation_new/components/services_form.d.ts +3 -1
  101. package/build/screens/conversation_new/components/services_form.d.ts.map +1 -1
  102. package/build/screens/conversation_new/components/services_form.js +22 -8
  103. package/build/screens/conversation_new/components/services_form.js.map +1 -1
  104. package/build/screens/conversation_new/conversation_new_screen.d.ts +2 -0
  105. package/build/screens/conversation_new/conversation_new_screen.d.ts.map +1 -1
  106. package/build/screens/conversation_new/conversation_new_screen.js +3 -3
  107. package/build/screens/conversation_new/conversation_new_screen.js.map +1 -1
  108. package/build/screens/team_conversation_screen.d.ts.map +1 -1
  109. package/build/screens/team_conversation_screen.js +1 -1
  110. package/build/screens/team_conversation_screen.js.map +1 -1
  111. package/build/types/resources/chat_configuration_resource.d.ts +8 -0
  112. package/build/types/resources/chat_configuration_resource.d.ts.map +1 -0
  113. package/build/types/resources/chat_configuration_resource.js +2 -0
  114. package/build/types/resources/chat_configuration_resource.js.map +1 -0
  115. package/build/utils/auth_events.d.ts +7 -0
  116. package/build/utils/auth_events.d.ts.map +1 -0
  117. package/build/utils/auth_events.js +17 -0
  118. package/build/utils/auth_events.js.map +1 -0
  119. package/build/utils/native_adapters/configuration.d.ts +3 -0
  120. package/build/utils/native_adapters/configuration.d.ts.map +1 -1
  121. package/build/utils/native_adapters/configuration.js +8 -0
  122. package/build/utils/native_adapters/configuration.js.map +1 -1
  123. package/build/utils/native_adapters/document_picker.d.ts +21 -0
  124. package/build/utils/native_adapters/document_picker.d.ts.map +1 -0
  125. package/build/utils/native_adapters/document_picker.js +7 -0
  126. package/build/utils/native_adapters/document_picker.js.map +1 -0
  127. package/build/utils/native_adapters/image_picker.d.ts +7 -1
  128. package/build/utils/native_adapters/image_picker.d.ts.map +1 -1
  129. package/build/utils/native_adapters/image_picker.js.map +1 -1
  130. package/build/utils/native_adapters/index.d.ts +1 -0
  131. package/build/utils/native_adapters/index.d.ts.map +1 -1
  132. package/build/utils/native_adapters/index.js +1 -0
  133. package/build/utils/native_adapters/index.js.map +1 -1
  134. package/build/utils/request/get_chat_configuration.d.ts +10 -0
  135. package/build/utils/request/get_chat_configuration.d.ts.map +1 -0
  136. package/build/utils/request/get_chat_configuration.js +21 -0
  137. package/build/utils/request/get_chat_configuration.js.map +1 -0
  138. package/package.json +4 -3
  139. package/src/__tests__/hooks/use_attachment_uploader.test.tsx +219 -0
  140. package/src/__tests__/hooks/use_chat_configuration.test.tsx +80 -0
  141. package/src/__tests__/utils/native_adapters/configuration.ts +25 -1
  142. package/src/components/conversation/message_form.tsx +39 -1
  143. package/src/components/display/emoji_avatar.tsx +7 -2
  144. package/src/components/display/icon_avatar.tsx +7 -2
  145. package/src/components/display/utils/avatar_gradient_colors.ts +10 -3
  146. package/src/components/page/error_boundary.tsx +16 -9
  147. package/src/components/primitive/avatar_primitive.tsx +11 -2
  148. package/src/contexts/api_provider.tsx +3 -0
  149. package/src/hooks/attachments/fallback_chat_configuration.ts +61 -0
  150. package/src/hooks/groups/use_groups_conversation_create.ts +2 -1
  151. package/src/hooks/services/use_find_or_create_services_conversation.ts +7 -7
  152. package/src/hooks/use_attachment_uploader.ts +39 -15
  153. package/src/hooks/use_chat_configuration.ts +54 -0
  154. package/src/hooks/use_conversation_avatar_update.ts +163 -0
  155. package/src/hooks/use_features.ts +1 -0
  156. package/src/navigation/index.tsx +13 -0
  157. package/src/screens/avatar_picker/__tests__/avatar_picker_state.test.ts +157 -0
  158. package/src/screens/avatar_picker/avatar_picker_screen.tsx +312 -0
  159. package/src/screens/avatar_picker/avatar_picker_state.ts +141 -0
  160. package/src/screens/avatar_picker/avatar_preview.tsx +46 -0
  161. package/src/screens/avatar_picker/color_picker.tsx +91 -0
  162. package/src/screens/avatar_picker/constants.ts +53 -0
  163. package/src/screens/avatar_picker/emoji_tab.tsx +76 -0
  164. package/src/screens/avatar_picker/icon_grid.tsx +81 -0
  165. package/src/screens/avatar_picker/upload_tab.tsx +62 -0
  166. package/src/screens/conversation_details_screen.tsx +60 -1
  167. package/src/screens/conversation_new/components/avatar_selection_row.tsx +82 -0
  168. package/src/screens/conversation_new/components/gender_filter_toggle.tsx +3 -9
  169. package/src/screens/conversation_new/components/groups_form.tsx +33 -6
  170. package/src/screens/conversation_new/components/services_form.tsx +37 -6
  171. package/src/screens/conversation_new/conversation_new_screen.tsx +17 -3
  172. package/src/screens/team_conversation_screen.tsx +2 -1
  173. package/src/types/resources/chat_configuration_resource.ts +11 -0
  174. package/src/utils/auth_events.ts +21 -0
  175. package/src/utils/native_adapters/configuration.ts +10 -0
  176. package/src/utils/native_adapters/document_picker.ts +26 -0
  177. package/src/utils/native_adapters/image_picker.ts +8 -1
  178. package/src/utils/native_adapters/index.ts +1 -0
  179. package/src/utils/request/get_chat_configuration.ts +23 -0
  180. package/build/hooks/attachments/supported_extensions.d.ts +0 -2
  181. package/build/hooks/attachments/supported_extensions.d.ts.map +0 -1
  182. package/build/hooks/attachments/supported_extensions.js +0 -48
  183. package/build/hooks/attachments/supported_extensions.js.map +0 -1
  184. package/src/hooks/attachments/supported_extensions.ts +0 -47
@@ -1,13 +1,12 @@
1
1
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
- import { SUPPORTED_EXTENSIONS } from './attachments/supported_extensions';
3
2
  import { useApiClient } from './use_api_client';
3
+ import { useChatConfiguration } from './use_chat_configuration';
4
4
  import { useUploadClient } from './use_upload_client';
5
- const MAX_FILE_SIZE_IN_MB = 50;
6
- const MAX_FILE_SIZE_IN_BYTES = MAX_FILE_SIZE_IN_MB * 1024 * 1024;
7
- const MAX_NUMBER_OF_ATTACHMENTS = 10;
8
5
  export function useAttachmentUploader({ conversationId, draftAttachments, }) {
9
6
  const apiClient = useApiClient();
10
7
  const uploadApi = useUploadClient();
8
+ const { allowedFileExtensions, maxFileSizeInBytes, maxAttachmentsPerMessage } = useChatConfiguration();
9
+ const maxFileSizeInMb = Number((maxFileSizeInBytes / (1024 * 1024)).toFixed(1));
11
10
  const [attachments, setAttachments] = useState(() => draftAttachments || []);
12
11
  const uploadState = useRef({});
13
12
  const [lastUploadId, setLastUploadId] = useState();
@@ -16,9 +15,9 @@ export function useAttachmentUploader({ conversationId, draftAttachments, }) {
16
15
  const handleFilesAttached = useCallback((files) => {
17
16
  const fileErrors = {};
18
17
  const validFiles = files.filter(file => {
19
- const extension = file.name.split('.').pop();
20
- const isValidExtension = SUPPORTED_EXTENSIONS.includes(`.${extension}`);
21
- const isValidFileSize = file.size <= MAX_FILE_SIZE_IN_BYTES;
18
+ const extension = file.name.toLowerCase().split('.').pop();
19
+ const isValidExtension = allowedFileExtensions.includes(`.${extension}`);
20
+ const isValidFileSize = file.size <= maxFileSizeInBytes;
22
21
  if (!isValidExtension) {
23
22
  fileErrors.file_type ||= [];
24
23
  fileErrors.file_type.push(extension);
@@ -33,10 +32,10 @@ export function useAttachmentUploader({ conversationId, draftAttachments, }) {
33
32
  errorMessages.push(`The following file types are not supported: ${fileErrors.file_type.join(', ')}`);
34
33
  }
35
34
  if (fileErrors.file_size) {
36
- errorMessages.push(`File size exceeds ${MAX_FILE_SIZE_IN_MB} MB`);
35
+ errorMessages.push(`File size exceeds ${maxFileSizeInMb} MB`);
37
36
  }
38
- if (numberOfAttachments + validFiles.length > MAX_NUMBER_OF_ATTACHMENTS) {
39
- errorMessages.push(`You can't attach more than ${MAX_NUMBER_OF_ATTACHMENTS} files at once.`);
37
+ if (numberOfAttachments + validFiles.length > maxAttachmentsPerMessage) {
38
+ errorMessages.push(`You can't attach more than ${maxAttachmentsPerMessage} files at once.`);
40
39
  }
41
40
  if (errorMessages.length > 0) {
42
41
  setErrorMessage(errorMessages.join('\n'));
@@ -68,20 +67,33 @@ export function useAttachmentUploader({ conversationId, draftAttachments, }) {
68
67
  };
69
68
  setLastUploadId(messageAttachmentId);
70
69
  })
71
- .catch(err => {
70
+ .catch(async (err) => {
72
71
  const isFlagged = err?.code === 'image_flagged';
73
72
  uploadState.current[attachment.file.name] = {
74
73
  status: 'error',
75
74
  flagged: isFlagged,
76
75
  };
77
76
  if (!isFlagged) {
78
- setErrorMessage('This file could not be uploaded.');
77
+ // Dev builds surface the raw server detail to shorten the
78
+ // feedback loop on backend errors (e.g. AWS SSO re-auth).
79
+ // Production users always see the generic message.
80
+ const serverDetail = __DEV__ ? await extractDevOnlyServerErrorDetail(err) : null;
81
+ setErrorMessage(serverDetail ?? 'This file could not be uploaded.');
79
82
  }
80
83
  setLastUploadId(attachment.file.name);
81
84
  });
82
85
  });
83
86
  }
84
- }, [numberOfAttachments, uploadApi, apiClient.chat, conversationId]);
87
+ }, [
88
+ numberOfAttachments,
89
+ uploadApi,
90
+ apiClient.chat,
91
+ conversationId,
92
+ allowedFileExtensions,
93
+ maxFileSizeInBytes,
94
+ maxFileSizeInMb,
95
+ maxAttachmentsPerMessage,
96
+ ]);
85
97
  useEffect(() => {
86
98
  if (!lastUploadId)
87
99
  return;
@@ -120,7 +132,20 @@ export function useAttachmentUploader({ conversationId, draftAttachments, }) {
120
132
  pendingUploads,
121
133
  errorMessage,
122
134
  flaggedAttachmentCount,
123
- remainingAttachable: MAX_NUMBER_OF_ATTACHMENTS - numberOfAttachments,
135
+ remainingAttachable: Math.max(0, maxAttachmentsPerMessage - numberOfAttachments),
124
136
  };
125
137
  }
138
+ async function extractDevOnlyServerErrorDetail(err) {
139
+ const response = err;
140
+ if (!response?.clone)
141
+ return null;
142
+ try {
143
+ const body = await response.clone().json();
144
+ const detail = body?.errors?.[0]?.detail ?? body?.detail;
145
+ return typeof detail === 'string' ? detail : null;
146
+ }
147
+ catch {
148
+ return null;
149
+ }
150
+ }
126
151
  //# sourceMappingURL=use_attachment_uploader.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"use_attachment_uploader.js","sourceRoot":"","sources":["../../src/hooks/use_attachment_uploader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAOzE,OAAO,EAAE,oBAAoB,EAAE,MAAM,oCAAoC,CAAA;AACzE,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAOrD,MAAM,mBAAmB,GAAG,EAAE,CAAA;AAC9B,MAAM,sBAAsB,GAAG,mBAAmB,GAAG,IAAI,GAAG,IAAI,CAAA;AAChE,MAAM,yBAAyB,GAAG,EAAE,CAAA;AAEpC,MAAM,UAAU,qBAAqB,CAAC,EACpC,cAAc,EACd,gBAAgB,GAIjB;IACC,MAAM,SAAS,GAAG,YAAY,EAAE,CAAA;IAChC,MAAM,SAAS,GAAG,eAAe,EAAE,CAAA;IACnC,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAAmB,GAAG,EAAE,CAAC,gBAAgB,IAAI,EAAE,CAAC,CAAA;IAC9F,MAAM,WAAW,GAAG,MAAM,CAAkB,EAAE,CAAC,CAAA;IAC/C,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,GAAG,QAAQ,EAAU,CAAA;IAC1D,MAAM,mBAAmB,GAAG,WAAW,CAAC,MAAM,CAAA;IAC9C,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAA;IAErE,MAAM,mBAAmB,GAAG,WAAW,CACrC,CAAC,KAA6B,EAAE,EAAE;QAChC,MAAM,UAAU,GAAG,EAAe,CAAA;QAElC,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE;YACrC,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAY,CAAA;YACtD,MAAM,gBAAgB,GAAG,oBAAoB,CAAC,QAAQ,CAAC,IAAI,SAAS,EAAE,CAAC,CAAA;YACvE,MAAM,eAAe,GAAG,IAAI,CAAC,IAAI,IAAI,sBAAsB,CAAA;YAE3D,IAAI,CAAC,gBAAgB,EAAE,CAAC;gBACtB,UAAU,CAAC,SAAS,KAAK,EAAE,CAAA;gBAC3B,UAAU,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;YACtC,CAAC;YACD,IAAI,CAAC,eAAe,EAAE,CAAC;gBACrB,UAAU,CAAC,SAAS,GAAG,IAAI,CAAA;YAC7B,CAAC;YAED,OAAO,eAAe,IAAI,gBAAgB,CAAA;QAC5C,CAAC,CAAC,CAAA;QAEF,MAAM,aAAa,GAAa,EAAE,CAAA;QAClC,IAAI,UAAU,CAAC,SAAS,EAAE,CAAC;YACzB,aAAa,CAAC,IAAI,CAChB,+CAA+C,UAAU,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CACjF,CAAA;QACH,CAAC;QACD,IAAI,UAAU,CAAC,SAAS,EAAE,CAAC;YACzB,aAAa,CAAC,IAAI,CAAC,qBAAqB,mBAAmB,KAAK,CAAC,CAAA;QACnE,CAAC;QACD,IAAI,mBAAmB,GAAG,UAAU,CAAC,MAAM,GAAG,yBAAyB,EAAE,CAAC;YACxE,aAAa,CAAC,IAAI,CAAC,8BAA8B,yBAAyB,iBAAiB,CAAC,CAAA;QAC9F,CAAC;QACD,IAAI,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC7B,eAAe,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAA;YACzC,OAAM;QACR,CAAC;QAED,MAAM,cAAc,GAAqB,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC/D,IAAI;YACJ,MAAM,EAAE,WAAW;YACnB,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE;SACvB,CAAC,CAAC,CAAA;QAEH,IAAI,cAAc,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAChD,cAAc,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC,GAAG,eAAe,EAAE,GAAG,cAAc,CAAC,CAAC,CAAA;YAE1E,cAAc,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE;gBAClC,SAAS;qBACN,UAAU,CAAC,UAAU,CAAC,IAAI,CAAC;qBAC3B,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,cAAc,EAAE,EAAE,EAAE,CAC/B,SAAS,CAAC,IAAI,CAAC,IAAI,CAAyC;oBAC1D,GAAG,EAAE,qBAAqB,cAAc,sBAAsB;oBAC9D,IAAI,EAAE;wBACJ,IAAI,EAAE;4BACJ,IAAI,EAAE,mBAAmB;4BACzB,UAAU,EAAE,EAAE,gBAAgB,EAAE,cAAc,EAAE;yBACjD;qBACF;iBACF,CAAC,CACH;qBACA,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,mBAAmB,EAAE,EAAE,EAAE,EAAE;oBAC9C,WAAW,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;wBAC1C,MAAM,EAAE,SAAS;wBACjB,EAAE,EAAE,mBAAmB;qBACxB,CAAA;oBACD,eAAe,CAAC,mBAAmB,CAAC,CAAA;gBACtC,CAAC,CAAC;qBACD,KAAK,CAAC,GAAG,CAAC,EAAE;oBACX,MAAM,SAAS,GAAG,GAAG,EAAE,IAAI,KAAK,eAAe,CAAA;oBAC/C,WAAW,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;wBAC1C,MAAM,EAAE,OAAO;wBACf,OAAO,EAAE,SAAS;qBACnB,CAAA;oBACD,IAAI,CAAC,SAAS,EAAE,CAAC;wBACf,eAAe,CAAC,kCAAkC,CAAC,CAAA;oBACrD,CAAC;oBACD,eAAe,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;gBACvC,CAAC,CAAC,CAAA;YACN,CAAC,CAAC,CAAA;QACJ,CAAC;IACH,CAAC,EACD,CAAC,mBAAmB,EAAE,SAAS,EAAE,SAAS,CAAC,IAAI,EAAE,cAAc,CAAC,CACjE,CAAA;IAED,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,YAAY;YAAE,OAAM;QAEzB,eAAe,CAAC,SAAS,CAAC,CAAA;QAC1B,cAAc,CACZ,WAAW,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE;YAC3B,yCAAyC;YACzC,IAAI,UAAU,CAAC,EAAE;gBAAE,OAAO,UAAU,CAAA;YAEpC,MAAM,KAAK,GAAG,WAAW,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YACvD,IAAI,KAAK,EAAE,CAAC;gBACV,OAAO,EAAE,GAAG,UAAU,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAA;YACtF,CAAC;YACD,OAAO,UAAU,CAAA;QACnB,CAAC,CAAC,CACH,CAAA;IACH,CAAC,EAAE,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC,CAAA;IAE/B,MAAM,gBAAgB,GAAG,WAAW,CAAC,CAAC,UAA0B,EAAE,EAAE;QAClE,cAAc,CAAC,eAAe,CAAC,EAAE,CAC/B,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,KAAK,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAChE,CAAA;IACH,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,MAAM,wBAAwB,GAAG,WAAW,CAAC,GAAG,EAAE;QAChD,cAAc,CAAC,eAAe,CAAC,EAAE,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAA;IAC5E,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;QAC7B,cAAc,CAAC,EAAE,CAAC,CAAA;QAClB,eAAe,CAAC,IAAI,CAAC,CAAA;IACvB,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,MAAM,cAAc,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,WAAW,CAAC,CAAC,MAAM,GAAG,CAAC,CAAA;IACnF,MAAM,sBAAsB,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM,CAAA;IAExE,MAAM,aAAa,GAAG,OAAO,CAC3B,GAAG,EAAE,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,SAAS,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAY,CAAC,EACtF,CAAC,WAAW,CAAC,CACd,CAAA;IAED,OAAO;QACL,WAAW;QACX,aAAa;QACb,mBAAmB;QACnB,gBAAgB;QAChB,wBAAwB;QACxB,KAAK;QACL,cAAc;QACd,YAAY;QACZ,sBAAsB;QACtB,mBAAmB,EAAE,yBAAyB,GAAG,mBAAmB;KACrE,CAAA;AACH,CAAC","sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { ApiResource } from '../types'\nimport {\n FileAttachment,\n FileUploadState,\n NativeAttachmentFile,\n} from '../types/resources/denormalized_attachment_resource_for_create'\nimport { SUPPORTED_EXTENSIONS } from './attachments/supported_extensions'\nimport { useApiClient } from './use_api_client'\nimport { useUploadClient } from './use_upload_client'\n\nexport interface FileError {\n file_type?: string[]\n file_size?: boolean\n}\n\nconst MAX_FILE_SIZE_IN_MB = 50\nconst MAX_FILE_SIZE_IN_BYTES = MAX_FILE_SIZE_IN_MB * 1024 * 1024\nconst MAX_NUMBER_OF_ATTACHMENTS = 10\n\nexport function useAttachmentUploader({\n conversationId,\n draftAttachments,\n}: {\n conversationId: number\n draftAttachments?: FileAttachment[]\n}) {\n const apiClient = useApiClient()\n const uploadApi = useUploadClient()\n const [attachments, setAttachments] = useState<FileAttachment[]>(() => draftAttachments || [])\n const uploadState = useRef<FileUploadState>({})\n const [lastUploadId, setLastUploadId] = useState<string>()\n const numberOfAttachments = attachments.length\n const [errorMessage, setErrorMessage] = useState<string | null>(null)\n\n const handleFilesAttached = useCallback(\n (files: NativeAttachmentFile[]) => {\n const fileErrors = {} as FileError\n\n const validFiles = files.filter(file => {\n const extension = file.name.split('.').pop() as string\n const isValidExtension = SUPPORTED_EXTENSIONS.includes(`.${extension}`)\n const isValidFileSize = file.size <= MAX_FILE_SIZE_IN_BYTES\n\n if (!isValidExtension) {\n fileErrors.file_type ||= []\n fileErrors.file_type.push(extension)\n }\n if (!isValidFileSize) {\n fileErrors.file_size = true\n }\n\n return isValidFileSize && isValidExtension\n })\n\n const errorMessages: string[] = []\n if (fileErrors.file_type) {\n errorMessages.push(\n `The following file types are not supported: ${fileErrors.file_type.join(', ')}`\n )\n }\n if (fileErrors.file_size) {\n errorMessages.push(`File size exceeds ${MAX_FILE_SIZE_IN_MB} MB`)\n }\n if (numberOfAttachments + validFiles.length > MAX_NUMBER_OF_ATTACHMENTS) {\n errorMessages.push(`You can't attach more than ${MAX_NUMBER_OF_ATTACHMENTS} files at once.`)\n }\n if (errorMessages.length > 0) {\n setErrorMessage(errorMessages.join('\\n'))\n return\n }\n\n const newAttachments: FileAttachment[] = validFiles.map(file => ({\n file,\n status: 'uploading',\n uploadedAt: Date.now(),\n }))\n\n if (newAttachments && newAttachments.length > 0) {\n setAttachments(prevAttachments => [...prevAttachments, ...newAttachments])\n\n newAttachments.forEach(attachment => {\n uploadApi\n .uploadFile(attachment.file)\n .then(({ id: uploadedFileId }) =>\n apiClient.chat.post<ApiResource<MessageAttachmentResource>>({\n url: `/me/conversations/${conversationId}/message_attachments`,\n data: {\n data: {\n type: 'MessageAttachment',\n attributes: { uploaded_file_id: uploadedFileId },\n },\n },\n })\n )\n .then(({ data: { id: messageAttachmentId } }) => {\n uploadState.current[attachment.file.name] = {\n status: 'success',\n id: messageAttachmentId,\n }\n setLastUploadId(messageAttachmentId)\n })\n .catch(err => {\n const isFlagged = err?.code === 'image_flagged'\n uploadState.current[attachment.file.name] = {\n status: 'error',\n flagged: isFlagged,\n }\n if (!isFlagged) {\n setErrorMessage('This file could not be uploaded.')\n }\n setLastUploadId(attachment.file.name)\n })\n })\n }\n },\n [numberOfAttachments, uploadApi, apiClient.chat, conversationId]\n )\n\n useEffect(() => {\n if (!lastUploadId) return\n\n setLastUploadId(undefined)\n setAttachments(\n attachments.map(attachment => {\n // Don't risk overwriting ids already set\n if (attachment.id) return attachment\n\n const state = uploadState.current[attachment.file.name]\n if (state) {\n return { ...attachment, id: state.id, status: state.status, flagged: state.flagged }\n }\n return attachment\n })\n )\n }, [attachments, lastUploadId])\n\n const removeAttachment = useCallback((attachment: FileAttachment) => {\n setAttachments(prevAttachments =>\n prevAttachments.filter(a => a.file.uri !== attachment.file.uri)\n )\n }, [])\n\n const removeFlaggedAttachments = useCallback(() => {\n setAttachments(prevAttachments => prevAttachments.filter(a => !a.flagged))\n }, [])\n\n const reset = useCallback(() => {\n setAttachments([])\n setErrorMessage(null)\n }, [])\n\n const pendingUploads = attachments.filter(a => a.status === 'uploading').length > 0\n const flaggedAttachmentCount = attachments.filter(a => a.flagged).length\n\n const attachmentIds = useMemo(\n () => attachments.filter(a => a.status === 'success' && a.id).map(a => a.id as string),\n [attachments]\n )\n\n return {\n attachments,\n attachmentIds,\n handleFilesAttached,\n removeAttachment,\n removeFlaggedAttachments,\n reset,\n pendingUploads,\n errorMessage,\n flaggedAttachmentCount,\n remainingAttachable: MAX_NUMBER_OF_ATTACHMENTS - numberOfAttachments,\n }\n}\n\ninterface MessageAttachmentResource {\n type: 'MessageAttachment'\n id: string\n uploadedFileId: string\n filename: string\n messageSortKey: string\n metadata: Record<string, unknown>\n checksum: string\n contentType: string\n byteSize: number\n}\n"]}
1
+ {"version":3,"file":"use_attachment_uploader.js","sourceRoot":"","sources":["../../src/hooks/use_attachment_uploader.ts"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { ApiResource } from '../types'\nimport {\n FileAttachment,\n FileUploadState,\n NativeAttachmentFile,\n} from '../types/resources/denormalized_attachment_resource_for_create'\nimport { useApiClient } from './use_api_client'\nimport { useChatConfiguration } from './use_chat_configuration'\nimport { useUploadClient } from './use_upload_client'\n\nexport interface FileError {\n file_type?: string[]\n file_size?: boolean\n}\n\nexport function useAttachmentUploader({\n conversationId,\n draftAttachments,\n}: {\n conversationId: number\n draftAttachments?: FileAttachment[]\n}) {\n const apiClient = useApiClient()\n const uploadApi = useUploadClient()\n const { allowedFileExtensions, maxFileSizeInBytes, maxAttachmentsPerMessage } =\n useChatConfiguration()\n const maxFileSizeInMb = Number((maxFileSizeInBytes / (1024 * 1024)).toFixed(1))\n const [attachments, setAttachments] = useState<FileAttachment[]>(() => draftAttachments || [])\n const uploadState = useRef<FileUploadState>({})\n const [lastUploadId, setLastUploadId] = useState<string>()\n const numberOfAttachments = attachments.length\n const [errorMessage, setErrorMessage] = useState<string | null>(null)\n\n const handleFilesAttached = useCallback(\n (files: NativeAttachmentFile[]) => {\n const fileErrors = {} as FileError\n\n const validFiles = files.filter(file => {\n const extension = file.name.toLowerCase().split('.').pop() as string\n const isValidExtension = allowedFileExtensions.includes(`.${extension}`)\n const isValidFileSize = file.size <= maxFileSizeInBytes\n\n if (!isValidExtension) {\n fileErrors.file_type ||= []\n fileErrors.file_type.push(extension)\n }\n if (!isValidFileSize) {\n fileErrors.file_size = true\n }\n\n return isValidFileSize && isValidExtension\n })\n\n const errorMessages: string[] = []\n if (fileErrors.file_type) {\n errorMessages.push(\n `The following file types are not supported: ${fileErrors.file_type.join(', ')}`\n )\n }\n if (fileErrors.file_size) {\n errorMessages.push(`File size exceeds ${maxFileSizeInMb} MB`)\n }\n if (numberOfAttachments + validFiles.length > maxAttachmentsPerMessage) {\n errorMessages.push(`You can't attach more than ${maxAttachmentsPerMessage} files at once.`)\n }\n if (errorMessages.length > 0) {\n setErrorMessage(errorMessages.join('\\n'))\n return\n }\n\n const newAttachments: FileAttachment[] = validFiles.map(file => ({\n file,\n status: 'uploading',\n uploadedAt: Date.now(),\n }))\n\n if (newAttachments && newAttachments.length > 0) {\n setAttachments(prevAttachments => [...prevAttachments, ...newAttachments])\n\n newAttachments.forEach(attachment => {\n uploadApi\n .uploadFile(attachment.file)\n .then(({ id: uploadedFileId }) =>\n apiClient.chat.post<ApiResource<MessageAttachmentResource>>({\n url: `/me/conversations/${conversationId}/message_attachments`,\n data: {\n data: {\n type: 'MessageAttachment',\n attributes: { uploaded_file_id: uploadedFileId },\n },\n },\n })\n )\n .then(({ data: { id: messageAttachmentId } }) => {\n uploadState.current[attachment.file.name] = {\n status: 'success',\n id: messageAttachmentId,\n }\n setLastUploadId(messageAttachmentId)\n })\n .catch(async err => {\n const isFlagged = err?.code === 'image_flagged'\n uploadState.current[attachment.file.name] = {\n status: 'error',\n flagged: isFlagged,\n }\n if (!isFlagged) {\n // Dev builds surface the raw server detail to shorten the\n // feedback loop on backend errors (e.g. AWS SSO re-auth).\n // Production users always see the generic message.\n const serverDetail = __DEV__ ? await extractDevOnlyServerErrorDetail(err) : null\n setErrorMessage(serverDetail ?? 'This file could not be uploaded.')\n }\n setLastUploadId(attachment.file.name)\n })\n })\n }\n },\n [\n numberOfAttachments,\n uploadApi,\n apiClient.chat,\n conversationId,\n allowedFileExtensions,\n maxFileSizeInBytes,\n maxFileSizeInMb,\n maxAttachmentsPerMessage,\n ]\n )\n\n useEffect(() => {\n if (!lastUploadId) return\n\n setLastUploadId(undefined)\n setAttachments(\n attachments.map(attachment => {\n // Don't risk overwriting ids already set\n if (attachment.id) return attachment\n\n const state = uploadState.current[attachment.file.name]\n if (state) {\n return { ...attachment, id: state.id, status: state.status, flagged: state.flagged }\n }\n return attachment\n })\n )\n }, [attachments, lastUploadId])\n\n const removeAttachment = useCallback((attachment: FileAttachment) => {\n setAttachments(prevAttachments =>\n prevAttachments.filter(a => a.file.uri !== attachment.file.uri)\n )\n }, [])\n\n const removeFlaggedAttachments = useCallback(() => {\n setAttachments(prevAttachments => prevAttachments.filter(a => !a.flagged))\n }, [])\n\n const reset = useCallback(() => {\n setAttachments([])\n setErrorMessage(null)\n }, [])\n\n const pendingUploads = attachments.filter(a => a.status === 'uploading').length > 0\n const flaggedAttachmentCount = attachments.filter(a => a.flagged).length\n\n const attachmentIds = useMemo(\n () => attachments.filter(a => a.status === 'success' && a.id).map(a => a.id as string),\n [attachments]\n )\n\n return {\n attachments,\n attachmentIds,\n handleFilesAttached,\n removeAttachment,\n removeFlaggedAttachments,\n reset,\n pendingUploads,\n errorMessage,\n flaggedAttachmentCount,\n remainingAttachable: Math.max(0, maxAttachmentsPerMessage - numberOfAttachments),\n }\n}\n\nasync function extractDevOnlyServerErrorDetail(err: unknown): Promise<string | null> {\n const response = err as Response | null\n if (!response?.clone) return null\n try {\n const body = await response.clone().json()\n const detail = body?.errors?.[0]?.detail ?? body?.detail\n return typeof detail === 'string' ? detail : null\n } catch {\n return null\n }\n}\n\ninterface MessageAttachmentResource {\n type: 'MessageAttachment'\n id: string\n uploadedFileId: string\n filename: string\n messageSortKey: string\n metadata: Record<string, unknown>\n checksum: string\n contentType: string\n byteSize: number\n}\n"]}
@@ -0,0 +1,6 @@
1
+ export declare function useChatConfiguration(): {
2
+ allowedFileExtensions: string[];
3
+ maxFileSizeInBytes: number;
4
+ maxAttachmentsPerMessage: number;
5
+ };
6
+ //# sourceMappingURL=use_chat_configuration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use_chat_configuration.d.ts","sourceRoot":"","sources":["../../src/hooks/use_chat_configuration.ts"],"names":[],"mappings":"AAkBA,wBAAgB,oBAAoB;;;;EAqBnC"}
@@ -0,0 +1,41 @@
1
+ import { useSuspenseQuery } from '@tanstack/react-query';
2
+ import { getChatConfigurationRequestArgs, getChatConfigurationQueryKey, } from '../utils/request/get_chat_configuration';
3
+ import { FALLBACK_ALLOWED_FILE_EXTENSIONS, FALLBACK_MAX_ATTACHMENTS_PER_MESSAGE, FALLBACK_MAX_FILE_SIZE_IN_BYTES, } from './attachments/fallback_chat_configuration';
4
+ import { useApiClient } from './use_api_client';
5
+ // Client-side mirror of ChatConfiguration (server is source of truth).
6
+ // Returns fallback values if the API request fails so that the attachment
7
+ // UX keeps working during transient server issues. The server still
8
+ // enforces its own limits — this hook only drives pre-upload UX.
9
+ export function useChatConfiguration() {
10
+ const apiClient = useApiClient();
11
+ const requestArgs = getChatConfigurationRequestArgs();
12
+ const { data } = useSuspenseQuery({
13
+ queryKey: getChatConfigurationQueryKey(),
14
+ queryFn: () => {
15
+ return apiClient.chat
16
+ .get(requestArgs)
17
+ .catch(() => stableFallbackConfiguration);
18
+ },
19
+ staleTime: 1000 * 60 * 60, // 1 hour — this rarely changes
20
+ });
21
+ const attrs = data.data;
22
+ return {
23
+ allowedFileExtensions: attrs.allowedFileExtensions,
24
+ maxFileSizeInBytes: attrs.maxFileSizeInBytes,
25
+ maxAttachmentsPerMessage: attrs.maxAttachmentsPerMessage,
26
+ };
27
+ }
28
+ // Shape matches what consumers see after transform_response flattens
29
+ // attributes and camelCases keys.
30
+ const stableFallbackConfiguration = {
31
+ data: {
32
+ type: 'ChatConfiguration',
33
+ id: 'current',
34
+ allowedFileExtensions: FALLBACK_ALLOWED_FILE_EXTENSIONS,
35
+ maxFileSizeInBytes: FALLBACK_MAX_FILE_SIZE_IN_BYTES,
36
+ maxAttachmentsPerMessage: FALLBACK_MAX_ATTACHMENTS_PER_MESSAGE,
37
+ },
38
+ links: {},
39
+ meta: {},
40
+ };
41
+ //# sourceMappingURL=use_chat_configuration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use_chat_configuration.js","sourceRoot":"","sources":["../../src/hooks/use_chat_configuration.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAGxD,OAAO,EACL,+BAA+B,EAC/B,4BAA4B,GAC7B,MAAM,yCAAyC,CAAA;AAChD,OAAO,EACL,gCAAgC,EAChC,oCAAoC,EACpC,+BAA+B,GAChC,MAAM,2CAA2C,CAAA;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAE/C,uEAAuE;AACvE,0EAA0E;AAC1E,oEAAoE;AACpE,iEAAiE;AACjE,MAAM,UAAU,oBAAoB;IAClC,MAAM,SAAS,GAAG,YAAY,EAAE,CAAA;IAChC,MAAM,WAAW,GAAG,+BAA+B,EAAE,CAAA;IAErD,MAAM,EAAE,IAAI,EAAE,GAAG,gBAAgB,CAAC;QAChC,QAAQ,EAAE,4BAA4B,EAAE;QACxC,OAAO,EAAE,GAAG,EAAE;YACZ,OAAO,SAAS,CAAC,IAAI;iBAClB,GAAG,CAAyC,WAAW,CAAC;iBACxD,KAAK,CAAC,GAAG,EAAE,CAAC,2BAA2B,CAAC,CAAA;QAC7C,CAAC;QACD,SAAS,EAAE,IAAI,GAAG,EAAE,GAAG,EAAE,EAAE,+BAA+B;KAC3D,CAAC,CAAA;IAEF,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAA;IAEvB,OAAO;QACL,qBAAqB,EAAE,KAAK,CAAC,qBAAqB;QAClD,kBAAkB,EAAE,KAAK,CAAC,kBAAkB;QAC5C,wBAAwB,EAAE,KAAK,CAAC,wBAAwB;KACzD,CAAA;AACH,CAAC;AAED,qEAAqE;AACrE,kCAAkC;AAClC,MAAM,2BAA2B,GAA2C;IAC1E,IAAI,EAAE;QACJ,IAAI,EAAE,mBAAmB;QACzB,EAAE,EAAE,SAAS;QACb,qBAAqB,EAAE,gCAAgC;QACvD,kBAAkB,EAAE,+BAA+B;QACnD,wBAAwB,EAAE,oCAAoC;KAC/D;IACD,KAAK,EAAE,EAAE;IACT,IAAI,EAAE,EAAE;CACT,CAAA","sourcesContent":["import { useSuspenseQuery } from '@tanstack/react-query'\nimport { ApiResource } from '../types'\nimport type { ChatConfigurationResource } from '../types/resources/chat_configuration_resource'\nimport {\n getChatConfigurationRequestArgs,\n getChatConfigurationQueryKey,\n} from '../utils/request/get_chat_configuration'\nimport {\n FALLBACK_ALLOWED_FILE_EXTENSIONS,\n FALLBACK_MAX_ATTACHMENTS_PER_MESSAGE,\n FALLBACK_MAX_FILE_SIZE_IN_BYTES,\n} from './attachments/fallback_chat_configuration'\nimport { useApiClient } from './use_api_client'\n\n// Client-side mirror of ChatConfiguration (server is source of truth).\n// Returns fallback values if the API request fails so that the attachment\n// UX keeps working during transient server issues. The server still\n// enforces its own limits — this hook only drives pre-upload UX.\nexport function useChatConfiguration() {\n const apiClient = useApiClient()\n const requestArgs = getChatConfigurationRequestArgs()\n\n const { data } = useSuspenseQuery({\n queryKey: getChatConfigurationQueryKey(),\n queryFn: () => {\n return apiClient.chat\n .get<ApiResource<ChatConfigurationResource>>(requestArgs)\n .catch(() => stableFallbackConfiguration)\n },\n staleTime: 1000 * 60 * 60, // 1 hour — this rarely changes\n })\n\n const attrs = data.data\n\n return {\n allowedFileExtensions: attrs.allowedFileExtensions,\n maxFileSizeInBytes: attrs.maxFileSizeInBytes,\n maxAttachmentsPerMessage: attrs.maxAttachmentsPerMessage,\n }\n}\n\n// Shape matches what consumers see after transform_response flattens\n// attributes and camelCases keys.\nconst stableFallbackConfiguration: ApiResource<ChatConfigurationResource> = {\n data: {\n type: 'ChatConfiguration',\n id: 'current',\n allowedFileExtensions: FALLBACK_ALLOWED_FILE_EXTENSIONS,\n maxFileSizeInBytes: FALLBACK_MAX_FILE_SIZE_IN_BYTES,\n maxAttachmentsPerMessage: FALLBACK_MAX_ATTACHMENTS_PER_MESSAGE,\n },\n links: {},\n meta: {},\n}\n"]}
@@ -0,0 +1,26 @@
1
+ import type { ConversationResource } from '../types';
2
+ import type { ApiResource } from '../types';
3
+ import type { ImagePickerAsset } from '../utils/native_adapters/image_picker';
4
+ import { ApiClient } from './use_api_client';
5
+ import { useUploadClient } from './use_upload_client';
6
+ export type AvatarType = 'icon' | 'emoji' | 'image';
7
+ export type AvatarUpdatePayload = {
8
+ kind: Extract<AvatarType, 'icon' | 'emoji'>;
9
+ key: string;
10
+ color: string;
11
+ } | {
12
+ kind: Extract<AvatarType, 'image'>;
13
+ imageAsset: ImagePickerAsset;
14
+ color: string;
15
+ } | {
16
+ kind: 'clear';
17
+ };
18
+ export declare const useConversationAvatarUpdate: ({ conversationId }: {
19
+ conversationId: number;
20
+ }) => import("@tanstack/react-query").UseMutationResult<ApiResource<ConversationResource>, Error, AvatarUpdatePayload, {
21
+ previous: ApiResource<ConversationResource> | undefined;
22
+ }>;
23
+ export declare function patchConversationAvatar(apiClient: ApiClient, uploadClient: ReturnType<typeof useUploadClient>, conversationId: number, payload: Exclude<AvatarUpdatePayload, {
24
+ kind: 'clear';
25
+ }>): Promise<void>;
26
+ //# sourceMappingURL=use_conversation_avatar_update.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use_conversation_avatar_update.d.ts","sourceRoot":"","sources":["../../src/hooks/use_conversation_avatar_update.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAA;AACpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAE3C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAA;AAC7E,OAAO,EAAE,SAAS,EAAgB,MAAM,kBAAkB,CAAA;AAG1D,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAErD,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,CAAA;AAEnD,MAAM,MAAM,mBAAmB,GAC3B;IAAE,IAAI,EAAE,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAC3E;IAAE,IAAI,EAAE,OAAO,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IAAC,UAAU,EAAE,gBAAgB,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACnF;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAA;AAErB,eAAO,MAAM,2BAA2B,uBAAwB;IAAE,cAAc,EAAE,MAAM,CAAA;CAAE;;EA0CzF,CAAA;AAED,wBAAsB,uBAAuB,CAC3C,SAAS,EAAE,SAAS,EACpB,YAAY,EAAE,UAAU,CAAC,OAAO,eAAe,CAAC,EAChD,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,OAAO,CAAC,mBAAmB,EAAE;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC,iBAiBzD"}
@@ -0,0 +1,130 @@
1
+ import { useMutation, useQueryClient } from '@tanstack/react-query';
2
+ import { Alert } from 'react-native';
3
+ import { transformGetToPost } from '../utils/client/request_helpers';
4
+ import { useApiClient } from './use_api_client';
5
+ import { getConversationRequestArgs } from './use_conversation';
6
+ import { getRequestQueryKey } from './use_suspense_api';
7
+ import { useUploadClient } from './use_upload_client';
8
+ export const useConversationAvatarUpdate = ({ conversationId }) => {
9
+ const apiClient = useApiClient();
10
+ const uploadClient = useUploadClient();
11
+ const queryClient = useQueryClient();
12
+ const requestArgs = getConversationRequestArgs({ conversation_id: conversationId });
13
+ const queryKey = getRequestQueryKey(requestArgs);
14
+ return useMutation({
15
+ mutationKey: ['updateConversationAvatar', conversationId],
16
+ mutationFn: async (payload) => {
17
+ const postArgs = transformGetToPost(requestArgs).data;
18
+ const attributes = await buildAttributes(payload, uploadClient);
19
+ return apiClient.chat.patch({
20
+ url: `/me/conversations/${conversationId}/`,
21
+ data: { data: { type: '', attributes }, ...postArgs },
22
+ });
23
+ },
24
+ onMutate: async (payload) => {
25
+ await queryClient.cancelQueries({ queryKey });
26
+ const previous = queryClient.getQueryData(queryKey);
27
+ queryClient.setQueryData(queryKey, prev => {
28
+ if (!prev?.data)
29
+ return prev;
30
+ return { ...prev, data: buildOptimisticData(prev.data, payload) };
31
+ });
32
+ return { previous };
33
+ },
34
+ onSuccess: (response, _payload, _context) => {
35
+ queryClient.setQueryData(queryKey, () => response);
36
+ queryClient.invalidateQueries({ queryKey: ['/me/conversations'] });
37
+ },
38
+ onError: (_error, _payload, context) => {
39
+ if (context?.previous) {
40
+ queryClient.setQueryData(queryKey, context.previous);
41
+ }
42
+ Alert.alert('Error', 'Failed to update conversation avatar. Please try again.');
43
+ },
44
+ });
45
+ };
46
+ export async function patchConversationAvatar(apiClient, uploadClient, conversationId, payload) {
47
+ const requestArgs = getConversationRequestArgs({ conversation_id: conversationId });
48
+ const postArgs = transformGetToPost(requestArgs).data;
49
+ try {
50
+ const attributes = await buildAttributes(payload, uploadClient);
51
+ await apiClient.chat.patch({
52
+ url: `/me/conversations/${conversationId}/`,
53
+ data: { data: { type: '', attributes }, ...postArgs },
54
+ });
55
+ }
56
+ catch {
57
+ Alert.alert('Avatar not saved', 'The conversation was created, but the avatar could not be saved. You can update it from the conversation details.');
58
+ }
59
+ }
60
+ function buildOptimisticData(data, payload) {
61
+ switch (payload.kind) {
62
+ case 'icon':
63
+ return {
64
+ ...data,
65
+ customAvatarType: 'icon',
66
+ customAvatarKey: payload.key,
67
+ customAvatarColor: payload.color,
68
+ customAvatarImageUrl: null,
69
+ };
70
+ case 'emoji':
71
+ return {
72
+ ...data,
73
+ customAvatarType: 'emoji',
74
+ customAvatarKey: payload.key,
75
+ customAvatarColor: payload.color,
76
+ customAvatarImageUrl: null,
77
+ };
78
+ case 'image':
79
+ return {
80
+ ...data,
81
+ customAvatarType: 'image',
82
+ customAvatarKey: null,
83
+ customAvatarColor: payload.color,
84
+ customAvatarImageUrl: payload.imageAsset.uri,
85
+ };
86
+ case 'clear':
87
+ return {
88
+ ...data,
89
+ customAvatarType: null,
90
+ customAvatarKey: null,
91
+ customAvatarColor: null,
92
+ customAvatarImageUrl: null,
93
+ };
94
+ }
95
+ }
96
+ async function buildAttributes(payload, uploadClient) {
97
+ switch (payload.kind) {
98
+ case 'icon':
99
+ return {
100
+ custom_avatar_type: 'icon',
101
+ custom_avatar_key: payload.key,
102
+ custom_avatar_color: payload.color,
103
+ };
104
+ case 'emoji':
105
+ return {
106
+ custom_avatar_type: 'emoji',
107
+ custom_avatar_key: payload.key,
108
+ custom_avatar_color: payload.color,
109
+ };
110
+ case 'image': {
111
+ const uploaded = await uploadClient.uploadFile({
112
+ uri: payload.imageAsset.uri,
113
+ name: payload.imageAsset.fileName || 'avatar.jpg',
114
+ type: payload.imageAsset.mimeType || 'image/jpeg',
115
+ });
116
+ return {
117
+ custom_avatar_type: 'image',
118
+ custom_avatar_color: payload.color,
119
+ avatar_uploaded_file_id: uploaded.id,
120
+ };
121
+ }
122
+ case 'clear':
123
+ return {
124
+ custom_avatar_type: null,
125
+ custom_avatar_key: null,
126
+ custom_avatar_color: null,
127
+ };
128
+ }
129
+ }
130
+ //# sourceMappingURL=use_conversation_avatar_update.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use_conversation_avatar_update.js","sourceRoot":"","sources":["../../src/hooks/use_conversation_avatar_update.ts"],"names":[],"mappings":"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","sourcesContent":["import { useMutation, useQueryClient } from '@tanstack/react-query'\nimport { Alert } from 'react-native'\nimport type { ConversationResource } from '../types'\nimport type { ApiResource } from '../types'\nimport { transformGetToPost } from '../utils/client/request_helpers'\nimport type { ImagePickerAsset } from '../utils/native_adapters/image_picker'\nimport { ApiClient, useApiClient } from './use_api_client'\nimport { getConversationRequestArgs } from './use_conversation'\nimport { getRequestQueryKey } from './use_suspense_api'\nimport { useUploadClient } from './use_upload_client'\n\nexport type AvatarType = 'icon' | 'emoji' | 'image'\n\nexport type AvatarUpdatePayload =\n | { kind: Extract<AvatarType, 'icon' | 'emoji'>; key: string; color: string }\n | { kind: Extract<AvatarType, 'image'>; imageAsset: ImagePickerAsset; color: string }\n | { kind: 'clear' }\n\nexport const useConversationAvatarUpdate = ({ conversationId }: { conversationId: number }) => {\n const apiClient = useApiClient()\n const uploadClient = useUploadClient()\n const queryClient = useQueryClient()\n const requestArgs = getConversationRequestArgs({ conversation_id: conversationId })\n const queryKey = getRequestQueryKey(requestArgs)\n\n return useMutation({\n mutationKey: ['updateConversationAvatar', conversationId],\n mutationFn: async (payload: AvatarUpdatePayload) => {\n const postArgs = transformGetToPost(requestArgs).data\n const attributes = await buildAttributes(payload, uploadClient)\n\n return apiClient.chat.patch<ApiResource<ConversationResource>>({\n url: `/me/conversations/${conversationId}/`,\n data: { data: { type: '', attributes }, ...postArgs },\n })\n },\n onMutate: async (payload: AvatarUpdatePayload) => {\n await queryClient.cancelQueries({ queryKey })\n\n const previous = queryClient.getQueryData<ApiResource<ConversationResource>>(queryKey)\n\n queryClient.setQueryData<ApiResource<ConversationResource>>(queryKey, prev => {\n if (!prev?.data) return prev\n return { ...prev, data: buildOptimisticData(prev.data, payload) }\n })\n\n return { previous }\n },\n onSuccess: (response, _payload, _context) => {\n queryClient.setQueryData<ApiResource<ConversationResource>>(queryKey, () => response)\n queryClient.invalidateQueries({ queryKey: ['/me/conversations'] })\n },\n onError: (_error, _payload, context) => {\n if (context?.previous) {\n queryClient.setQueryData(queryKey, context.previous)\n }\n\n Alert.alert('Error', 'Failed to update conversation avatar. Please try again.')\n },\n })\n}\n\nexport async function patchConversationAvatar(\n apiClient: ApiClient,\n uploadClient: ReturnType<typeof useUploadClient>,\n conversationId: number,\n payload: Exclude<AvatarUpdatePayload, { kind: 'clear' }>\n) {\n const requestArgs = getConversationRequestArgs({ conversation_id: conversationId })\n const postArgs = transformGetToPost(requestArgs).data\n\n try {\n const attributes = await buildAttributes(payload, uploadClient)\n await apiClient.chat.patch({\n url: `/me/conversations/${conversationId}/`,\n data: { data: { type: '', attributes }, ...postArgs },\n })\n } catch {\n Alert.alert(\n 'Avatar not saved',\n 'The conversation was created, but the avatar could not be saved. You can update it from the conversation details.'\n )\n }\n}\n\nfunction buildOptimisticData(\n data: ConversationResource,\n payload: AvatarUpdatePayload\n): ConversationResource {\n switch (payload.kind) {\n case 'icon':\n return {\n ...data,\n customAvatarType: 'icon',\n customAvatarKey: payload.key,\n customAvatarColor: payload.color,\n customAvatarImageUrl: null,\n }\n case 'emoji':\n return {\n ...data,\n customAvatarType: 'emoji',\n customAvatarKey: payload.key,\n customAvatarColor: payload.color,\n customAvatarImageUrl: null,\n }\n case 'image':\n return {\n ...data,\n customAvatarType: 'image',\n customAvatarKey: null,\n customAvatarColor: payload.color,\n customAvatarImageUrl: payload.imageAsset.uri,\n }\n case 'clear':\n return {\n ...data,\n customAvatarType: null,\n customAvatarKey: null,\n customAvatarColor: null,\n customAvatarImageUrl: null,\n }\n }\n}\n\nasync function buildAttributes(\n payload: AvatarUpdatePayload,\n uploadClient: ReturnType<typeof useUploadClient>\n) {\n switch (payload.kind) {\n case 'icon':\n return {\n custom_avatar_type: 'icon',\n custom_avatar_key: payload.key,\n custom_avatar_color: payload.color,\n }\n case 'emoji':\n return {\n custom_avatar_type: 'emoji',\n custom_avatar_key: payload.key,\n custom_avatar_color: payload.color,\n }\n case 'image': {\n const uploaded = await uploadClient.uploadFile({\n uri: payload.imageAsset.uri,\n name: payload.imageAsset.fileName || 'avatar.jpg',\n type: payload.imageAsset.mimeType || 'image/jpeg',\n })\n\n return {\n custom_avatar_type: 'image',\n custom_avatar_color: payload.color,\n avatar_uploaded_file_id: uploaded.id,\n }\n }\n case 'clear':\n return {\n custom_avatar_type: null,\n custom_avatar_key: null,\n custom_avatar_color: null,\n }\n }\n}\n"]}
@@ -7,5 +7,6 @@ export declare const availableFeatures: {
7
7
  gender_specific_conversations: string;
8
8
  message_reporting: string;
9
9
  granular_notifications_ui: string;
10
+ custom_conversation_avatars: string;
10
11
  };
11
12
  //# sourceMappingURL=use_features.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"use_features.d.ts","sourceRoot":"","sources":["../../src/hooks/use_features.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qCAAqC,CAAA;AAI1E,wBAAgB,WAAW;;kCAiBT,MAAM;EASvB;AAED,eAAO,MAAM,iBAAiB;;;;CAI7B,CAAA"}
1
+ {"version":3,"file":"use_features.d.ts","sourceRoot":"","sources":["../../src/hooks/use_features.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qCAAqC,CAAA;AAI1E,wBAAgB,WAAW;;kCAiBT,MAAM;EASvB;AAED,eAAO,MAAM,iBAAiB;;;;;CAK7B,CAAA"}
@@ -25,6 +25,7 @@ export const availableFeatures = {
25
25
  gender_specific_conversations: 'ROLLOUT_gender_specific_conversations',
26
26
  message_reporting: 'ROLLOUT_MOBILE_message_reporting',
27
27
  granular_notifications_ui: 'ROLLOUT_granular_notification_preferences_ui',
28
+ custom_conversation_avatars: 'ROLLOUT_custom_conversation_avatars',
28
29
  };
29
30
  const stableEmptyFeatures = {
30
31
  data: [],
@@ -1 +1 @@
1
- {"version":3,"file":"use_features.js","sourceRoot":"","sources":["../../src/hooks/use_features.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,OAAO,CAAA;AAGnC,OAAO,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAA;AAC3F,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAE/C,MAAM,UAAU,WAAW;IACzB,MAAM,SAAS,GAAG,YAAY,EAAE,CAAA;IAChC,MAAM,WAAW,GAAG,sBAAsB,EAAE,CAAA;IAE5C,MAAM,EAAE,IAAI,EAAE,GAAG,gBAAgB,CAAC;QAChC,QAAQ,EAAE,mBAAmB,EAAE;QAC/B,OAAO,EAAE,GAAG,EAAE;YACZ,OAAO,SAAS,CAAC,IAAI;iBAClB,GAAG,CAAiC,WAAW,CAAC;iBAChD,KAAK,CAAC,GAAG,EAAE,CAAC,mBAAmB,CAAC,CAAA;QACrC,CAAC;QACD,SAAS,EAAE,IAAI,GAAG,EAAE,GAAG,CAAC,EAAE,YAAY;KACvC,CAAC,CAAA;IAEF,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAA;IAE1B,MAAM,cAAc,GAAG,WAAW,CAChC,CAAC,WAAmB,EAAE,EAAE,CACtB,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC,EAC3E,CAAC,QAAQ,CAAC,CACX,CAAA;IAED,OAAO;QACL,QAAQ;QACR,cAAc;KACf,CAAA;AACH,CAAC;AAED,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,6BAA6B,EAAE,uCAAuC;IACtE,iBAAiB,EAAE,kCAAkC;IACrD,yBAAyB,EAAE,8CAA8C;CAC1E,CAAA;AAED,MAAM,mBAAmB,GAAmC;IAC1D,IAAI,EAAE,EAAE;IACR,KAAK,EAAE,EAAE;IACT,IAAI,EAAE;QACJ,KAAK,EAAE,CAAC;QACR,UAAU,EAAE,CAAC;KACd;CACF,CAAA","sourcesContent":["import { useSuspenseQuery } from '@tanstack/react-query'\nimport { useCallback } from 'react'\nimport { ApiCollection } from '../types'\nimport type { FeatureResource } from '../types/resources/feature_resource'\nimport { getFeaturesRequestArgs, getFeaturesQueryKey } from '../utils/request/get_features'\nimport { useApiClient } from './use_api_client'\n\nexport function useFeatures() {\n const apiClient = useApiClient()\n const requestArgs = getFeaturesRequestArgs()\n\n const { data } = useSuspenseQuery({\n queryKey: getFeaturesQueryKey(),\n queryFn: () => {\n return apiClient.chat\n .get<ApiCollection<FeatureResource>>(requestArgs)\n .catch(() => stableEmptyFeatures)\n },\n staleTime: 1000 * 60 * 5, // 5 minutes\n })\n\n const features = data.data\n\n const featureEnabled = useCallback(\n (featureName: string) =>\n features.some(feature => feature.name === featureName && feature.enabled),\n [features]\n )\n\n return {\n features,\n featureEnabled,\n }\n}\n\nexport const availableFeatures = {\n gender_specific_conversations: 'ROLLOUT_gender_specific_conversations',\n message_reporting: 'ROLLOUT_MOBILE_message_reporting',\n granular_notifications_ui: 'ROLLOUT_granular_notification_preferences_ui',\n}\n\nconst stableEmptyFeatures: ApiCollection<FeatureResource> = {\n data: [],\n links: {},\n meta: {\n count: 0,\n totalCount: 0,\n },\n}\n"]}
1
+ {"version":3,"file":"use_features.js","sourceRoot":"","sources":["../../src/hooks/use_features.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,OAAO,CAAA;AAGnC,OAAO,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAA;AAC3F,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAE/C,MAAM,UAAU,WAAW;IACzB,MAAM,SAAS,GAAG,YAAY,EAAE,CAAA;IAChC,MAAM,WAAW,GAAG,sBAAsB,EAAE,CAAA;IAE5C,MAAM,EAAE,IAAI,EAAE,GAAG,gBAAgB,CAAC;QAChC,QAAQ,EAAE,mBAAmB,EAAE;QAC/B,OAAO,EAAE,GAAG,EAAE;YACZ,OAAO,SAAS,CAAC,IAAI;iBAClB,GAAG,CAAiC,WAAW,CAAC;iBAChD,KAAK,CAAC,GAAG,EAAE,CAAC,mBAAmB,CAAC,CAAA;QACrC,CAAC;QACD,SAAS,EAAE,IAAI,GAAG,EAAE,GAAG,CAAC,EAAE,YAAY;KACvC,CAAC,CAAA;IAEF,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAA;IAE1B,MAAM,cAAc,GAAG,WAAW,CAChC,CAAC,WAAmB,EAAE,EAAE,CACtB,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC,EAC3E,CAAC,QAAQ,CAAC,CACX,CAAA;IAED,OAAO;QACL,QAAQ;QACR,cAAc;KACf,CAAA;AACH,CAAC;AAED,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,6BAA6B,EAAE,uCAAuC;IACtE,iBAAiB,EAAE,kCAAkC;IACrD,yBAAyB,EAAE,8CAA8C;IACzE,2BAA2B,EAAE,qCAAqC;CACnE,CAAA;AAED,MAAM,mBAAmB,GAAmC;IAC1D,IAAI,EAAE,EAAE;IACR,KAAK,EAAE,EAAE;IACT,IAAI,EAAE;QACJ,KAAK,EAAE,CAAC;QACR,UAAU,EAAE,CAAC;KACd;CACF,CAAA","sourcesContent":["import { useSuspenseQuery } from '@tanstack/react-query'\nimport { useCallback } from 'react'\nimport { ApiCollection } from '../types'\nimport type { FeatureResource } from '../types/resources/feature_resource'\nimport { getFeaturesRequestArgs, getFeaturesQueryKey } from '../utils/request/get_features'\nimport { useApiClient } from './use_api_client'\n\nexport function useFeatures() {\n const apiClient = useApiClient()\n const requestArgs = getFeaturesRequestArgs()\n\n const { data } = useSuspenseQuery({\n queryKey: getFeaturesQueryKey(),\n queryFn: () => {\n return apiClient.chat\n .get<ApiCollection<FeatureResource>>(requestArgs)\n .catch(() => stableEmptyFeatures)\n },\n staleTime: 1000 * 60 * 5, // 5 minutes\n })\n\n const features = data.data\n\n const featureEnabled = useCallback(\n (featureName: string) =>\n features.some(feature => feature.name === featureName && feature.enabled),\n [features]\n )\n\n return {\n features,\n featureEnabled,\n }\n}\n\nexport const availableFeatures = {\n gender_specific_conversations: 'ROLLOUT_gender_specific_conversations',\n message_reporting: 'ROLLOUT_MOBILE_message_reporting',\n granular_notifications_ui: 'ROLLOUT_granular_notification_preferences_ui',\n custom_conversation_avatars: 'ROLLOUT_custom_conversation_avatars',\n}\n\nconst stableEmptyFeatures: ApiCollection<FeatureResource> = {\n data: [],\n links: {},\n meta: {\n count: 0,\n totalCount: 0,\n },\n}\n"]}
@@ -3,6 +3,7 @@ import { NativeStackHeaderRightProps } from '@react-navigation/native-stack';
3
3
  import { CardStyleInterpolators } from '@react-navigation/stack';
4
4
  import React from 'react';
5
5
  import { AttachmentActionsScreen } from '../screens/attachment_actions/attachment_actions_screen';
6
+ import { AvatarPickerScreen } from '../screens/avatar_picker/avatar_picker_screen';
6
7
  import { BugReportScreen } from '../screens/bug_report_screen';
7
8
  import { MessageReadReceiptsScreen } from '../screens/conversation/message_read_receipts_screen';
8
9
  import { ConversationDetailsScreen } from '../screens/conversation_details_screen';
@@ -88,6 +89,7 @@ export declare const NewConversationStack: import("@react-navigation/native").Ty
88
89
  source_app_name: import("../types/resources/app_name").AppName;
89
90
  chat_group_graph_id?: import("..").GraphId;
90
91
  group_source_app_name?: import("../types/resources/app_name").AppName;
92
+ avatar_selection?: import("../hooks/use_conversation_avatar_update").AvatarUpdatePayload;
91
93
  };
92
94
  };
93
95
  }) => React.JSX.Element;
@@ -101,6 +103,10 @@ export declare const NewConversationStack: import("@react-navigation/native").Ty
101
103
  headerRight: (props: NativeStackHeaderRightProps) => React.JSX.Element;
102
104
  };
103
105
  };
106
+ readonly AvatarPicker: {
107
+ readonly screen: typeof AvatarPickerScreen;
108
+ readonly options: import("@react-navigation/native-stack").NativeStackNavigationOptions;
109
+ };
104
110
  };
105
111
  }>;
106
112
  export declare const ChatStack: import("@react-navigation/native").TypedNavigator<{
@@ -179,6 +185,10 @@ export declare const ChatStack: import("@react-navigation/native").TypedNavigato
179
185
  headerRight: (props: NativeStackHeaderRightProps) => React.JSX.Element;
180
186
  };
181
187
  };
188
+ readonly AvatarPicker: {
189
+ readonly screen: typeof AvatarPickerScreen;
190
+ readonly options: import("@react-navigation/native-stack").NativeStackNavigationOptions;
191
+ };
182
192
  readonly NotificationSettings: {
183
193
  readonly screen: typeof NotificationSettingsScreen;
184
194
  readonly options: ({ navigation }: {
@@ -283,6 +293,7 @@ export declare const ChatStack: import("@react-navigation/native").TypedNavigato
283
293
  source_app_name: import("../types/resources/app_name").AppName;
284
294
  chat_group_graph_id?: import("..").GraphId;
285
295
  group_source_app_name?: import("../types/resources/app_name").AppName;
296
+ avatar_selection?: import("../hooks/use_conversation_avatar_update").AvatarUpdatePayload;
286
297
  };
287
298
  };
288
299
  }) => React.JSX.Element;
@@ -296,6 +307,10 @@ export declare const ChatStack: import("@react-navigation/native").TypedNavigato
296
307
  headerRight: (props: NativeStackHeaderRightProps) => React.JSX.Element;
297
308
  };
298
309
  };
310
+ readonly AvatarPicker: {
311
+ readonly screen: typeof AvatarPickerScreen;
312
+ readonly options: import("@react-navigation/native-stack").NativeStackNavigationOptions;
313
+ };
299
314
  };
300
315
  }>;
301
316
  readonly if: () => boolean;
@@ -315,6 +330,7 @@ export declare const ChatStack: import("@react-navigation/native").TypedNavigato
315
330
  source_app_name: import("../types/resources/app_name").AppName;
316
331
  chat_group_graph_id?: import("..").GraphId;
317
332
  group_source_app_name?: import("../types/resources/app_name").AppName;
333
+ avatar_selection?: import("../hooks/use_conversation_avatar_update").AvatarUpdatePayload;
318
334
  };
319
335
  };
320
336
  }) => React.JSX.Element;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/navigation/index.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/navigation/index.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;AAC1D,OAAO,EAEL,2BAA2B,EAC5B,MAAM,gCAAgC,CAAA;AACvC,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAA;AAChE,OAAO,KAAK,MAAM,OAAO,CAAA;AAQzB,OAAO,EACL,uBAAuB,EAExB,MAAM,yDAAyD,CAAA;AAChE,OAAO,EACL,kBAAkB,EAGnB,MAAM,+CAA+C,CAAA;AACtD,OAAO,EAAE,eAAe,EAA0B,MAAM,8BAA8B,CAAA;AACtF,OAAO,EACL,yBAAyB,EAE1B,MAAM,sDAAsD,CAAA;AAC7D,OAAO,EAAE,yBAAyB,EAAE,MAAM,wCAAwC,CAAA;AAWlF,OAAO,EACL,yCAAyC,EAE1C,MAAM,0DAA0D,CAAA;AACjE,OAAO,EAEL,kBAAkB,EAEnB,MAAM,gCAAgC,CAAA;AAKvC,OAAO,EACL,4BAA4B,EAE7B,MAAM,4CAA4C,CAAA;AACnD,OAAO,EACL,8BAA8B,EAE/B,MAAM,4DAA4D,CAAA;AACnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,+CAA+C,CAAA;AAEnF,OAAO,EACL,kCAAkC,EAEnC,MAAM,mDAAmD,CAAA;AAC1D,OAAO,EAAE,+BAA+B,EAAE,MAAM,+CAA+C,CAAA;AAC/F,OAAO,EACL,oBAAoB,EAErB,MAAM,mCAAmC,CAAA;AAC1C,OAAO,EAAE,mBAAmB,EAA8B,MAAM,kCAAkC,CAAA;AAClG,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AAC/C,OAAO,EAAE,0BAA0B,EAAE,MAAM,yCAAyC,CAAA;AACpF,OAAO,EAAE,2BAA2B,EAAE,MAAM,2CAA2C,CAAA;AACvF,OAAO,EAAE,eAAe,EAA0B,MAAM,6BAA6B,CAAA;AACrF,OAAO,EAAE,eAAe,EAA0B,MAAM,8BAA8B,CAAA;AACtF,OAAO,EACL,yBAAyB,EAE1B,MAAM,yCAAyC,CAAA;AAEhD,OAAO,EAAE,8BAA8B,EAAE,MAAM,gBAAgB,CAAA;AAO/D,eAAO,MAAM,oBAAoB;;;;;;;;;uOA5E5B,mBAAmB;;;;;;;;;;;;;uBA4XgkK,gBAAiB,KAAK;;;qCArSjlK,2BAA2B;;;;;;;;;uBAqSgiK,gBAAiB,KAAK;;;qCA3RjlK,2BAA2B;;;;;;;;uBA2RgiK,gBAAiB,KAAK;;;qCA9QjlK,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;uBA8QgiK,gBAAiB,KAAK;;;;qCA5PjlK,2BAA2B;;;;;;;;EAkBtD,CAAA;AAEF,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;uBAwOkkK,gBAAiB,KAAK;;;;qCA7NjlK,2BAA2B;;;;;;;;;;;;uBA6NgiK,gBAAiB,KAAK;;qCA7MjlK,2BAA2B;qCAgB3B,2BAA2B;;;;;;;;;uBA6LgiK,gBAAiB,KAAK;;;;;;;;;;;;;;;;;;;;uBAAtB,gBAAiB,KAAK;;;;qCAxIjlK,2BAA2B;;;;;;;;;;;;uBAwIgiK,gBAAiB,KAAK;;;;qCA1HjlK,2BAA2B;;;;;;;;;;;;;;uBA0HgiK,gBAAiB,KAAK;;;qCA3GjlK,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mDA2GijK,KAAK;;;iDArSjlK,2BAA2B;;;;;;;;;mDAqSijK,KAAK;;;iDA3RjlK,2BAA2B;;;;;;;;mDA2RijK,KAAK;;;iDA9QjlK,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;mDA8QijK,KAAK;;;;iDA5PjlK,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uBA4PgiK,gBAAiB,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uBAAtB,gBAAiB,KAAK;;;;;;;;;;;;;;;;;;EAT5mK,CAAA;AAEF,KAAK,kBAAkB,GAAG,eAAe,CAAC,OAAO,SAAS,CAAC,CAAA;AAE3D,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,eAAe,CAAC;QACxB,UAAU,aAAc,SAAQ,kBAAkB;SAAG;KACtD;CACF"}
@@ -7,6 +7,7 @@ import { Icon } from '../components';
7
7
  import { HeaderDoneButton, HeaderTextButton, } from '../components/display/platform_modal_header_buttons';
8
8
  import { useQualifiedByAge } from '../hooks';
9
9
  import { AttachmentActionsScreen, AttachmentActionsScreenOptions, } from '../screens/attachment_actions/attachment_actions_screen';
10
+ import { AvatarPickerScreen, AvatarPickerScreenOptions, AvatarPickerCreateScreenOptions, } from '../screens/avatar_picker/avatar_picker_screen';
10
11
  import { BugReportScreen, BugReportScreenOptions } from '../screens/bug_report_screen';
11
12
  import { MessageReadReceiptsScreen, MessageReadReceiptsScreenOptions, } from '../screens/conversation/message_read_receipts_screen';
12
13
  import { ConversationDetailsScreen } from '../screens/conversation_details_screen';
@@ -82,6 +83,10 @@ export const NewConversationStack = createNativeStackNavigator({
82
83
  }} title="Cancel"/>),
83
84
  }),
84
85
  },
86
+ AvatarPicker: {
87
+ screen: AvatarPickerScreen,
88
+ options: AvatarPickerCreateScreenOptions,
89
+ },
85
90
  },
86
91
  });
87
92
  export const ChatStack = createNativeStackNavigator({
@@ -149,6 +154,10 @@ export const ChatStack = createNativeStackNavigator({
149
154
  headerRight: (props) => (<HeaderTextButton {...props} onPress={navigation.goBack} title="Done"/>),
150
155
  }),
151
156
  },
157
+ AvatarPicker: {
158
+ screen: AvatarPickerScreen,
159
+ options: AvatarPickerScreenOptions,
160
+ },
152
161
  NotificationSettings: {
153
162
  screen: NotificationSettingsScreen,
154
163
  options: ({ navigation }) => ({