@repzo/widget-react-native 1.0.0 → 1.0.4
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.
- package/README.md +167 -74
- package/lib/commonjs/api/client.js +3 -1
- package/lib/commonjs/api/client.js.map +1 -1
- package/lib/commonjs/api/realtime.js +2 -0
- package/lib/commonjs/api/realtime.js.map +1 -1
- package/lib/commonjs/attachments/pickers.js +493 -0
- package/lib/commonjs/attachments/pickers.js.map +1 -0
- package/lib/commonjs/i18n.js +45 -0
- package/lib/commonjs/i18n.js.map +1 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/locales.js +21 -0
- package/lib/commonjs/locales.js.map +1 -1
- package/lib/commonjs/state/widget-store.js +256 -18
- package/lib/commonjs/state/widget-store.js.map +1 -1
- package/lib/commonjs/types.js +16 -0
- package/lib/commonjs/types.js.map +1 -1
- package/lib/commonjs/ui/WidgetProvider.js +70 -10
- package/lib/commonjs/ui/WidgetProvider.js.map +1 -1
- package/lib/commonjs/ui/components/AttachmentChips.js +135 -0
- package/lib/commonjs/ui/components/AttachmentChips.js.map +1 -0
- package/lib/commonjs/ui/components/AttachmentMenu.js +125 -0
- package/lib/commonjs/ui/components/AttachmentMenu.js.map +1 -0
- package/lib/commonjs/ui/components/ImageViewer.js +181 -0
- package/lib/commonjs/ui/components/ImageViewer.js.map +1 -0
- package/lib/commonjs/ui/components/InAppNotification.js +210 -0
- package/lib/commonjs/ui/components/InAppNotification.js.map +1 -0
- package/lib/commonjs/ui/components/TeamAvatars.js +63 -0
- package/lib/commonjs/ui/components/TeamAvatars.js.map +1 -0
- package/lib/commonjs/ui/components/icons.js +50 -0
- package/lib/commonjs/ui/components/icons.js.map +1 -1
- package/lib/commonjs/ui/screens/chat/ChatView.js +303 -24
- package/lib/commonjs/ui/screens/chat/ChatView.js.map +1 -1
- package/lib/commonjs/ui/screens/chat/ChatView.styles.js +70 -0
- package/lib/commonjs/ui/screens/chat/ChatView.styles.js.map +1 -1
- package/lib/module/api/client.js +3 -1
- package/lib/module/api/client.js.map +1 -1
- package/lib/module/api/realtime.js +2 -0
- package/lib/module/api/realtime.js.map +1 -1
- package/lib/module/attachments/pickers.js +479 -0
- package/lib/module/attachments/pickers.js.map +1 -0
- package/lib/module/i18n.js +44 -0
- package/lib/module/i18n.js.map +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/locales.js +21 -0
- package/lib/module/locales.js.map +1 -1
- package/lib/module/state/widget-store.js +256 -18
- package/lib/module/state/widget-store.js.map +1 -1
- package/lib/module/types.js +16 -0
- package/lib/module/types.js.map +1 -1
- package/lib/module/ui/WidgetProvider.js +71 -11
- package/lib/module/ui/WidgetProvider.js.map +1 -1
- package/lib/module/ui/components/AttachmentChips.js +131 -0
- package/lib/module/ui/components/AttachmentChips.js.map +1 -0
- package/lib/module/ui/components/AttachmentMenu.js +120 -0
- package/lib/module/ui/components/AttachmentMenu.js.map +1 -0
- package/lib/module/ui/components/ImageViewer.js +177 -0
- package/lib/module/ui/components/ImageViewer.js.map +1 -0
- package/lib/module/ui/components/InAppNotification.js +208 -0
- package/lib/module/ui/components/InAppNotification.js.map +1 -0
- package/lib/module/ui/components/TeamAvatars.js +59 -0
- package/lib/module/ui/components/TeamAvatars.js.map +1 -0
- package/lib/module/ui/components/icons.js +49 -0
- package/lib/module/ui/components/icons.js.map +1 -1
- package/lib/module/ui/screens/chat/ChatView.js +306 -27
- package/lib/module/ui/screens/chat/ChatView.js.map +1 -1
- package/lib/module/ui/screens/chat/ChatView.styles.js +70 -0
- package/lib/module/ui/screens/chat/ChatView.styles.js.map +1 -1
- package/lib/typescript/commonjs/api/client.d.ts +8 -2
- package/lib/typescript/commonjs/api/client.d.ts.map +1 -1
- package/lib/typescript/commonjs/api/realtime.d.ts.map +1 -1
- package/lib/typescript/commonjs/attachments/pickers.d.ts +81 -0
- package/lib/typescript/commonjs/attachments/pickers.d.ts.map +1 -0
- package/lib/typescript/commonjs/i18n.d.ts +23 -0
- package/lib/typescript/commonjs/i18n.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +1 -1
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/locales.d.ts.map +1 -1
- package/lib/typescript/commonjs/state/widget-store.d.ts +40 -2
- package/lib/typescript/commonjs/state/widget-store.d.ts.map +1 -1
- package/lib/typescript/commonjs/types.d.ts +88 -1
- package/lib/typescript/commonjs/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/WidgetProvider.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/AttachmentChips.d.ts +16 -0
- package/lib/typescript/commonjs/ui/components/AttachmentChips.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/AttachmentMenu.d.ts +28 -0
- package/lib/typescript/commonjs/ui/components/AttachmentMenu.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/ImageViewer.d.ts +23 -0
- package/lib/typescript/commonjs/ui/components/ImageViewer.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/InAppNotification.d.ts +28 -0
- package/lib/typescript/commonjs/ui/components/InAppNotification.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/TeamAvatars.d.ts +17 -0
- package/lib/typescript/commonjs/ui/components/TeamAvatars.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/icons.d.ts +2 -0
- package/lib/typescript/commonjs/ui/components/icons.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/chat/ChatView.d.ts +3 -2
- package/lib/typescript/commonjs/ui/screens/chat/ChatView.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/chat/ChatView.styles.d.ts +69 -0
- package/lib/typescript/commonjs/ui/screens/chat/ChatView.styles.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/chat/ChatView.types.d.ts +41 -2
- package/lib/typescript/commonjs/ui/screens/chat/ChatView.types.d.ts.map +1 -1
- package/lib/typescript/module/api/client.d.ts +8 -2
- package/lib/typescript/module/api/client.d.ts.map +1 -1
- package/lib/typescript/module/api/realtime.d.ts.map +1 -1
- package/lib/typescript/module/attachments/pickers.d.ts +81 -0
- package/lib/typescript/module/attachments/pickers.d.ts.map +1 -0
- package/lib/typescript/module/i18n.d.ts +23 -0
- package/lib/typescript/module/i18n.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +1 -1
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/locales.d.ts.map +1 -1
- package/lib/typescript/module/state/widget-store.d.ts +40 -2
- package/lib/typescript/module/state/widget-store.d.ts.map +1 -1
- package/lib/typescript/module/types.d.ts +88 -1
- package/lib/typescript/module/types.d.ts.map +1 -1
- package/lib/typescript/module/ui/WidgetProvider.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/AttachmentChips.d.ts +16 -0
- package/lib/typescript/module/ui/components/AttachmentChips.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/AttachmentMenu.d.ts +28 -0
- package/lib/typescript/module/ui/components/AttachmentMenu.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/ImageViewer.d.ts +23 -0
- package/lib/typescript/module/ui/components/ImageViewer.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/InAppNotification.d.ts +28 -0
- package/lib/typescript/module/ui/components/InAppNotification.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/TeamAvatars.d.ts +17 -0
- package/lib/typescript/module/ui/components/TeamAvatars.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/icons.d.ts +2 -0
- package/lib/typescript/module/ui/components/icons.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/chat/ChatView.d.ts +3 -2
- package/lib/typescript/module/ui/screens/chat/ChatView.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/chat/ChatView.styles.d.ts +69 -0
- package/lib/typescript/module/ui/screens/chat/ChatView.styles.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/chat/ChatView.types.d.ts +41 -2
- package/lib/typescript/module/ui/screens/chat/ChatView.types.d.ts.map +1 -1
- package/metro.js +17 -32
- package/package.json +18 -1
- package/src/api/client.ts +5 -3
- package/src/api/realtime.ts +2 -0
- package/src/attachments/pickers.ts +515 -0
- package/src/i18n.ts +48 -0
- package/src/index.tsx +4 -0
- package/src/locales.ts +21 -0
- package/src/state/widget-store.ts +256 -19
- package/src/types.ts +95 -0
- package/src/ui/WidgetProvider.tsx +90 -9
- package/src/ui/components/AttachmentChips.tsx +135 -0
- package/src/ui/components/AttachmentMenu.tsx +132 -0
- package/src/ui/components/ImageViewer.tsx +178 -0
- package/src/ui/components/InAppNotification.tsx +225 -0
- package/src/ui/components/TeamAvatars.tsx +58 -0
- package/src/ui/components/icons.tsx +37 -0
- package/src/ui/screens/chat/ChatView.styles.ts +70 -0
- package/src/ui/screens/chat/ChatView.tsx +345 -35
- package/src/ui/screens/chat/ChatView.types.ts +51 -2
|
@@ -7,11 +7,16 @@ exports.ChatView = ChatView;
|
|
|
7
7
|
var _react = require("react");
|
|
8
8
|
var _reactNative = require("react-native");
|
|
9
9
|
var _agentUiBlocks = require("../../../agent-ui-blocks.js");
|
|
10
|
+
var _pickers = require("../../../attachments/pickers.js");
|
|
10
11
|
var _AgentUiBlocks = require("../../components/AgentUiBlocks.js");
|
|
12
|
+
var _AttachmentChips = require("../../components/AttachmentChips.js");
|
|
13
|
+
var _AttachmentMenu = require("../../components/AttachmentMenu.js");
|
|
11
14
|
var _Avatar = require("../../components/Avatar.js");
|
|
15
|
+
var _ImageViewer = require("../../components/ImageViewer.js");
|
|
12
16
|
var _ErrorBoundary = require("../../components/ErrorBoundary.js");
|
|
13
17
|
var _Gradient = require("../../components/Gradient.js");
|
|
14
18
|
var _icons = require("../../components/icons.js");
|
|
19
|
+
var _TeamAvatars = require("../../components/TeamAvatars.js");
|
|
15
20
|
var _Markdown = require("../../components/Markdown.js");
|
|
16
21
|
var _useReduceMotion = require("../../hooks/use-reduce-motion.js");
|
|
17
22
|
var _global = require("../../styles/global.js");
|
|
@@ -28,7 +33,8 @@ var _jsxRuntime = require("react/jsx-runtime");
|
|
|
28
33
|
*
|
|
29
34
|
* Mobile differences: older messages load through an explicit "Load earlier
|
|
30
35
|
* messages" control (a scroll-anchored infinite list fights the keyboard), and
|
|
31
|
-
* the
|
|
36
|
+
* the attach button only shows when the host app has a picker to open (see
|
|
37
|
+
* `attachments/pickers.ts`).
|
|
32
38
|
*/
|
|
33
39
|
|
|
34
40
|
function ChatView({
|
|
@@ -39,11 +45,17 @@ function ChatView({
|
|
|
39
45
|
color,
|
|
40
46
|
agentName,
|
|
41
47
|
agentAvatar,
|
|
48
|
+
agentOnline,
|
|
42
49
|
isOnline,
|
|
50
|
+
teamName,
|
|
51
|
+
teamAvatars = [],
|
|
52
|
+
replyTimeLabel,
|
|
43
53
|
locale,
|
|
44
54
|
prefillText,
|
|
45
55
|
offlineMessage,
|
|
46
56
|
labels,
|
|
57
|
+
attachmentSources = [],
|
|
58
|
+
onPickAttachments,
|
|
47
59
|
onSendMessage,
|
|
48
60
|
onIdentifyVisitor,
|
|
49
61
|
onLoadMore,
|
|
@@ -53,14 +65,38 @@ function ChatView({
|
|
|
53
65
|
}) {
|
|
54
66
|
const [input, setInput] = (0, _react.useState)(prefillText || '');
|
|
55
67
|
const [composerFocused, setComposerFocused] = (0, _react.useState)(false);
|
|
68
|
+
const [drafts, setDrafts] = (0, _react.useState)([]);
|
|
69
|
+
const [menuOpen, setMenuOpen] = (0, _react.useState)(false);
|
|
70
|
+
const [viewerImage, setViewerImage] = (0, _react.useState)(null);
|
|
71
|
+
const [picking, setPicking] = (0, _react.useState)(false);
|
|
72
|
+
// Only attachment sends hold the composer; text sends stay fire-and-forget.
|
|
73
|
+
const [sending, setSending] = (0, _react.useState)(false);
|
|
74
|
+
const [notice, setNotice] = (0, _react.useState)(null);
|
|
56
75
|
const reduceMotion = (0, _useReduceMotion.useReduceMotion)();
|
|
57
76
|
const scrollRef = (0, _react.useRef)(null);
|
|
58
77
|
const typingStopTimer = (0, _react.useRef)(null);
|
|
78
|
+
const noticeTimer = (0, _react.useRef)(null);
|
|
79
|
+
const pickerTimer = (0, _react.useRef)(null);
|
|
80
|
+
const mounted = (0, _react.useRef)(false);
|
|
59
81
|
(0, _react.useEffect)(() => {
|
|
60
82
|
if (prefillText) setInput(prefillText);
|
|
61
83
|
}, [prefillText]);
|
|
62
|
-
|
|
63
|
-
|
|
84
|
+
|
|
85
|
+
// The provider keys this screen by the store's thread generation. Pending
|
|
86
|
+
// picks and sends belong to this mount, even before a draft gets a server ID.
|
|
87
|
+
(0, _react.useEffect)(() => {
|
|
88
|
+
mounted.current = true;
|
|
89
|
+
return () => {
|
|
90
|
+
mounted.current = false;
|
|
91
|
+
if (typingStopTimer.current) clearTimeout(typingStopTimer.current);
|
|
92
|
+
if (noticeTimer.current) clearTimeout(noticeTimer.current);
|
|
93
|
+
if (pickerTimer.current) clearTimeout(pickerTimer.current);
|
|
94
|
+
};
|
|
95
|
+
}, []);
|
|
96
|
+
const showNotice = (0, _react.useCallback)(message => {
|
|
97
|
+
if (noticeTimer.current) clearTimeout(noticeTimer.current);
|
|
98
|
+
setNotice(message);
|
|
99
|
+
if (message) noticeTimer.current = setTimeout(() => setNotice(null), 5000);
|
|
64
100
|
}, []);
|
|
65
101
|
const scrollToBottom = (0, _react.useCallback)((animated = true) => {
|
|
66
102
|
scrollRef.current?.scrollToEnd({
|
|
@@ -104,15 +140,101 @@ function ChatView({
|
|
|
104
140
|
viewportHeight.current = height;
|
|
105
141
|
if (shrank) scrollToBottom(false);
|
|
106
142
|
}, [scrollToBottom]);
|
|
107
|
-
const
|
|
143
|
+
const showAttach = attachmentSources.length > 0 && !!onPickAttachments;
|
|
144
|
+
const canSend = !sending && (input.trim().length > 0 || drafts.length > 0);
|
|
145
|
+
const problemLabel = problem => {
|
|
146
|
+
switch (problem) {
|
|
147
|
+
case 'tooMany':
|
|
148
|
+
return labels.attach.tooMany;
|
|
149
|
+
case 'tooLarge':
|
|
150
|
+
return labels.attach.tooLarge;
|
|
151
|
+
case 'tooLargeTogether':
|
|
152
|
+
return labels.attach.tooLargeTogether;
|
|
153
|
+
case 'cameraDenied':
|
|
154
|
+
return labels.attach.cameraDenied;
|
|
155
|
+
case 'failed':
|
|
156
|
+
return labels.attach.failed;
|
|
157
|
+
default:
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
const handlePick = async source => {
|
|
162
|
+
if (!mounted.current || !onPickAttachments || picking) return;
|
|
163
|
+
setPicking(true);
|
|
164
|
+
try {
|
|
165
|
+
const result = await onPickAttachments(source, drafts);
|
|
166
|
+
if (!mounted.current) return;
|
|
167
|
+
if (result.added.length > 0) {
|
|
168
|
+
setDrafts(current => [...current, ...result.added].slice(0, _pickers.MAX_ATTACHMENTS));
|
|
169
|
+
}
|
|
170
|
+
showNotice(problemLabel(result.problem));
|
|
171
|
+
} finally {
|
|
172
|
+
if (mounted.current) setPicking(false);
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
const menuOptions = attachmentSources.map(source => ({
|
|
176
|
+
key: source,
|
|
177
|
+
label: source === 'photo' ? labels.attach.photoLibrary : source === 'camera' ? labels.attach.takePhoto : labels.attach.file
|
|
178
|
+
}));
|
|
179
|
+
const handleAttachPress = () => {
|
|
180
|
+
if (sending || picking) return;
|
|
181
|
+
if (drafts.length >= _pickers.MAX_ATTACHMENTS) {
|
|
182
|
+
showNotice(labels.attach.tooMany);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
const only = attachmentSources.length === 1 ? attachmentSources[0] : undefined;
|
|
186
|
+
if (only) {
|
|
187
|
+
void handlePick(only);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const native = (0, _AttachmentMenu.showNativeAttachmentMenu)(menuOptions, labels.attach.cancel, source => {
|
|
191
|
+
if (!mounted.current) return;
|
|
192
|
+
// Presenting a picker while the action sheet is still animating away
|
|
193
|
+
// fails silently on iOS; let it finish first.
|
|
194
|
+
pickerTimer.current = setTimeout(() => void handlePick(source), 350);
|
|
195
|
+
});
|
|
196
|
+
if (!native) setMenuOpen(true);
|
|
197
|
+
};
|
|
198
|
+
const handleMenuSelect = source => {
|
|
199
|
+
setMenuOpen(false);
|
|
200
|
+
void handlePick(source);
|
|
201
|
+
};
|
|
108
202
|
const handleSend = () => {
|
|
109
203
|
const trimmed = input.trim();
|
|
110
|
-
if (!
|
|
204
|
+
if (!canSend) return;
|
|
111
205
|
setInput('');
|
|
112
206
|
if (typingStopTimer.current) clearTimeout(typingStopTimer.current);
|
|
113
207
|
onTyping(false);
|
|
114
|
-
onSendMessage(trimmed);
|
|
115
208
|
scrollToBottom();
|
|
209
|
+
if (drafts.length === 0) {
|
|
210
|
+
// The failed bubble already surfaces a failure to the visitor.
|
|
211
|
+
onSendMessage(trimmed).catch(() => {});
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
const sent = drafts;
|
|
215
|
+
setSending(true);
|
|
216
|
+
showNotice(null);
|
|
217
|
+
onSendMessage(trimmed, sent.map(draft => ({
|
|
218
|
+
name: draft.name,
|
|
219
|
+
url: draft.dataUrl,
|
|
220
|
+
contentType: draft.contentType,
|
|
221
|
+
size: draft.size,
|
|
222
|
+
previewUrl: draft.previewUri
|
|
223
|
+
}))).then(() => {
|
|
224
|
+
if (!mounted.current) return;
|
|
225
|
+
const sentIds = new Set(sent.map(draft => draft.id));
|
|
226
|
+
setDrafts(current => current.filter(draft => !sentIds.has(draft.id)));
|
|
227
|
+
}).catch(error => {
|
|
228
|
+
// The visitor has moved to another thread: nothing to restore there.
|
|
229
|
+
if (!mounted.current) return;
|
|
230
|
+
// Chips stay for a retry; put the text back too unless they've
|
|
231
|
+
// already started typing something else.
|
|
232
|
+
setInput(current => current || trimmed);
|
|
233
|
+
const status = error?.status;
|
|
234
|
+
showNotice(status === 413 ? labels.attach.tooLargeTogether : labels.failedToSend);
|
|
235
|
+
}).finally(() => {
|
|
236
|
+
if (mounted.current) setSending(false);
|
|
237
|
+
});
|
|
116
238
|
};
|
|
117
239
|
const handleChangeText = text => {
|
|
118
240
|
setInput(text);
|
|
@@ -129,12 +251,19 @@ function ChatView({
|
|
|
129
251
|
phone: stringPayload(action.payload.phone)
|
|
130
252
|
});
|
|
131
253
|
}
|
|
132
|
-
onSendMessage((0, _agentUiBlocks.formatAgentUiActionMessage)(action), undefined, action);
|
|
254
|
+
onSendMessage((0, _agentUiBlocks.formatAgentUiActionMessage)(action), undefined, action).catch(() => {});
|
|
133
255
|
scrollToBottom();
|
|
134
256
|
}, [onIdentifyVisitor, onSendMessage, scrollToBottom]);
|
|
135
257
|
const headerTextColor = (0, _theme.contrastingTextColor)(color);
|
|
136
258
|
const onDarkHeader = headerTextColor === _theme.palette.white;
|
|
137
259
|
const isEmptyThread = !loading && messages.length === 0;
|
|
260
|
+
const agentIsActive = agentOnline ?? isOnline;
|
|
261
|
+
// "Waiting for a teammate" until someone replies: no teammate in the header
|
|
262
|
+
// and the visitor spoke last (bot replies before that still count as waiting).
|
|
263
|
+
const lastChatMessage = [...messages].reverse().find(message => (message.messageType ?? 'message') === 'message' && !message.deletedAt);
|
|
264
|
+
const awaitingTeammate = !agentName && !agentTyping && lastChatMessage?.direction === 'inbound';
|
|
265
|
+
// "Sent" / "Seen" goes under the visitor's latest message only.
|
|
266
|
+
const lastVisitorMessageId = [...messages].reverse().find(message => message.direction === 'inbound' && (message.messageType ?? 'message') === 'message')?.id;
|
|
138
267
|
// Two stops off the brand color: flat fill looked like an unstyled block.
|
|
139
268
|
// A non-hex brand color falls through mixHex unchanged and renders solid.
|
|
140
269
|
const headerGradient = [color, (0, _theme.mixHex)(color, '#000000', 0.16)];
|
|
@@ -161,7 +290,10 @@ function ChatView({
|
|
|
161
290
|
direction: (0, _rtl.backDirection)(),
|
|
162
291
|
color: headerTextColor
|
|
163
292
|
})
|
|
164
|
-
}),
|
|
293
|
+
}), agentName ?
|
|
294
|
+
/*#__PURE__*/
|
|
295
|
+
// A teammate joined: their photo, name and own presence.
|
|
296
|
+
(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
165
297
|
style: _ChatViewStyles.styles.headerIdentity,
|
|
166
298
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
167
299
|
style: _ChatViewStyles.styles.avatarWrapper,
|
|
@@ -171,7 +303,7 @@ function ChatView({
|
|
|
171
303
|
size: 36,
|
|
172
304
|
translucent: onDarkHeader,
|
|
173
305
|
color: _theme.palette.ink
|
|
174
|
-
}),
|
|
306
|
+
}), agentIsActive ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
175
307
|
style: _ChatViewStyles.styles.onlineDot
|
|
176
308
|
}) : null]
|
|
177
309
|
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
@@ -181,15 +313,48 @@ function ChatView({
|
|
|
181
313
|
color: headerTextColor
|
|
182
314
|
}],
|
|
183
315
|
numberOfLines: 1,
|
|
184
|
-
children: agentName
|
|
316
|
+
children: agentName
|
|
185
317
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
186
318
|
style: [_ChatViewStyles.styles.headerStatus, {
|
|
187
319
|
color: (0, _theme.withAlpha)(headerTextColor, 0.75)
|
|
188
320
|
}],
|
|
189
321
|
numberOfLines: 1,
|
|
190
|
-
children:
|
|
322
|
+
children: agentIsActive ? labels.statusOnline : labels.statusAway
|
|
191
323
|
})]
|
|
192
324
|
})]
|
|
325
|
+
}) :
|
|
326
|
+
/*#__PURE__*/
|
|
327
|
+
// No teammate yet, or the conversation closed: the team, Intercom-style.
|
|
328
|
+
(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
329
|
+
style: _ChatViewStyles.styles.headerIdentity,
|
|
330
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_TeamAvatars.TeamAvatars, {
|
|
331
|
+
avatars: teamAvatars,
|
|
332
|
+
size: 30,
|
|
333
|
+
color: _theme.palette.ink,
|
|
334
|
+
ringColor: color,
|
|
335
|
+
translucent: onDarkHeader
|
|
336
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
337
|
+
style: _ChatViewStyles.styles.headerText,
|
|
338
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
339
|
+
style: [_ChatViewStyles.styles.headerName, {
|
|
340
|
+
color: headerTextColor
|
|
341
|
+
}],
|
|
342
|
+
numberOfLines: 1,
|
|
343
|
+
children: teamName || labels.team
|
|
344
|
+
}), replyTimeLabel ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
345
|
+
style: _ChatViewStyles.styles.headerReplyTime,
|
|
346
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_icons.ClockIcon, {
|
|
347
|
+
size: 12,
|
|
348
|
+
color: (0, _theme.withAlpha)(headerTextColor, 0.75)
|
|
349
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
350
|
+
style: [_ChatViewStyles.styles.headerStatus, _ChatViewStyles.styles.headerReplyTimeText, {
|
|
351
|
+
color: (0, _theme.withAlpha)(headerTextColor, 0.75)
|
|
352
|
+
}],
|
|
353
|
+
numberOfLines: 1,
|
|
354
|
+
children: replyTimeLabel
|
|
355
|
+
})]
|
|
356
|
+
}) : null]
|
|
357
|
+
})]
|
|
193
358
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
|
|
194
359
|
onPress: onClose,
|
|
195
360
|
accessibilityRole: "button",
|
|
@@ -266,14 +431,26 @@ function ChatView({
|
|
|
266
431
|
message: message,
|
|
267
432
|
color: color,
|
|
268
433
|
labels: labels,
|
|
434
|
+
onOpenImage: setViewerImage,
|
|
269
435
|
senderLabel: isAgentMessage && (startsRun || startsDay) ? message.senderName || agentName || labels.team : null,
|
|
270
436
|
timeLabel: (0, _conversations.formatClockTime)(message.createdAt, locale),
|
|
271
|
-
|
|
437
|
+
receiptLabel: message.id === lastVisitorMessageId ? receiptLabel(message, labels) : undefined,
|
|
438
|
+
onSubmitProcedureForm: shouldShowProcedureForm(messages, message) ? body => void onSendMessage(body).catch(() => {}) : undefined,
|
|
272
439
|
onAgentUiAction: shouldShowAgentUiBlocks(messages, message) ? handleAgentUiAction : undefined
|
|
273
440
|
})
|
|
274
441
|
})]
|
|
275
442
|
}, message.id);
|
|
276
|
-
}),
|
|
443
|
+
}), awaitingTeammate ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
444
|
+
style: _ChatViewStyles.styles.waitingRow,
|
|
445
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_TeamAvatars.TeamAvatars, {
|
|
446
|
+
avatars: teamAvatars,
|
|
447
|
+
size: 20,
|
|
448
|
+
color: color
|
|
449
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
450
|
+
style: _ChatViewStyles.styles.waitingText,
|
|
451
|
+
children: labels.waitingForTeammate
|
|
452
|
+
})]
|
|
453
|
+
}) : null, agentTyping ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
277
454
|
style: _ChatViewStyles.styles.typingRow,
|
|
278
455
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
279
456
|
style: _ChatViewStyles.styles.typingBubble,
|
|
@@ -298,13 +475,44 @@ function ChatView({
|
|
|
298
475
|
style: _ChatViewStyles.styles.offlineText,
|
|
299
476
|
children: offlineMessage
|
|
300
477
|
})
|
|
301
|
-
}) : null, /*#__PURE__*/(0, _jsxRuntime.
|
|
478
|
+
}) : null, /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
302
479
|
style: _ChatViewStyles.styles.composerWrapper,
|
|
303
|
-
children: /*#__PURE__*/(0, _jsxRuntime.
|
|
304
|
-
style:
|
|
480
|
+
children: [notice ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
481
|
+
style: _ChatViewStyles.styles.composerNotice,
|
|
482
|
+
accessibilityRole: "alert",
|
|
483
|
+
accessibilityLiveRegion: "polite",
|
|
484
|
+
children: notice
|
|
485
|
+
}) : null, drafts.length > 0 ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_AttachmentChips.AttachmentChips, {
|
|
486
|
+
attachments: drafts,
|
|
487
|
+
disabled: sending,
|
|
488
|
+
removeLabel: labels.attach.remove,
|
|
489
|
+
onRemove: id => {
|
|
490
|
+
setDrafts(current => current.filter(draft => draft.id !== id));
|
|
491
|
+
showNotice(null);
|
|
492
|
+
}
|
|
493
|
+
}) : null, /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
494
|
+
style: [_ChatViewStyles.styles.composer, showAttach && _ChatViewStyles.styles.composerWithAttach, composerFocused && _ChatViewStyles.styles.composerFocused, composerFocused && {
|
|
305
495
|
borderColor: (0, _theme.withAlpha)(color, 0.55)
|
|
306
496
|
}],
|
|
307
|
-
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.
|
|
497
|
+
children: [showAttach ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
|
|
498
|
+
onPress: handleAttachPress,
|
|
499
|
+
disabled: sending || picking,
|
|
500
|
+
accessibilityRole: "button",
|
|
501
|
+
accessibilityLabel: labels.attach.button,
|
|
502
|
+
accessibilityState: {
|
|
503
|
+
disabled: sending || picking
|
|
504
|
+
}
|
|
505
|
+
// 36pt circle + 4pt on every side = a 44pt target.
|
|
506
|
+
,
|
|
507
|
+
hitSlop: 4,
|
|
508
|
+
style: ({
|
|
509
|
+
pressed
|
|
510
|
+
}) => [_ChatViewStyles.styles.attachButton, (pressed || sending || picking) && _global.globalStyles.pressed],
|
|
511
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_icons.PaperclipIcon, {
|
|
512
|
+
size: 20,
|
|
513
|
+
color: _theme.palette.textMuted
|
|
514
|
+
})
|
|
515
|
+
}) : null, /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TextInput, {
|
|
308
516
|
value: input,
|
|
309
517
|
onChangeText: handleChangeText,
|
|
310
518
|
onFocus: () => setComposerFocused(true),
|
|
@@ -332,7 +540,21 @@ function ChatView({
|
|
|
332
540
|
color: canSend ? (0, _theme.contrastingTextColor)(color) : _theme.palette.textSubtle
|
|
333
541
|
})
|
|
334
542
|
})]
|
|
335
|
-
})
|
|
543
|
+
})]
|
|
544
|
+
}), showAttach && _reactNative.Platform.OS !== 'ios' ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_AttachmentMenu.AttachmentMenu, {
|
|
545
|
+
visible: menuOpen,
|
|
546
|
+
options: menuOptions,
|
|
547
|
+
cancelLabel: labels.attach.cancel,
|
|
548
|
+
onSelect: handleMenuSelect,
|
|
549
|
+
onClose: () => setMenuOpen(false)
|
|
550
|
+
}) : null, /*#__PURE__*/(0, _jsxRuntime.jsx)(_ImageViewer.ImageViewer, {
|
|
551
|
+
image: viewerImage,
|
|
552
|
+
labels: labels.viewer,
|
|
553
|
+
onClose: () => setViewerImage(null),
|
|
554
|
+
onOpenExternally: url => {
|
|
555
|
+
setViewerImage(null);
|
|
556
|
+
void (0, _safeUrl.openExternalUrl)(url);
|
|
557
|
+
}
|
|
336
558
|
})]
|
|
337
559
|
});
|
|
338
560
|
}
|
|
@@ -342,9 +564,29 @@ function MessageBubble({
|
|
|
342
564
|
labels,
|
|
343
565
|
senderLabel,
|
|
344
566
|
timeLabel,
|
|
567
|
+
receiptLabel,
|
|
345
568
|
onSubmitProcedureForm,
|
|
346
|
-
onAgentUiAction
|
|
569
|
+
onAgentUiAction,
|
|
570
|
+
onOpenImage
|
|
347
571
|
}) {
|
|
572
|
+
const joinedName = message.metadata?.event === 'agent_joined' ? message.metadata?.agentName : null;
|
|
573
|
+
if (message.messageType === 'system' && typeof joinedName === 'string' && joinedName) {
|
|
574
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
575
|
+
style: [_ChatViewStyles.styles.systemRow, _ChatViewStyles.styles.joinedRow],
|
|
576
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_Avatar.Avatar, {
|
|
577
|
+
avatar: typeof message.metadata?.agentAvatar === 'string' ? message.metadata.agentAvatar : null,
|
|
578
|
+
name: joinedName,
|
|
579
|
+
size: 20,
|
|
580
|
+
color: _theme.palette.ink
|
|
581
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
|
|
582
|
+
style: _ChatViewStyles.styles.systemText,
|
|
583
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
584
|
+
style: _ChatViewStyles.styles.joinedName,
|
|
585
|
+
children: joinedName
|
|
586
|
+
}), " ", labels.joinedConversation]
|
|
587
|
+
})]
|
|
588
|
+
});
|
|
589
|
+
}
|
|
348
590
|
if (message.messageType === 'system') {
|
|
349
591
|
const text = message.bodyPlain || (0, _html.stripHtml)(message.body || '') || labels.conversationUpdated;
|
|
350
592
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
@@ -356,6 +598,18 @@ function MessageBubble({
|
|
|
356
598
|
});
|
|
357
599
|
}
|
|
358
600
|
const isVisitor = message.direction === 'inbound';
|
|
601
|
+
if (message.deletedAt) {
|
|
602
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
603
|
+
style: [_ChatViewStyles.styles.messageRow, isVisitor ? _ChatViewStyles.styles.messageRowRight : _ChatViewStyles.styles.messageRowLeft],
|
|
604
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
605
|
+
style: _ChatViewStyles.styles.deletedBubble,
|
|
606
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
607
|
+
style: _ChatViewStyles.styles.deletedText,
|
|
608
|
+
children: labels.messageDeleted
|
|
609
|
+
})
|
|
610
|
+
})
|
|
611
|
+
});
|
|
612
|
+
}
|
|
359
613
|
const rtl = (0, _rtl.isRTL)();
|
|
360
614
|
const messageText = message.bodyPlain || (0, _html.stripHtml)(message.body || '');
|
|
361
615
|
const hasBody = !!messageText;
|
|
@@ -383,7 +637,8 @@ function MessageBubble({
|
|
|
383
637
|
style: [_ChatViewStyles.styles.attachments, hasBody && _ChatViewStyles.styles.attachmentsSpaced],
|
|
384
638
|
children: message.attachments.map(attachment => /*#__PURE__*/(0, _jsxRuntime.jsx)(AttachmentPreview, {
|
|
385
639
|
attachment: attachment,
|
|
386
|
-
isVisitor: isVisitor
|
|
640
|
+
isVisitor: isVisitor,
|
|
641
|
+
onOpenImage: onOpenImage
|
|
387
642
|
}, attachment.id))
|
|
388
643
|
}) : null, message.status === 'failed' ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
389
644
|
style: [_ChatViewStyles.styles.failedText, {
|
|
@@ -396,7 +651,10 @@ function MessageBubble({
|
|
|
396
651
|
}],
|
|
397
652
|
children: timeLabel
|
|
398
653
|
}) : null]
|
|
399
|
-
}),
|
|
654
|
+
}), receiptLabel ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
655
|
+
style: _ChatViewStyles.styles.receiptText,
|
|
656
|
+
children: receiptLabel
|
|
657
|
+
}) : null, agentUiBlocks.length > 0 && onAgentUiAction ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_AgentUiBlocks.AgentUiBlocks, {
|
|
400
658
|
blocks: agentUiBlocks,
|
|
401
659
|
color: color,
|
|
402
660
|
labels: labels.blocks,
|
|
@@ -409,16 +667,32 @@ function MessageBubble({
|
|
|
409
667
|
}) : null]
|
|
410
668
|
});
|
|
411
669
|
}
|
|
670
|
+
|
|
671
|
+
/** Sent once the server has it, Seen once a teammate opened the conversation. */
|
|
672
|
+
function receiptLabel(message, labels) {
|
|
673
|
+
if (message.deletedAt || message.status === 'pending' || message.status === 'failed') return undefined;
|
|
674
|
+
return message.status === 'read' ? labels.messageSeen : labels.messageSent;
|
|
675
|
+
}
|
|
412
676
|
function AttachmentPreview({
|
|
413
677
|
attachment,
|
|
414
|
-
isVisitor
|
|
678
|
+
isVisitor,
|
|
679
|
+
onOpenImage
|
|
415
680
|
}) {
|
|
416
681
|
// `url` is empty when the server sent a non-http(s) URL (see resolveUrl).
|
|
417
|
-
|
|
418
|
-
|
|
682
|
+
// An unsent attachment shows its local file (or data URL): it renders, but
|
|
683
|
+
// there is nothing to open until the server copy arrives.
|
|
684
|
+
const [imageFailed, setImageFailed] = (0, _react.useState)(false);
|
|
685
|
+
const isImage = (0, _pickers.isImageAttachment)(attachment.contentType) && !!attachment.url && !imageFailed;
|
|
686
|
+
const openable = (0, _safeUrl.isHttpUrl)(attachment.url);
|
|
687
|
+
// Images open in the in-app viewer; other files (PDF, sheets) need the OS.
|
|
688
|
+
const open = !openable ? undefined : isImage ? () => onOpenImage({
|
|
689
|
+
url: attachment.url,
|
|
690
|
+
name: attachment.name
|
|
691
|
+
}) : () => void (0, _safeUrl.openExternalUrl)(attachment.url);
|
|
419
692
|
if (isImage) {
|
|
420
693
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
|
|
421
694
|
onPress: open,
|
|
695
|
+
disabled: !openable,
|
|
422
696
|
accessibilityRole: "imagebutton",
|
|
423
697
|
accessibilityLabel: attachment.name,
|
|
424
698
|
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Image, {
|
|
@@ -428,11 +702,16 @@ function AttachmentPreview({
|
|
|
428
702
|
style: _ChatViewStyles.styles.attachmentImage,
|
|
429
703
|
resizeMode: "cover",
|
|
430
704
|
accessibilityIgnoresInvertColors: true
|
|
705
|
+
// An image that can't load (expired link, deleted file) falls back to
|
|
706
|
+
// the file row below instead of an empty box.
|
|
707
|
+
,
|
|
708
|
+
onError: () => setImageFailed(true)
|
|
431
709
|
})
|
|
432
710
|
});
|
|
433
711
|
}
|
|
434
712
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
|
|
435
713
|
onPress: open,
|
|
714
|
+
disabled: !openable,
|
|
436
715
|
accessibilityRole: "button",
|
|
437
716
|
style: [_ChatViewStyles.styles.attachmentFile, {
|
|
438
717
|
borderColor: isVisitor ? (0, _theme.withAlpha)(_theme.palette.white, 0.35) : _theme.palette.border
|