@droppii-org/chat-mobile 0.2.31 → 0.2.33

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/ThreadCard/ThreadCard.js +4 -0
  2. package/lib/module/components/ThreadCard/ThreadCard.js.map +1 -1
  3. package/lib/module/context/ChatContext.js +46 -12
  4. package/lib/module/context/ChatContext.js.map +1 -1
  5. package/lib/module/context/global.js +6 -0
  6. package/lib/module/context/global.js.map +1 -0
  7. package/lib/module/core/useChatListener.js +48 -12
  8. package/lib/module/core/useChatListener.js.map +1 -1
  9. package/lib/module/hooks/message/usePinMessage.js +133 -0
  10. package/lib/module/hooks/message/usePinMessage.js.map +1 -0
  11. package/lib/module/hooks/message/useSendMessage.js +9 -3
  12. package/lib/module/hooks/message/useSendMessage.js.map +1 -1
  13. package/lib/module/hooks/useChatMessages.js +5 -1
  14. package/lib/module/hooks/useChatMessages.js.map +1 -1
  15. package/lib/module/hooks/useImageAttachment.js +2 -0
  16. package/lib/module/hooks/useImageAttachment.js.map +1 -1
  17. package/lib/module/index.js +9 -1
  18. package/lib/module/index.js.map +1 -1
  19. package/lib/module/screens/chat-detail/ChatDetail.js +13 -1
  20. package/lib/module/screens/chat-detail/ChatDetail.js.map +1 -1
  21. package/lib/module/screens/chat-detail/PinnedMessageBar.js +158 -0
  22. package/lib/module/screens/chat-detail/PinnedMessageBar.js.map +1 -0
  23. package/lib/module/screens/chat-detail/legend/LegendChatMessage.js +47 -10
  24. package/lib/module/screens/chat-detail/legend/LegendChatMessage.js.map +1 -1
  25. package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxMenu.js +16 -0
  26. package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxMenu.js.map +1 -1
  27. package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxOverlay.js +15 -1
  28. package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxOverlay.js.map +1 -1
  29. package/lib/module/screens/chat-detail/messageToolbox/messageToolbox.utils.js +16 -1
  30. package/lib/module/screens/chat-detail/messageToolbox/messageToolbox.utils.js.map +1 -1
  31. package/lib/module/services/attachmentOrchestrator.js +3 -1
  32. package/lib/module/services/attachmentOrchestrator.js.map +1 -1
  33. package/lib/module/store/pinnedMessage.js +34 -0
  34. package/lib/module/store/pinnedMessage.js.map +1 -0
  35. package/lib/module/translation/resources/i18n.js +12 -0
  36. package/lib/module/translation/resources/i18n.js.map +1 -1
  37. package/lib/module/types/chat.js.map +1 -1
  38. package/lib/module/utils/messageUtils.js +77 -1
  39. package/lib/module/utils/messageUtils.js.map +1 -1
  40. package/lib/module/utils/toastFlashMessage.js +56 -0
  41. package/lib/module/utils/toastFlashMessage.js.map +1 -0
  42. package/lib/typescript/src/components/ThreadCard/ThreadCard.d.ts.map +1 -1
  43. package/lib/typescript/src/context/ChatContext.d.ts.map +1 -1
  44. package/lib/typescript/src/context/global.d.ts +3 -0
  45. package/lib/typescript/src/context/global.d.ts.map +1 -0
  46. package/lib/typescript/src/core/useChatListener.d.ts.map +1 -1
  47. package/lib/typescript/src/hooks/message/usePinMessage.d.ts +9 -0
  48. package/lib/typescript/src/hooks/message/usePinMessage.d.ts.map +1 -0
  49. package/lib/typescript/src/hooks/message/useSendMessage.d.ts +3 -1
  50. package/lib/typescript/src/hooks/message/useSendMessage.d.ts.map +1 -1
  51. package/lib/typescript/src/hooks/useChatMessages.d.ts.map +1 -1
  52. package/lib/typescript/src/hooks/useImageAttachment.d.ts.map +1 -1
  53. package/lib/typescript/src/index.d.ts +4 -3
  54. package/lib/typescript/src/index.d.ts.map +1 -1
  55. package/lib/typescript/src/screens/chat-detail/ChatDetail.d.ts.map +1 -1
  56. package/lib/typescript/src/screens/chat-detail/PinnedMessageBar.d.ts +8 -0
  57. package/lib/typescript/src/screens/chat-detail/PinnedMessageBar.d.ts.map +1 -0
  58. package/lib/typescript/src/screens/chat-detail/legend/LegendChatMessage.d.ts.map +1 -1
  59. package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxMenu.d.ts +5 -1
  60. package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxMenu.d.ts.map +1 -1
  61. package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxOverlay.d.ts +2 -0
  62. package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxOverlay.d.ts.map +1 -1
  63. package/lib/typescript/src/screens/chat-detail/messageToolbox/messageToolbox.utils.d.ts +4 -0
  64. package/lib/typescript/src/screens/chat-detail/messageToolbox/messageToolbox.utils.d.ts.map +1 -1
  65. package/lib/typescript/src/services/attachmentOrchestrator.d.ts.map +1 -1
  66. package/lib/typescript/src/store/pinnedMessage.d.ts +11 -0
  67. package/lib/typescript/src/store/pinnedMessage.d.ts.map +1 -0
  68. package/lib/typescript/src/translation/resources/i18n.d.ts.map +1 -1
  69. package/lib/typescript/src/types/chat.d.ts +8 -1
  70. package/lib/typescript/src/types/chat.d.ts.map +1 -1
  71. package/lib/typescript/src/types/imageUpload.d.ts +1 -1
  72. package/lib/typescript/src/types/imageUpload.d.ts.map +1 -1
  73. package/lib/typescript/src/utils/messageUtils.d.ts +8 -0
  74. package/lib/typescript/src/utils/messageUtils.d.ts.map +1 -1
  75. package/lib/typescript/src/utils/toastFlashMessage.d.ts +12 -0
  76. package/lib/typescript/src/utils/toastFlashMessage.d.ts.map +1 -0
  77. package/package.json +5 -3
  78. package/src/components/ThreadCard/ThreadCard.tsx +4 -0
  79. package/src/context/ChatContext.tsx +66 -9
  80. package/src/context/global.ts +6 -0
  81. package/src/core/useChatListener.ts +73 -11
  82. package/src/hooks/message/usePinMessage.ts +153 -0
  83. package/src/hooks/message/useSendMessage.ts +13 -9
  84. package/src/hooks/useChatMessages.ts +6 -2
  85. package/src/hooks/useImageAttachment.ts +2 -0
  86. package/src/index.tsx +10 -18
  87. package/src/screens/chat-detail/ChatDetail.tsx +12 -0
  88. package/src/screens/chat-detail/PinnedMessageBar.tsx +177 -0
  89. package/src/screens/chat-detail/legend/LegendChatMessage.tsx +47 -10
  90. package/src/screens/chat-detail/messageToolbox/MessageToolboxMenu.tsx +24 -0
  91. package/src/screens/chat-detail/messageToolbox/MessageToolboxOverlay.tsx +142 -116
  92. package/src/screens/chat-detail/messageToolbox/messageToolbox.utils.ts +31 -0
  93. package/src/services/attachmentOrchestrator.ts +2 -0
  94. package/src/store/pinnedMessage.ts +38 -0
  95. package/src/translation/resources/i18n.ts +13 -0
  96. package/src/types/chat.ts +8 -1
  97. package/src/types/imageUpload.ts +1 -1
  98. package/src/utils/messageUtils.ts +91 -3
  99. package/src/utils/toastFlashMessage.ts +67 -0
@@ -14,6 +14,7 @@ import { useMessageStore } from '../store/message';
14
14
  import { useUserStore } from '../store/user';
15
15
  import { useChatContext } from '../context';
16
16
  import { allSettled } from '../utils/promise';
17
+ import { useSetIsShowFLashMessage } from '../context/global';
17
18
 
18
19
  type UseChatMessagesOptions = {
19
20
  conversationId: string;
@@ -46,6 +47,8 @@ export function useChatMessages({
46
47
  const conversationIdRef = useRef(conversationId);
47
48
  conversationIdRef.current = conversationId;
48
49
 
50
+ const setIsShowFlashMessage = useSetIsShowFLashMessage();
51
+
49
52
  const fetchInitial = useCallback(async () => {
50
53
  if (!conversationId) return;
51
54
 
@@ -217,16 +220,17 @@ export function useChatMessages({
217
220
 
218
221
  useEffect(() => {
219
222
  if (!enabled || !conversationId) return;
220
-
223
+ setIsShowFlashMessage(false);
221
224
  const handleNewMessages = (incoming: MessageItem[]) =>
222
225
  appendIncomingMessages(incoming);
223
226
 
224
227
  OpenIMSDK.on(OpenIMEvent.OnRecvNewMessages, handleNewMessages);
225
228
 
226
229
  return () => {
230
+ setIsShowFlashMessage(true);
227
231
  OpenIMSDK.off(OpenIMEvent.OnRecvNewMessages, handleNewMessages);
228
232
  };
229
- }, [appendIncomingMessages, conversationId, enabled]);
233
+ }, [appendIncomingMessages, conversationId, enabled, setIsShowFlashMessage]);
230
234
 
231
235
  return {
232
236
  messages,
@@ -171,6 +171,8 @@ export const useImageAttachment = (): UseImageAttachmentReturn => {
171
171
  const maxSize = isVideoFromPicker(item)
172
172
  ? UPLOAD_LIMITS.video
173
173
  : UPLOAD_LIMITS.image;
174
+ console.log('maxSize', maxSize);
175
+ console.log('item.size', item.size);
174
176
  return (item.size || 0) <= maxSize;
175
177
  };
176
178
 
package/src/index.tsx CHANGED
@@ -29,17 +29,16 @@ export { AttachmentPreview } from './components/AttachmentPreview';
29
29
  export { MediaViewerModal } from './components/MediaViewerModal';
30
30
  export { Inbox } from './screens/inbox';
31
31
  export { default as MediaViewerScreen } from './screens/MediaView';
32
- export {
33
- ChatDetail,
34
- ChatDetailHeader,
35
- ChatComposer,
36
- ChatQuickActions,
37
- ChatAttachmentPanel,
38
- DEFAULT_QUICK_ACTIONS,
39
- DEFAULT_PANNEL_ACTIONS,
40
- DChatPannelActionItemType,
41
- DChatQuickActionItemType,
42
- } from './screens/chat-detail';
32
+ export * from './screens/chat-detail';
33
+
34
+ // export type {
35
+ // PeerType,
36
+ // MessageItem,
37
+ // ConversationItem,
38
+ // } from '@droppii/openim-rn-client-sdk';
39
+ export type { ConversationItem } from '@droppii/openim-rn-client-sdk';
40
+ export { LoginStatus, MessageType } from '@droppii/openim-rn-client-sdk';
41
+ export * from './screens/chat-detail';
43
42
  export type {
44
43
  DChatActionItem,
45
44
  DChatActionIconProvider,
@@ -54,13 +53,6 @@ export type {
54
53
  ChatPanelProps,
55
54
  } from './screens/chat-detail';
56
55
 
57
- export { LoginStatus, MessageType } from '@droppii/openim-rn-client-sdk';
58
- export type {
59
- PeerType,
60
- ConversationItem,
61
- MessageItem,
62
- } from '@droppii/openim-rn-client-sdk';
63
-
64
56
  export { registerUIUtils, UIUtils } from './utils/UIUtils';
65
57
 
66
58
  export { DFlowContext } from './types/flows';
@@ -6,6 +6,8 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';
6
6
  import { KContainer } from '@droppii/libs';
7
7
  import { useNavigation } from '@react-navigation/native';
8
8
  import { useChatMessages } from '../../hooks/useChatMessages';
9
+ import { usePinMessage } from '../../hooks/message/usePinMessage';
10
+ import { PinnedMessageBar } from './PinnedMessageBar';
9
11
  import {
10
12
  MediaViewerModal,
11
13
  type MediaViewerModalHandle,
@@ -76,6 +78,9 @@ const ChatDetail = memo(
76
78
  hasMoreEarlier,
77
79
  } = useChatMessages({ conversationId, enabled });
78
80
 
81
+ const { pinnedMessages, pinMessage, unpinMessage } =
82
+ usePinMessage(conversationId);
83
+
79
84
  useEffect(() => {
80
85
  if (conversationId) {
81
86
  useConversationStore.getState().setCurrentConversation(conversationId);
@@ -121,6 +126,11 @@ const ChatDetail = memo(
121
126
  onPressAvatar={onPressAvatar}
122
127
  />
123
128
 
129
+ <PinnedMessageBar
130
+ messages={pinnedMessages}
131
+ currentUserId={currentUserId}
132
+ />
133
+
124
134
  <KeyboardAvoidingView
125
135
  style={styles.keyboardAvoiding}
126
136
  behavior="padding"
@@ -150,6 +160,8 @@ const ChatDetail = memo(
150
160
  ref={messageToolboxRef}
151
161
  currentUserId={currentUserId}
152
162
  customMessageItemFactory={customMessageItemFactory}
163
+ onPinMessage={pinMessage}
164
+ onUnpinMessage={unpinMessage}
153
165
  />
154
166
  </KContainer.Page>
155
167
  );
@@ -0,0 +1,177 @@
1
+ import { memo, useMemo } from 'react';
2
+ import { StyleSheet } from 'react-native';
3
+ import {
4
+ KContainer,
5
+ KImage,
6
+ KLabel,
7
+ KColors,
8
+ KSpacingValue,
9
+ KBadge,
10
+ } from '@droppii/libs';
11
+ import { trans } from '../../translation';
12
+ import {
13
+ getMessagePreview,
14
+ getPinnedMessagePreview,
15
+ } from '../../utils/messageUtils';
16
+ import type { DMessageItem } from '../../types/chat';
17
+
18
+ interface PinnedMessageBarProps {
19
+ messages: DMessageItem[];
20
+ currentUserId?: string;
21
+ }
22
+
23
+ const PinnedMessageThumbnail = memo(
24
+ ({ message }: { message: DMessageItem }) => {
25
+ const preview = useMemo(() => getPinnedMessagePreview(message), [message]);
26
+
27
+ if (preview.mediaType === 'image' && preview.thumbnailUrl) {
28
+ return (
29
+ <KImage.Base
30
+ uri={preview.thumbnailUrl}
31
+ style={styles.thumbnail}
32
+ resizeMode="cover"
33
+ />
34
+ );
35
+ }
36
+
37
+ if (preview.mediaType === 'video') {
38
+ return (
39
+ <KContainer.View style={styles.thumbnail}>
40
+ {preview.thumbnailUrl && (
41
+ <KImage.Base
42
+ uri={preview.thumbnailUrl}
43
+ style={styles.thumbnail}
44
+ resizeMode="cover"
45
+ />
46
+ )}
47
+ <KContainer.View style={styles.videoOverlay} center>
48
+ <KImage.VectorIcons
49
+ name="play-circle"
50
+ provider="MaterialCommunityIcons"
51
+ size={16}
52
+ color={KColors.white}
53
+ />
54
+ </KContainer.View>
55
+ </KContainer.View>
56
+ );
57
+ }
58
+
59
+ if (preview.mediaType === 'file' && preview.fileIcon) {
60
+ return (
61
+ <KContainer.View marginL="0.75rem">
62
+ <KImage.Base
63
+ uri={preview.fileIcon}
64
+ style={styles.fileIcon}
65
+ resizeMode="contain"
66
+ />
67
+ </KContainer.View>
68
+ );
69
+ }
70
+
71
+ return null;
72
+ }
73
+ );
74
+
75
+ PinnedMessageThumbnail.displayName = 'PinnedMessageThumbnail';
76
+
77
+ export const PinnedMessageBar = memo(
78
+ ({ messages, currentUserId }: PinnedMessageBarProps) => {
79
+ if (messages.length === 0) {
80
+ return null;
81
+ }
82
+
83
+ const latest = messages[0]!;
84
+ const sender =
85
+ latest.sendID === currentUserId ? 'Bạn' : latest.senderNickname || '';
86
+ const remainingCount = messages.length - 1;
87
+ const { subtitleKey } = getPinnedMessagePreview(latest);
88
+
89
+ return (
90
+ <KContainer.View style={styles.container}>
91
+ <PinnedMessageThumbnail message={latest} />
92
+
93
+ <KContainer.View row alignItems flex paddingR="0.75rem">
94
+ <KContainer.View style={styles.content}>
95
+ <KLabel.Text typo="TextNmMedium" numberOfLines={1}>
96
+ {getMessagePreview(latest)}
97
+ </KLabel.Text>
98
+
99
+ <KContainer.View row alignItems paddingR="0.75rem">
100
+ <KImage.VectorIcons
101
+ name="thumbtack-b"
102
+ size={12}
103
+ color={KColors.primary.normal}
104
+ />
105
+ <KLabel.Text
106
+ typo="TextSmNormal"
107
+ color={KColors.gray.light}
108
+ numberOfLines={1}
109
+ marginL={'0.25rem'}
110
+ >
111
+ {trans(subtitleKey, { sender })}
112
+ </KLabel.Text>
113
+ </KContainer.View>
114
+ </KContainer.View>
115
+
116
+ {remainingCount > 0 && (
117
+ <KBadge.BaseV2
118
+ badge={`+${remainingCount}`}
119
+ skipConvertBadge
120
+ badgeSize={{ width: 26, height: 20 }}
121
+ wrapperLabelTextStyle={{ paddingHorizontal: 3 }}
122
+ />
123
+ )}
124
+ <KImage.VectorIcons
125
+ name="angle-down-o"
126
+ size={20}
127
+ color={KColors.gray.normal}
128
+ />
129
+ </KContainer.View>
130
+ </KContainer.View>
131
+ );
132
+ }
133
+ );
134
+
135
+ PinnedMessageBar.displayName = 'PinnedMessageBar';
136
+
137
+ const styles = StyleSheet.create({
138
+ container: {
139
+ flexDirection: 'row',
140
+ alignItems: 'center',
141
+ backgroundColor: KColors.palette.gray.w25,
142
+ },
143
+ content: {
144
+ flex: 1,
145
+ paddingRight: KSpacingValue['0.75rem'],
146
+ paddingLeft: KSpacingValue['0.75rem'],
147
+ paddingVertical: KSpacingValue['0.25rem'],
148
+ },
149
+ thumbnail: {
150
+ width: 51,
151
+ height: 51,
152
+ overflow: 'hidden',
153
+ backgroundColor: '#000',
154
+ },
155
+ videoOverlay: {
156
+ position: 'absolute',
157
+ top: 0,
158
+ left: 0,
159
+ right: 0,
160
+ bottom: 0,
161
+ backgroundColor: 'rgba(0, 0, 0, 0.25)',
162
+ },
163
+ fileIcon: {
164
+ width: 24,
165
+ height: 24,
166
+ },
167
+ badge: {
168
+ minWidth: 32,
169
+ height: 22,
170
+ paddingHorizontal: KSpacingValue['0.25rem'],
171
+ borderRadius: KSpacingValue['1rem'],
172
+ alignItems: 'center',
173
+ justifyContent: 'center',
174
+ backgroundColor: KColors.primary.normal,
175
+ marginRight: KSpacingValue['0.75rem'],
176
+ },
177
+ });
@@ -1,4 +1,5 @@
1
- import { KContainer } from '@droppii/libs';
1
+ import { KContainer, KImage, KColors } from '@droppii/libs';
2
+ import { StyleSheet } from 'react-native';
2
3
  import { memo, useMemo } from 'react';
3
4
  import type { ComponentType, PropsWithChildren, ReactNode } from 'react';
4
5
  import { MessageType } from '@droppii/openim-rn-client-sdk';
@@ -89,15 +90,27 @@ export const LegendChatMessage = memo(
89
90
  isOutgoing={isOutgoing}
90
91
  onRetry={onRetry}
91
92
  />
92
- <LongPressWrapper onLongPress={onLongPress}>
93
- <MessageComponent
94
- message={message}
95
- isOutgoing={isOutgoing}
96
- createdAtTime={createdAtTime}
97
- onMediaPress={hasMediaPress ? onMediaPress : undefined}
98
- onPressUrl={hasUrlPress ? onPressUrl : undefined}
99
- />
100
- </LongPressWrapper>
93
+ <KContainer.View style={styles.content}>
94
+ <LongPressWrapper onLongPress={onLongPress}>
95
+ <MessageComponent
96
+ message={message}
97
+ isOutgoing={isOutgoing}
98
+ createdAtTime={createdAtTime}
99
+ onMediaPress={hasMediaPress ? onMediaPress : undefined}
100
+ onPressUrl={hasUrlPress ? onPressUrl : undefined}
101
+ />
102
+ </LongPressWrapper>
103
+
104
+ {message?.isPinned && (
105
+ <KContainer.View style={styles.pinBadge}>
106
+ <KImage.VectorIcons
107
+ name="thumbtack-b"
108
+ size={12}
109
+ color={KColors.primary.normal}
110
+ />
111
+ </KContainer.View>
112
+ )}
113
+ </KContainer.View>
101
114
  </KContainer.View>
102
115
  );
103
116
  }
@@ -105,6 +118,30 @@ export const LegendChatMessage = memo(
105
118
 
106
119
  LegendChatMessage.displayName = 'LegendChatMessage';
107
120
 
121
+ const styles = StyleSheet.create({
122
+ content: {
123
+ position: 'relative',
124
+ },
125
+ pinBadge: {
126
+ position: 'absolute',
127
+ top: -8,
128
+ right: -8,
129
+ width: 24,
130
+ height: 20,
131
+ borderRadius: 100,
132
+ alignItems: 'center',
133
+ justifyContent: 'center',
134
+ backgroundColor: KColors.pageBackground,
135
+ shadowColor: KColors.black,
136
+ shadowOffset: { width: 0, height: 1 },
137
+ shadowOpacity: 0.15,
138
+ shadowRadius: 2,
139
+ elevation: 3,
140
+ borderWidth: 1,
141
+ borderColor: KColors.white,
142
+ },
143
+ });
144
+
108
145
  const LongPressWrapper = ({
109
146
  children,
110
147
  onLongPress,
@@ -7,10 +7,14 @@ import { MessageToolboxItem } from './MessageToolboxItem';
7
7
  interface MessageToolboxMenuProps {
8
8
  showCopyMessage?: boolean;
9
9
  showCopyMessageLink?: boolean;
10
+ showPinMessage?: boolean;
11
+ showUnpinMessage?: boolean;
10
12
  showRetryMessage?: boolean;
11
13
  showRevokeMessage?: boolean;
12
14
  onCopyMessage: () => void;
13
15
  onCopyMessageLink: () => void;
16
+ onPinMessage: () => void;
17
+ onUnpinMessage: () => void;
14
18
  onRetryMessage: () => void;
15
19
  onRevokeMessage: () => void;
16
20
  }
@@ -19,10 +23,14 @@ export const MessageToolboxMenu = memo(
19
23
  ({
20
24
  showCopyMessage,
21
25
  showCopyMessageLink,
26
+ showPinMessage,
27
+ showUnpinMessage,
22
28
  showRetryMessage,
23
29
  showRevokeMessage,
24
30
  onCopyMessage,
25
31
  onCopyMessageLink,
32
+ onPinMessage,
33
+ onUnpinMessage,
26
34
  onRetryMessage,
27
35
  onRevokeMessage,
28
36
  }: MessageToolboxMenuProps) => {
@@ -44,6 +52,22 @@ export const MessageToolboxMenu = memo(
44
52
  onPress={onCopyMessageLink}
45
53
  />
46
54
 
55
+ <MessageToolboxItem
56
+ icon="thumbtack-o"
57
+ color={KColors.gray.dark}
58
+ label={trans('chat:pin_message')}
59
+ visible={showPinMessage}
60
+ onPress={onPinMessage}
61
+ />
62
+
63
+ <MessageToolboxItem
64
+ icon="thumbtack-off-o"
65
+ color={KColors.gray.dark}
66
+ label={trans('chat:unpin_message')}
67
+ visible={showUnpinMessage}
68
+ onPress={onUnpinMessage}
69
+ />
70
+
47
71
  <MessageToolboxItem
48
72
  icon="arrow-reload-o"
49
73
  color={KColors.danger.normal}