@glitchgrab/whatsapp 0.1.0
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 +147 -0
- package/dist/index.d.mts +280 -0
- package/dist/index.d.ts +280 -0
- package/dist/index.js +330 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +299 -0
- package/dist/index.mjs.map +1 -0
- package/dist/react.d.mts +52 -0
- package/dist/react.d.ts +52 -0
- package/dist/react.js +462 -0
- package/dist/react.js.map +1 -0
- package/dist/react.mjs +435 -0
- package/dist/react.mjs.map +1 -0
- package/dist/types-BKOEzRzb.d.mts +97 -0
- package/dist/types-BKOEzRzb.d.ts +97 -0
- package/package.json +64 -0
package/dist/react.js
ADDED
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
"use client";
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/react.ts
|
|
22
|
+
var react_exports = {};
|
|
23
|
+
__export(react_exports, {
|
|
24
|
+
WhatsappInbox: () => WhatsappInbox,
|
|
25
|
+
useInbox: () => useInbox
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(react_exports);
|
|
28
|
+
|
|
29
|
+
// src/inbox.tsx
|
|
30
|
+
var import_react2 = require("react");
|
|
31
|
+
|
|
32
|
+
// src/use-inbox.ts
|
|
33
|
+
var import_react = require("react");
|
|
34
|
+
async function call(url, init) {
|
|
35
|
+
const res = await fetch(url, init);
|
|
36
|
+
const body = await res.json().catch(() => ({}));
|
|
37
|
+
if (!res.ok) throw new Error(body.error ?? `Request failed (${res.status})`);
|
|
38
|
+
return body;
|
|
39
|
+
}
|
|
40
|
+
function useInbox(options) {
|
|
41
|
+
const { api, live = true } = options;
|
|
42
|
+
const base = api.replace(/\/$/, "");
|
|
43
|
+
const [conversations, setConversations] = (0, import_react.useState)([]);
|
|
44
|
+
const [selected, setSelected] = (0, import_react.useState)(null);
|
|
45
|
+
const [agents, setAgents] = (0, import_react.useState)([]);
|
|
46
|
+
const [loading, setLoading] = (0, import_react.useState)(true);
|
|
47
|
+
const [sending, setSending] = (0, import_react.useState)(false);
|
|
48
|
+
const [error, setError] = (0, import_react.useState)(null);
|
|
49
|
+
const [connected, setConnected] = (0, import_react.useState)(false);
|
|
50
|
+
const selectedIdRef = (0, import_react.useRef)(options.initialConversationId ?? null);
|
|
51
|
+
const refresh = (0, import_react.useCallback)(async () => {
|
|
52
|
+
try {
|
|
53
|
+
const data = await call(`${base}/conversations`);
|
|
54
|
+
setConversations(data.conversations);
|
|
55
|
+
setError(null);
|
|
56
|
+
} catch (err) {
|
|
57
|
+
setError(err instanceof Error ? err.message : "Could not load conversations");
|
|
58
|
+
} finally {
|
|
59
|
+
setLoading(false);
|
|
60
|
+
}
|
|
61
|
+
}, [base]);
|
|
62
|
+
const openThread = (0, import_react.useCallback)(
|
|
63
|
+
async (conversationId) => {
|
|
64
|
+
selectedIdRef.current = conversationId;
|
|
65
|
+
try {
|
|
66
|
+
const data = await call(
|
|
67
|
+
`${base}/conversations/${conversationId}`
|
|
68
|
+
);
|
|
69
|
+
setSelected(data.conversation);
|
|
70
|
+
setConversations(
|
|
71
|
+
(prev) => prev.map((c2) => c2.id === conversationId ? { ...c2, unreadCount: 0 } : c2)
|
|
72
|
+
);
|
|
73
|
+
} catch (err) {
|
|
74
|
+
setError(err instanceof Error ? err.message : "Could not open conversation");
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
[base]
|
|
78
|
+
);
|
|
79
|
+
(0, import_react.useEffect)(() => {
|
|
80
|
+
void refresh();
|
|
81
|
+
void call(`${base}/agents`).then((d) => setAgents(d.agents)).catch(() => void 0);
|
|
82
|
+
if (options.initialConversationId) void openThread(options.initialConversationId);
|
|
83
|
+
}, [base, refresh, openThread, options.initialConversationId]);
|
|
84
|
+
(0, import_react.useEffect)(() => {
|
|
85
|
+
if (!live) return;
|
|
86
|
+
let source = null;
|
|
87
|
+
let cancelled = false;
|
|
88
|
+
let retry = null;
|
|
89
|
+
const connect = async () => {
|
|
90
|
+
if (cancelled) return;
|
|
91
|
+
try {
|
|
92
|
+
const session = await call(`${base}/ticket`, {
|
|
93
|
+
method: "POST"
|
|
94
|
+
});
|
|
95
|
+
if (cancelled) return;
|
|
96
|
+
source = new EventSource(
|
|
97
|
+
`${session.baseUrl}/api/v1/wa/inbox/stream?ticket=${encodeURIComponent(session.ticket)}`
|
|
98
|
+
);
|
|
99
|
+
source.addEventListener("ready", () => setConnected(true));
|
|
100
|
+
source.addEventListener("conversation", (event) => {
|
|
101
|
+
const updated = JSON.parse(event.data);
|
|
102
|
+
setConversations((prev) => {
|
|
103
|
+
const rest = prev.filter((c2) => c2.id !== updated.id);
|
|
104
|
+
return [updated, ...rest];
|
|
105
|
+
});
|
|
106
|
+
if (selectedIdRef.current === updated.id) void openThread(updated.id);
|
|
107
|
+
});
|
|
108
|
+
source.addEventListener("reconnect", () => {
|
|
109
|
+
source?.close();
|
|
110
|
+
setConnected(false);
|
|
111
|
+
retry = setTimeout(connect, 100);
|
|
112
|
+
});
|
|
113
|
+
source.onerror = () => {
|
|
114
|
+
setConnected(false);
|
|
115
|
+
source?.close();
|
|
116
|
+
retry = setTimeout(connect, 3e3);
|
|
117
|
+
};
|
|
118
|
+
} catch {
|
|
119
|
+
if (!cancelled) retry = setTimeout(connect, 5e3);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
void connect();
|
|
123
|
+
return () => {
|
|
124
|
+
cancelled = true;
|
|
125
|
+
if (retry) clearTimeout(retry);
|
|
126
|
+
source?.close();
|
|
127
|
+
};
|
|
128
|
+
}, [base, live, openThread]);
|
|
129
|
+
const send = (0, import_react.useCallback)(
|
|
130
|
+
async (text) => {
|
|
131
|
+
if (!selected || !text.trim()) return;
|
|
132
|
+
setSending(true);
|
|
133
|
+
try {
|
|
134
|
+
await call(`${base}/send`, {
|
|
135
|
+
method: "POST",
|
|
136
|
+
headers: { "Content-Type": "application/json" },
|
|
137
|
+
body: JSON.stringify({ to: selected.contactPhone, body: text })
|
|
138
|
+
});
|
|
139
|
+
await openThread(selected.id);
|
|
140
|
+
setError(null);
|
|
141
|
+
} catch (err) {
|
|
142
|
+
setError(err instanceof Error ? err.message : "Could not send");
|
|
143
|
+
} finally {
|
|
144
|
+
setSending(false);
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
[base, selected, openThread]
|
|
148
|
+
);
|
|
149
|
+
const patch = (0, import_react.useCallback)(
|
|
150
|
+
async (body) => {
|
|
151
|
+
if (!selected) return;
|
|
152
|
+
try {
|
|
153
|
+
await call(`${base}/conversations/${selected.id}`, {
|
|
154
|
+
method: "PATCH",
|
|
155
|
+
headers: { "Content-Type": "application/json" },
|
|
156
|
+
body: JSON.stringify(body)
|
|
157
|
+
});
|
|
158
|
+
await openThread(selected.id);
|
|
159
|
+
} catch (err) {
|
|
160
|
+
setError(err instanceof Error ? err.message : "Could not update conversation");
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
[base, selected, openThread]
|
|
164
|
+
);
|
|
165
|
+
return {
|
|
166
|
+
conversations,
|
|
167
|
+
selected,
|
|
168
|
+
agents,
|
|
169
|
+
loading,
|
|
170
|
+
sending,
|
|
171
|
+
error,
|
|
172
|
+
connected,
|
|
173
|
+
select: (id) => void openThread(id),
|
|
174
|
+
send,
|
|
175
|
+
assign: (agentId) => patch({ assignedAgentId: agentId }),
|
|
176
|
+
setStatus: (status) => patch({ status }),
|
|
177
|
+
refresh
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// src/inbox.tsx
|
|
182
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
183
|
+
var c = {
|
|
184
|
+
border: "var(--gg-wa-border, #e4e6eb)",
|
|
185
|
+
bg: "var(--gg-wa-bg, #ffffff)",
|
|
186
|
+
panel: "var(--gg-wa-panel, #f7f8fa)",
|
|
187
|
+
text: "var(--gg-wa-text, #111827)",
|
|
188
|
+
muted: "var(--gg-wa-muted, #6b7280)",
|
|
189
|
+
accent: "var(--gg-wa-accent, #128c7e)",
|
|
190
|
+
bubbleIn: "var(--gg-wa-bubble-in, #ffffff)",
|
|
191
|
+
bubbleOut: "var(--gg-wa-bubble-out, #d9fdd3)",
|
|
192
|
+
danger: "var(--gg-wa-danger, #b42318)"
|
|
193
|
+
};
|
|
194
|
+
function messageText(message) {
|
|
195
|
+
const payload = message.payload;
|
|
196
|
+
if (payload && "body" in payload && payload.body) return payload.body;
|
|
197
|
+
if (payload && "name" in payload && payload.name) return `\u{1F4C4} ${payload.name}`;
|
|
198
|
+
const inbound = payload?.messages?.[0];
|
|
199
|
+
return inbound?.text?.body ?? "(no text)";
|
|
200
|
+
}
|
|
201
|
+
function timeLabel(iso) {
|
|
202
|
+
const d = new Date(iso);
|
|
203
|
+
const today = /* @__PURE__ */ new Date();
|
|
204
|
+
const sameDay = d.toDateString() === today.toDateString();
|
|
205
|
+
return sameDay ? d.toLocaleTimeString(void 0, { hour: "2-digit", minute: "2-digit" }) : d.toLocaleDateString(void 0, { day: "numeric", month: "short" });
|
|
206
|
+
}
|
|
207
|
+
function ConversationRow(props) {
|
|
208
|
+
const { conversation: conv, active, onSelect } = props;
|
|
209
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
210
|
+
"button",
|
|
211
|
+
{
|
|
212
|
+
type: "button",
|
|
213
|
+
onClick: onSelect,
|
|
214
|
+
style: {
|
|
215
|
+
display: "block",
|
|
216
|
+
width: "100%",
|
|
217
|
+
textAlign: "left",
|
|
218
|
+
padding: "10px 12px",
|
|
219
|
+
border: "none",
|
|
220
|
+
borderBottom: `1px solid ${c.border}`,
|
|
221
|
+
background: active ? c.panel : "transparent",
|
|
222
|
+
cursor: "pointer",
|
|
223
|
+
font: "inherit",
|
|
224
|
+
color: c.text
|
|
225
|
+
},
|
|
226
|
+
children: [
|
|
227
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", justifyContent: "space-between", gap: 8 }, children: [
|
|
228
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontWeight: conv.unreadCount > 0 ? 600 : 500, fontSize: 14 }, children: conv.contactName || conv.contactPhone }),
|
|
229
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 11, color: c.muted, whiteSpace: "nowrap" }, children: timeLabel(conv.updatedAt) })
|
|
230
|
+
] }),
|
|
231
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 6, marginTop: 3 }, children: [
|
|
232
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
233
|
+
"span",
|
|
234
|
+
{
|
|
235
|
+
style: {
|
|
236
|
+
fontSize: 12,
|
|
237
|
+
color: c.muted,
|
|
238
|
+
overflow: "hidden",
|
|
239
|
+
textOverflow: "ellipsis",
|
|
240
|
+
whiteSpace: "nowrap",
|
|
241
|
+
flex: 1
|
|
242
|
+
},
|
|
243
|
+
children: conv.lastMessage ? messageText(conv.lastMessage) : "\u2014"
|
|
244
|
+
}
|
|
245
|
+
),
|
|
246
|
+
conv.unreadCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
247
|
+
"span",
|
|
248
|
+
{
|
|
249
|
+
style: {
|
|
250
|
+
background: c.accent,
|
|
251
|
+
color: "#fff",
|
|
252
|
+
borderRadius: 999,
|
|
253
|
+
fontSize: 11,
|
|
254
|
+
padding: "1px 6px"
|
|
255
|
+
},
|
|
256
|
+
children: conv.unreadCount
|
|
257
|
+
}
|
|
258
|
+
),
|
|
259
|
+
conv.optedOut && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 10, color: c.danger, border: `1px solid ${c.danger}`, borderRadius: 4, padding: "0 4px" }, children: "opted out" })
|
|
260
|
+
] })
|
|
261
|
+
]
|
|
262
|
+
}
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
function WhatsappInbox(props) {
|
|
266
|
+
const { height = 600, emptyLabel = "No conversations yet", ...options } = props;
|
|
267
|
+
const inbox = useInbox(options);
|
|
268
|
+
const [draft, setDraft] = (0, import_react2.useState)("");
|
|
269
|
+
const selected = inbox.selected;
|
|
270
|
+
const windowOpen = selected?.windowOpen ?? false;
|
|
271
|
+
const orderedMessages = (0, import_react2.useMemo)(() => selected?.messages ?? [], [selected]);
|
|
272
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
273
|
+
"div",
|
|
274
|
+
{
|
|
275
|
+
style: {
|
|
276
|
+
display: "flex",
|
|
277
|
+
height: typeof height === "number" ? `${height}px` : height,
|
|
278
|
+
border: `1px solid ${c.border}`,
|
|
279
|
+
borderRadius: 8,
|
|
280
|
+
overflow: "hidden",
|
|
281
|
+
background: c.bg,
|
|
282
|
+
color: c.text,
|
|
283
|
+
fontFamily: "system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif"
|
|
284
|
+
},
|
|
285
|
+
children: [
|
|
286
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("aside", { style: { width: 280, borderRight: `1px solid ${c.border}`, display: "flex", flexDirection: "column" }, children: [
|
|
287
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
288
|
+
"header",
|
|
289
|
+
{
|
|
290
|
+
style: {
|
|
291
|
+
padding: "10px 12px",
|
|
292
|
+
borderBottom: `1px solid ${c.border}`,
|
|
293
|
+
display: "flex",
|
|
294
|
+
alignItems: "center",
|
|
295
|
+
gap: 6
|
|
296
|
+
},
|
|
297
|
+
children: [
|
|
298
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { style: { fontSize: 14, flex: 1 }, children: "Inbox" }),
|
|
299
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
300
|
+
"span",
|
|
301
|
+
{
|
|
302
|
+
title: inbox.connected ? "Live" : "Reconnecting\u2026",
|
|
303
|
+
style: {
|
|
304
|
+
width: 8,
|
|
305
|
+
height: 8,
|
|
306
|
+
borderRadius: 999,
|
|
307
|
+
background: inbox.connected ? c.accent : c.muted
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
)
|
|
311
|
+
]
|
|
312
|
+
}
|
|
313
|
+
),
|
|
314
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { overflowY: "auto", flex: 1 }, children: [
|
|
315
|
+
inbox.loading && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { padding: 12, fontSize: 13, color: c.muted }, children: "Loading\u2026" }),
|
|
316
|
+
!inbox.loading && inbox.conversations.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { padding: 12, fontSize: 13, color: c.muted }, children: emptyLabel }),
|
|
317
|
+
inbox.conversations.map((conv) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
318
|
+
ConversationRow,
|
|
319
|
+
{
|
|
320
|
+
conversation: conv,
|
|
321
|
+
active: selected?.id === conv.id,
|
|
322
|
+
onSelect: () => inbox.select(conv.id)
|
|
323
|
+
},
|
|
324
|
+
conv.id
|
|
325
|
+
))
|
|
326
|
+
] })
|
|
327
|
+
] }),
|
|
328
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { style: { flex: 1, display: "flex", flexDirection: "column", background: c.panel }, children: [
|
|
329
|
+
!selected && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { margin: "auto", color: c.muted, fontSize: 14 }, children: "Select a conversation" }),
|
|
330
|
+
selected && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
331
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
332
|
+
"header",
|
|
333
|
+
{
|
|
334
|
+
style: {
|
|
335
|
+
padding: "10px 14px",
|
|
336
|
+
borderBottom: `1px solid ${c.border}`,
|
|
337
|
+
background: c.bg,
|
|
338
|
+
display: "flex",
|
|
339
|
+
alignItems: "center",
|
|
340
|
+
gap: 10
|
|
341
|
+
},
|
|
342
|
+
children: [
|
|
343
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { flex: 1 }, children: [
|
|
344
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontSize: 14, fontWeight: 600 }, children: selected.contactName || selected.contactPhone }),
|
|
345
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontSize: 11, color: c.muted }, children: selected.contactPhone })
|
|
346
|
+
] }),
|
|
347
|
+
inbox.agents.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
348
|
+
"select",
|
|
349
|
+
{
|
|
350
|
+
value: selected.assignedAgentId ?? "",
|
|
351
|
+
onChange: (e) => void inbox.assign(e.target.value || null),
|
|
352
|
+
style: { fontSize: 12, padding: "4px 6px", borderRadius: 6, border: `1px solid ${c.border}` },
|
|
353
|
+
children: [
|
|
354
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "", children: "Unassigned" }),
|
|
355
|
+
inbox.agents.map((a) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: a.id, children: a.name }, a.id))
|
|
356
|
+
]
|
|
357
|
+
}
|
|
358
|
+
),
|
|
359
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
360
|
+
"select",
|
|
361
|
+
{
|
|
362
|
+
value: selected.status,
|
|
363
|
+
onChange: (e) => void inbox.setStatus(e.target.value),
|
|
364
|
+
style: { fontSize: 12, padding: "4px 6px", borderRadius: 6, border: `1px solid ${c.border}` },
|
|
365
|
+
children: [
|
|
366
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "OPEN", children: "Open" }),
|
|
367
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "SNOOZED", children: "Snoozed" }),
|
|
368
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "CLOSED", children: "Closed" })
|
|
369
|
+
]
|
|
370
|
+
}
|
|
371
|
+
)
|
|
372
|
+
]
|
|
373
|
+
}
|
|
374
|
+
),
|
|
375
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { flex: 1, overflowY: "auto", padding: 14, display: "flex", flexDirection: "column", gap: 8 }, children: orderedMessages.map((m) => {
|
|
376
|
+
const outbound = m.direction === "OUTBOUND";
|
|
377
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
378
|
+
"div",
|
|
379
|
+
{
|
|
380
|
+
style: {
|
|
381
|
+
alignSelf: outbound ? "flex-end" : "flex-start",
|
|
382
|
+
maxWidth: "72%",
|
|
383
|
+
background: outbound ? c.bubbleOut : c.bubbleIn,
|
|
384
|
+
border: `1px solid ${c.border}`,
|
|
385
|
+
borderRadius: 10,
|
|
386
|
+
padding: "7px 10px"
|
|
387
|
+
},
|
|
388
|
+
children: [
|
|
389
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontSize: 13, whiteSpace: "pre-wrap", wordBreak: "break-word" }, children: messageText(m) }),
|
|
390
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { fontSize: 10, color: m.status === "FAILED" ? c.danger : c.muted, marginTop: 3, textAlign: "right" }, children: [
|
|
391
|
+
timeLabel(m.createdAt),
|
|
392
|
+
outbound && ` \xB7 ${m.status.toLowerCase()}`
|
|
393
|
+
] })
|
|
394
|
+
]
|
|
395
|
+
},
|
|
396
|
+
m.id
|
|
397
|
+
);
|
|
398
|
+
}) }),
|
|
399
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("footer", { style: { borderTop: `1px solid ${c.border}`, background: c.bg, padding: 10 }, children: [
|
|
400
|
+
inbox.error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { margin: "0 0 8px", fontSize: 12, color: c.danger }, children: inbox.error }),
|
|
401
|
+
!windowOpen ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { margin: 0, fontSize: 12, color: c.muted }, children: "This contact hasn't messaged in the last 24 hours, so a free reply can't be delivered. Send an approved template instead." }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
402
|
+
"form",
|
|
403
|
+
{
|
|
404
|
+
onSubmit: (e) => {
|
|
405
|
+
e.preventDefault();
|
|
406
|
+
const text = draft;
|
|
407
|
+
setDraft("");
|
|
408
|
+
void inbox.send(text);
|
|
409
|
+
},
|
|
410
|
+
style: { display: "flex", gap: 8 },
|
|
411
|
+
children: [
|
|
412
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
413
|
+
"input",
|
|
414
|
+
{
|
|
415
|
+
value: draft,
|
|
416
|
+
onChange: (e) => setDraft(e.target.value),
|
|
417
|
+
placeholder: "Type a reply\u2026",
|
|
418
|
+
disabled: inbox.sending,
|
|
419
|
+
style: {
|
|
420
|
+
flex: 1,
|
|
421
|
+
padding: "8px 10px",
|
|
422
|
+
borderRadius: 8,
|
|
423
|
+
border: `1px solid ${c.border}`,
|
|
424
|
+
font: "inherit",
|
|
425
|
+
fontSize: 13
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
),
|
|
429
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
430
|
+
"button",
|
|
431
|
+
{
|
|
432
|
+
type: "submit",
|
|
433
|
+
disabled: inbox.sending || !draft.trim(),
|
|
434
|
+
style: {
|
|
435
|
+
padding: "8px 14px",
|
|
436
|
+
borderRadius: 8,
|
|
437
|
+
border: "none",
|
|
438
|
+
background: c.accent,
|
|
439
|
+
color: "#fff",
|
|
440
|
+
fontSize: 13,
|
|
441
|
+
cursor: inbox.sending || !draft.trim() ? "not-allowed" : "pointer",
|
|
442
|
+
opacity: inbox.sending || !draft.trim() ? 0.6 : 1
|
|
443
|
+
},
|
|
444
|
+
children: inbox.sending ? "Sending\u2026" : "Send"
|
|
445
|
+
}
|
|
446
|
+
)
|
|
447
|
+
]
|
|
448
|
+
}
|
|
449
|
+
)
|
|
450
|
+
] })
|
|
451
|
+
] })
|
|
452
|
+
] })
|
|
453
|
+
]
|
|
454
|
+
}
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
458
|
+
0 && (module.exports = {
|
|
459
|
+
WhatsappInbox,
|
|
460
|
+
useInbox
|
|
461
|
+
});
|
|
462
|
+
//# sourceMappingURL=react.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/react.ts","../src/inbox.tsx","../src/use-inbox.ts"],"sourcesContent":["\"use client\";\n\nexport { WhatsappInbox, type WhatsappInboxProps } from \"./inbox\";\nexport { useInbox, type UseInboxOptions, type UseInboxResult } from \"./use-inbox\";\nexport type { WaConversation, WaMessage, WaAgent } from \"./types\";\n","\"use client\";\n\nimport { useMemo, useState } from \"react\";\nimport { useInbox, type UseInboxOptions } from \"./use-inbox\";\nimport type { WaConversation, WaMessage } from \"./types\";\n\n/**\n * A drop-in shared inbox.\n *\n * Styled with inline styles rather than a CSS file or a framework: this ships\n * into other people's apps, and a stylesheet that has to be imported (or a\n * Tailwind class that only works if they use Tailwind) is the fastest way to\n * make a \"drop-in\" component not drop in. Colours come from CSS custom\n * properties so a host can restyle without forking.\n */\n\nexport interface WhatsappInboxProps extends UseInboxOptions {\n height?: number | string;\n emptyLabel?: string;\n}\n\nconst c = {\n border: \"var(--gg-wa-border, #e4e6eb)\",\n bg: \"var(--gg-wa-bg, #ffffff)\",\n panel: \"var(--gg-wa-panel, #f7f8fa)\",\n text: \"var(--gg-wa-text, #111827)\",\n muted: \"var(--gg-wa-muted, #6b7280)\",\n accent: \"var(--gg-wa-accent, #128c7e)\",\n bubbleIn: \"var(--gg-wa-bubble-in, #ffffff)\",\n bubbleOut: \"var(--gg-wa-bubble-out, #d9fdd3)\",\n danger: \"var(--gg-wa-danger, #b42318)\",\n};\n\nfunction messageText(message: WaMessage): string {\n const payload = message.payload as\n | { type?: string; body?: string; name?: string }\n | { messages?: { text?: { body?: string } }[] }\n | undefined;\n\n if (payload && \"body\" in payload && payload.body) return payload.body;\n if (payload && \"name\" in payload && payload.name) return `📄 ${payload.name}`;\n\n // Inbound rows store Meta's raw webhook value, which nests the text.\n const inbound = (payload as { messages?: { text?: { body?: string } }[] } | undefined)?.messages?.[0];\n return inbound?.text?.body ?? \"(no text)\";\n}\n\nfunction timeLabel(iso: string): string {\n const d = new Date(iso);\n const today = new Date();\n const sameDay = d.toDateString() === today.toDateString();\n return sameDay\n ? d.toLocaleTimeString(undefined, { hour: \"2-digit\", minute: \"2-digit\" })\n : d.toLocaleDateString(undefined, { day: \"numeric\", month: \"short\" });\n}\n\nfunction ConversationRow(props: {\n conversation: WaConversation;\n active: boolean;\n onSelect: () => void;\n}) {\n const { conversation: conv, active, onSelect } = props;\n return (\n <button\n type=\"button\"\n onClick={onSelect}\n style={{\n display: \"block\",\n width: \"100%\",\n textAlign: \"left\",\n padding: \"10px 12px\",\n border: \"none\",\n borderBottom: `1px solid ${c.border}`,\n background: active ? c.panel : \"transparent\",\n cursor: \"pointer\",\n font: \"inherit\",\n color: c.text,\n }}\n >\n <div style={{ display: \"flex\", justifyContent: \"space-between\", gap: 8 }}>\n <span style={{ fontWeight: conv.unreadCount > 0 ? 600 : 500, fontSize: 14 }}>\n {conv.contactName || conv.contactPhone}\n </span>\n <span style={{ fontSize: 11, color: c.muted, whiteSpace: \"nowrap\" }}>\n {timeLabel(conv.updatedAt)}\n </span>\n </div>\n <div style={{ display: \"flex\", alignItems: \"center\", gap: 6, marginTop: 3 }}>\n <span\n style={{\n fontSize: 12,\n color: c.muted,\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\",\n flex: 1,\n }}\n >\n {conv.lastMessage ? messageText(conv.lastMessage) : \"—\"}\n </span>\n {conv.unreadCount > 0 && (\n <span\n style={{\n background: c.accent,\n color: \"#fff\",\n borderRadius: 999,\n fontSize: 11,\n padding: \"1px 6px\",\n }}\n >\n {conv.unreadCount}\n </span>\n )}\n {/* Opted out is worth a permanent marker: it silently changes what an\n agent is allowed to send, and there is no error until they try. */}\n {conv.optedOut && (\n <span style={{ fontSize: 10, color: c.danger, border: `1px solid ${c.danger}`, borderRadius: 4, padding: \"0 4px\" }}>\n opted out\n </span>\n )}\n </div>\n </button>\n );\n}\n\nexport function WhatsappInbox(props: WhatsappInboxProps) {\n const { height = 600, emptyLabel = \"No conversations yet\", ...options } = props;\n const inbox = useInbox(options);\n const [draft, setDraft] = useState(\"\");\n\n const selected = inbox.selected;\n const windowOpen = selected?.windowOpen ?? false;\n\n const orderedMessages = useMemo(() => selected?.messages ?? [], [selected]);\n\n return (\n <div\n style={{\n display: \"flex\",\n height: typeof height === \"number\" ? `${height}px` : height,\n border: `1px solid ${c.border}`,\n borderRadius: 8,\n overflow: \"hidden\",\n background: c.bg,\n color: c.text,\n fontFamily:\n \"system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif\",\n }}\n >\n <aside style={{ width: 280, borderRight: `1px solid ${c.border}`, display: \"flex\", flexDirection: \"column\" }}>\n <header\n style={{\n padding: \"10px 12px\",\n borderBottom: `1px solid ${c.border}`,\n display: \"flex\",\n alignItems: \"center\",\n gap: 6,\n }}\n >\n <strong style={{ fontSize: 14, flex: 1 }}>Inbox</strong>\n {/* A dot rather than a label: agents need to know the feed is live,\n not read a status sentence on every render. */}\n <span\n title={inbox.connected ? \"Live\" : \"Reconnecting…\"}\n style={{\n width: 8,\n height: 8,\n borderRadius: 999,\n background: inbox.connected ? c.accent : c.muted,\n }}\n />\n </header>\n\n <div style={{ overflowY: \"auto\", flex: 1 }}>\n {inbox.loading && <p style={{ padding: 12, fontSize: 13, color: c.muted }}>Loading…</p>}\n {!inbox.loading && inbox.conversations.length === 0 && (\n <p style={{ padding: 12, fontSize: 13, color: c.muted }}>{emptyLabel}</p>\n )}\n {inbox.conversations.map((conv) => (\n <ConversationRow\n key={conv.id}\n conversation={conv}\n active={selected?.id === conv.id}\n onSelect={() => inbox.select(conv.id)}\n />\n ))}\n </div>\n </aside>\n\n <section style={{ flex: 1, display: \"flex\", flexDirection: \"column\", background: c.panel }}>\n {!selected && (\n <div style={{ margin: \"auto\", color: c.muted, fontSize: 14 }}>\n Select a conversation\n </div>\n )}\n\n {selected && (\n <>\n <header\n style={{\n padding: \"10px 14px\",\n borderBottom: `1px solid ${c.border}`,\n background: c.bg,\n display: \"flex\",\n alignItems: \"center\",\n gap: 10,\n }}\n >\n <div style={{ flex: 1 }}>\n <div style={{ fontSize: 14, fontWeight: 600 }}>\n {selected.contactName || selected.contactPhone}\n </div>\n <div style={{ fontSize: 11, color: c.muted }}>{selected.contactPhone}</div>\n </div>\n\n {inbox.agents.length > 0 && (\n <select\n value={selected.assignedAgentId ?? \"\"}\n onChange={(e) => void inbox.assign(e.target.value || null)}\n style={{ fontSize: 12, padding: \"4px 6px\", borderRadius: 6, border: `1px solid ${c.border}` }}\n >\n <option value=\"\">Unassigned</option>\n {inbox.agents.map((a) => (\n <option key={a.id} value={a.id}>\n {a.name}\n </option>\n ))}\n </select>\n )}\n\n <select\n value={selected.status}\n onChange={(e) => void inbox.setStatus(e.target.value as \"OPEN\" | \"SNOOZED\" | \"CLOSED\")}\n style={{ fontSize: 12, padding: \"4px 6px\", borderRadius: 6, border: `1px solid ${c.border}` }}\n >\n <option value=\"OPEN\">Open</option>\n <option value=\"SNOOZED\">Snoozed</option>\n <option value=\"CLOSED\">Closed</option>\n </select>\n </header>\n\n <div style={{ flex: 1, overflowY: \"auto\", padding: 14, display: \"flex\", flexDirection: \"column\", gap: 8 }}>\n {orderedMessages.map((m) => {\n const outbound = m.direction === \"OUTBOUND\";\n return (\n <div\n key={m.id}\n style={{\n alignSelf: outbound ? \"flex-end\" : \"flex-start\",\n maxWidth: \"72%\",\n background: outbound ? c.bubbleOut : c.bubbleIn,\n border: `1px solid ${c.border}`,\n borderRadius: 10,\n padding: \"7px 10px\",\n }}\n >\n <div style={{ fontSize: 13, whiteSpace: \"pre-wrap\", wordBreak: \"break-word\" }}>\n {messageText(m)}\n </div>\n <div style={{ fontSize: 10, color: m.status === \"FAILED\" ? c.danger : c.muted, marginTop: 3, textAlign: \"right\" }}>\n {timeLabel(m.createdAt)}\n {outbound && ` · ${m.status.toLowerCase()}`}\n </div>\n </div>\n );\n })}\n </div>\n\n <footer style={{ borderTop: `1px solid ${c.border}`, background: c.bg, padding: 10 }}>\n {inbox.error && (\n <p style={{ margin: \"0 0 8px\", fontSize: 12, color: c.danger }}>{inbox.error}</p>\n )}\n\n {/* The single most confusing thing about WhatsApp for an agent:\n outside the 24-hour window free text is accepted by Meta and\n never delivered. Saying so up front beats a silent failure. */}\n {!windowOpen ? (\n <p style={{ margin: 0, fontSize: 12, color: c.muted }}>\n This contact hasn't messaged in the last 24 hours, so a free reply\n can't be delivered. Send an approved template instead.\n </p>\n ) : (\n <form\n onSubmit={(e) => {\n e.preventDefault();\n const text = draft;\n setDraft(\"\");\n void inbox.send(text);\n }}\n style={{ display: \"flex\", gap: 8 }}\n >\n <input\n value={draft}\n onChange={(e) => setDraft(e.target.value)}\n placeholder=\"Type a reply…\"\n disabled={inbox.sending}\n style={{\n flex: 1,\n padding: \"8px 10px\",\n borderRadius: 8,\n border: `1px solid ${c.border}`,\n font: \"inherit\",\n fontSize: 13,\n }}\n />\n <button\n type=\"submit\"\n disabled={inbox.sending || !draft.trim()}\n style={{\n padding: \"8px 14px\",\n borderRadius: 8,\n border: \"none\",\n background: c.accent,\n color: \"#fff\",\n fontSize: 13,\n cursor: inbox.sending || !draft.trim() ? \"not-allowed\" : \"pointer\",\n opacity: inbox.sending || !draft.trim() ? 0.6 : 1,\n }}\n >\n {inbox.sending ? \"Sending…\" : \"Send\"}\n </button>\n </form>\n )}\n </footer>\n </>\n )}\n </section>\n </div>\n );\n}\n","\"use client\";\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport type { WaAgent, WaConversation, WaMessage } from \"./types\";\n\n/**\n * The inbox's data layer.\n *\n * Talks to *your* proxy route (see `createInboxHandler`), never to us directly —\n * except the SSE stream, which needs a long-lived connection and so goes to us\n * with a short-lived ticket your route mints.\n */\n\nexport interface UseInboxOptions {\n /** Where you mounted `createInboxHandler`. */\n api: string;\n /** Open this thread on mount. */\n initialConversationId?: string;\n /** Set false to load once and skip the live stream. */\n live?: boolean;\n}\n\nexport interface UseInboxResult {\n conversations: WaConversation[];\n selected: (WaConversation & { messages: WaMessage[] }) | null;\n agents: WaAgent[];\n loading: boolean;\n sending: boolean;\n error: string | null;\n connected: boolean;\n select: (conversationId: string) => void;\n send: (text: string) => Promise<void>;\n assign: (agentId: string | null) => Promise<void>;\n setStatus: (status: \"OPEN\" | \"SNOOZED\" | \"CLOSED\") => Promise<void>;\n refresh: () => Promise<void>;\n}\n\nasync function call<T>(url: string, init?: RequestInit): Promise<T> {\n const res = await fetch(url, init);\n const body = (await res.json().catch(() => ({}))) as T & { error?: string };\n if (!res.ok) throw new Error(body.error ?? `Request failed (${res.status})`);\n return body as T;\n}\n\nexport function useInbox(options: UseInboxOptions): UseInboxResult {\n const { api, live = true } = options;\n const base = api.replace(/\\/$/, \"\");\n\n const [conversations, setConversations] = useState<WaConversation[]>([]);\n const [selected, setSelected] = useState<(WaConversation & { messages: WaMessage[] }) | null>(null);\n const [agents, setAgents] = useState<WaAgent[]>([]);\n const [loading, setLoading] = useState(true);\n const [sending, setSending] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [connected, setConnected] = useState(false);\n\n const selectedIdRef = useRef<string | null>(options.initialConversationId ?? null);\n\n const refresh = useCallback(async () => {\n try {\n const data = await call<{ conversations: WaConversation[] }>(`${base}/conversations`);\n setConversations(data.conversations);\n setError(null);\n } catch (err) {\n setError(err instanceof Error ? err.message : \"Could not load conversations\");\n } finally {\n setLoading(false);\n }\n }, [base]);\n\n const openThread = useCallback(\n async (conversationId: string) => {\n selectedIdRef.current = conversationId;\n try {\n const data = await call<{ conversation: WaConversation & { messages: WaMessage[] } }>(\n `${base}/conversations/${conversationId}`\n );\n setSelected(data.conversation);\n // Reading clears the badge server-side; mirror it so the list does not\n // keep showing an unread count until the next stream tick.\n setConversations((prev) =>\n prev.map((c) => (c.id === conversationId ? { ...c, unreadCount: 0 } : c))\n );\n } catch (err) {\n setError(err instanceof Error ? err.message : \"Could not open conversation\");\n }\n },\n [base]\n );\n\n useEffect(() => {\n void refresh();\n void call<{ agents: WaAgent[] }>(`${base}/agents`)\n .then((d) => setAgents(d.agents))\n .catch(() => undefined); // seats are optional; the inbox works without them\n if (options.initialConversationId) void openThread(options.initialConversationId);\n }, [base, refresh, openThread, options.initialConversationId]);\n\n /**\n * The live stream.\n *\n * `EventSource` reconnects on its own and replays `Last-Event-ID`, so a\n * dropped connection resumes rather than losing messages. The server closes\n * the stream deliberately before the platform's function timeout; that arrives\n * as a `reconnect` event and is a normal part of the lifecycle, not an error.\n */\n useEffect(() => {\n if (!live) return;\n\n let source: EventSource | null = null;\n let cancelled = false;\n let retry: ReturnType<typeof setTimeout> | null = null;\n\n const connect = async () => {\n if (cancelled) return;\n try {\n const session = await call<{ ticket: string; baseUrl: string }>(`${base}/ticket`, {\n method: \"POST\",\n });\n if (cancelled) return;\n\n source = new EventSource(\n `${session.baseUrl}/api/v1/wa/inbox/stream?ticket=${encodeURIComponent(session.ticket)}`\n );\n\n source.addEventListener(\"ready\", () => setConnected(true));\n\n source.addEventListener(\"conversation\", (event) => {\n const updated = JSON.parse((event as MessageEvent).data) as WaConversation;\n setConversations((prev) => {\n const rest = prev.filter((c) => c.id !== updated.id);\n return [updated, ...rest];\n });\n // Refresh the open thread so a new message appears in it, not just in\n // the list beside it.\n if (selectedIdRef.current === updated.id) void openThread(updated.id);\n });\n\n source.addEventListener(\"reconnect\", () => {\n source?.close();\n setConnected(false);\n // Immediate: the server closed on purpose and a new ticket is needed.\n retry = setTimeout(connect, 100);\n });\n\n source.onerror = () => {\n setConnected(false);\n source?.close();\n // The ticket may simply have expired, so reconnect rather than\n // surfacing an error the user cannot act on.\n retry = setTimeout(connect, 3000);\n };\n } catch {\n if (!cancelled) retry = setTimeout(connect, 5000);\n }\n };\n\n void connect();\n\n return () => {\n cancelled = true;\n if (retry) clearTimeout(retry);\n source?.close();\n };\n }, [base, live, openThread]);\n\n const send = useCallback(\n async (text: string) => {\n if (!selected || !text.trim()) return;\n setSending(true);\n try {\n await call(`${base}/send`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ to: selected.contactPhone, body: text }),\n });\n await openThread(selected.id);\n setError(null);\n } catch (err) {\n setError(err instanceof Error ? err.message : \"Could not send\");\n } finally {\n setSending(false);\n }\n },\n [base, selected, openThread]\n );\n\n const patch = useCallback(\n async (body: Record<string, unknown>) => {\n if (!selected) return;\n try {\n await call(`${base}/conversations/${selected.id}`, {\n method: \"PATCH\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n await openThread(selected.id);\n } catch (err) {\n setError(err instanceof Error ? err.message : \"Could not update conversation\");\n }\n },\n [base, selected, openThread]\n );\n\n return {\n conversations,\n selected,\n agents,\n loading,\n sending,\n error,\n connected,\n select: (id) => void openThread(id),\n send,\n assign: (agentId) => patch({ assignedAgentId: agentId }),\n setStatus: (status) => patch({ status }),\n refresh,\n };\n}\n"],"mappings":"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","names":["import_react","c"]}
|