@droppii-org/chat-mobile 0.2.52 → 0.2.54

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 (99) hide show
  1. package/lib/module/components/Avatar/Avatar.utils.js +4 -2
  2. package/lib/module/components/Avatar/Avatar.utils.js.map +1 -1
  3. package/lib/module/components/Avatar/SingleAvatar.js +2 -0
  4. package/lib/module/components/Avatar/SingleAvatar.js.map +1 -1
  5. package/lib/module/components/ThreadCard/MuteThread.js +4 -61
  6. package/lib/module/components/ThreadCard/MuteThread.js.map +1 -1
  7. package/lib/module/context/ChatDetailContext.js +2 -1
  8. package/lib/module/context/ChatDetailContext.js.map +1 -1
  9. package/lib/module/hooks/flows/useFlow.js +10 -0
  10. package/lib/module/hooks/flows/useFlow.js.map +1 -1
  11. package/lib/module/hooks/query-keys.js +1 -0
  12. package/lib/module/hooks/query-keys.js.map +1 -1
  13. package/lib/module/hooks/useMuteConversation.js +66 -1
  14. package/lib/module/hooks/useMuteConversation.js.map +1 -1
  15. package/lib/module/index.js +1 -0
  16. package/lib/module/index.js.map +1 -1
  17. package/lib/module/screens/ChatInfo/ChatInfo.js +75 -0
  18. package/lib/module/screens/ChatInfo/ChatInfo.js.map +1 -0
  19. package/lib/module/screens/ChatInfo/ChatInfoActions.js +141 -0
  20. package/lib/module/screens/ChatInfo/ChatInfoActions.js.map +1 -0
  21. package/lib/module/screens/ChatInfo/ChatInfoMediaRow.js +58 -0
  22. package/lib/module/screens/ChatInfo/ChatInfoMediaRow.js.map +1 -0
  23. package/lib/module/screens/ChatInfo/ChatInfoProfile.js +134 -0
  24. package/lib/module/screens/ChatInfo/ChatInfoProfile.js.map +1 -0
  25. package/lib/module/screens/ChatInfo/index.js +4 -0
  26. package/lib/module/screens/ChatInfo/index.js.map +1 -0
  27. package/lib/module/screens/ChatInfo/types.js +4 -0
  28. package/lib/module/screens/ChatInfo/types.js.map +1 -0
  29. package/lib/module/screens/chat-detail/BotMenuSheet.js +73 -0
  30. package/lib/module/screens/chat-detail/BotMenuSheet.js.map +1 -0
  31. package/lib/module/screens/chat-detail/ChatComposer.js +78 -3
  32. package/lib/module/screens/chat-detail/ChatComposer.js.map +1 -1
  33. package/lib/module/screens/chat-detail/components/ChatMessageInput.js +11 -6
  34. package/lib/module/screens/chat-detail/components/ChatMessageInput.js.map +1 -1
  35. package/lib/module/services/endpoints.js +1 -0
  36. package/lib/module/services/endpoints.js.map +1 -1
  37. package/lib/module/translation/resources/i18n.js +18 -1
  38. package/lib/module/translation/resources/i18n.js.map +1 -1
  39. package/lib/module/types/chat.js.map +1 -1
  40. package/lib/typescript/src/components/Avatar/Avatar.types.d.ts +1 -1
  41. package/lib/typescript/src/components/Avatar/Avatar.types.d.ts.map +1 -1
  42. package/lib/typescript/src/components/Avatar/Avatar.utils.d.ts +2 -0
  43. package/lib/typescript/src/components/Avatar/Avatar.utils.d.ts.map +1 -1
  44. package/lib/typescript/src/components/Avatar/SingleAvatar.d.ts.map +1 -1
  45. package/lib/typescript/src/components/ThreadCard/MuteThread.d.ts.map +1 -1
  46. package/lib/typescript/src/context/ChatDetailContext.d.ts.map +1 -1
  47. package/lib/typescript/src/hooks/flows/useFlow.d.ts +1 -0
  48. package/lib/typescript/src/hooks/flows/useFlow.d.ts.map +1 -1
  49. package/lib/typescript/src/hooks/query-keys.d.ts +1 -0
  50. package/lib/typescript/src/hooks/query-keys.d.ts.map +1 -1
  51. package/lib/typescript/src/hooks/useMuteConversation.d.ts.map +1 -1
  52. package/lib/typescript/src/index.d.ts +2 -0
  53. package/lib/typescript/src/index.d.ts.map +1 -1
  54. package/lib/typescript/src/screens/ChatInfo/ChatInfo.d.ts +4 -0
  55. package/lib/typescript/src/screens/ChatInfo/ChatInfo.d.ts.map +1 -0
  56. package/lib/typescript/src/screens/ChatInfo/ChatInfoActions.d.ts +11 -0
  57. package/lib/typescript/src/screens/ChatInfo/ChatInfoActions.d.ts.map +1 -0
  58. package/lib/typescript/src/screens/ChatInfo/ChatInfoMediaRow.d.ts +7 -0
  59. package/lib/typescript/src/screens/ChatInfo/ChatInfoMediaRow.d.ts.map +1 -0
  60. package/lib/typescript/src/screens/ChatInfo/ChatInfoProfile.d.ts +9 -0
  61. package/lib/typescript/src/screens/ChatInfo/ChatInfoProfile.d.ts.map +1 -0
  62. package/lib/typescript/src/screens/ChatInfo/index.d.ts +3 -0
  63. package/lib/typescript/src/screens/ChatInfo/index.d.ts.map +1 -0
  64. package/lib/typescript/src/screens/ChatInfo/types.d.ts +14 -0
  65. package/lib/typescript/src/screens/ChatInfo/types.d.ts.map +1 -0
  66. package/lib/typescript/src/screens/chat-detail/BotMenuSheet.d.ts +7 -0
  67. package/lib/typescript/src/screens/chat-detail/BotMenuSheet.d.ts.map +1 -0
  68. package/lib/typescript/src/screens/chat-detail/ChatComposer.d.ts.map +1 -1
  69. package/lib/typescript/src/screens/chat-detail/components/ChatMessageInput.d.ts +2 -1
  70. package/lib/typescript/src/screens/chat-detail/components/ChatMessageInput.d.ts.map +1 -1
  71. package/lib/typescript/src/services/endpoints.d.ts +1 -0
  72. package/lib/typescript/src/services/endpoints.d.ts.map +1 -1
  73. package/lib/typescript/src/translation/resources/i18n.d.ts.map +1 -1
  74. package/lib/typescript/src/types/chat.d.ts +2 -1
  75. package/lib/typescript/src/types/chat.d.ts.map +1 -1
  76. package/package.json +1 -1
  77. package/src/build-ignore.d.ts +1 -0
  78. package/src/components/Avatar/Avatar.types.ts +1 -1
  79. package/src/components/Avatar/Avatar.utils.ts +2 -0
  80. package/src/components/Avatar/SingleAvatar.tsx +2 -0
  81. package/src/components/ThreadCard/MuteThread.tsx +4 -61
  82. package/src/context/ChatDetailContext.tsx +1 -0
  83. package/src/hooks/flows/useFlow.ts +14 -0
  84. package/src/hooks/query-keys.ts +4 -0
  85. package/src/hooks/useMuteConversation.tsx +124 -0
  86. package/src/index.tsx +2 -0
  87. package/src/screens/ChatInfo/ChatInfo.tsx +75 -0
  88. package/src/screens/ChatInfo/ChatInfoActions.tsx +168 -0
  89. package/src/screens/ChatInfo/ChatInfoMediaRow.tsx +73 -0
  90. package/src/screens/ChatInfo/ChatInfoProfile.tsx +147 -0
  91. package/src/screens/ChatInfo/index.ts +2 -0
  92. package/src/screens/ChatInfo/types.ts +14 -0
  93. package/src/screens/chat-detail/BotMenuSheet.tsx +94 -0
  94. package/src/screens/chat-detail/ChatComposer.tsx +100 -3
  95. package/src/screens/chat-detail/components/ChatMessageInput.tsx +11 -7
  96. package/src/services/endpoints.ts +2 -0
  97. package/src/translation/resources/i18n.ts +18 -0
  98. package/src/types/chat.ts +2 -0
  99. package/src/hooks/useMuteConversation.ts +0 -59
@@ -0,0 +1,94 @@
1
+ import { memo, useCallback } from 'react';
2
+ import { StyleSheet } from 'react-native';
3
+ import {
4
+ KContainer,
5
+ KLabel,
6
+ KColors,
7
+ KImage,
8
+ KSpacingValue,
9
+ KDivider,
10
+ } from '@droppii/libs';
11
+ import { UIUtils } from '../../utils/UIUtils';
12
+ import { trans } from '../../translation';
13
+ import type { InputButtonItem } from '../../types/flows';
14
+ import { DEFAULT_BUTTON_ID } from '../../utils/flows';
15
+
16
+ interface BotMenuSheetContentProps {
17
+ options: InputButtonItem[];
18
+ onSelect: (item: InputButtonItem) => void;
19
+ }
20
+
21
+ const BotMenuSheetContent = memo(
22
+ ({ options, onSelect }: BotMenuSheetContentProps) => {
23
+ const handlePress = useCallback(
24
+ (item: InputButtonItem) => {
25
+ UIUtils.bottomSheet.dismiss();
26
+ onSelect(item);
27
+ },
28
+ [onSelect]
29
+ );
30
+
31
+ return (
32
+ <KContainer.View paddingH="0.75rem" paddingV="0.25rem">
33
+ {options?.map((item, index) => (
34
+ <>
35
+ <KContainer.Touchable
36
+ key={item.id}
37
+ row
38
+ alignItems
39
+ justifyContent="space-between"
40
+ style={styles.item}
41
+ onPress={() => handlePress(item)}
42
+ >
43
+ <KLabel.Text typo="TextMdMedium" flex>
44
+ {item?.id === DEFAULT_BUTTON_ID
45
+ ? trans('restart')
46
+ : item?.label || ''}
47
+ </KLabel.Text>
48
+ <KImage.VectorIcons
49
+ name="angle-right-o"
50
+ size={20}
51
+ color={KColors.gray.light}
52
+ />
53
+ </KContainer.Touchable>
54
+ <KContainer.VisibleView visible={index !== options?.length - 1}>
55
+ <KDivider size="hairline" />
56
+ </KContainer.VisibleView>
57
+ </>
58
+ ))}
59
+ </KContainer.View>
60
+ );
61
+ }
62
+ );
63
+
64
+ BotMenuSheetContent.displayName = 'BotMenuSheetContent';
65
+
66
+ const styles = StyleSheet.create({
67
+ item: {
68
+ paddingVertical: KSpacingValue['0.75rem'],
69
+ },
70
+ });
71
+
72
+ export interface OpenBotMenuSheetParams {
73
+ options: InputButtonItem[];
74
+ onSelect: (item: InputButtonItem) => void;
75
+ }
76
+
77
+ export const openBotMenuSheet = ({
78
+ options,
79
+ onSelect,
80
+ }: OpenBotMenuSheetParams) => {
81
+ UIUtils.bottomSheet.open({
82
+ header: {
83
+ title: { text: trans('chat:bot_menu_title') },
84
+ alignment: 'left',
85
+ showCloseButton: true,
86
+ },
87
+ touchOutsideToDismiss: true,
88
+ body: {
89
+ renderContent: () => (
90
+ <BotMenuSheetContent options={options} onSelect={onSelect} />
91
+ ),
92
+ },
93
+ });
94
+ };
@@ -48,13 +48,17 @@ import { useChatComposerState } from './hooks/useChatComposerState';
48
48
  import { useChatComposerAnimation } from './hooks/useChatComposerAnimation';
49
49
  import { useChatDetailContext } from '../../context/ChatDetailContext';
50
50
  import { trans } from '../../translation';
51
- import { useRunFlow } from '../../hooks/flows/useFlow';
52
- import { ReactFlowsNodeTypes } from '../../types/flows';
51
+ import { useCloseSupportSession, useRunFlow } from '../../hooks/flows/useFlow';
52
+ import { ReactFlowsNodeTypes, type InputButtonItem } from '../../types/flows';
53
+ import { isContextButton } from '../../utils/flows';
54
+ import { DFlowState } from '../../types/chat';
53
55
  import { useAttachmentMapping } from './hooks/useAttachmentMapping';
54
56
  import type { AttachmentType } from '../../types/attachment';
55
57
  import { useChatCompose } from '../../hooks/useChatCompose';
56
58
  import { ChatCurrentQuotedMessage } from './ChatCurrentQuotedMessage';
57
59
  import { useSendMessage } from '../../hooks/message/useSendMessage';
60
+ import { openBotMenuSheet } from './BotMenuSheet';
61
+ import { UIUtils } from '../../utils/UIUtils';
58
62
 
59
63
  interface ChatComposerInternalProps {
60
64
  isEmptyMessage?: boolean;
@@ -76,8 +80,14 @@ export const ChatComposer = memo(
76
80
  const isDisableCompose = isComposeDisabled;
77
81
  const isKeyboardVisible = useKeyboardState((state) => state.isVisible);
78
82
 
79
- const { isBotConversation, conversationFlow } = useChatDetailContext();
83
+ const {
84
+ isBotConversation,
85
+ conversationFlow,
86
+ conversationFlowMainMenu,
87
+ flowState,
88
+ } = useChatDetailContext();
80
89
  const { mutate: runFlow, isPending } = useRunFlow();
90
+ const { mutateAsync: closeSupportSession } = useCloseSupportSession();
81
91
  const { sendMessage } = useSendMessage();
82
92
  const compose = useChatCompose();
83
93
 
@@ -205,6 +215,92 @@ export const ChatComposer = memo(
205
215
  });
206
216
  }, [runFlow, conversationFlow]);
207
217
 
218
+ const botMenuOptions = useMemo(
219
+ () =>
220
+ conversationFlowMainMenu?.filter(
221
+ (item) => !isContextButton(item.label)
222
+ ) || [],
223
+ [conversationFlowMainMenu]
224
+ );
225
+
226
+ const hasBotMenuOptions = isBotConversation && botMenuOptions.length > 0;
227
+
228
+ const runBotMenuOption = useCallback(
229
+ (item: InputButtonItem) => {
230
+ runFlow({
231
+ actionId: item.id,
232
+ groupId: '',
233
+ childId: '',
234
+ plainText: item.label,
235
+ });
236
+ },
237
+ [runFlow]
238
+ );
239
+
240
+ const handleSelectBotMenuOption = useCallback(
241
+ (item: InputButtonItem) => {
242
+ if (flowState === DFlowState.CS) {
243
+ UIUtils.popup.open({
244
+ header: {
245
+ title: { text: trans('chat:end_support_session_title') },
246
+ alignment: 'center',
247
+ },
248
+ body: {
249
+ renderContent: () => (
250
+ <KContainer.View padding={'1rem'}>
251
+ <KLabel.RichText
252
+ typo="TextMdNormal"
253
+ textAlign="center"
254
+ color={KColors.gray.dark}
255
+ richTextOptions={{
256
+ i18nKey: trans('chat:end_support_session_desc'),
257
+ components: {
258
+ bold: (
259
+ <KLabel.Text
260
+ typo="TextMdBold"
261
+ color={KColors.gray.dark}
262
+ />
263
+ ),
264
+ },
265
+ }}
266
+ />
267
+ </KContainer.View>
268
+ ),
269
+ },
270
+ actions: {
271
+ buttons: [
272
+ {
273
+ label: trans('chat:end_support_session_secondary'),
274
+ kind: 'light',
275
+ onPress: () => {},
276
+ },
277
+ {
278
+ label: trans('chat:end_support_session_primary'),
279
+ kind: 'danger',
280
+ onPress: async () => {
281
+ await closeSupportSession();
282
+ runBotMenuOption(item);
283
+ },
284
+ },
285
+ ],
286
+ type: 'horizontal-button',
287
+ },
288
+ });
289
+ return;
290
+ }
291
+ runBotMenuOption(item);
292
+ },
293
+ [flowState, runBotMenuOption, closeSupportSession]
294
+ );
295
+
296
+ const handleOpenBotMenu = useCallback(() => {
297
+ if (!hasBotMenuOptions) return;
298
+ openBotMenuSheet({
299
+ options: botMenuOptions,
300
+ onSelect: handleSelectBotMenuOption,
301
+ });
302
+ }, [hasBotMenuOptions, botMenuOptions, handleSelectBotMenuOption]);
303
+
208
304
  useEffect(() => {
209
305
  if (!compose.text) setIsInputWrapped(false);
210
306
  }, [compose.text, setIsInputWrapped]);
@@ -373,6 +469,7 @@ export const ChatComposer = memo(
373
469
  isMultilineInput={isMultilineInput}
374
470
  inputRef={inputRef}
375
471
  onEmojiPress={handleToggleSticker}
472
+ onSuggestPress={hasBotMenuOptions ? handleOpenBotMenu : undefined}
376
473
  />
377
474
 
378
475
  <KContainer.Touchable
@@ -25,6 +25,7 @@ export interface ChatMessageInputProps {
25
25
  isInputFocused: boolean;
26
26
  isMultilineInput: boolean;
27
27
  onEmojiPress?: () => void;
28
+ onSuggestPress?: () => void;
28
29
  inputRef: React.RefObject<TextInput>;
29
30
  }
30
31
 
@@ -39,6 +40,7 @@ export const ChatMessageInput = memo(
39
40
  isInputFocused,
40
41
  isMultilineInput,
41
42
  onEmojiPress,
43
+ onSuggestPress,
42
44
  inputRef,
43
45
  }: ChatMessageInputProps) => {
44
46
  const renderEmojiNode = useCallback(
@@ -69,13 +71,15 @@ export const ChatMessageInput = memo(
69
71
  paddingL="0.75rem"
70
72
  style={styles.inputContainer}
71
73
  >
72
- <KContainer.View marginR="0.75rem">
73
- <KImage.VectorIcons
74
- name="chat-suggest-o"
75
- size={24}
76
- color={KColors.gray.normal}
77
- />
78
- </KContainer.View>
74
+ <KContainer.VisibleView visible={!!onSuggestPress}>
75
+ <KContainer.Touchable marginR="0.75rem" onPress={onSuggestPress}>
76
+ <KImage.VectorIcons
77
+ name="chat-suggest-o"
78
+ size={24}
79
+ color={KColors.gray.normal}
80
+ />
81
+ </KContainer.Touchable>
82
+ </KContainer.VisibleView>
79
83
 
80
84
  <TextInput
81
85
  ref={inputRef}
@@ -28,6 +28,8 @@ export const ENDPOINTS = {
28
28
  `${getApiEndpoint('bot')}/${botId}/conversation`,
29
29
  conversationFlowMainMenu: (id: string) =>
30
30
  `${getApiEndpoint('conversation')}/${id}/bot-flows/main-menu`,
31
+ closeConversationSession: (id: string) =>
32
+ `${getApiEndpoint('conversation')}/${id}/session/close`,
31
33
  getConversationStatusByBotId: (botId: string) =>
32
34
  `${getApiEndpoint('bot')}/${botId}`,
33
35
  getConversationTarget: (conversationId: string) =>
@@ -80,6 +80,18 @@ const i18nKey: Record<string, string> = {
80
80
  'chat:mute_confirm_secondary': 'Quay lại',
81
81
  'chat:mute_toast_success': 'Đã tắt thông báo',
82
82
  'chat:unmute_toast_success': 'Đã bật thông báo',
83
+ 'chat:info_title': 'Tuỳ chọn',
84
+ 'chat:info_search_action': 'Tìm tin nhắn',
85
+ 'chat:info_media_photo': 'Ảnh',
86
+ 'chat:info_media_video': 'Video',
87
+ 'chat:info_media_file': 'Tập tin',
88
+ 'chat:info_media_link': 'Liên kết',
89
+ 'chat:info_orders': 'Đơn hàng đã tạo cho {{name}}',
90
+ 'chat:info_create_group': 'Tạo nhóm trò chuyện với {{name}}',
91
+ 'chat:info_add_to_group': 'Thêm vào nhóm',
92
+ 'chat:info_pin_message': 'Ghim trò chuyện',
93
+ 'chat:info_unpin_message': 'Bỏ ghim trò chuyện',
94
+ 'chat:info_delete_history': 'Xoá lịch sử trò chuyện',
83
95
  'start': 'Bắt đầu',
84
96
  'restart': 'Bắt đầu lại',
85
97
  'gallery': 'Ảnh/Video',
@@ -100,6 +112,12 @@ const i18nKey: Record<string, string> = {
100
112
  'chat:resource_center_tab_link': 'Liên kết',
101
113
  'chat:resource_center_empty_generic': 'Không tìm thấy tập tin nào',
102
114
  'chat:resource_center_empty_link': 'Không tìm thấy liên kết nào',
115
+ 'chat:bot_menu_title': 'Gợi ý',
116
+ 'chat:end_support_session_title': 'Kết thúc phiên hỗ trợ',
117
+ 'chat:end_support_session_desc':
118
+ 'Bạn đang trong phiên hỗ trợ với hỗ trợ viên, bạn có muốn <bold>kết thúc phiên hỗ trợ</bold> ngay bây giờ?',
119
+ 'chat:end_support_session_primary': 'Kết thúc',
120
+ 'chat:end_support_session_secondary': 'Tiếp tục',
103
121
  };
104
122
 
105
123
  export default {
package/src/types/chat.ts CHANGED
@@ -8,6 +8,7 @@ import type {
8
8
  DFlowContext,
9
9
  IConversationFlow,
10
10
  IConversationFlowStep,
11
+ InputButtonItem,
11
12
  IRunConversationFlowPayload,
12
13
  } from './flows';
13
14
  import type { GetOpenIMTokenResponse } from './auth';
@@ -209,6 +210,7 @@ export interface ChatDetailContextType {
209
210
  conversation?: ConversationItem;
210
211
  quotedMessage?: MessageItem;
211
212
  setQuotedMessage: (item?: MessageItem) => void;
213
+ conversationFlowMainMenu?: InputButtonItem[];
212
214
  }
213
215
 
214
216
  export interface ChatDetailProviderProps extends PropsWithChildren {}
@@ -1,59 +0,0 @@
1
- import { useCallback } from 'react';
2
- import OpenIMSDK, {
3
- MessageReceiveOptType,
4
- } from '@droppii/openim-rn-client-sdk';
5
- import type { ConversationItem } from '@droppii/openim-rn-client-sdk';
6
- import { UIUtils } from '../utils/UIUtils';
7
- import { trans } from '../translation';
8
-
9
- export function useMuteConversation() {
10
- const muteConversation = useCallback(
11
- async (conversation?: ConversationItem) => {
12
- if (conversation == null) return;
13
-
14
- try {
15
- await OpenIMSDK.setConversationRecvMessageOpt({
16
- conversationID: conversation.conversationID,
17
- opt: MessageReceiveOptType.NotNotify,
18
- });
19
- UIUtils.toast.open({
20
- title: trans('chat:mute_toast_success'),
21
- theme: 'light',
22
- });
23
- } catch (error) {
24
- console.error('muteConversation failed', error);
25
- UIUtils.toast.open({
26
- title: trans('chat:generic_error'),
27
- theme: 'error',
28
- });
29
- }
30
- },
31
- []
32
- );
33
-
34
- const unmuteConversation = useCallback(
35
- async (conversation?: ConversationItem) => {
36
- if (conversation == null) return;
37
-
38
- try {
39
- await OpenIMSDK.setConversationRecvMessageOpt({
40
- conversationID: conversation.conversationID,
41
- opt: MessageReceiveOptType.Normal,
42
- });
43
- UIUtils.toast.open({
44
- title: trans('chat:unmute_toast_success'),
45
- theme: 'light',
46
- });
47
- } catch (error) {
48
- console.error('unmuteConversation failed', error);
49
- UIUtils.toast.open({
50
- title: trans('chat:generic_error'),
51
- theme: 'error',
52
- });
53
- }
54
- },
55
- []
56
- );
57
-
58
- return { muteConversation, unmuteConversation };
59
- }