@planningcenter/chat-react-native 3.14.0-rc.0 → 3.14.0-rc.2

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 (66) hide show
  1. package/build/components/conversation/message.d.ts.map +1 -1
  2. package/build/components/conversation/message.js +4 -0
  3. package/build/components/conversation/message.js.map +1 -1
  4. package/build/components/conversation/message_form.d.ts.map +1 -1
  5. package/build/components/conversation/message_form.js +23 -3
  6. package/build/components/conversation/message_form.js.map +1 -1
  7. package/build/components/conversations/conversations.d.ts.map +1 -1
  8. package/build/components/conversations/conversations.js +2 -0
  9. package/build/components/conversations/conversations.js.map +1 -1
  10. package/build/components/display/toggle_button.d.ts +5 -1
  11. package/build/components/display/toggle_button.d.ts.map +1 -1
  12. package/build/components/display/toggle_button.js +8 -2
  13. package/build/components/display/toggle_button.js.map +1 -1
  14. package/build/hooks/use_attachment_uploader.d.ts +2 -1
  15. package/build/hooks/use_attachment_uploader.d.ts.map +1 -1
  16. package/build/hooks/use_attachment_uploader.js +6 -2
  17. package/build/hooks/use_attachment_uploader.js.map +1 -1
  18. package/build/hooks/use_conversations_jolt_events.d.ts.map +1 -1
  19. package/build/hooks/use_conversations_jolt_events.js +10 -5
  20. package/build/hooks/use_conversations_jolt_events.js.map +1 -1
  21. package/build/hooks/use_jolt.d.ts +1 -1
  22. package/build/hooks/use_jolt.d.ts.map +1 -1
  23. package/build/hooks/use_jolt.js +3 -2
  24. package/build/hooks/use_jolt.js.map +1 -1
  25. package/build/hooks/use_message_draft.d.ts +13 -0
  26. package/build/hooks/use_message_draft.d.ts.map +1 -0
  27. package/build/hooks/use_message_draft.js +83 -0
  28. package/build/hooks/use_message_draft.js.map +1 -0
  29. package/build/screens/conversation_new/components/services_form.d.ts.map +1 -1
  30. package/build/screens/conversation_new/components/services_form.js +12 -8
  31. package/build/screens/conversation_new/components/services_form.js.map +1 -1
  32. package/build/screens/conversation_screen.d.ts.map +1 -1
  33. package/build/screens/conversation_screen.js +6 -1
  34. package/build/screens/conversation_screen.js.map +1 -1
  35. package/build/screens/conversation_select_recipients/conversation_select_recipients_screen.d.ts.map +1 -1
  36. package/build/screens/conversation_select_recipients/conversation_select_recipients_screen.js +10 -6
  37. package/build/screens/conversation_select_recipients/conversation_select_recipients_screen.js.map +1 -1
  38. package/build/screens/conversations/conversations_screen.d.ts.map +1 -1
  39. package/build/screens/conversations/conversations_screen.js +0 -2
  40. package/build/screens/conversations/conversations_screen.js.map +1 -1
  41. package/build/screens/message_actions_screen.d.ts.map +1 -1
  42. package/build/screens/message_actions_screen.js +2 -1
  43. package/build/screens/message_actions_screen.js.map +1 -1
  44. package/build/types/resources/denormalized_attachment_resource_for_create.d.ts +1 -0
  45. package/build/types/resources/denormalized_attachment_resource_for_create.d.ts.map +1 -1
  46. package/build/types/resources/denormalized_attachment_resource_for_create.js.map +1 -1
  47. package/package.json +2 -2
  48. package/src/components/conversation/message.tsx +4 -0
  49. package/src/components/conversation/message_form.tsx +25 -3
  50. package/src/components/conversations/conversations.tsx +3 -1
  51. package/src/components/display/toggle_button.tsx +15 -1
  52. package/src/hooks/use_attachment_uploader.ts +12 -2
  53. package/src/hooks/use_conversations_jolt_events.ts +13 -6
  54. package/src/hooks/use_jolt.ts +6 -2
  55. package/src/hooks/use_message_draft.ts +108 -0
  56. package/src/screens/conversation_new/components/services_form.tsx +4 -2
  57. package/src/screens/conversation_screen.tsx +7 -0
  58. package/src/screens/conversation_select_recipients/conversation_select_recipients_screen.tsx +4 -2
  59. package/src/screens/conversations/conversations_screen.tsx +0 -2
  60. package/src/screens/message_actions_screen.tsx +2 -1
  61. package/src/types/resources/denormalized_attachment_resource_for_create.ts +1 -0
  62. package/build/screens/conversations/components/conversations_jolt_events.d.ts +0 -7
  63. package/build/screens/conversations/components/conversations_jolt_events.d.ts.map +0 -1
  64. package/build/screens/conversations/components/conversations_jolt_events.js +0 -14
  65. package/build/screens/conversations/components/conversations_jolt_events.js.map +0 -1
  66. package/src/screens/conversations/components/conversations_jolt_events.tsx +0 -23
@@ -5,10 +5,10 @@ import { useApiClient } from './use_api_client';
5
5
  const MAX_FILE_SIZE_IN_MB = 50;
6
6
  const MAX_FILE_SIZE_IN_BYTES = MAX_FILE_SIZE_IN_MB * 1024 * 1024;
7
7
  const MAX_NUMBER_OF_ATTACHMENTS = 10;
8
- export function useAttachmentUploader({ conversationId }) {
8
+ export function useAttachmentUploader({ conversationId, draftAttachments, }) {
9
9
  const apiClient = useApiClient();
10
10
  const uploadApi = useUploadClient();
11
- const [attachments, setAttachments] = useState([]);
11
+ const [attachments, setAttachments] = useState(() => draftAttachments || []);
12
12
  const uploadState = useRef({});
13
13
  const [lastUploadId, setLastUploadId] = useState();
14
14
  const numberOfAttachments = attachments.length;
@@ -45,6 +45,7 @@ export function useAttachmentUploader({ conversationId }) {
45
45
  const newAttachments = validFiles.map(file => ({
46
46
  file,
47
47
  status: 'uploading',
48
+ uploadedAt: Date.now(),
48
49
  }));
49
50
  if (newAttachments && newAttachments.length > 0) {
50
51
  setAttachments(prevAttachments => [...prevAttachments, ...newAttachments]);
@@ -81,6 +82,9 @@ export function useAttachmentUploader({ conversationId }) {
81
82
  return;
82
83
  setLastUploadId(undefined);
83
84
  setAttachments(attachments.map(attachment => {
85
+ // Don't risk overwriting ids already set
86
+ if (attachment.id)
87
+ return attachment;
84
88
  const state = uploadState.current[attachment.file.name];
85
89
  if (state) {
86
90
  return { ...attachment, id: state.id, status: state.status };
@@ -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;AACzE,OAAO,EAAE,oBAAoB,EAAE,MAAM,oCAAoC,CAAA;AACzE,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAa/C,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,EAAE,cAAc,EAA8B;IAClF,MAAM,SAAS,GAAG,YAAY,EAAE,CAAA;IAChC,MAAM,SAAS,GAAG,eAAe,EAAE,CAAA;IACnC,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAAmB,EAAE,CAAC,CAAA;IACpE,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;SACpB,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,EAAE;oBACV,WAAW,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;wBAC1C,MAAM,EAAE,OAAO;qBAChB,CAAA;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,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,CAAA;YAC9D,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,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;IAEnF,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,KAAK;QACL,cAAc;QACd,YAAY;QACZ,mBAAmB,EAAE,yBAAyB,GAAG,mBAAmB;KACrE,CAAA;AACH,CAAC","sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { SUPPORTED_EXTENSIONS } from './attachments/supported_extensions'\nimport { useUploadClient } from './use_upload_client'\nimport { useApiClient } from './use_api_client'\nimport { ApiResource } from '../types'\nimport {\n FileAttachment,\n FileUploadState,\n NativeAttachmentFile,\n} from '../types/resources/denormalized_attachment_resource_for_create'\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({ conversationId }: { conversationId: number }) {\n const apiClient = useApiClient()\n const uploadApi = useUploadClient()\n const [attachments, setAttachments] = useState<FileAttachment[]>([])\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 }))\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(() => {\n uploadState.current[attachment.file.name] = {\n status: 'error',\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 const state = uploadState.current[attachment.file.name]\n if (state) {\n return { ...attachment, id: state.id, status: state.status }\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 reset = useCallback(() => {\n setAttachments([])\n setErrorMessage(null)\n }, [])\n\n const pendingUploads = attachments.filter(a => a.status === 'uploading').length > 0\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 reset,\n pendingUploads,\n errorMessage,\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 { SUPPORTED_EXTENSIONS } from './attachments/supported_extensions'\nimport { useUploadClient } from './use_upload_client'\nimport { useApiClient } from './use_api_client'\nimport { ApiResource } from '../types'\nimport {\n FileAttachment,\n FileUploadState,\n NativeAttachmentFile,\n} from '../types/resources/denormalized_attachment_resource_for_create'\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(() => {\n uploadState.current[attachment.file.name] = {\n status: 'error',\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 }\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 reset = useCallback(() => {\n setAttachments([])\n setErrorMessage(null)\n }, [])\n\n const pendingUploads = attachments.filter(a => a.status === 'uploading').length > 0\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 reset,\n pendingUploads,\n errorMessage,\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 +1 @@
1
- {"version":3,"file":"use_conversations_jolt_events.d.ts","sourceRoot":"","sources":["../../src/hooks/use_conversations_jolt_events.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAA;AAOvE,wBAAgB,0BAA0B,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,uBAAuB,CAAC,QA4BjF"}
1
+ {"version":3,"file":"use_conversations_jolt_events.d.ts","sourceRoot":"","sources":["../../src/hooks/use_conversations_jolt_events.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAA;AAavE,wBAAgB,0BAA0B,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,uBAAuB,CAAC,QA6BjF"}
@@ -1,17 +1,22 @@
1
1
  import { useConversationsCache } from './use_conversations_cache';
2
- import { useCurrentPerson, useCurrentPersonCache } from './use_current_person';
2
+ import { currentPersonRequestArgs, useCurrentPersonCache } from './use_current_person';
3
3
  import { useJoltChannel, useJoltEvent } from './use_jolt';
4
- import { completeMessageCreationConversationTracking } from '../utils/performance_tracking';
4
+ import { useApiGet } from './use_api';
5
5
  import { useApiClient } from './use_api_client';
6
+ import { completeMessageCreationConversationTracking } from '../utils/performance_tracking';
7
+ const useCurrentPerson = () => {
8
+ return useApiGet(currentPersonRequestArgs);
9
+ };
6
10
  export function useConversationsJoltEvents(args) {
7
- const currentPerson = useCurrentPerson();
8
- const joltChannel = useJoltChannel(`chat.people.${currentPerson.id}`);
11
+ const { data: currentPerson } = useCurrentPerson();
9
12
  const cache = useConversationsCache(args);
10
13
  const currentPersonCache = useCurrentPersonCache();
11
14
  const apiClient = useApiClient();
15
+ const enabled = currentPerson?.id ? true : false;
16
+ const joltChannel = useJoltChannel(`chat.people.${currentPerson?.id}`, enabled);
12
17
  useJoltEvent(joltChannel, 'conversation.updated', (e) => {
13
18
  if (e.data.data.last_message_idempotent_key &&
14
- e.data.data.last_message_author_id === currentPerson.id) {
19
+ e.data.data.last_message_author_id === currentPerson?.id) {
15
20
  completeMessageCreationConversationTracking({
16
21
  apiClient,
17
22
  idempotentKey: e.data.data.last_message_idempotent_key,
@@ -1 +1 @@
1
- {"version":3,"file":"use_conversations_jolt_events.js","sourceRoot":"","sources":["../../src/hooks/use_conversations_jolt_events.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAA;AACjE,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAA;AAC9E,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AACzD,OAAO,EAAE,2CAA2C,EAAE,MAAM,+BAA+B,CAAA;AAC3F,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAE/C,MAAM,UAAU,0BAA0B,CAAC,IAAuC;IAChF,MAAM,aAAa,GAAG,gBAAgB,EAAE,CAAA;IACxC,MAAM,WAAW,GAAG,cAAc,CAAC,eAAe,aAAa,CAAC,EAAE,EAAE,CAAC,CAAA;IACrE,MAAM,KAAK,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAA;IACzC,MAAM,kBAAkB,GAAG,qBAAqB,EAAE,CAAA;IAClD,MAAM,SAAS,GAAG,YAAY,EAAE,CAAA;IAEhC,YAAY,CAAC,WAAW,EAAE,sBAAsB,EAAE,CAAC,CAAwB,EAAE,EAAE;QAC7E,IACE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,2BAA2B;YACvC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,sBAAsB,KAAK,aAAa,CAAC,EAAE,EACvD,CAAC;YACD,2CAA2C,CAAC;gBAC1C,SAAS;gBACT,aAAa,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,2BAA2B;aACvD,CAAC,CAAA;QACJ,CAAC;QACD,KAAK,CAAC,WAAW,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAA;IAC3C,CAAC,CAAC,CAAA;IACF,YAAY,CAAC,WAAW,EAAE,sBAAsB,EAAE,CAAC,CAAwB,EAAE,EAAE,CAC7E,KAAK,CAAC,WAAW,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAC1C,CAAA;IACD,YAAY,CAAC,WAAW,EAAE,wBAAwB,EAAE,CAAC,CAAwB,EAAE,EAAE,CAC/E,KAAK,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CACtC,CAAA;IAED,YAAY,CAAC,WAAW,EAAE,qBAAqB,EAAE,kBAAkB,CAAC,UAAU,CAAC,CAAA,CAAC,gDAAgD;IAChI,YAAY,CAAC,WAAW,EAAE,mBAAmB,EAAE,kBAAkB,CAAC,UAAU,CAAC,CAAA;AAC/E,CAAC","sourcesContent":["import { JoltConversationEvent } from '../types/jolt_events'\nimport { ConversationRequestArgs } from '../utils/request/conversation'\nimport { useConversationsCache } from './use_conversations_cache'\nimport { useCurrentPerson, useCurrentPersonCache } from './use_current_person'\nimport { useJoltChannel, useJoltEvent } from './use_jolt'\nimport { completeMessageCreationConversationTracking } from '../utils/performance_tracking'\nimport { useApiClient } from './use_api_client'\n\nexport function useConversationsJoltEvents(args?: Partial<ConversationRequestArgs>) {\n const currentPerson = useCurrentPerson()\n const joltChannel = useJoltChannel(`chat.people.${currentPerson.id}`)\n const cache = useConversationsCache(args)\n const currentPersonCache = useCurrentPersonCache()\n const apiClient = useApiClient()\n\n useJoltEvent(joltChannel, 'conversation.updated', (e: JoltConversationEvent) => {\n if (\n e.data.data.last_message_idempotent_key &&\n e.data.data.last_message_author_id === currentPerson.id\n ) {\n completeMessageCreationConversationTracking({\n apiClient,\n idempotentKey: e.data.data.last_message_idempotent_key,\n })\n }\n cache.fetchUpdate({ id: e.data.data.id })\n })\n useJoltEvent(joltChannel, 'conversation.created', (e: JoltConversationEvent) =>\n cache.fetchCreate({ id: e.data.data.id })\n )\n useJoltEvent(joltChannel, 'conversation.destroyed', (e: JoltConversationEvent) =>\n cache.destroy({ id: e.data.data.id })\n )\n\n useJoltEvent(joltChannel, 'STREAM_USER_UPDATED', currentPersonCache.invalidate) // Would be nice to have a non-stream chat event\n useJoltEvent(joltChannel, 'conversation.read', currentPersonCache.invalidate)\n}\n"]}
1
+ {"version":3,"file":"use_conversations_jolt_events.js","sourceRoot":"","sources":["../../src/hooks/use_conversations_jolt_events.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAA;AACjE,OAAO,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAA;AACtF,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AACzD,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AACrC,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAE/C,OAAO,EAAE,2CAA2C,EAAE,MAAM,+BAA+B,CAAA;AAE3F,MAAM,gBAAgB,GAAG,GAAG,EAAE;IAC5B,OAAO,SAAS,CAAwB,wBAAwB,CAAC,CAAA;AACnE,CAAC,CAAA;AAED,MAAM,UAAU,0BAA0B,CAAC,IAAuC;IAChF,MAAM,EAAE,IAAI,EAAE,aAAa,EAAE,GAAG,gBAAgB,EAAE,CAAA;IAClD,MAAM,KAAK,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAA;IACzC,MAAM,kBAAkB,GAAG,qBAAqB,EAAE,CAAA;IAClD,MAAM,SAAS,GAAG,YAAY,EAAE,CAAA;IAChC,MAAM,OAAO,GAAG,aAAa,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAA;IAChD,MAAM,WAAW,GAAG,cAAc,CAAC,eAAe,aAAa,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,CAAA;IAE/E,YAAY,CAAC,WAAW,EAAE,sBAAsB,EAAE,CAAC,CAAwB,EAAE,EAAE;QAC7E,IACE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,2BAA2B;YACvC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,sBAAsB,KAAK,aAAa,EAAE,EAAE,EACxD,CAAC;YACD,2CAA2C,CAAC;gBAC1C,SAAS;gBACT,aAAa,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,2BAA2B;aACvD,CAAC,CAAA;QACJ,CAAC;QACD,KAAK,CAAC,WAAW,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAA;IAC3C,CAAC,CAAC,CAAA;IACF,YAAY,CAAC,WAAW,EAAE,sBAAsB,EAAE,CAAC,CAAwB,EAAE,EAAE,CAC7E,KAAK,CAAC,WAAW,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAC1C,CAAA;IACD,YAAY,CAAC,WAAW,EAAE,wBAAwB,EAAE,CAAC,CAAwB,EAAE,EAAE,CAC/E,KAAK,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CACtC,CAAA;IAED,YAAY,CAAC,WAAW,EAAE,qBAAqB,EAAE,kBAAkB,CAAC,UAAU,CAAC,CAAA,CAAC,gDAAgD;IAChI,YAAY,CAAC,WAAW,EAAE,mBAAmB,EAAE,kBAAkB,CAAC,UAAU,CAAC,CAAA;AAC/E,CAAC","sourcesContent":["import { CurrentPersonResource } from '../types'\nimport { ConversationRequestArgs } from '../utils/request/conversation'\nimport { useConversationsCache } from './use_conversations_cache'\nimport { currentPersonRequestArgs, useCurrentPersonCache } from './use_current_person'\nimport { useJoltChannel, useJoltEvent } from './use_jolt'\nimport { useApiGet } from './use_api'\nimport { useApiClient } from './use_api_client'\nimport { JoltConversationEvent } from '../types/jolt_events'\nimport { completeMessageCreationConversationTracking } from '../utils/performance_tracking'\n\nconst useCurrentPerson = () => {\n return useApiGet<CurrentPersonResource>(currentPersonRequestArgs)\n}\n\nexport function useConversationsJoltEvents(args?: Partial<ConversationRequestArgs>) {\n const { data: currentPerson } = useCurrentPerson()\n const cache = useConversationsCache(args)\n const currentPersonCache = useCurrentPersonCache()\n const apiClient = useApiClient()\n const enabled = currentPerson?.id ? true : false\n const joltChannel = useJoltChannel(`chat.people.${currentPerson?.id}`, enabled)\n\n useJoltEvent(joltChannel, 'conversation.updated', (e: JoltConversationEvent) => {\n if (\n e.data.data.last_message_idempotent_key &&\n e.data.data.last_message_author_id === currentPerson?.id\n ) {\n completeMessageCreationConversationTracking({\n apiClient,\n idempotentKey: e.data.data.last_message_idempotent_key,\n })\n }\n cache.fetchUpdate({ id: e.data.data.id })\n })\n useJoltEvent(joltChannel, 'conversation.created', (e: JoltConversationEvent) =>\n cache.fetchCreate({ id: e.data.data.id })\n )\n useJoltEvent(joltChannel, 'conversation.destroyed', (e: JoltConversationEvent) =>\n cache.destroy({ id: e.data.data.id })\n )\n\n useJoltEvent(joltChannel, 'STREAM_USER_UPDATED', currentPersonCache.invalidate) // Would be nice to have a non-stream chat event\n useJoltEvent(joltChannel, 'conversation.read', currentPersonCache.invalidate)\n}\n"]}
@@ -2,7 +2,7 @@ import JoltClient from '@planningcenter/jolt-client';
2
2
  import { CustomMessage } from '@planningcenter/jolt-client/dist/types/JoltConnection';
3
3
  import { JoltSubscription } from '@planningcenter/jolt-client/dist/types/JoltSubscription';
4
4
  export declare const useJoltClient: () => JoltClient | undefined;
5
- export declare function useJoltChannel(channelName: string): JoltSubscription | undefined | null;
5
+ export declare function useJoltChannel(channelName: string, enabled?: boolean): JoltSubscription | undefined | null;
6
6
  type UserCallbackFn<T> = (_event: T) => void;
7
7
  export declare function useJoltEvent<T extends CustomMessage>(channel: JoltSubscription | undefined | null, eventName: string, callback: UserCallbackFn<T>): void;
8
8
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"use_jolt.d.ts","sourceRoot":"","sources":["../../src/hooks/use_jolt.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,6BAA6B,CAAA;AACpD,OAAO,EACL,aAAa,EAEd,MAAM,uDAAuD,CAAA;AAC9D,OAAO,EAEL,gBAAgB,EACjB,MAAM,yDAAyD,CAAA;AAchE,eAAO,MAAM,aAAa,QAAO,UAAU,GAAG,SAiG7C,CAAA;AAED,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,GAAG,IAAI,CAuCvF;AAED,KAAK,cAAc,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,KAAK,IAAI,CAAA;AAE5C,wBAAgB,YAAY,CAAC,CAAC,SAAS,aAAa,EAClD,OAAO,EAAE,gBAAgB,GAAG,SAAS,GAAG,IAAI,EAC5C,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,cAAc,CAAC,CAAC,CAAC,QAO5B"}
1
+ {"version":3,"file":"use_jolt.d.ts","sourceRoot":"","sources":["../../src/hooks/use_jolt.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,6BAA6B,CAAA;AACpD,OAAO,EACL,aAAa,EAEd,MAAM,uDAAuD,CAAA;AAC9D,OAAO,EAEL,gBAAgB,EACjB,MAAM,yDAAyD,CAAA;AAchE,eAAO,MAAM,aAAa,QAAO,UAAU,GAAG,SAiG7C,CAAA;AAED,wBAAgB,cAAc,CAC5B,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,OAAc,GACtB,gBAAgB,GAAG,SAAS,GAAG,IAAI,CAwCrC;AAED,KAAK,cAAc,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,KAAK,IAAI,CAAA;AAE5C,wBAAgB,YAAY,CAAC,CAAC,SAAS,aAAa,EAClD,OAAO,EAAE,gBAAgB,GAAG,SAAS,GAAG,IAAI,EAC5C,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,cAAc,CAAC,CAAC,CAAC,QAO5B"}
@@ -83,10 +83,11 @@ export const useJoltClient = () => {
83
83
  });
84
84
  return joltClient;
85
85
  };
86
- export function useJoltChannel(channelName) {
86
+ export function useJoltChannel(channelName, enabled = true) {
87
87
  const jolt = useJoltClient();
88
88
  const appState = useAppState();
89
89
  const queryClient = useQueryClient();
90
+ const ready = Boolean(jolt) && appState !== 'background' && enabled;
90
91
  const handleSubscribe = useCallback(async () => {
91
92
  if (!jolt)
92
93
  return null;
@@ -99,7 +100,7 @@ export function useJoltChannel(channelName) {
99
100
  const { data: subscription } = useQuery({
100
101
  queryKey: ['jolt-subscription', channelName],
101
102
  queryFn: handleSubscribe,
102
- enabled: Boolean(jolt) && appState !== 'background',
103
+ enabled: ready,
103
104
  });
104
105
  const handleUnsubscribe = useCallback(() => {
105
106
  queryClient.removeQueries({
@@ -1 +1 @@
1
- {"version":3,"file":"use_jolt.js","sourceRoot":"","sources":["../../src/hooks/use_jolt.ts"],"names":[],"mappings":"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","sourcesContent":["import JoltClient from '@planningcenter/jolt-client'\nimport {\n CustomMessage,\n FetchAuthToken,\n} from '@planningcenter/jolt-client/dist/types/JoltConnection'\nimport {\n FetchSubscribeToken,\n JoltSubscription,\n} from '@planningcenter/jolt-client/dist/types/JoltSubscription'\nimport { useQuery, useQueryClient, useSuspenseQuery } from '@tanstack/react-query'\nimport { useCallback, useEffect, useMemo } from 'react'\nimport { useChatContext } from '../contexts/chat_context'\nimport { ApiResource } from '../types'\nimport { Client, Uri } from '../utils'\nimport { useAppState } from './use_app_state'\n\ninterface JoltResponse {\n type: 'JoltToken'\n id: string\n wssUrl: string\n}\n\nexport const useJoltClient = (): JoltClient | undefined => {\n const { session } = useChatContext()\n const sessionAccessToken = session.token?.access_token\n const queryClient = useQueryClient()\n const uri = useMemo(() => new Uri({ session }), [session])\n const apiClient = useMemo(\n () =>\n new Client({\n root: uri.api(`/chat/v2`),\n defaultHeaders: uri.headers,\n version: '2018-11-01',\n }),\n [uri]\n )\n\n const fetchJoltToken = async () => {\n return queryClient.fetchQuery({\n queryKey: ['jolt-token'],\n queryFn: () => {\n return apiClient.post<ApiResource<JoltResponse>>({\n url: '/me/jolt_authorize',\n data: {\n data: {\n type: 'JoltToken',\n attributes: {},\n },\n },\n })\n },\n staleTime: 5000, // 5 seconds\n })\n }\n\n /** The wssUrl is static and doesn't change so we can cache it to infinity and beyond */\n const { data: wssUrl } = useSuspenseQuery<string>({\n queryKey: ['wss-url'],\n queryFn: () => fetchJoltToken().then(res => res.data.wssUrl),\n staleTime: Infinity,\n gcTime: Infinity,\n })\n\n const fetchAuthTokenFn: FetchAuthToken = () => {\n return queryClient.fetchQuery({\n queryKey: ['jolt-auth-token'],\n queryFn: () => fetchJoltToken().then(res => res.data.id),\n })\n }\n\n const fetchSubscribeToken: FetchSubscribeToken = (channel: string, connectionId: string) => {\n return apiClient\n .post<ApiResource<JoltResponse>>({\n url: '/me/jolt_subscribe',\n data: {\n data: {\n type: 'JoltSubscribeToken',\n attributes: { channel, cid: connectionId },\n },\n },\n })\n .then(res => res.data.id)\n .catch((res: unknown) => {\n console.error('failed to subscribe to Jolt channel', res)\n return ''\n })\n }\n\n const fetchSubscribeTokenFn: FetchSubscribeToken = (\n channel: string,\n connectionId: string,\n options\n ) => {\n return queryClient.fetchQuery({\n queryKey: ['jolt-subscribe-token', channel, connectionId],\n queryFn: () => fetchSubscribeToken(channel, connectionId, options),\n })\n }\n\n const { data: joltClient } = useQuery({\n enabled: Boolean(wssUrl),\n queryKey: ['jolt-client', wssUrl, sessionAccessToken],\n queryFn: async () => {\n if (!wssUrl) return undefined\n\n return new JoltClient(\n wssUrl,\n {\n fetchAuthTokenFn,\n fetchSubscribeTokenFn,\n },\n { logToConsole: false }\n )\n },\n staleTime: Infinity,\n gcTime: Infinity,\n })\n\n return joltClient\n}\n\nexport function useJoltChannel(channelName: string): JoltSubscription | undefined | null {\n const jolt = useJoltClient()\n const appState = useAppState()\n const queryClient = useQueryClient()\n\n const handleSubscribe = useCallback(async () => {\n if (!jolt) return null\n\n // If the subscription already exists, we don't need to subscribe again\n const alreadySubscribed = jolt.subscriptions.find(c => c.channel === channelName)\n if (alreadySubscribed) return alreadySubscribed\n\n return jolt.subscribe(channelName)\n }, [channelName, jolt])\n\n const { data: subscription } = useQuery<JoltSubscription | null>({\n queryKey: ['jolt-subscription', channelName],\n queryFn: handleSubscribe,\n enabled: Boolean(jolt) && appState !== 'background',\n })\n\n const handleUnsubscribe = useCallback(() => {\n queryClient.removeQueries({\n queryKey: ['jolt-subscription', channelName],\n exact: true,\n })\n\n jolt?.unsubscribe(channelName)\n }, [queryClient, channelName, jolt])\n\n useEffect(() => {\n if (appState !== 'background') return handleUnsubscribe\n\n handleUnsubscribe()\n\n return () => null\n }, [appState, handleUnsubscribe])\n\n return subscription\n}\n\ntype UserCallbackFn<T> = (_event: T) => void\n\nexport function useJoltEvent<T extends CustomMessage>(\n channel: JoltSubscription | undefined | null,\n eventName: string,\n callback: UserCallbackFn<T>\n) {\n useEffect(() => {\n if (!channel) return () => {}\n\n return channel.bind(eventName, e => callback(e as T))\n }, [channel, eventName, callback])\n}\n"]}
1
+ {"version":3,"file":"use_jolt.js","sourceRoot":"","sources":["../../src/hooks/use_jolt.ts"],"names":[],"mappings":"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","sourcesContent":["import JoltClient from '@planningcenter/jolt-client'\nimport {\n CustomMessage,\n FetchAuthToken,\n} from '@planningcenter/jolt-client/dist/types/JoltConnection'\nimport {\n FetchSubscribeToken,\n JoltSubscription,\n} from '@planningcenter/jolt-client/dist/types/JoltSubscription'\nimport { useQuery, useQueryClient, useSuspenseQuery } from '@tanstack/react-query'\nimport { useCallback, useEffect, useMemo } from 'react'\nimport { useChatContext } from '../contexts/chat_context'\nimport { ApiResource } from '../types'\nimport { Client, Uri } from '../utils'\nimport { useAppState } from './use_app_state'\n\ninterface JoltResponse {\n type: 'JoltToken'\n id: string\n wssUrl: string\n}\n\nexport const useJoltClient = (): JoltClient | undefined => {\n const { session } = useChatContext()\n const sessionAccessToken = session.token?.access_token\n const queryClient = useQueryClient()\n const uri = useMemo(() => new Uri({ session }), [session])\n const apiClient = useMemo(\n () =>\n new Client({\n root: uri.api(`/chat/v2`),\n defaultHeaders: uri.headers,\n version: '2018-11-01',\n }),\n [uri]\n )\n\n const fetchJoltToken = async () => {\n return queryClient.fetchQuery({\n queryKey: ['jolt-token'],\n queryFn: () => {\n return apiClient.post<ApiResource<JoltResponse>>({\n url: '/me/jolt_authorize',\n data: {\n data: {\n type: 'JoltToken',\n attributes: {},\n },\n },\n })\n },\n staleTime: 5000, // 5 seconds\n })\n }\n\n /** The wssUrl is static and doesn't change so we can cache it to infinity and beyond */\n const { data: wssUrl } = useSuspenseQuery<string>({\n queryKey: ['wss-url'],\n queryFn: () => fetchJoltToken().then(res => res.data.wssUrl),\n staleTime: Infinity,\n gcTime: Infinity,\n })\n\n const fetchAuthTokenFn: FetchAuthToken = () => {\n return queryClient.fetchQuery({\n queryKey: ['jolt-auth-token'],\n queryFn: () => fetchJoltToken().then(res => res.data.id),\n })\n }\n\n const fetchSubscribeToken: FetchSubscribeToken = (channel: string, connectionId: string) => {\n return apiClient\n .post<ApiResource<JoltResponse>>({\n url: '/me/jolt_subscribe',\n data: {\n data: {\n type: 'JoltSubscribeToken',\n attributes: { channel, cid: connectionId },\n },\n },\n })\n .then(res => res.data.id)\n .catch((res: unknown) => {\n console.error('failed to subscribe to Jolt channel', res)\n return ''\n })\n }\n\n const fetchSubscribeTokenFn: FetchSubscribeToken = (\n channel: string,\n connectionId: string,\n options\n ) => {\n return queryClient.fetchQuery({\n queryKey: ['jolt-subscribe-token', channel, connectionId],\n queryFn: () => fetchSubscribeToken(channel, connectionId, options),\n })\n }\n\n const { data: joltClient } = useQuery({\n enabled: Boolean(wssUrl),\n queryKey: ['jolt-client', wssUrl, sessionAccessToken],\n queryFn: async () => {\n if (!wssUrl) return undefined\n\n return new JoltClient(\n wssUrl,\n {\n fetchAuthTokenFn,\n fetchSubscribeTokenFn,\n },\n { logToConsole: false }\n )\n },\n staleTime: Infinity,\n gcTime: Infinity,\n })\n\n return joltClient\n}\n\nexport function useJoltChannel(\n channelName: string,\n enabled: boolean = true\n): JoltSubscription | undefined | null {\n const jolt = useJoltClient()\n const appState = useAppState()\n const queryClient = useQueryClient()\n const ready = Boolean(jolt) && appState !== 'background' && enabled\n\n const handleSubscribe = useCallback(async () => {\n if (!jolt) return null\n\n // If the subscription already exists, we don't need to subscribe again\n const alreadySubscribed = jolt.subscriptions.find(c => c.channel === channelName)\n if (alreadySubscribed) return alreadySubscribed\n\n return jolt.subscribe(channelName)\n }, [channelName, jolt])\n\n const { data: subscription } = useQuery<JoltSubscription | null>({\n queryKey: ['jolt-subscription', channelName],\n queryFn: handleSubscribe,\n enabled: ready,\n })\n\n const handleUnsubscribe = useCallback(() => {\n queryClient.removeQueries({\n queryKey: ['jolt-subscription', channelName],\n exact: true,\n })\n\n jolt?.unsubscribe(channelName)\n }, [queryClient, channelName, jolt])\n\n useEffect(() => {\n if (appState !== 'background') return handleUnsubscribe\n\n handleUnsubscribe()\n\n return () => null\n }, [appState, handleUnsubscribe])\n\n return subscription\n}\n\ntype UserCallbackFn<T> = (_event: T) => void\n\nexport function useJoltEvent<T extends CustomMessage>(\n channel: JoltSubscription | undefined | null,\n eventName: string,\n callback: UserCallbackFn<T>\n) {\n useEffect(() => {\n if (!channel) return () => {}\n\n return channel.bind(eventName, e => callback(e as T))\n }, [channel, eventName, callback])\n}\n"]}
@@ -0,0 +1,13 @@
1
+ import type { FileAttachment } from '../types/resources/denormalized_attachment_resource_for_create';
2
+ interface MessageDraft {
3
+ text: string;
4
+ attachments: FileAttachment[];
5
+ lastUpdated: number;
6
+ }
7
+ export declare function useMessageDraft(conversationId: number): {
8
+ draft: MessageDraft | null;
9
+ saveDraft: (text: string, attachments: FileAttachment[]) => void;
10
+ clearDraft: () => void;
11
+ };
12
+ export {};
13
+ //# sourceMappingURL=use_message_draft.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use_message_draft.d.ts","sourceRoot":"","sources":["../../src/hooks/use_message_draft.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gEAAgE,CAAA;AAEpG,UAAU,YAAY;IACpB,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,cAAc,EAAE,CAAA;IAC7B,WAAW,EAAE,MAAM,CAAA;CACpB;AAQD,wBAAgB,eAAe,CAAC,cAAc,EAAE,MAAM;;sBAyC3C,MAAM,eAAe,cAAc,EAAE;;EAkD/C"}
@@ -0,0 +1,83 @@
1
+ import { useCallback, useEffect, useState } from 'react';
2
+ import { useAsyncStorage } from './use_async_storage';
3
+ const DRAFT_STORAGE_KEY = 'chat_message_drafts';
4
+ const DRAFT_EXPIRY_HOURS = 720; // 30 days
5
+ const DRAFT_EXPIRY_MS = DRAFT_EXPIRY_HOURS * 60 * 60 * 1000;
6
+ const ATTACHMENT_EXPIRY_HOURS = 48; // 2 days
7
+ const ATTACHMENT_EXPIRY_MS = ATTACHMENT_EXPIRY_HOURS * 60 * 60 * 1000;
8
+ export function useMessageDraft(conversationId) {
9
+ const conversationKey = conversationId.toString();
10
+ const [allDrafts, setAllDrafts] = useAsyncStorage(DRAFT_STORAGE_KEY, {});
11
+ const [draft, setDraft] = useState(() => {
12
+ const storedDraft = allDrafts[conversationKey] || null;
13
+ // Filter expired attachments
14
+ if (storedDraft?.attachments) {
15
+ const now = Date.now();
16
+ storedDraft.attachments = storedDraft.attachments.filter(attachment => {
17
+ if (!attachment.uploadedAt)
18
+ return false;
19
+ return now - attachment.uploadedAt < ATTACHMENT_EXPIRY_MS;
20
+ });
21
+ }
22
+ return storedDraft;
23
+ });
24
+ // Clean up expired drafts on mount
25
+ useEffect(() => {
26
+ const now = Date.now();
27
+ const validDrafts = {};
28
+ Object.entries(allDrafts).forEach(([id, draftData]) => {
29
+ if (draftData?.lastUpdated && now - draftData.lastUpdated < DRAFT_EXPIRY_MS) {
30
+ validDrafts[id] = draftData;
31
+ }
32
+ });
33
+ // If we filtered out any drafts, update storage
34
+ if (Object.keys(validDrafts).length !== Object.keys(allDrafts).length) {
35
+ setAllDrafts(validDrafts);
36
+ }
37
+ // eslint-disable-next-line react-hooks/exhaustive-deps
38
+ }, []); // I only want this to run on mount
39
+ const saveDraft = useCallback((text, attachments) => {
40
+ const hasContent = text.trim().length > 0 || attachments.length > 0;
41
+ const updatedDrafts = { ...allDrafts };
42
+ if (hasContent) {
43
+ // Convert attachments to serializable format (similar to web implementation)
44
+ const normalizedAttachments = attachments
45
+ .filter(att => att.id && att.status === 'success')
46
+ .map(att => ({
47
+ ...att,
48
+ file: {
49
+ uri: att.file.uri,
50
+ name: att.file.name,
51
+ type: att.file.type,
52
+ size: att.file.size,
53
+ width: att.file.width,
54
+ height: att.file.height,
55
+ },
56
+ }));
57
+ const newDraft = {
58
+ text,
59
+ attachments: normalizedAttachments,
60
+ lastUpdated: Date.now(),
61
+ };
62
+ updatedDrafts[conversationKey] = newDraft;
63
+ setDraft(newDraft);
64
+ }
65
+ else {
66
+ delete updatedDrafts[conversationKey];
67
+ setDraft(null);
68
+ }
69
+ setAllDrafts(updatedDrafts);
70
+ }, [conversationKey, allDrafts, setAllDrafts]);
71
+ const clearDraft = useCallback(() => {
72
+ const updatedDrafts = { ...allDrafts };
73
+ delete updatedDrafts[conversationKey];
74
+ setAllDrafts(updatedDrafts);
75
+ setDraft(null);
76
+ }, [conversationKey, allDrafts, setAllDrafts]);
77
+ return {
78
+ draft,
79
+ saveDraft,
80
+ clearDraft,
81
+ };
82
+ }
83
+ //# sourceMappingURL=use_message_draft.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use_message_draft.js","sourceRoot":"","sources":["../../src/hooks/use_message_draft.ts"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useEffect, useState } from 'react'\nimport { useAsyncStorage } from './use_async_storage'\nimport type { FileAttachment } from '../types/resources/denormalized_attachment_resource_for_create'\n\ninterface MessageDraft {\n text: string\n attachments: FileAttachment[]\n lastUpdated: number\n}\n\nconst DRAFT_STORAGE_KEY = 'chat_message_drafts'\nconst DRAFT_EXPIRY_HOURS = 720 // 30 days\nconst DRAFT_EXPIRY_MS = DRAFT_EXPIRY_HOURS * 60 * 60 * 1000\nconst ATTACHMENT_EXPIRY_HOURS = 48 // 2 days\nconst ATTACHMENT_EXPIRY_MS = ATTACHMENT_EXPIRY_HOURS * 60 * 60 * 1000\n\nexport function useMessageDraft(conversationId: number) {\n const conversationKey = conversationId.toString()\n const [allDrafts, setAllDrafts] = useAsyncStorage<Record<string, MessageDraft>>(\n DRAFT_STORAGE_KEY,\n {}\n )\n\n const [draft, setDraft] = useState<MessageDraft | null>(() => {\n const storedDraft = allDrafts[conversationKey] || null\n\n // Filter expired attachments\n if (storedDraft?.attachments) {\n const now = Date.now()\n storedDraft.attachments = storedDraft.attachments.filter(attachment => {\n if (!attachment.uploadedAt) return false\n return now - attachment.uploadedAt < ATTACHMENT_EXPIRY_MS\n })\n }\n\n return storedDraft\n })\n\n // Clean up expired drafts on mount\n useEffect(() => {\n const now = Date.now()\n const validDrafts: Record<string, MessageDraft> = {}\n\n Object.entries(allDrafts).forEach(([id, draftData]) => {\n if (draftData?.lastUpdated && now - draftData.lastUpdated < DRAFT_EXPIRY_MS) {\n validDrafts[id] = draftData\n }\n })\n\n // If we filtered out any drafts, update storage\n if (Object.keys(validDrafts).length !== Object.keys(allDrafts).length) {\n setAllDrafts(validDrafts)\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []) // I only want this to run on mount\n\n const saveDraft = useCallback(\n (text: string, attachments: FileAttachment[]) => {\n const hasContent = text.trim().length > 0 || attachments.length > 0\n const updatedDrafts = { ...allDrafts }\n\n if (hasContent) {\n // Convert attachments to serializable format (similar to web implementation)\n const normalizedAttachments = attachments\n .filter(att => att.id && att.status === 'success')\n .map(att => ({\n ...att,\n file: {\n uri: att.file.uri,\n name: att.file.name,\n type: att.file.type,\n size: att.file.size,\n width: att.file.width,\n height: att.file.height,\n },\n }))\n\n const newDraft: MessageDraft = {\n text,\n attachments: normalizedAttachments,\n lastUpdated: Date.now(),\n }\n\n updatedDrafts[conversationKey] = newDraft\n setDraft(newDraft)\n } else {\n delete updatedDrafts[conversationKey]\n setDraft(null)\n }\n\n setAllDrafts(updatedDrafts)\n },\n [conversationKey, allDrafts, setAllDrafts]\n )\n\n const clearDraft = useCallback(() => {\n const updatedDrafts = { ...allDrafts }\n delete updatedDrafts[conversationKey]\n setAllDrafts(updatedDrafts)\n setDraft(null)\n }, [conversationKey, allDrafts, setAllDrafts])\n\n return {\n draft,\n saveDraft,\n clearDraft,\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"services_form.d.ts","sourceRoot":"","sources":["../../../../src/screens/conversation_new/components/services_form.tsx"],"names":[],"mappings":"AACA,OAAO,KAAoD,MAAM,OAAO,CAAA;AAYxE,OAAO,EAAE,eAAe,EAAE,MAAM,4CAA4C,CAAA;AAG5E,KAAK,iBAAiB,GAAG;IACvB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAA;IACzB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,cAAc,CAAC,EAAE,eAAe,CAAA;CACjC,CAAA;AAED,eAAO,MAAM,YAAY,uDAItB,iBAAiB,sBA2EnB,CAAA"}
1
+ {"version":3,"file":"services_form.d.ts","sourceRoot":"","sources":["../../../../src/screens/conversation_new/components/services_form.tsx"],"names":[],"mappings":"AACA,OAAO,KAAoD,MAAM,OAAO,CAAA;AAYxE,OAAO,EAAE,eAAe,EAAE,MAAM,4CAA4C,CAAA;AAI5E,KAAK,iBAAiB,GAAG;IACvB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAA;IACzB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,cAAc,CAAC,EAAE,eAAe,CAAA;CACjC,CAAA;AAED,eAAO,MAAM,YAAY,uDAItB,iBAAiB,sBA2EnB,CAAA"}
@@ -11,6 +11,7 @@ import { useTeamMembersForNewConversation } from '../../../hooks/services/use_te
11
11
  import { useFindOrCreateServicesConversation } from '../../../hooks/services/use_find_or_create_services_conversation';
12
12
  import { tokens } from '../../../vendor/tapestry/tokens';
13
13
  import { useServicesTeams } from '../../../hooks/services/use_services_team';
14
+ import { Haptic } from '../../../utils/native_adapters';
14
15
  export const ServicesForm = ({ initialTeamIds, initialPlanId, teamFilterType, }) => {
15
16
  const styles = useStyles();
16
17
  const [selectedPlanId, setSelectedPlanId] = useState(initialPlanId);
@@ -69,14 +70,17 @@ function FormContent({ selectedTeamIds, removeSelection, selectedPlanId, setSele
69
70
  <View style={styles.toSection}>
70
71
  <View style={styles.toSectionRow}>
71
72
  <Heading variant="h3">To:</Heading>
72
- <TextButton accessibilityLabel="Select teams" textStyle={styles.teamCountHeader} onPress={() => navigation.navigate('New', {
73
- screen: 'ConversationFilterRecipients',
74
- params: {
75
- source_app_name: 'Services',
76
- team_ids: selectedTeamIds,
77
- team_filter_type: teamFilterType,
78
- },
79
- })}>
73
+ <TextButton accessibilityLabel="Select teams" textStyle={styles.teamCountHeader} onPress={() => {
74
+ Haptic.impactLight();
75
+ navigation.navigate('New', {
76
+ screen: 'ConversationFilterRecipients',
77
+ params: {
78
+ source_app_name: 'Services',
79
+ team_ids: selectedTeamIds,
80
+ team_filter_type: teamFilterType,
81
+ },
82
+ });
83
+ }}>
80
84
  {teamCountHeader}
81
85
  </TextButton>
82
86
  </View>
@@ -1 +1 @@
1
- {"version":3,"file":"services_form.js","sourceRoot":"","sources":["../../../../src/screens/conversation_new/components/services_form.tsx"],"names":[],"mappings":"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","sourcesContent":["import { StackActions, useNavigation } from '@react-navigation/native'\nimport React, { useCallback, useEffect, useMemo, useState } from 'react'\nimport { StyleSheet, View } from 'react-native'\nimport { Badge, Banner, ChildNotice, Heading, Switch, TextButton } from '../../../components'\nimport { ActionButton } from '../../../components/display/action_button'\nimport { Divider, FormList } from './form_list'\nimport { pluralize } from '../../../utils'\nimport { uniq } from 'lodash'\nimport { FilterByPlan } from './filter_by_plan'\nimport { useTeamMembersForNewConversation } from '../../../hooks/services/use_team_members_for_new_conversation'\nimport { useFindOrCreateServicesConversation } from '../../../hooks/services/use_find_or_create_services_conversation'\nimport { ConversationResource, MemberResource } from '../../../types'\nimport { tokens } from '../../../vendor/tapestry/tokens'\nimport { TeamFilterTypes } from '../../conversation_filter_recipients/types'\nimport { useServicesTeams } from '../../../hooks/services/use_services_team'\n\ntype ServicesFormProps = {\n initialTeamIds?: number[]\n initialPlanId?: number\n teamFilterType?: TeamFilterTypes\n}\n\nexport const ServicesForm = ({\n initialTeamIds,\n initialPlanId,\n teamFilterType,\n}: ServicesFormProps) => {\n const styles = useStyles()\n const [selectedPlanId, setSelectedPlanId] = useState<number | undefined>(initialPlanId)\n const initialState = useMemo(() => uniq(initialTeamIds) || [], [initialTeamIds]) // Uniq here because services can send duplicates in the teams_i_lead response.\n const [selectedTeamIds, setSelectedTeamIds] = useState<number[]>(initialState)\n\n // Sync with fresh props when they change (from navigation)\n useEffect(() => {\n setSelectedTeamIds(initialState)\n setSelectedPlanId(initialPlanId)\n }, [initialState, initialPlanId])\n\n const removeSelection = useCallback(\n (teamId: number) => {\n setSelectedTeamIds(selectedTeamIds.filter(id => id !== teamId))\n },\n [selectedTeamIds]\n )\n\n const [filerByPlan, setFilterByPlan] = useState(false)\n\n const { members, isError: isMemberError } = useTeamMembersForNewConversation({\n teamIds: selectedTeamIds,\n planId: selectedPlanId,\n })\n const adultMembers = useMemo(() => members.filter(member => !member.child), [members])\n\n const navigation = useNavigation()\n const {\n mutate: createConversation,\n isPending,\n selectionHasConversation,\n isLoadingConversationCheck,\n } = useFindOrCreateServicesConversation({\n teamIds: selectedTeamIds,\n planId: filerByPlan ? selectedPlanId : undefined,\n onSuccess: (conversation: ConversationResource) => {\n // exit from the create stack\n navigation.getParent()?.goBack()\n // navigate to the conversation screen\n navigation.dispatch(\n StackActions.push('Conversation', {\n conversation_id: conversation.id,\n })\n )\n },\n })\n\n return (\n <View style={styles.formContainer}>\n <FormList\n memberData={adultMembers}\n FormContent={\n <FormContent\n selectedTeamIds={selectedTeamIds}\n removeSelection={removeSelection}\n selectedPlanId={selectedPlanId}\n setSelectedPlanId={setSelectedPlanId}\n filterByPlan={filerByPlan}\n setFilterByPlan={setFilterByPlan}\n members={members}\n isMemberError={isMemberError}\n teamFilterType={teamFilterType}\n />\n }\n />\n <ActionButton\n disabled={!selectedTeamIds.length || isPending || isLoadingConversationCheck}\n title={selectionHasConversation ? 'Open conversation' : 'Start conversation'}\n onPress={createConversation}\n infoText=\"Conversation will be automatically updated if any members are added or removed from included teams.\"\n loading={isLoadingConversationCheck}\n />\n </View>\n )\n}\n\ninterface FormContentProps {\n selectedTeamIds: number[]\n removeSelection: (teamId: number) => void\n selectedPlanId?: number\n setSelectedPlanId: (planId: number | undefined) => void\n filterByPlan: boolean\n setFilterByPlan: (value: boolean) => void\n members: MemberResource[]\n isMemberError: boolean\n teamFilterType?: TeamFilterTypes\n}\n\nfunction FormContent({\n selectedTeamIds,\n removeSelection,\n selectedPlanId,\n setSelectedPlanId,\n filterByPlan,\n setFilterByPlan,\n members,\n isMemberError,\n teamFilterType,\n}: FormContentProps) {\n const navigation = useNavigation()\n const servicesTeams = useServicesTeams()\n const selectedTeams = useMemo(() => {\n return servicesTeams.filter(team => selectedTeamIds.includes(team.value.teamId))\n }, [selectedTeamIds, servicesTeams])\n\n const styles = useStyles()\n const teamCountHeader = pluralize(selectedTeamIds.length, 'team')\n const memberCount = members.length\n const childMembers = useMemo(() => members.filter(member => member.child), [members])\n const hasChildren = childMembers.length > 0\n const multipleServiceTypes =\n uniq(selectedTeams.flatMap(team => team.value.serviceTypeIds)).length > 1\n const firstTeamId = selectedTeamIds[0]\n const serviceTypeName =\n !multipleServiceTypes && selectedTeams.length > 0\n ? selectedTeams[0].serviceTypeNames[0]\n : undefined\n\n filterByPlan = filterByPlan && !!firstTeamId && !multipleServiceTypes\n\n return (\n <View style={styles.formContent}>\n <View style={styles.toSection}>\n <View style={styles.toSectionRow}>\n <Heading variant=\"h3\">To:</Heading>\n <TextButton\n accessibilityLabel=\"Select teams\"\n textStyle={styles.teamCountHeader}\n onPress={() =>\n navigation.navigate('New', {\n screen: 'ConversationFilterRecipients',\n params: {\n source_app_name: 'Services',\n team_ids: selectedTeamIds,\n team_filter_type: teamFilterType,\n },\n })\n }\n >\n {teamCountHeader}\n </TextButton>\n </View>\n <View style={styles.toSectionRow}>\n {selectedTeams.map(team => {\n const serviceTypeAcronyms = team.serviceTypeAcronyms.join(', ')\n const label = `${team.teamName} | ${serviceTypeAcronyms}`\n\n return (\n <View key={team.value.teamId} style={styles.badgeRow}>\n <Badge\n iconNameRight=\"general.x\"\n label={label}\n onPress={() => removeSelection(team.value.teamId)}\n />\n </View>\n )\n })}\n </View>\n </View>\n <Divider />\n <View style={styles.filterByPlanSection}>\n <View style={styles.filterByPlanSectionLead}>\n <Heading variant=\"h3\">Filter by plan</Heading>\n <Switch\n value={filterByPlan}\n onValueChange={value => {\n setFilterByPlan(value)\n if (!value) {\n setSelectedPlanId(undefined)\n }\n }}\n disabled={multipleServiceTypes}\n />\n </View>\n {multipleServiceTypes ? (\n <Banner\n appearance=\"neutral\"\n description=\"Plan filtering is not possible using teams from multiple service types. Try choosing teams above with only one service type.\"\n />\n ) : filterByPlan ? (\n <FilterByPlan\n teamIds={selectedTeamIds}\n selectedPlanId={selectedPlanId}\n serviceTypeName={serviceTypeName}\n onPlanSelect={setSelectedPlanId}\n />\n ) : null}\n </View>\n <Divider />\n <View style={styles.memberSection}>\n <Heading variant=\"h3\">{pluralize(memberCount, 'member')} selected</Heading>\n {hasChildren && (\n <ChildNotice childMembers={childMembers} showMembers={true} style={styles.banner} />\n )}\n {isMemberError && (\n <Banner\n appearance=\"error\"\n description=\"There was an issue loading team members, please refresh and try again.\"\n style={styles.banner}\n />\n )}\n </View>\n </View>\n )\n}\n\nconst useStyles = () => {\n const sectionPadding = 16\n\n return StyleSheet.create({\n formContainer: {\n flex: 1,\n },\n formContent: {\n paddingBottom: sectionPadding,\n flex: 1,\n },\n toSection: {\n padding: sectionPadding,\n gap: 8,\n },\n toSectionRow: {\n flexDirection: 'row',\n gap: 8,\n alignItems: 'baseline',\n flexWrap: 'wrap',\n },\n badgeRow: {\n flexDirection: 'row',\n },\n groupName: {\n fontSize: 18,\n },\n filterByPlanSection: {\n padding: sectionPadding,\n gap: 12,\n },\n filterByPlanSectionLead: {\n flexDirection: 'row',\n gap: 8,\n justifyContent: 'space-between',\n alignItems: 'center',\n },\n filterByPlanSectionRow: {\n flexDirection: 'row',\n gap: 8,\n },\n titleInput: {\n fontSize: 18,\n },\n memberSection: {\n padding: sectionPadding,\n paddingBottom: 0,\n },\n banner: {\n marginTop: 16,\n },\n teamCountHeader: {\n fontSize: tokens.fontSizeLg,\n },\n })\n}\n"]}
1
+ {"version":3,"file":"services_form.js","sourceRoot":"","sources":["../../../../src/screens/conversation_new/components/services_form.tsx"],"names":[],"mappings":"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","sourcesContent":["import { StackActions, useNavigation } from '@react-navigation/native'\nimport React, { useCallback, useEffect, useMemo, useState } from 'react'\nimport { StyleSheet, View } from 'react-native'\nimport { Badge, Banner, ChildNotice, Heading, Switch, TextButton } from '../../../components'\nimport { ActionButton } from '../../../components/display/action_button'\nimport { Divider, FormList } from './form_list'\nimport { pluralize } from '../../../utils'\nimport { uniq } from 'lodash'\nimport { FilterByPlan } from './filter_by_plan'\nimport { useTeamMembersForNewConversation } from '../../../hooks/services/use_team_members_for_new_conversation'\nimport { useFindOrCreateServicesConversation } from '../../../hooks/services/use_find_or_create_services_conversation'\nimport { ConversationResource, MemberResource } from '../../../types'\nimport { tokens } from '../../../vendor/tapestry/tokens'\nimport { TeamFilterTypes } from '../../conversation_filter_recipients/types'\nimport { useServicesTeams } from '../../../hooks/services/use_services_team'\nimport { Haptic } from '../../../utils/native_adapters'\n\ntype ServicesFormProps = {\n initialTeamIds?: number[]\n initialPlanId?: number\n teamFilterType?: TeamFilterTypes\n}\n\nexport const ServicesForm = ({\n initialTeamIds,\n initialPlanId,\n teamFilterType,\n}: ServicesFormProps) => {\n const styles = useStyles()\n const [selectedPlanId, setSelectedPlanId] = useState<number | undefined>(initialPlanId)\n const initialState = useMemo(() => uniq(initialTeamIds) || [], [initialTeamIds]) // Uniq here because services can send duplicates in the teams_i_lead response.\n const [selectedTeamIds, setSelectedTeamIds] = useState<number[]>(initialState)\n\n // Sync with fresh props when they change (from navigation)\n useEffect(() => {\n setSelectedTeamIds(initialState)\n setSelectedPlanId(initialPlanId)\n }, [initialState, initialPlanId])\n\n const removeSelection = useCallback(\n (teamId: number) => {\n setSelectedTeamIds(selectedTeamIds.filter(id => id !== teamId))\n },\n [selectedTeamIds]\n )\n\n const [filerByPlan, setFilterByPlan] = useState(false)\n\n const { members, isError: isMemberError } = useTeamMembersForNewConversation({\n teamIds: selectedTeamIds,\n planId: selectedPlanId,\n })\n const adultMembers = useMemo(() => members.filter(member => !member.child), [members])\n\n const navigation = useNavigation()\n const {\n mutate: createConversation,\n isPending,\n selectionHasConversation,\n isLoadingConversationCheck,\n } = useFindOrCreateServicesConversation({\n teamIds: selectedTeamIds,\n planId: filerByPlan ? selectedPlanId : undefined,\n onSuccess: (conversation: ConversationResource) => {\n // exit from the create stack\n navigation.getParent()?.goBack()\n // navigate to the conversation screen\n navigation.dispatch(\n StackActions.push('Conversation', {\n conversation_id: conversation.id,\n })\n )\n },\n })\n\n return (\n <View style={styles.formContainer}>\n <FormList\n memberData={adultMembers}\n FormContent={\n <FormContent\n selectedTeamIds={selectedTeamIds}\n removeSelection={removeSelection}\n selectedPlanId={selectedPlanId}\n setSelectedPlanId={setSelectedPlanId}\n filterByPlan={filerByPlan}\n setFilterByPlan={setFilterByPlan}\n members={members}\n isMemberError={isMemberError}\n teamFilterType={teamFilterType}\n />\n }\n />\n <ActionButton\n disabled={!selectedTeamIds.length || isPending || isLoadingConversationCheck}\n title={selectionHasConversation ? 'Open conversation' : 'Start conversation'}\n onPress={createConversation}\n infoText=\"Conversation will be automatically updated if any members are added or removed from included teams.\"\n loading={isLoadingConversationCheck}\n />\n </View>\n )\n}\n\ninterface FormContentProps {\n selectedTeamIds: number[]\n removeSelection: (teamId: number) => void\n selectedPlanId?: number\n setSelectedPlanId: (planId: number | undefined) => void\n filterByPlan: boolean\n setFilterByPlan: (value: boolean) => void\n members: MemberResource[]\n isMemberError: boolean\n teamFilterType?: TeamFilterTypes\n}\n\nfunction FormContent({\n selectedTeamIds,\n removeSelection,\n selectedPlanId,\n setSelectedPlanId,\n filterByPlan,\n setFilterByPlan,\n members,\n isMemberError,\n teamFilterType,\n}: FormContentProps) {\n const navigation = useNavigation()\n const servicesTeams = useServicesTeams()\n const selectedTeams = useMemo(() => {\n return servicesTeams.filter(team => selectedTeamIds.includes(team.value.teamId))\n }, [selectedTeamIds, servicesTeams])\n\n const styles = useStyles()\n const teamCountHeader = pluralize(selectedTeamIds.length, 'team')\n const memberCount = members.length\n const childMembers = useMemo(() => members.filter(member => member.child), [members])\n const hasChildren = childMembers.length > 0\n const multipleServiceTypes =\n uniq(selectedTeams.flatMap(team => team.value.serviceTypeIds)).length > 1\n const firstTeamId = selectedTeamIds[0]\n const serviceTypeName =\n !multipleServiceTypes && selectedTeams.length > 0\n ? selectedTeams[0].serviceTypeNames[0]\n : undefined\n\n filterByPlan = filterByPlan && !!firstTeamId && !multipleServiceTypes\n\n return (\n <View style={styles.formContent}>\n <View style={styles.toSection}>\n <View style={styles.toSectionRow}>\n <Heading variant=\"h3\">To:</Heading>\n <TextButton\n accessibilityLabel=\"Select teams\"\n textStyle={styles.teamCountHeader}\n onPress={() => {\n Haptic.impactLight()\n navigation.navigate('New', {\n screen: 'ConversationFilterRecipients',\n params: {\n source_app_name: 'Services',\n team_ids: selectedTeamIds,\n team_filter_type: teamFilterType,\n },\n })\n }}\n >\n {teamCountHeader}\n </TextButton>\n </View>\n <View style={styles.toSectionRow}>\n {selectedTeams.map(team => {\n const serviceTypeAcronyms = team.serviceTypeAcronyms.join(', ')\n const label = `${team.teamName} | ${serviceTypeAcronyms}`\n\n return (\n <View key={team.value.teamId} style={styles.badgeRow}>\n <Badge\n iconNameRight=\"general.x\"\n label={label}\n onPress={() => removeSelection(team.value.teamId)}\n />\n </View>\n )\n })}\n </View>\n </View>\n <Divider />\n <View style={styles.filterByPlanSection}>\n <View style={styles.filterByPlanSectionLead}>\n <Heading variant=\"h3\">Filter by plan</Heading>\n <Switch\n value={filterByPlan}\n onValueChange={value => {\n setFilterByPlan(value)\n if (!value) {\n setSelectedPlanId(undefined)\n }\n }}\n disabled={multipleServiceTypes}\n />\n </View>\n {multipleServiceTypes ? (\n <Banner\n appearance=\"neutral\"\n description=\"Plan filtering is not possible using teams from multiple service types. Try choosing teams above with only one service type.\"\n />\n ) : filterByPlan ? (\n <FilterByPlan\n teamIds={selectedTeamIds}\n selectedPlanId={selectedPlanId}\n serviceTypeName={serviceTypeName}\n onPlanSelect={setSelectedPlanId}\n />\n ) : null}\n </View>\n <Divider />\n <View style={styles.memberSection}>\n <Heading variant=\"h3\">{pluralize(memberCount, 'member')} selected</Heading>\n {hasChildren && (\n <ChildNotice childMembers={childMembers} showMembers={true} style={styles.banner} />\n )}\n {isMemberError && (\n <Banner\n appearance=\"error\"\n description=\"There was an issue loading team members, please refresh and try again.\"\n style={styles.banner}\n />\n )}\n </View>\n </View>\n )\n}\n\nconst useStyles = () => {\n const sectionPadding = 16\n\n return StyleSheet.create({\n formContainer: {\n flex: 1,\n },\n formContent: {\n paddingBottom: sectionPadding,\n flex: 1,\n },\n toSection: {\n padding: sectionPadding,\n gap: 8,\n },\n toSectionRow: {\n flexDirection: 'row',\n gap: 8,\n alignItems: 'baseline',\n flexWrap: 'wrap',\n },\n badgeRow: {\n flexDirection: 'row',\n },\n groupName: {\n fontSize: 18,\n },\n filterByPlanSection: {\n padding: sectionPadding,\n gap: 12,\n },\n filterByPlanSectionLead: {\n flexDirection: 'row',\n gap: 8,\n justifyContent: 'space-between',\n alignItems: 'center',\n },\n filterByPlanSectionRow: {\n flexDirection: 'row',\n gap: 8,\n },\n titleInput: {\n fontSize: 18,\n },\n memberSection: {\n padding: sectionPadding,\n paddingBottom: 0,\n },\n banner: {\n marginTop: 16,\n },\n teamCountHeader: {\n fontSize: tokens.fontSizeLg,\n },\n })\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"conversation_screen.d.ts","sourceRoot":"","sources":["../../src/screens/conversation_screen.tsx"],"names":[],"mappings":"AACA,OAAO,EAAe,gBAAgB,EAAqB,MAAM,4BAA4B,CAAA;AAC7F,OAAO,EAGL,iBAAiB,EAIlB,MAAM,0BAA0B,CAAA;AAEjC,OAAO,KAAmD,MAAM,OAAO,CAAA;AAkBvE,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAC1C,OAAO,EAAE,yBAAyB,EAAE,MAAM,uCAAuC,CAAA;AAOjF,MAAM,MAAM,sBAAsB,GAAG;IACnC,eAAe,EAAE,MAAM,CAAA;IACvB,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAClC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,yBAAyB,CAAA;IACjC,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB,CAAA;AAED,MAAM,MAAM,uBAAuB,GAAG,iBAAiB,CAAC,sBAAsB,CAAC,CAAA;AAE/E,wBAAgB,kBAAkB,CAAC,EAAE,KAAK,EAAE,EAAE,uBAAuB,qBAiHpE;AAED,MAAM,MAAM,aAAa,GAAG;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAA;AAwC/E,eAAO,MAAM,aAAa,OAAQ,eAAe,EAAE,wCAkClD,CAAA;AACD,UAAU,4BAA6B,SAAQ,gBAAgB;IAC7D,eAAe,EAAE,MAAM,CAAA;IACvB,KAAK,CAAC,EAAE,yBAAyB,CAAA;IACjC,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,eAAO,MAAM,uBAAuB,0DAMjC,4BAA4B,sBAiC9B,CAAA"}
1
+ {"version":3,"file":"conversation_screen.d.ts","sourceRoot":"","sources":["../../src/screens/conversation_screen.tsx"],"names":[],"mappings":"AACA,OAAO,EAAe,gBAAgB,EAAqB,MAAM,4BAA4B,CAAA;AAC7F,OAAO,EAGL,iBAAiB,EAIlB,MAAM,0BAA0B,CAAA;AAEjC,OAAO,KAAmD,MAAM,OAAO,CAAA;AAkBvE,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAC1C,OAAO,EAAE,yBAAyB,EAAE,MAAM,uCAAuC,CAAA;AAOjF,MAAM,MAAM,sBAAsB,GAAG;IACnC,eAAe,EAAE,MAAM,CAAA;IACvB,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAClC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,yBAAyB,CAAA;IACjC,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB,CAAA;AAED,MAAM,MAAM,uBAAuB,GAAG,iBAAiB,CAAC,sBAAsB,CAAC,CAAA;AAE/E,wBAAgB,kBAAkB,CAAC,EAAE,KAAK,EAAE,EAAE,uBAAuB,qBAwHpE;AAED,MAAM,MAAM,aAAa,GAAG;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAA;AAwC/E,eAAO,MAAM,aAAa,OAAQ,eAAe,EAAE,wCAkClD,CAAA;AACD,UAAU,4BAA6B,SAAQ,gBAAgB;IAC7D,eAAe,EAAE,MAAM,CAAA;IACvB,KAAK,CAAC,EAAE,yBAAyB,CAAA;IACjC,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,eAAO,MAAM,uBAAuB,0DAMjC,4BAA4B,sBAiC9B,CAAA"}
@@ -80,7 +80,12 @@ export function ConversationScreen({ route }) {
80
80
  <JumpToBottomButton onPress={handleReturnToBottom} visible={showJumpToBottomButton}/>
81
81
  {!noMessages && <TypingIndicator conversationId={conversation_id}/>}
82
82
  {showLeaderDisabledReplyBanner && <LeaderDisabledRepliesBanner />}
83
- {canReply ? (<MessageForm.Root conversation={conversation} currentlyEditingMessage={currentlyEditingMessage}>
83
+ {canReply ? (<MessageForm.Root conversation={conversation} currentlyEditingMessage={currentlyEditingMessage}
84
+ // We use a separate key so that it remounts component when switching between new
85
+ // and edit message. This simplifies internal state handling.
86
+ key={currentlyEditingMessage
87
+ ? `edit-message-form-${currentlyEditingMessage.id}`
88
+ : 'new-message-form'}>
84
89
  <MessageForm.AttachmentPicker />
85
90
  <MessageForm.Commands />
86
91
  <MessageForm.TextInput />