@messenger-box/tailwind-ui-inbox 10.0.3-alpha.65 → 10.0.3-alpha.69

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 (38) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/lib/components/InboxMessage/ConversationItem.d.ts +10 -7
  3. package/lib/components/InboxMessage/ConversationItem.d.ts.map +1 -1
  4. package/lib/components/InboxMessage/ConversationItem.js +58 -77
  5. package/lib/components/InboxMessage/ConversationItem.js.map +1 -1
  6. package/lib/components/InboxMessage/LeftSidebar.d.ts +2 -1
  7. package/lib/components/InboxMessage/LeftSidebar.d.ts.map +1 -1
  8. package/lib/components/InboxMessage/LeftSidebar.js +15 -8
  9. package/lib/components/InboxMessage/LeftSidebar.js.map +1 -1
  10. package/lib/components/InboxMessage/MessageInput.d.ts.map +1 -1
  11. package/lib/components/InboxMessage/MessageInput.js +15 -1
  12. package/lib/components/InboxMessage/MessageInput.js.map +1 -1
  13. package/lib/components/InboxMessage/SubscriptionHandler.d.ts +19 -0
  14. package/lib/components/InboxMessage/SubscriptionHandler.d.ts.map +1 -0
  15. package/lib/components/InboxMessage/SubscriptionHandler.js +41 -0
  16. package/lib/components/InboxMessage/SubscriptionHandler.js.map +1 -0
  17. package/lib/container/Inbox.d.ts.map +1 -1
  18. package/lib/container/Inbox.js +49 -28
  19. package/lib/container/Inbox.js.map +1 -1
  20. package/lib/container/InboxWithLoader.d.ts +10 -3
  21. package/lib/container/InboxWithLoader.d.ts.map +1 -1
  22. package/lib/container/InboxWithLoader.js +81 -30
  23. package/lib/container/InboxWithLoader.js.map +1 -1
  24. package/lib/container/ServiceInbox.js +1 -1
  25. package/lib/container/ServiceInbox.js.map +1 -1
  26. package/lib/container/ThreadMessages.js +1 -1
  27. package/lib/container/ThreadMessages.js.map +1 -1
  28. package/lib/container/ThreadMessagesInbox.js +1 -1
  29. package/lib/container/ThreadMessagesInbox.js.map +1 -1
  30. package/lib/container/Threads.js +1 -1
  31. package/lib/container/Threads.js.map +1 -1
  32. package/package.json +4 -4
  33. package/src/components/InboxMessage/ConversationItem.tsx +188 -186
  34. package/src/components/InboxMessage/LeftSidebar.tsx +20 -11
  35. package/src/components/InboxMessage/MessageInput.tsx +16 -1
  36. package/src/components/InboxMessage/SubscriptionHandler.tsx +55 -0
  37. package/src/container/Inbox.tsx +53 -35
  38. package/src/container/InboxWithLoader.tsx +104 -38
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@messenger-box/tailwind-ui-inbox",
3
- "version": "10.0.3-alpha.65",
3
+ "version": "10.0.3-alpha.69",
4
4
  "description": "Inbox UI components built with TailwindCSS",
5
5
  "license": "ISC",
6
6
  "author": "CDMBase LLC",
@@ -21,8 +21,8 @@
21
21
  "watch": "npm run build:lib:watch"
22
22
  },
23
23
  "dependencies": {
24
- "@messenger-box/core": "10.0.3-alpha.62",
25
- "@messenger-box/platform-client": "10.0.3-alpha.62",
24
+ "@messenger-box/core": "10.0.3-alpha.69",
25
+ "@messenger-box/platform-client": "10.0.3-alpha.69",
26
26
  "date-fns": "^4.1.0",
27
27
  "date-fns-tz": "^3.2.0",
28
28
  "emoji-mart": "^3.0.1",
@@ -53,5 +53,5 @@
53
53
  "typescript": {
54
54
  "definition": "lib/index.d.ts"
55
55
  },
56
- "gitHead": "5a23895406c9e1cf5e6d3a91d4604309de9baa42"
56
+ "gitHead": "011fbbc8fbbc97043d73323c02c1ac22b2a01bfb"
57
57
  }
@@ -1,6 +1,7 @@
1
1
  import { useMessagesQuery, OnChatMessageAddedDocument as CHAT_MESSAGE_ADDED } from 'common/graphql';
2
2
  import { format, isToday, isYesterday } from 'date-fns';
3
- import React, { useMemo, useEffect, useState } from 'react';
3
+ import React, { useMemo, useEffect, useState, useCallback } from 'react';
4
+ import { SubscriptionHandler } from './SubscriptionHandler';
4
5
 
5
6
  const createdAtText = (value) => {
6
7
  if (!value) return '';
@@ -20,177 +21,186 @@ const Skeleton = ({ className = '' }) => {
20
21
  );
21
22
  };
22
23
 
23
- export const ConversationItem = ({
24
- showBorder,
25
- currentUser,
26
- filter,
27
- channel,
28
- handleSelectChannel,
29
- users,
30
- selectedChannelId,
31
- }) => {
32
- // const {
33
- // data: messagesQuery,
34
- // loading: messageLoading,
35
- // } = useMessagesQuery({
36
- // variables: {
37
- // channelId: channel.id?.toString(),
38
- // limit: 1,
39
- // },
40
- // fetchPolicy: 'cache-and-network',
41
- // });
42
- const [messages, setMessages] = useState([]);
43
- const {
44
- data: messagesQuery,
45
- loading: messageLoading,
46
- refetch: refetchMessages,
47
- subscribeToMore,
48
- } = useMessagesQuery({
49
- variables: {
50
- channelId: channel?.id?.toString(),
51
- parentId: null,
52
- limit: 10,
53
- // sort: {
54
- // key: 'updatedAt',
55
- // value: SortEnum.Desc,
56
- // },
57
- },
58
- fetchPolicy: 'cache-and-network',
59
- refetchWritePolicy: 'merge',
60
- });
24
+ interface ConversationItemProps {
25
+ showBorder: boolean;
26
+ currentUser: any;
27
+ filter: string;
28
+ channel: any;
29
+ handleSelectChannel: (channelId: string) => void;
30
+ users: any[];
31
+ selectedChannelId: string;
32
+ messagesQuery: any;
33
+ }
61
34
 
62
- // const {
63
- // data: newMessage,
64
- // loading: newMsgLoading,
65
- // error: newMsgError,
66
- // }: any = useOnChatMessageAddedSubscription({
67
- // variables: {
68
- // channelId: channel?.id?.toString(),
69
- // },
70
- // });
71
-
72
- React.useEffect(() => {
73
- refetchMessages({
74
- channelId: channel?.id?.toString(),
75
- parentId: null,
76
- limit: 10,
77
- // sort: {
78
- // key: 'updatedAt',
79
- // value: SortEnum.Desc,
80
- // },
35
+ export const ConversationItem = React.memo(
36
+ ({
37
+ showBorder,
38
+ currentUser,
39
+ filter,
40
+ channel,
41
+ handleSelectChannel,
42
+ users,
43
+ selectedChannelId,
44
+ messagesQuery: messagesQueryProp,
45
+ }: ConversationItemProps) => {
46
+ // const {
47
+ // data: messagesQuery,
48
+ // loading: messageLoading,
49
+ // } = useMessagesQuery({
50
+ // variables: {
51
+ // channelId: channel.id?.toString(),
52
+ // limit: 1,
53
+ // },
54
+ // fetchPolicy: 'cache-and-network',
55
+ // });
56
+ const [messages, setMessages] = useState([]);
57
+ const {
58
+ data: messagesQuery,
59
+ loading: messageLoading,
60
+ refetch: refetchMessages,
61
+ subscribeToMore,
62
+ } = useMessagesQuery({
63
+ variables: {
64
+ channelId: channel?.id?.toString(),
65
+ parentId: null,
66
+ limit: 10,
67
+ // sort: {
68
+ // key: 'updatedAt',
69
+ // value: SortEnum.Desc,
70
+ // },
71
+ },
72
+ fetchPolicy: 'cache-and-network',
73
+ refetchWritePolicy: 'merge',
81
74
  });
82
- }, []);
83
75
 
84
- React.useEffect(() => {
85
- if (messagesQuery) {
86
- if (messagesQuery?.messages?.data?.length) {
87
- setMessages((pre: any) => [...pre, ...messagesQuery?.messages?.data]);
76
+ // const {
77
+ // data: newMessage,
78
+ // loading: newMsgLoading,
79
+ // error: newMsgError,
80
+ // }: any = useOnChatMessageAddedSubscription({
81
+ // variables: {
82
+ // channelId: channel?.id?.toString(),
83
+ // },
84
+ // });
85
+
86
+ React.useEffect(() => {
87
+ if (channel?.id) {
88
+ refetchMessages({
89
+ channelId: channel?.id?.toString(),
90
+ parentId: null,
91
+ limit: 10,
92
+ // sort: {
93
+ // key: 'updatedAt',
94
+ // value: SortEnum.Desc,
95
+ // },
96
+ });
88
97
  }
98
+ }, [channel?.id, refetchMessages]);
89
99
 
90
- // refetchMessages({
91
- // channelId: channel?.id?.toString(),
92
- // parentId: null,
93
- // limit: 10,
94
- // sort: {
95
- // key: 'updatedAt',
96
- // value: SortEnum.Desc,
97
- // },
98
- // });
99
- }
100
- }, [messagesQuery]);
100
+ React.useEffect(() => {
101
+ if (messagesQuery?.messages?.data?.length) {
102
+ setMessages(messagesQuery.messages.data);
103
+ }
104
+ }, [messagesQuery?.messages?.data]);
101
105
 
102
- const chatUser = useMemo(
103
- () =>
104
- users?.find(({ id }) => {
105
- const isNotCurrentUser = id !== currentUser?.id;
106
- if (isNotCurrentUser) {
107
- return channel?.members?.find(({ user }) => user.id === id);
108
- }
109
- return isNotCurrentUser;
110
- }),
111
- [users, currentUser, channel],
112
- );
106
+ const chatUser = useMemo(
107
+ () =>
108
+ users?.find(({ id }) => {
109
+ const isNotCurrentUser = id !== currentUser?.id;
110
+ if (isNotCurrentUser) {
111
+ return channel?.members?.find(({ user }) => user.id === id);
112
+ }
113
+ if (channel?.members?.length === 1 && channel?.members?.[0]?.user?.id === currentUser?.id) {
114
+ return currentUser;
115
+ }
116
+ return isNotCurrentUser;
117
+ }),
118
+ [users, currentUser, channel],
119
+ );
113
120
 
114
- // Last Message
115
- // const lastMessage = useMemo(() => {
116
- // if (!messagesQuery?.messages?.data?.length) {
117
- // return null;
118
- // }
119
- // const { data } = messagesQuery.messages;
120
- // return data[data.length - 1];
121
- // }, [messagesQuery]);
122
- const lastMessage = useMemo(() => {
123
- if (!messages?.length) {
124
- return null;
125
- }
126
- const data = messages;
127
- const filteredData: any = data?.filter((p: any) => p?.message !== '');
121
+ // Last Message
122
+ // const lastMessage = useMemo(() => {
123
+ // if (!messagesQuery?.messages?.data?.length) {
124
+ // return null;
125
+ // }
126
+ // const { data } = messagesQuery.messages;
127
+ // return data[data.length - 1];
128
+ // }, [messagesQuery]);
129
+ const lastMessage = useMemo(() => {
130
+ if (!messages?.length) {
131
+ return null;
132
+ }
133
+ const data = messages;
134
+ const filteredData: any = data?.filter((p: any) => p?.message !== '');
128
135
 
129
- //return filteredData[0];
130
- let filteredLastMessage =
131
- filteredData && filteredData?.length
132
- ? filteredData?.reduce((a, b) => {
133
- return new Date(a?.updatedAt) > new Date(b?.updatedAt) ? a : b;
134
- }, []) ?? null
135
- : null;
136
- return filteredLastMessage;
137
- // //return data[data.length - 1];
138
- }, [messages]);
136
+ //return filteredData[0];
137
+ let filteredLastMessage =
138
+ filteredData && filteredData?.length
139
+ ? filteredData?.reduce((a, b) => {
140
+ return new Date(a?.updatedAt) > new Date(b?.updatedAt) ? a : b;
141
+ }, []) ?? null
142
+ : null;
143
+ return filteredLastMessage;
144
+ // //return data[data.length - 1];
145
+ }, [messages]);
139
146
 
140
- const channelType = useMemo(() => {
141
- return channel?.type;
142
- }, [channel]);
147
+ const channelType = useMemo(() => {
148
+ return channel?.type;
149
+ }, [channel]);
143
150
 
144
- if (
145
- !chatUser?.username.toLowerCase().includes(filter.toLowerCase()) &&
146
- !lastMessage?.message.toLowerCase().includes(filter.toLowerCase())
147
- ) {
148
- return null;
149
- }
151
+ if (
152
+ filter &&
153
+ !chatUser?.username?.toLowerCase().includes(filter.toLowerCase()) &&
154
+ !lastMessage?.message?.toLowerCase().includes(filter.toLowerCase())
155
+ ) {
156
+ return null;
157
+ }
150
158
 
151
- return (
152
- <div
153
- key={`conv_channel_${channel.id}`}
154
- className={`cursor-pointer flex items-center p-3 border-b ${
155
- showBorder ? 'border-gray-300' : 'border-transparent'
156
- } ${
157
- channel.id == selectedChannelId
158
- ? 'bg-gray-300 dark:bg-gray-500'
159
- : 'hover:bg-gray-50 dark:hover:bg-gray-700'
160
- }`}
161
- onClick={() => channel.id !== selectedChannelId && handleSelectChannel(channel.id)}
162
- >
163
- <img
164
- className="w-10 h-10 rounded-full bg-gray-400 object-cover flex-shrink-0"
165
- src={chatUser?.picture || '/default-avatar.svg'}
166
- alt={chatUser?.givenName || 'User avatar'}
167
- onError={(e) => {
168
- // Prevent infinite loop by checking if we're already showing the fallback
169
- if (e.currentTarget.src.includes('default-avatar.svg')) {
170
- // If SVG also fails, use a data URL fallback
171
- e.currentTarget.src =
172
- 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHZpZXdCb3g9IjAgMCA0MCA0MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8Y2lyY2xlIGN4PSIyMCIgY3k9IjIwIiByPSIyMCIgZmlsbD0iI0U1RTdFQiIvPgogIDxjaXJjbGUgY3g9IjIwIiBjeT0iMTYiIHI9IjYiIGZpbGw9IiM5Q0EzQUYiLz4KICA8cGF0aCBkPSJNOCAzMmMwLTYuNjI3IDUuMzczLTEyIDEyLTEyczEyIDUuMzczIDEyIDEyIiBmaWxsPSIjOUNBM0FGIi8+Cjwvc3ZnPgo=';
173
- } else {
174
- e.currentTarget.src = '/default-avatar.svg';
175
- }
176
- }}
177
- />
178
- <div className="ml-2 flex-grow min-w-10 max-w-96">
179
- {messageLoading && <Skeleton className="w-full h-16" />}
180
- {!messageLoading && (
181
- <LastMessageComponent
182
- lastMessage={lastMessage}
183
- channelType={channelType}
184
- chatUser={chatUser}
185
- subscribeToNewMessages={() =>
186
- subscribeToMore({
187
- document: CHAT_MESSAGE_ADDED,
188
- variables: {
189
- channelId: channel.id?.toString(),
190
- },
191
- updateQuery: (prev, { subscriptionData }: any) => {
159
+ return (
160
+ <div
161
+ key={`conv_channel_${channel.id}`}
162
+ className={`cursor-pointer flex items-center p-3 border-b ${
163
+ showBorder ? 'border-gray-300' : 'border-transparent'
164
+ } ${
165
+ channel.id == selectedChannelId
166
+ ? 'bg-gray-300 dark:bg-gray-500'
167
+ : 'hover:bg-gray-50 dark:hover:bg-gray-700'
168
+ }`}
169
+ onClick={() => channel.id !== selectedChannelId && handleSelectChannel(channel.id)}
170
+ >
171
+ <img
172
+ className="w-10 h-10 rounded-full bg-gray-400 object-cover flex-shrink-0"
173
+ src={chatUser?.picture || '/default-avatar.svg'}
174
+ alt={chatUser?.givenName || 'User avatar'}
175
+ onError={(e) => {
176
+ // Prevent infinite loop by checking if we're already showing the fallback
177
+ if (e.currentTarget.src.includes('default-avatar.svg')) {
178
+ // If SVG also fails, use a data URL fallback
179
+ e.currentTarget.src =
180
+ 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHZpZXdCb3g9IjAgMCA0MCA0MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8Y2lyY2xlIGN4PSIyMCIgY3k9IjIwIiByPSIyMCIgZmlsbD0iI0U1RTdFQiIvPgogIDxjaXJjbGUgY3g9IjIwIiBjeT0iMTYiIHI9IjYiIGZpbGw9IiM5Q0EzQUYiLz4KICA8cGF0aCBkPSJNOCAzMmMwLTYuNjI3IDUuMzczLTEyIDEyLTEyczEyIDUuMzczIDEyIDEyIiBmaWxsPSIjOUNBM0FGIi8+Cjwvc3ZnPgo=';
181
+ } else {
182
+ e.currentTarget.src = '/default-avatar.svg';
183
+ }
184
+ }}
185
+ />
186
+ <div className="ml-2 flex-grow min-w-10 max-w-96">
187
+ {messageLoading && <Skeleton className="w-full h-16" />}
188
+ {!messageLoading && (
189
+ <>
190
+ <LastMessageComponent
191
+ lastMessage={lastMessage}
192
+ channelType={channelType}
193
+ chatUser={chatUser}
194
+ />
195
+ <SubscriptionHandler
196
+ subscribeToMore={subscribeToMore}
197
+ document={CHAT_MESSAGE_ADDED}
198
+ variables={{ channelId: channel?.id?.toString() }}
199
+ enabled={!!channel?.id && !!subscribeToMore}
200
+ updateQuery={(prev, { subscriptionData }: any) => {
192
201
  if (!subscriptionData.data) return prev;
193
202
  const newMessage: any = subscriptionData?.data?.chatMessageAdded;
203
+ console.log('ConversationItem: New message received via subscription:', newMessage);
194
204
  const previousData = prev?.messages?.data
195
205
  ? [...prev.messages.data, newMessage]
196
206
  : [];
@@ -204,34 +214,26 @@ export const ConversationItem = ({
204
214
  },
205
215
  };
206
216
  return merged;
207
- },
208
- })
209
- }
210
- />
211
- // <Box display="flex" flexDirection={'column'} width="100%">
212
- // <Box w="100%" d="flex" justifyContent={'space-between'} alignItems="center">
213
- // <Text fontSize="10px" isTruncated color="green.500">
214
- // {channelType}
215
- // </Text>
216
- // <Text fontSize="12px" color="gray.500">
217
- // {lastMessage ? createdAtText(lastMessage?.createdAt) : ''}
218
- // </Text>
219
- // </Box>
220
- // <Text fontSize="14px" color="gray.600" fontWeight={'bold'} mt="5px">
221
- // {chatUser?.givenName + ' ' + chatUser?.familyName}
222
- // </Text>
223
- // <Text fontSize="14px" isTruncated w="80%" mt="5px" color="gray.600">
224
- // {lastMessage?.message}
225
- // </Text>
226
- // </Box>
227
- )}
217
+ }}
218
+ onError={(error) => {
219
+ console.error('ConversationItem: Subscription error:', error);
220
+ }}
221
+ />
222
+ </>
223
+ )}
224
+ </div>
228
225
  </div>
229
- </div>
230
- );
231
- };
226
+ );
227
+ },
228
+ );
232
229
 
233
- const LastMessageComponent = ({ subscribeToNewMessages, lastMessage, channelType, chatUser }) => {
234
- useEffect(() => subscribeToNewMessages(), []);
230
+ interface LastMessageComponentProps {
231
+ lastMessage: any;
232
+ channelType: string;
233
+ chatUser: any;
234
+ }
235
+
236
+ const LastMessageComponent = React.memo(({ lastMessage, channelType, chatUser }: LastMessageComponentProps) => {
235
237
  return (
236
238
  <div className="flex flex-col w-full">
237
239
  <div className="w-full flex justify-between items-center">
@@ -250,4 +252,4 @@ const LastMessageComponent = ({ subscribeToNewMessages, lastMessage, channelType
250
252
  </p>
251
253
  </div>
252
254
  );
253
- };
255
+ });
@@ -1,6 +1,6 @@
1
1
  import { GoSettings } from '@react-icons/all-files/go/GoSettings.js';
2
2
  import { GoSearch } from '@react-icons/all-files/go/GoSearch.js';
3
- import React, { useState, useEffect } from 'react';
3
+ import React, { useState, useEffect, useMemo } from 'react';
4
4
  import { useTranslation } from 'react-i18next';
5
5
  import { orderBy, uniqBy } from 'lodash-es';
6
6
  import { ConversationItem } from './ConversationItem';
@@ -19,9 +19,10 @@ type LeftSidebarProps = {
19
19
  getChannelsRefetch: any;
20
20
  supportServices?: any;
21
21
  role?: any;
22
+ messagesQuery?: any;
22
23
  };
23
24
 
24
- export const LeftSidebar = (props: LeftSidebarProps) => {
25
+ export const LeftSidebar = React.memo((props: LeftSidebarProps) => {
25
26
  const {
26
27
  currentUser,
27
28
  handleSelectChannel,
@@ -31,10 +32,21 @@ export const LeftSidebar = (props: LeftSidebarProps) => {
31
32
  selectedChannelId,
32
33
  supportServices,
33
34
  role,
35
+ messagesQuery,
34
36
  } = props;
35
37
  const [keyword, setKeyword] = useState('');
36
38
  const { t } = useTranslation('translations');
37
39
 
40
+ // Memoize the sorted channels to prevent unnecessary re-renders
41
+ const sortedChannels = useMemo(() => {
42
+ if (!userChannels?.length) return [];
43
+ return orderBy(
44
+ uniqBy([...userChannels], ({ id }) => id),
45
+ ['updatedAt'],
46
+ ['desc'],
47
+ );
48
+ }, [userChannels]);
49
+
38
50
  if (userChannelsLoading) {
39
51
  return (
40
52
  <div className="space-y-4">
@@ -56,12 +68,8 @@ export const LeftSidebar = (props: LeftSidebarProps) => {
56
68
  <>
57
69
  {supportServices ? supportServices : <></>}
58
70
 
59
- {userChannels?.length > 0 ? (
60
- orderBy(
61
- uniqBy([...userChannels], ({ id }) => id),
62
- ['updatedAt'],
63
- ['desc'],
64
- )?.map((channel, index) =>
71
+ {sortedChannels.length > 0 ? (
72
+ sortedChannels.map((channel, index) =>
65
73
  channel?.type === RoomType.Service ? (
66
74
  <ServiceConversationItem
67
75
  key={`service_channel_${channel.id}`}
@@ -71,7 +79,7 @@ export const LeftSidebar = (props: LeftSidebarProps) => {
71
79
  handleSelectChannel={handleSelectChannel}
72
80
  users={users}
73
81
  selectedChannelId={selectedChannelId}
74
- showBorder={index != userChannels.length - 1}
82
+ showBorder={index != sortedChannels.length - 1}
75
83
  role={role || null}
76
84
  />
77
85
  ) : (
@@ -83,7 +91,8 @@ export const LeftSidebar = (props: LeftSidebarProps) => {
83
91
  handleSelectChannel={handleSelectChannel}
84
92
  users={users}
85
93
  selectedChannelId={selectedChannelId}
86
- showBorder={index != userChannels.length - 1}
94
+ showBorder={index != sortedChannels.length - 1}
95
+ messagesQuery={messagesQuery}
87
96
  />
88
97
  ),
89
98
  )
@@ -98,7 +107,7 @@ export const LeftSidebar = (props: LeftSidebarProps) => {
98
107
  </div>
99
108
  </div>
100
109
  );
101
- };
110
+ });
102
111
 
103
112
  const SearchInput = ({ keyword, setKeyword }: any) => {
104
113
  const { t } = useTranslation('translations');
@@ -1,4 +1,4 @@
1
- import React, { useCallback, useMemo, useState } from 'react';
1
+ import React, { useCallback, useMemo, useState, useRef, useEffect } from 'react';
2
2
  import { useTranslation } from 'react-i18next';
3
3
  import { config } from '../../config';
4
4
  import { UploadImageButton } from './UploadImageButton';
@@ -17,8 +17,16 @@ export const MessageInput = ({ handleSend: handleSendProp, placeholder }: Messag
17
17
  const [showToast, setShowToast] = useState(false);
18
18
  const [toastMessage, setToastMessage] = useState('');
19
19
  const [isFocused, setIsFocused] = useState(false);
20
+ const textareaRef = useRef<HTMLTextAreaElement>(null);
20
21
  const { t } = useTranslation('translations');
21
22
 
23
+ // Auto-focus the textarea when component mounts
24
+ useEffect(() => {
25
+ if (textareaRef.current) {
26
+ textareaRef.current.focus();
27
+ }
28
+ }, []);
29
+
22
30
  const showToastMessage = useCallback((message: string) => {
23
31
  setToastMessage(message);
24
32
  setShowToast(true);
@@ -33,6 +41,12 @@ export const MessageInput = ({ handleSend: handleSendProp, placeholder }: Messag
33
41
  .then(() => {
34
42
  setMessage('');
35
43
  setFiles([]);
44
+ // Auto-focus the textarea after sending a message
45
+ setTimeout(() => {
46
+ if (textareaRef.current) {
47
+ textareaRef.current.focus();
48
+ }
49
+ }, 100);
36
50
  })
37
51
  .finally(() => setSending(false));
38
52
  }, [files, handleSendProp, message]);
@@ -114,6 +128,7 @@ export const MessageInput = ({ handleSend: handleSendProp, placeholder }: Messag
114
128
 
115
129
  {/* Textarea */}
116
130
  <textarea
131
+ ref={textareaRef}
117
132
  className="w-full text-base pl-14 pr-20 py-3 bg-transparent border-none resize-none overflow-hidden placeholder-gray-500 text-gray-900 focus:outline-none focus:ring-0 scrollbar-thin scrollbar-thumb-gray-300 scrollbar-track-transparent"
118
133
  style={{
119
134
  height: `${inputHeight}px`,
@@ -0,0 +1,55 @@
1
+ import { useEffect, useRef } from 'react';
2
+
3
+ /**
4
+ * Shared SubscriptionHandler for Apollo subscribeToMore
5
+ *
6
+ * @param subscribeToMore - Apollo subscribeToMore function
7
+ * @param document - GraphQL subscription document
8
+ * @param variables - Variables for the subscription
9
+ * @param updateQuery - Apollo updateQuery function
10
+ * @param onError - Optional error handler
11
+ * @param enabled - If false, disables the subscription
12
+ */
13
+ export function SubscriptionHandler({
14
+ subscribeToMore,
15
+ document,
16
+ variables,
17
+ updateQuery,
18
+ onError,
19
+ enabled = true,
20
+ }: {
21
+ subscribeToMore: Function;
22
+ document: any;
23
+ variables: Record<string, any>;
24
+ updateQuery: (prev: any, { subscriptionData }: any) => any;
25
+ onError?: (error: any) => void;
26
+ enabled?: boolean;
27
+ }) {
28
+ useEffect(() => {
29
+ if (!enabled) return;
30
+
31
+ console.log('SubscriptionHandler: Setting up subscription with variables:', variables);
32
+
33
+ const unsubscribe = subscribeToMore({
34
+ document,
35
+ variables,
36
+ updateQuery,
37
+ onError,
38
+ });
39
+
40
+ console.log('SubscriptionHandler: Subscription setup successful, unsubscribe function:', unsubscribe);
41
+
42
+ return () => {
43
+ console.log('SubscriptionHandler: Cleaning up subscription');
44
+ if (unsubscribe && typeof unsubscribe === 'function') {
45
+ try {
46
+ unsubscribe();
47
+ } catch (error) {
48
+ console.error('Error unsubscribing:', error);
49
+ }
50
+ }
51
+ };
52
+ }, [subscribeToMore, document, variables, updateQuery, onError, enabled]);
53
+
54
+ return null;
55
+ }