@mailoo/chat 0.0.0-stage → 1.32.2

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 (58) hide show
  1. package/LICENSE +16 -0
  2. package/README.md +150 -2
  3. package/dist/chat-message-Dw6tKBEb.d.cts +10 -0
  4. package/dist/chat-message-Dw6tKBEb.d.ts +10 -0
  5. package/dist/chunk-2Y37RYEL.js +141 -0
  6. package/dist/chunk-2Y37RYEL.js.map +1 -0
  7. package/dist/chunk-4TNHJERE.js +19 -0
  8. package/dist/chunk-4TNHJERE.js.map +1 -0
  9. package/dist/chunk-77E6HUHY.js +41 -0
  10. package/dist/chunk-77E6HUHY.js.map +1 -0
  11. package/dist/chunk-AU6EFFLV.js +431 -0
  12. package/dist/chunk-AU6EFFLV.js.map +1 -0
  13. package/dist/chunk-QZ7HWQDO.js +839 -0
  14. package/dist/chunk-QZ7HWQDO.js.map +1 -0
  15. package/dist/client/index.cjs +1658 -0
  16. package/dist/client/index.cjs.map +1 -0
  17. package/dist/client/index.d.cts +33 -0
  18. package/dist/client/index.d.ts +33 -0
  19. package/dist/client/index.js +597 -0
  20. package/dist/client/index.js.map +1 -0
  21. package/dist/hooks/index.cjs +751 -0
  22. package/dist/hooks/index.cjs.map +1 -0
  23. package/dist/hooks/index.d.cts +79 -0
  24. package/dist/hooks/index.d.ts +79 -0
  25. package/dist/hooks/index.js +32 -0
  26. package/dist/hooks/index.js.map +1 -0
  27. package/dist/index.cjs +213 -0
  28. package/dist/index.cjs.map +1 -0
  29. package/dist/index.d.cts +53 -0
  30. package/dist/index.d.ts +53 -0
  31. package/dist/index.js +27 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/mailoo-site-chat-widget-C7B797Lu.d.ts +130 -0
  34. package/dist/mailoo-site-chat-widget-h_cBqsJw.d.cts +130 -0
  35. package/dist/routes/index.cjs +204 -0
  36. package/dist/routes/index.cjs.map +1 -0
  37. package/dist/routes/index.d.cts +30 -0
  38. package/dist/routes/index.d.ts +30 -0
  39. package/dist/routes/index.js +174 -0
  40. package/dist/routes/index.js.map +1 -0
  41. package/dist/shared-glVmghqk.d.cts +40 -0
  42. package/dist/shared-glVmghqk.d.ts +40 -0
  43. package/dist/styles/widget.css +122 -0
  44. package/dist/welcome-presets/index.cjs +479 -0
  45. package/dist/welcome-presets/index.cjs.map +1 -0
  46. package/dist/welcome-presets/index.d.cts +53 -0
  47. package/dist/welcome-presets/index.d.ts +53 -0
  48. package/dist/welcome-presets/index.js +38 -0
  49. package/dist/welcome-presets/index.js.map +1 -0
  50. package/dist/welcome-presets/parse.cjs +44 -0
  51. package/dist/welcome-presets/parse.cjs.map +1 -0
  52. package/dist/welcome-presets/parse.d.cts +11 -0
  53. package/dist/welcome-presets/parse.d.ts +11 -0
  54. package/dist/welcome-presets/parse.js +9 -0
  55. package/dist/welcome-presets/parse.js.map +1 -0
  56. package/dist/widget-config-B5Tf7gqn.d.cts +32 -0
  57. package/dist/widget-config-B5Tf7gqn.d.ts +32 -0
  58. package/package.json +85 -3
@@ -0,0 +1,1658 @@
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/client/index.ts
22
+ var client_exports = {};
23
+ __export(client_exports, {
24
+ MAILOO_SITE_CHAT_OPEN_EVENT: () => MAILOO_SITE_CHAT_OPEN_EVENT,
25
+ MailooChatUnavailablePanel: () => MailooChatUnavailablePanel,
26
+ MailooSiteChatShell: () => MailooSiteChatShell,
27
+ MailooSiteChatWidget: () => MailooSiteChatWidget,
28
+ openMailooSiteChat: () => openMailooSiteChat
29
+ });
30
+ module.exports = __toCommonJS(client_exports);
31
+
32
+ // src/components/mailoo-site-chat-widget.tsx
33
+ var import_react3 = require("react");
34
+ var import_outline = require("@heroicons/react/24/outline");
35
+
36
+ // src/hooks/index.ts
37
+ var import_react2 = require("react");
38
+
39
+ // src/lib/chat-message.ts
40
+ function parseChatMessages(rows) {
41
+ const parsed = [];
42
+ for (const row of rows) {
43
+ if (!row || typeof row !== "object") continue;
44
+ const r = row;
45
+ if (typeof r.id !== "string" || typeof r.content !== "string") continue;
46
+ parsed.push({
47
+ id: r.id,
48
+ messageType: typeof r.messageType === "string" ? r.messageType : "INBOUND",
49
+ content: r.content,
50
+ senderEmail: typeof r.senderEmail === "string" ? r.senderEmail : null,
51
+ senderName: typeof r.senderName === "string" ? r.senderName : null,
52
+ createdAt: typeof r.createdAt === "string" ? r.createdAt : (/* @__PURE__ */ new Date()).toISOString()
53
+ });
54
+ }
55
+ return parsed;
56
+ }
57
+ function messagesEqual(a, b) {
58
+ if (a.length !== b.length) return false;
59
+ for (let i = 0; i < a.length; i++) {
60
+ const x = a[i];
61
+ const y = b[i];
62
+ if (x.id !== y.id || x.content !== y.content || x.messageType !== y.messageType || x.createdAt !== y.createdAt || x.senderEmail !== y.senderEmail || x.senderName !== y.senderName) {
63
+ return false;
64
+ }
65
+ }
66
+ return true;
67
+ }
68
+ function mergeChatMessages(prev, incoming) {
69
+ if (incoming.length === 0) return prev;
70
+ const byId = /* @__PURE__ */ new Map();
71
+ for (const m of prev) byId.set(m.id, m);
72
+ for (const m of incoming) byId.set(m.id, m);
73
+ const next = Array.from(byId.values()).sort(
74
+ (a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
75
+ );
76
+ return messagesEqual(prev, next) ? prev : next;
77
+ }
78
+ function latestMessageCreatedAt(messages) {
79
+ if (messages.length === 0) return void 0;
80
+ let latest = messages[0].createdAt;
81
+ for (const m of messages) {
82
+ if (new Date(m.createdAt).getTime() > new Date(latest).getTime()) {
83
+ latest = m.createdAt;
84
+ }
85
+ }
86
+ return latest;
87
+ }
88
+
89
+ // src/hooks/shared.ts
90
+ var DEFAULT_CHAT_SUBMIT_ENDPOINT = "/api/v1/webhooks/chat/submit";
91
+ var DEFAULT_CHAT_MESSAGES_ENDPOINT = "/api/v1/webhooks/chat/messages";
92
+ var DEFAULT_CHAT_SESSION_STORAGE_KEY = "mailoo_site_chat_session_public_id";
93
+ var DEFAULT_CHAT_GUEST_PROFILE_KEY = "mailoo_site_chat_guest_profile";
94
+ var DEFAULT_CHAT_POLL_INTERVAL_MS = 5e3;
95
+ function messageFromPayload(data, fallback) {
96
+ if (!data || typeof data !== "object") return fallback;
97
+ const d = data;
98
+ if (typeof d.message === "string") return d.message;
99
+ if (typeof d.error === "string") return d.error;
100
+ return fallback;
101
+ }
102
+ function classifyChatFault(status, payload, fallbackMessage = "Request failed") {
103
+ if (status === 429) {
104
+ return {
105
+ kind: "rate_limited",
106
+ status,
107
+ message: messageFromPayload(payload, fallbackMessage)
108
+ };
109
+ }
110
+ if (status === 0 || status === 502 || status === 503 || status >= 500) {
111
+ return {
112
+ kind: "unavailable",
113
+ status,
114
+ message: messageFromPayload(payload, fallbackMessage)
115
+ };
116
+ }
117
+ return null;
118
+ }
119
+ function isChatSessionNotFound(status, data) {
120
+ if (status !== 404) return false;
121
+ const text = messageFromPayload(data, "").toLowerCase();
122
+ return text.includes("chat session not found");
123
+ }
124
+ function persistSessionPublicId(id, storageKey, ctx) {
125
+ const created = !ctx.sessionPublicIdRef.current;
126
+ ctx.setSessionPublicId(id);
127
+ ctx.sessionPublicIdRef.current = id;
128
+ try {
129
+ sessionStorage.setItem(storageKey, id);
130
+ } catch {
131
+ }
132
+ if (created) ctx.onCreated?.(id);
133
+ }
134
+
135
+ // src/hooks/use-mailoo-chat-session.ts
136
+ var import_react = require("react");
137
+
138
+ // src/lib/guest-copy.ts
139
+ function isValidEmail(s) {
140
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(s.trim());
141
+ }
142
+ function unavailableCopy(locale) {
143
+ switch (locale) {
144
+ case "ru":
145
+ return {
146
+ rateLimitedTitle: "\u0427\u0430\u0442 \u0432\u0440\u0435\u043C\u0435\u043D\u043D\u043E \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D",
147
+ rateLimitedBody: "\u0421\u043B\u0438\u0448\u043A\u043E\u043C \u043C\u043D\u043E\u0433\u043E \u0437\u0430\u043F\u0440\u043E\u0441\u043E\u0432. \u041F\u043E\u0434\u043E\u0436\u0434\u0438\u0442\u0435 \u043D\u0435\u043C\u043D\u043E\u0433\u043E \u0438 \u043F\u043E\u043F\u0440\u043E\u0431\u0443\u0439\u0442\u0435 \u0441\u043D\u043E\u0432\u0430 \u0438\u043B\u0438 \u0441\u0432\u044F\u0436\u0438\u0442\u0435\u0441\u044C \u0441 \u043D\u0430\u043C\u0438 \u0434\u0440\u0443\u0433\u0438\u043C \u0441\u043F\u043E\u0441\u043E\u0431\u043E\u043C.",
148
+ unavailableTitle: "\u0427\u0430\u0442 \u0432\u0440\u0435\u043C\u0435\u043D\u043D\u043E \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D",
149
+ unavailableBody: "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0441\u0432\u044F\u0437\u0430\u0442\u044C\u0441\u044F \u0441 \u0447\u0430\u0442\u043E\u043C. \u041F\u043E\u043F\u0440\u043E\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0451 \u0440\u0430\u0437 \u0438\u043B\u0438 \u043D\u0430\u043F\u0438\u0448\u0438\u0442\u0435 \u043D\u0430\u043C \u0434\u0440\u0443\u0433\u0438\u043C \u0441\u043F\u043E\u0441\u043E\u0431\u043E\u043C.",
150
+ retryCta: "\u041F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u044C",
151
+ contactCta: "\u0421\u0432\u044F\u0437\u0430\u0442\u044C\u0441\u044F \u0441 \u043D\u0430\u043C\u0438"
152
+ };
153
+ case "de":
154
+ return {
155
+ rateLimitedTitle: "Chat vor\xFCbergehend nicht verf\xFCgbar",
156
+ rateLimitedBody: "Zu viele Anfragen. Bitte warten Sie kurz und versuchen Sie es erneut, oder kontaktieren Sie uns auf anderem Wege.",
157
+ unavailableTitle: "Chat vor\xFCbergehend nicht verf\xFCgbar",
158
+ unavailableBody: "Der Chat ist gerade nicht erreichbar. Bitte versuchen Sie es erneut oder kontaktieren Sie uns auf anderem Wege.",
159
+ retryCta: "Erneut versuchen",
160
+ contactCta: "Kontakt aufnehmen"
161
+ };
162
+ case "es":
163
+ return {
164
+ rateLimitedTitle: "Chat temporalmente no disponible",
165
+ rateLimitedBody: "Hay demasiadas solicitudes. Espera un momento e int\xE9ntalo de nuevo, o cont\xE1ctanos de otra forma.",
166
+ unavailableTitle: "Chat temporalmente no disponible",
167
+ unavailableBody: "No pudimos conectar con el chat. Int\xE9ntalo de nuevo o cont\xE1ctanos de otra forma.",
168
+ retryCta: "Reintentar",
169
+ contactCta: "Contactarnos"
170
+ };
171
+ default:
172
+ return {
173
+ rateLimitedTitle: "Chat temporarily unavailable",
174
+ rateLimitedBody: "Too many requests right now. Please wait a moment and try again, or reach us another way.",
175
+ unavailableTitle: "Chat temporarily unavailable",
176
+ unavailableBody: "We could not reach chat right now. Please try again, or contact us another way.",
177
+ retryCta: "Try again",
178
+ contactCta: "Contact us"
179
+ };
180
+ }
181
+ }
182
+ function guestCopy(locale) {
183
+ switch (locale) {
184
+ case "ru":
185
+ return {
186
+ intro: "\u0427\u0442\u043E\u0431\u044B \u043C\u044B \u043C\u043E\u0433\u043B\u0438 \u0441 \u0432\u0430\u043C\u0438 \u0441\u0432\u044F\u0437\u0430\u0442\u044C\u0441\u044F, \u0443\u043A\u0430\u0436\u0438\u0442\u0435 \u0430\u0434\u0440\u0435\u0441 \u044D\u043B\u0435\u043A\u0442\u0440\u043E\u043D\u043D\u043E\u0439 \u043F\u043E\u0447\u0442\u044B (\u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E) \u0438 \u043F\u0440\u0438 \u0436\u0435\u043B\u0430\u043D\u0438\u0438 \u2014 \u0438\u043C\u044F.",
187
+ emailLabel: "\u042D\u043B\u0435\u043A\u0442\u0440\u043E\u043D\u043D\u0430\u044F \u043F\u043E\u0447\u0442\u0430",
188
+ nameLabel: "\u0418\u043C\u044F",
189
+ emailPlaceholder: "you@example.com",
190
+ namePlaceholder: "\u041D\u0435\u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E",
191
+ continueCta: "\u041F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442\u044C",
192
+ changeContact: "\u0418\u0437\u043C\u0435\u043D\u0438\u0442\u044C \u043A\u043E\u043D\u0442\u0430\u043A\u0442\u044B",
193
+ backToTopics: "\u041A \u0441\u043F\u0438\u0441\u043A\u0443 \u0442\u0435\u043C",
194
+ emailInvalid: "\u041F\u043E\u0436\u0430\u043B\u0443\u0439\u0441\u0442\u0430, \u0432\u0432\u0435\u0434\u0438\u0442\u0435 \u043A\u043E\u0440\u0440\u0435\u043A\u0442\u043D\u044B\u0439 \u0430\u0434\u0440\u0435\u0441 \u044D\u043B\u0435\u043A\u0442\u0440\u043E\u043D\u043D\u043E\u0439 \u043F\u043E\u0447\u0442\u044B.",
195
+ threadHint: "\u041F\u0435\u0440\u0435\u043F\u0438\u0441\u043A\u0430 \u043E\u0442\u043E\u0431\u0440\u0430\u0436\u0430\u0435\u0442\u0441\u044F \u043D\u0438\u0436\u0435. \u041C\u044B \u043F\u0440\u043E\u0434\u0443\u0431\u043B\u0438\u0440\u0443\u0435\u043C \u043F\u0435\u0440\u0435\u043F\u0438\u0441\u043A\u0443 \u043D\u0430 \u0443\u043A\u0430\u0437\u0430\u043D\u043D\u044B\u0439 email \u0432 \u0441\u043B\u0443\u0447\u0430\u0435 \u043E\u0431\u0440\u044B\u0432\u0430 \u0441\u0432\u044F\u0437\u0438.",
196
+ threadHintIconLabel: "\u041F\u043E\u0434\u0441\u043A\u0430\u0437\u043A\u0430: \u043F\u0435\u0440\u0435\u043F\u0438\u0441\u043A\u0430 \u0438 \u0434\u0443\u0431\u043B\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u0435 \u043D\u0430 email",
197
+ threadHintCollapse: "\u0421\u0432\u0435\u0440\u043D\u0443\u0442\u044C",
198
+ emptyThreadGuest: "\u041D\u0430\u043F\u0438\u0448\u0438\u0442\u0435 \u043F\u0435\u0440\u0432\u043E\u0435 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435 \u0432 \u043F\u043E\u043B\u0435 \u043D\u0438\u0436\u0435.",
199
+ loadingProfile: "\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430\u2026"
200
+ };
201
+ case "de":
202
+ return {
203
+ intro: "Damit wir antworten k\xF6nnen, geben Sie bitte Ihre E-Mail-Adresse an (Pflichtfeld) und optional Ihren Namen.",
204
+ emailLabel: "E-Mail",
205
+ nameLabel: "Name",
206
+ emailPlaceholder: "you@example.com",
207
+ namePlaceholder: "Optional",
208
+ continueCta: "Weiter",
209
+ changeContact: "Kontakt \xE4ndern",
210
+ backToTopics: "Zur\xFCck zu den Themen",
211
+ emailInvalid: "Bitte geben Sie eine g\xFCltige E-Mail-Adresse ein.",
212
+ threadHint: "Der Chatverlauf erscheint unten. Wir antworten per E-Mail.",
213
+ threadHintIconLabel: "Hinweis zu Chat und E-Mail",
214
+ threadHintCollapse: "Einklappen",
215
+ emptyThreadGuest: "Schreiben Sie Ihre erste Nachricht unten.",
216
+ loadingProfile: "Wird geladen\u2026"
217
+ };
218
+ case "es":
219
+ return {
220
+ intro: "Para poder responderte, indica tu correo electr\xF3nico (obligatorio) y, si quieres, tu nombre.",
221
+ emailLabel: "Correo electr\xF3nico",
222
+ nameLabel: "Nombre",
223
+ emailPlaceholder: "tu@ejemplo.com",
224
+ namePlaceholder: "Opcional",
225
+ continueCta: "Continuar",
226
+ changeContact: "Cambiar datos",
227
+ backToTopics: "Volver a los temas",
228
+ emailInvalid: "Introduce un correo electr\xF3nico v\xE1lido.",
229
+ threadHint: "Los mensajes aparecen abajo. Te responderemos por correo.",
230
+ threadHintIconLabel: "Aviso sobre el chat y el correo",
231
+ threadHintCollapse: "Ocultar",
232
+ emptyThreadGuest: "Escribe tu primer mensaje abajo.",
233
+ loadingProfile: "Cargando\u2026"
234
+ };
235
+ default:
236
+ return {
237
+ intro: "So we can reach you, please enter your email address (required) and your name if you\u2019d like (optional).",
238
+ emailLabel: "Email",
239
+ nameLabel: "Name",
240
+ emailPlaceholder: "you@example.com",
241
+ namePlaceholder: "Optional",
242
+ continueCta: "Continue",
243
+ changeContact: "Change contact details",
244
+ backToTopics: "Back to topics",
245
+ emailInvalid: "Please enter a valid email address.",
246
+ threadHint: "Your conversation appears below. We\u2019ll reply to your email.",
247
+ threadHintIconLabel: "Note about chat and email",
248
+ threadHintCollapse: "Collapse",
249
+ emptyThreadGuest: "Write your first message in the box below.",
250
+ loadingProfile: "Loading\u2026"
251
+ };
252
+ }
253
+ }
254
+ function defaultChatUiLabels(locale) {
255
+ return {
256
+ ...guestCopy(locale),
257
+ ...unavailableCopy(locale),
258
+ title: "Chat",
259
+ launcherOpen: "Open chat",
260
+ launcherClose: "Close chat",
261
+ send: "Send",
262
+ composerPlaceholder: "Type a message\u2026",
263
+ emptyThreadAuthenticated: "Say hello to start a conversation."
264
+ };
265
+ }
266
+
267
+ // src/lib/visitor-ws-url.ts
268
+ function buildVisitorWsUrl(apiOrigin, projectUid, integrationId, sessionPublicId) {
269
+ const u = new URL(apiOrigin);
270
+ const wsProto = u.protocol === "https:" ? "wss:" : "ws:";
271
+ const qs = new URLSearchParams({
272
+ projectUid,
273
+ integrationId,
274
+ sessionPublicId
275
+ });
276
+ return `${wsProto}//${u.host}/api/v1/chat/visitor-ws?${qs.toString()}`;
277
+ }
278
+
279
+ // src/hooks/use-mailoo-chat-session.ts
280
+ function useMailooChatSession(options) {
281
+ const {
282
+ apiOrigin,
283
+ projectUid,
284
+ integrationId,
285
+ isAuthenticated,
286
+ submitEndpoint = DEFAULT_CHAT_SUBMIT_ENDPOINT,
287
+ messagesEndpoint = DEFAULT_CHAT_MESSAGES_ENDPOINT,
288
+ storageKey = DEFAULT_CHAT_SESSION_STORAGE_KEY,
289
+ guestProfileKey = DEFAULT_CHAT_GUEST_PROFILE_KEY,
290
+ pollIntervalMs = DEFAULT_CHAT_POLL_INTERVAL_MS,
291
+ enableRealtime = true,
292
+ enablePolling = true,
293
+ enableWebSocket = true,
294
+ enabled = true,
295
+ onSessionCreated,
296
+ onMessageSent,
297
+ onError,
298
+ onFault
299
+ } = options;
300
+ const [sessionPublicId, setSessionPublicId] = (0, import_react.useState)(null);
301
+ const [messages, setMessages] = (0, import_react.useState)([]);
302
+ const [sending, setSending] = (0, import_react.useState)(false);
303
+ const [error, setError] = (0, import_react.useState)(null);
304
+ const [fault, setFault] = (0, import_react.useState)(null);
305
+ const [connectionState, setConnectionState] = (0, import_react.useState)("idle");
306
+ const [guestEmail, setGuestEmail] = (0, import_react.useState)("");
307
+ const [guestName, setGuestName] = (0, import_react.useState)("");
308
+ const [guestContactOk, setGuestContactOk] = (0, import_react.useState)(isAuthenticated);
309
+ const [guestProfileHydrated, setGuestProfileHydrated] = (0, import_react.useState)(false);
310
+ const wsRef = (0, import_react.useRef)(null);
311
+ const pollRef = (0, import_react.useRef)(null);
312
+ const sessionPublicIdRef = (0, import_react.useRef)(null);
313
+ const sinceRef = (0, import_react.useRef)(void 0);
314
+ const faultRef = (0, import_react.useRef)(null);
315
+ const onSessionCreatedRef = (0, import_react.useRef)(onSessionCreated);
316
+ const onMessageSentRef = (0, import_react.useRef)(onMessageSent);
317
+ const onErrorRef = (0, import_react.useRef)(onError);
318
+ const onFaultRef = (0, import_react.useRef)(onFault);
319
+ sessionPublicIdRef.current = sessionPublicId;
320
+ faultRef.current = fault;
321
+ onSessionCreatedRef.current = onSessionCreated;
322
+ onMessageSentRef.current = onMessageSent;
323
+ onErrorRef.current = onError;
324
+ onFaultRef.current = onFault;
325
+ const clearFault = (0, import_react.useCallback)(() => {
326
+ faultRef.current = null;
327
+ setFault(null);
328
+ }, []);
329
+ const reportFault = (0, import_react.useCallback)((next) => {
330
+ faultRef.current = next;
331
+ setFault(next);
332
+ setError(null);
333
+ onFaultRef.current?.(next);
334
+ onErrorRef.current?.(next.message);
335
+ }, []);
336
+ const reportError = (0, import_react.useCallback)((message) => {
337
+ setError(message);
338
+ if (message) {
339
+ clearFault();
340
+ onErrorRef.current?.(message);
341
+ }
342
+ }, [clearFault]);
343
+ (0, import_react.useEffect)(() => {
344
+ if (typeof window === "undefined") return;
345
+ const existing = sessionStorage.getItem(storageKey);
346
+ if (existing) setSessionPublicId(existing);
347
+ }, [storageKey]);
348
+ (0, import_react.useEffect)(() => {
349
+ if (typeof window === "undefined" || isAuthenticated) {
350
+ setGuestProfileHydrated(true);
351
+ return;
352
+ }
353
+ try {
354
+ const raw = sessionStorage.getItem(guestProfileKey);
355
+ if (raw) {
356
+ const j = JSON.parse(raw);
357
+ const em = typeof j.email === "string" ? j.email.trim().toLowerCase() : "";
358
+ if (em && isValidEmail(em)) {
359
+ setGuestEmail(em);
360
+ setGuestName(typeof j.name === "string" ? j.name.trim() : "");
361
+ setGuestContactOk(true);
362
+ }
363
+ }
364
+ } catch {
365
+ }
366
+ setGuestProfileHydrated(true);
367
+ }, [isAuthenticated, guestProfileKey]);
368
+ (0, import_react.useEffect)(() => {
369
+ if (isAuthenticated) {
370
+ setGuestContactOk(true);
371
+ }
372
+ }, [isAuthenticated]);
373
+ const persistGuestProfile = (0, import_react.useCallback)(
374
+ (email, name) => {
375
+ try {
376
+ sessionStorage.setItem(
377
+ guestProfileKey,
378
+ JSON.stringify({
379
+ email: email.trim().toLowerCase(),
380
+ name: name.trim()
381
+ })
382
+ );
383
+ } catch {
384
+ }
385
+ },
386
+ [guestProfileKey]
387
+ );
388
+ const confirmGuestContact = (0, import_react.useCallback)(
389
+ (invalidEmailMessage) => {
390
+ reportError(null);
391
+ if (isAuthenticated) {
392
+ setGuestContactOk(true);
393
+ return true;
394
+ }
395
+ if (!isValidEmail(guestEmail)) {
396
+ reportError(
397
+ invalidEmailMessage ?? "Please enter a valid email address."
398
+ );
399
+ return false;
400
+ }
401
+ persistGuestProfile(guestEmail, guestName);
402
+ setGuestContactOk(true);
403
+ return true;
404
+ },
405
+ [
406
+ isAuthenticated,
407
+ guestEmail,
408
+ guestName,
409
+ persistGuestProfile,
410
+ reportError
411
+ ]
412
+ );
413
+ const resetGuestContact = (0, import_react.useCallback)(() => {
414
+ setGuestContactOk(false);
415
+ reportError(null);
416
+ }, [reportError]);
417
+ const mergeIncoming = (0, import_react.useCallback)((rows) => {
418
+ const parsed = parseChatMessages(rows);
419
+ setMessages((prev) => {
420
+ const next = mergeChatMessages(prev, parsed);
421
+ sinceRef.current = latestMessageCreatedAt(next);
422
+ return next;
423
+ });
424
+ }, []);
425
+ const mergeMessage = (0, import_react.useCallback)((message) => {
426
+ setMessages((prev) => {
427
+ const next = mergeChatMessages(prev, [message]);
428
+ sinceRef.current = latestMessageCreatedAt(next);
429
+ return next;
430
+ });
431
+ }, []);
432
+ const clearSession = (0, import_react.useCallback)(() => {
433
+ try {
434
+ sessionStorage.removeItem(storageKey);
435
+ } catch {
436
+ }
437
+ setSessionPublicId(null);
438
+ sessionPublicIdRef.current = null;
439
+ setMessages([]);
440
+ sinceRef.current = void 0;
441
+ }, [storageKey]);
442
+ const stopRealtime = (0, import_react.useCallback)(() => {
443
+ if (pollRef.current) {
444
+ clearInterval(pollRef.current);
445
+ pollRef.current = null;
446
+ }
447
+ wsRef.current?.close();
448
+ wsRef.current = null;
449
+ setConnectionState("closed");
450
+ }, []);
451
+ const refreshMessages = (0, import_react.useCallback)(
452
+ async (sid, opts) => {
453
+ const id = sid ?? sessionPublicIdRef.current;
454
+ if (!id) return;
455
+ if (faultRef.current) return;
456
+ const params = new URLSearchParams({ sessionPublicId: id });
457
+ if (!opts?.full && sinceRef.current) {
458
+ params.set("since", sinceRef.current);
459
+ }
460
+ let res;
461
+ try {
462
+ res = await fetch(`${messagesEndpoint}?${params.toString()}`);
463
+ } catch {
464
+ const next = classifyChatFault(0, null, "Network error");
465
+ if (next) {
466
+ reportFault(next);
467
+ stopRealtime();
468
+ }
469
+ return;
470
+ }
471
+ const data = await res.json().catch(() => ({}));
472
+ if (!res.ok) {
473
+ if (isChatSessionNotFound(res.status, data)) {
474
+ clearSession();
475
+ reportError(null);
476
+ return;
477
+ }
478
+ const classified = classifyChatFault(
479
+ res.status,
480
+ data,
481
+ "Failed to load messages"
482
+ );
483
+ if (classified) {
484
+ reportFault(classified);
485
+ stopRealtime();
486
+ return;
487
+ }
488
+ reportError(messageFromPayload(data, "Failed to load messages"));
489
+ return;
490
+ }
491
+ if (Array.isArray(data.data)) {
492
+ mergeIncoming(data.data);
493
+ }
494
+ },
495
+ [
496
+ messagesEndpoint,
497
+ mergeIncoming,
498
+ clearSession,
499
+ reportError,
500
+ reportFault,
501
+ stopRealtime
502
+ ]
503
+ );
504
+ (0, import_react.useEffect)(() => {
505
+ const realtimeOn = enabled && enableRealtime && guestContactOk && !fault;
506
+ if (!realtimeOn || !sessionPublicId) {
507
+ return;
508
+ }
509
+ const connectWs = () => {
510
+ if (!enableWebSocket || !apiOrigin || !projectUid || !integrationId) {
511
+ return;
512
+ }
513
+ try {
514
+ const url = buildVisitorWsUrl(
515
+ apiOrigin,
516
+ projectUid,
517
+ integrationId,
518
+ sessionPublicId
519
+ );
520
+ setConnectionState("connecting");
521
+ const ws = new WebSocket(url);
522
+ wsRef.current = ws;
523
+ ws.onopen = () => setConnectionState("open");
524
+ ws.onmessage = (ev) => {
525
+ try {
526
+ const msg = JSON.parse(ev.data);
527
+ if (msg.type !== "chat.message") return;
528
+ const p = msg.payload;
529
+ if (p && typeof p.messageId === "string" && typeof p.content === "string") {
530
+ mergeMessage({
531
+ id: p.messageId,
532
+ content: p.content,
533
+ messageType: typeof p.messageType === "string" ? p.messageType : "OUTBOUND",
534
+ senderEmail: typeof p.senderEmail === "string" ? p.senderEmail : null,
535
+ senderName: typeof p.senderName === "string" ? p.senderName : null,
536
+ createdAt: typeof p.createdAt === "string" ? p.createdAt : (/* @__PURE__ */ new Date()).toISOString()
537
+ });
538
+ }
539
+ void refreshMessages(sessionPublicId);
540
+ } catch {
541
+ }
542
+ };
543
+ ws.onclose = () => {
544
+ wsRef.current = null;
545
+ setConnectionState("closed");
546
+ };
547
+ } catch {
548
+ setConnectionState("closed");
549
+ }
550
+ };
551
+ connectWs();
552
+ void refreshMessages(sessionPublicId, { full: true });
553
+ if (enablePolling) {
554
+ pollRef.current = setInterval(() => {
555
+ void refreshMessages(sessionPublicId);
556
+ }, pollIntervalMs);
557
+ }
558
+ return () => {
559
+ if (pollRef.current) clearInterval(pollRef.current);
560
+ pollRef.current = null;
561
+ wsRef.current?.close();
562
+ wsRef.current = null;
563
+ setConnectionState("idle");
564
+ };
565
+ }, [
566
+ enabled,
567
+ enableRealtime,
568
+ enablePolling,
569
+ enableWebSocket,
570
+ guestContactOk,
571
+ fault,
572
+ sessionPublicId,
573
+ apiOrigin,
574
+ projectUid,
575
+ integrationId,
576
+ pollIntervalMs,
577
+ refreshMessages,
578
+ mergeMessage
579
+ ]);
580
+ const sendMessage = (0, import_react.useCallback)(
581
+ async (content, opts) => {
582
+ const text = content.trim();
583
+ if (!text || sending) return false;
584
+ if (faultRef.current) return false;
585
+ if (!isAuthenticated && !isValidEmail(guestEmail)) {
586
+ reportError(
587
+ opts?.invalidEmailMessage ?? "Please enter a valid email address."
588
+ );
589
+ return false;
590
+ }
591
+ setSending(true);
592
+ reportError(null);
593
+ clearFault();
594
+ try {
595
+ const buildBody = (includeSession) => {
596
+ const body = { content: text };
597
+ const pageUrl = opts?.pageUrl ?? (typeof window !== "undefined" ? window.location.href : void 0);
598
+ if (pageUrl) body.pageUrl = pageUrl;
599
+ const sid = includeSession ? sessionPublicIdRef.current : null;
600
+ if (sid) body.sessionPublicId = sid;
601
+ else if (opts?.topicId?.trim()) {
602
+ body.topicId = opts.topicId.trim();
603
+ }
604
+ if (!isAuthenticated) {
605
+ body.senderEmail = guestEmail.trim().toLowerCase();
606
+ if (guestName.trim()) body.senderName = guestName.trim();
607
+ }
608
+ return body;
609
+ };
610
+ const post = async (includeSession) => {
611
+ let res2;
612
+ try {
613
+ res2 = await fetch(submitEndpoint, {
614
+ method: "POST",
615
+ headers: { "Content-Type": "application/json" },
616
+ body: JSON.stringify(buildBody(includeSession))
617
+ });
618
+ } catch {
619
+ return {
620
+ res: null,
621
+ data: {},
622
+ networkError: true
623
+ };
624
+ }
625
+ const data2 = await res2.json().catch(() => ({}));
626
+ return { res: res2, data: data2, networkError: false };
627
+ };
628
+ let result = await post(true);
629
+ if (result.networkError) {
630
+ const next = classifyChatFault(0, null, "Network error");
631
+ if (next) {
632
+ reportFault(next);
633
+ stopRealtime();
634
+ }
635
+ return false;
636
+ }
637
+ let { res, data } = result;
638
+ if (!res) return false;
639
+ if (!res.ok && isChatSessionNotFound(res.status, data)) {
640
+ clearSession();
641
+ result = await post(false);
642
+ if (result.networkError) {
643
+ const next = classifyChatFault(0, null, "Network error");
644
+ if (next) {
645
+ reportFault(next);
646
+ stopRealtime();
647
+ }
648
+ return false;
649
+ }
650
+ ;
651
+ ({ res, data } = result);
652
+ if (!res) return false;
653
+ }
654
+ if (!res.ok) {
655
+ const classified = classifyChatFault(
656
+ res.status,
657
+ data,
658
+ "Send failed"
659
+ );
660
+ if (classified) {
661
+ reportFault(classified);
662
+ stopRealtime();
663
+ return false;
664
+ }
665
+ reportError(messageFromPayload(data, "Send failed"));
666
+ return false;
667
+ }
668
+ if (data.sessionPublicId) {
669
+ persistSessionPublicId(data.sessionPublicId, storageKey, {
670
+ sessionPublicIdRef,
671
+ setSessionPublicId,
672
+ onCreated: onSessionCreatedRef.current
673
+ });
674
+ }
675
+ if (!isAuthenticated) {
676
+ persistGuestProfile(guestEmail, guestName);
677
+ }
678
+ if (typeof data.messageId === "string" && data.messageId) {
679
+ mergeMessage({
680
+ id: data.messageId,
681
+ content: text,
682
+ messageType: "INBOUND",
683
+ senderEmail: isAuthenticated ? null : guestEmail.trim().toLowerCase(),
684
+ senderName: isAuthenticated ? null : guestName.trim() || null,
685
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
686
+ });
687
+ }
688
+ const nextSid = data.sessionPublicId ?? sessionPublicIdRef.current;
689
+ if (nextSid) {
690
+ await refreshMessages(nextSid, { full: true });
691
+ }
692
+ onMessageSentRef.current?.(text);
693
+ return true;
694
+ } catch (e) {
695
+ reportError(e instanceof Error ? e.message : "Send failed");
696
+ return false;
697
+ } finally {
698
+ setSending(false);
699
+ }
700
+ },
701
+ [
702
+ sending,
703
+ isAuthenticated,
704
+ guestEmail,
705
+ guestName,
706
+ submitEndpoint,
707
+ storageKey,
708
+ persistGuestProfile,
709
+ mergeMessage,
710
+ refreshMessages,
711
+ clearSession,
712
+ reportError,
713
+ reportFault,
714
+ clearFault,
715
+ stopRealtime
716
+ ]
717
+ );
718
+ const retryAfterFault = (0, import_react.useCallback)(() => {
719
+ clearFault();
720
+ reportError(null);
721
+ }, [clearFault, reportError]);
722
+ const canCompose = isAuthenticated || guestProfileHydrated && guestContactOk;
723
+ return {
724
+ sessionPublicId,
725
+ messages,
726
+ sending,
727
+ error,
728
+ fault,
729
+ setError: reportError,
730
+ clearFault,
731
+ retryAfterFault,
732
+ connectionState,
733
+ guestEmail,
734
+ guestName,
735
+ setGuestEmail,
736
+ setGuestName,
737
+ guestContactOk,
738
+ guestProfileHydrated,
739
+ confirmGuestContact,
740
+ resetGuestContact,
741
+ canCompose,
742
+ sendMessage,
743
+ refreshMessages,
744
+ clearSession
745
+ };
746
+ }
747
+
748
+ // src/lib/widget-config.ts
749
+ var MAILOO_CHAT_LAUNCHER_POSITIONS = [
750
+ "bottom-right",
751
+ "bottom-left",
752
+ "top-right",
753
+ "top-left",
754
+ "middle-right",
755
+ "middle-left"
756
+ ];
757
+ function isMailooChatLauncherPosition(value) {
758
+ return typeof value === "string" && MAILOO_CHAT_LAUNCHER_POSITIONS.includes(value);
759
+ }
760
+ function resolveMailooChatLauncherPosition(prop, config) {
761
+ if (prop) return prop;
762
+ if (config?.position && isMailooChatLauncherPosition(config.position)) {
763
+ return config.position;
764
+ }
765
+ return "bottom-right";
766
+ }
767
+ function mailooChatLauncherRootClassNames(position) {
768
+ const parts = ["mailoo-chat-root"];
769
+ if (position.endsWith("-left")) parts.push("mailoo-chat-root--left");
770
+ if (position.startsWith("top-")) parts.push("mailoo-chat-root--top");
771
+ if (position.startsWith("middle-")) parts.push("mailoo-chat-root--middle");
772
+ return parts.join(" ");
773
+ }
774
+ function chatWidgetCssVariables(config) {
775
+ return {
776
+ ["--mailoo-chat-brand"]: config?.brandPrimary ?? "#2563EB",
777
+ ["--mailoo-chat-brand-on"]: config?.brandOnPrimary ?? "#FFFFFF"
778
+ };
779
+ }
780
+
781
+ // src/components/mailoo-chat-unavailable-panel.tsx
782
+ var import_jsx_runtime = require("react/jsx-runtime");
783
+ function actionHref(action) {
784
+ if (action.type === "mailto") {
785
+ return `mailto:${action.email.trim()}`;
786
+ }
787
+ return action.href;
788
+ }
789
+ function actionLabel(action, labels) {
790
+ if (action.type === "mailto") {
791
+ return action.label?.trim() || labels.contactCta;
792
+ }
793
+ return action.label;
794
+ }
795
+ function MailooChatUnavailablePanel({
796
+ fault,
797
+ labels,
798
+ retry,
799
+ unavailableAction,
800
+ brandBtnClass
801
+ }) {
802
+ const isRateLimited = fault.kind === "rate_limited";
803
+ const title = isRateLimited ? labels.rateLimitedTitle : labels.unavailableTitle;
804
+ const body = isRateLimited ? labels.rateLimitedBody : labels.unavailableBody;
805
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
806
+ "div",
807
+ {
808
+ className: "flex min-h-0 flex-1 flex-col justify-center gap-3 p-4 text-sm",
809
+ role: "alert",
810
+ children: [
811
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "space-y-1", children: [
812
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "font-medium text-gray-900 dark:text-white", children: title }),
813
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-xs leading-snug text-gray-600 dark:text-gray-300", children: body })
814
+ ] }),
815
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col gap-2", children: [
816
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
817
+ "button",
818
+ {
819
+ type: "button",
820
+ onClick: retry,
821
+ className: `w-full py-2 text-sm font-medium ${brandBtnClass}`,
822
+ children: labels.retryCta
823
+ }
824
+ ),
825
+ unavailableAction ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
826
+ "a",
827
+ {
828
+ href: actionHref(unavailableAction),
829
+ ...unavailableAction.type === "link" ? { target: "_blank", rel: "noopener noreferrer" } : {},
830
+ className: "w-full rounded border border-gray-300 py-2 text-center text-sm font-medium text-gray-800 hover:bg-gray-50 dark:border-gray-600 dark:text-gray-100 dark:hover:bg-gray-800",
831
+ children: actionLabel(unavailableAction, labels)
832
+ }
833
+ ) : null
834
+ ] })
835
+ ]
836
+ }
837
+ );
838
+ }
839
+
840
+ // src/components/mailoo-site-chat-widget.tsx
841
+ var import_jsx_runtime2 = require("react/jsx-runtime");
842
+ function MailooSiteChatWidget(props) {
843
+ const {
844
+ apiOrigin,
845
+ projectUid,
846
+ integrationId,
847
+ welcomeMessage,
848
+ locale,
849
+ isAuthenticated,
850
+ widgetConfig,
851
+ className,
852
+ panelClassName,
853
+ launcherClassName,
854
+ open: openProp,
855
+ defaultOpen = false,
856
+ onOpenChange,
857
+ title: titleProp,
858
+ launcherLabel,
859
+ labels: labelsProp,
860
+ submitEndpoint,
861
+ messagesEndpoint,
862
+ pollIntervalMs,
863
+ enablePolling,
864
+ enableWebSocket,
865
+ position: positionProp,
866
+ zIndex,
867
+ onSessionCreated,
868
+ onMessageSent,
869
+ onError,
870
+ onFault,
871
+ unavailableAction,
872
+ renderLauncher,
873
+ renderHeader,
874
+ renderEmpty,
875
+ renderWelcome,
876
+ renderUnavailable
877
+ } = props;
878
+ const t = {
879
+ ...defaultChatUiLabels(locale),
880
+ ...labelsProp
881
+ };
882
+ const title = titleProp ?? t.title;
883
+ const isControlled = openProp !== void 0;
884
+ const [uncontrolledOpen, setUncontrolledOpen] = (0, import_react3.useState)(defaultOpen);
885
+ const open = isControlled ? openProp : uncontrolledOpen;
886
+ const setOpen = (next) => {
887
+ onOpenChange?.(next);
888
+ if (!isControlled) setUncontrolledOpen(next);
889
+ };
890
+ const [input, setInput] = (0, import_react3.useState)("");
891
+ const [threadHintExpanded, setThreadHintExpanded] = (0, import_react3.useState)(false);
892
+ const [selectedTopicId, setSelectedTopicId] = (0, import_react3.useState)(null);
893
+ const [welcomeDone, setWelcomeDone] = (0, import_react3.useState)(false);
894
+ const messagesScrollRef = (0, import_react3.useRef)(null);
895
+ const stickToBottomRef = (0, import_react3.useRef)(true);
896
+ const SCROLL_NEAR_BOTTOM_PX = 80;
897
+ const brandStyle = chatWidgetCssVariables(widgetConfig ?? void 0);
898
+ const brandBtn = "rounded bg-[var(--mailoo-chat-brand,#2563eb)] text-[var(--mailoo-chat-brand-on,#ffffff)] hover:opacity-90";
899
+ const headerTitle = widgetConfig?.welcomeTitle ?? title;
900
+ const headerTagline = widgetConfig?.welcomeTagline ?? welcomeMessage;
901
+ const topics = widgetConfig?.topics ?? [];
902
+ const anyOperatorOnline = (widgetConfig?.operators ?? []).some(
903
+ (op) => op.online
904
+ );
905
+ const session = useMailooChatSession({
906
+ apiOrigin,
907
+ projectUid,
908
+ integrationId,
909
+ isAuthenticated,
910
+ submitEndpoint,
911
+ messagesEndpoint,
912
+ pollIntervalMs,
913
+ enablePolling,
914
+ enableWebSocket,
915
+ enabled: open,
916
+ onSessionCreated,
917
+ onMessageSent,
918
+ onError,
919
+ onFault
920
+ });
921
+ const {
922
+ messages,
923
+ sending,
924
+ error,
925
+ fault,
926
+ retryAfterFault,
927
+ guestEmail,
928
+ guestName,
929
+ setGuestEmail,
930
+ setGuestName,
931
+ guestContactOk,
932
+ guestProfileHydrated,
933
+ confirmGuestContact,
934
+ resetGuestContact,
935
+ canCompose,
936
+ sendMessage
937
+ } = session;
938
+ const onMessagesScroll = (0, import_react3.useCallback)(() => {
939
+ const el = messagesScrollRef.current;
940
+ if (!el) return;
941
+ const distanceFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight;
942
+ stickToBottomRef.current = distanceFromBottom <= SCROLL_NEAR_BOTTOM_PX;
943
+ }, []);
944
+ (0, import_react3.useEffect)(() => {
945
+ if (!stickToBottomRef.current) return;
946
+ const el = messagesScrollRef.current;
947
+ if (!el) return;
948
+ el.scrollTop = el.scrollHeight;
949
+ }, [messages]);
950
+ const send = async () => {
951
+ const text = input.trim();
952
+ if (!text || sending) return;
953
+ stickToBottomRef.current = true;
954
+ const ok = await sendMessage(text, {
955
+ invalidEmailMessage: t.emailInvalid,
956
+ topicId: selectedTopicId ?? void 0
957
+ });
958
+ if (ok) setInput("");
959
+ };
960
+ const toggle = () => setOpen(!open);
961
+ const position = resolveMailooChatLauncherPosition(
962
+ positionProp,
963
+ widgetConfig
964
+ );
965
+ const needsGuestFields = !isAuthenticated && !guestContactOk;
966
+ const showUnavailable = fault != null;
967
+ const showEntry = !showUnavailable && guestProfileHydrated && messages.length === 0 && !welcomeDone;
968
+ const showWelcomeSkin = showEntry && renderWelcome != null;
969
+ const showBuiltinEntry = showEntry && renderWelcome == null;
970
+ const showComposer = !showUnavailable && (welcomeDone || messages.length > 0) && canCompose;
971
+ const showDefaultHeader = !showWelcomeSkin || showUnavailable;
972
+ const unavailableArgs = fault ? {
973
+ fault,
974
+ labels: t,
975
+ retry: retryAfterFault,
976
+ unavailableAction,
977
+ brandBtnClass: brandBtn
978
+ } : null;
979
+ const enterFromWelcome = (invalidEmailMessage) => {
980
+ if (isAuthenticated || guestContactOk) {
981
+ setWelcomeDone(true);
982
+ return true;
983
+ }
984
+ const ok = confirmGuestContact(invalidEmailMessage);
985
+ if (ok) setWelcomeDone(true);
986
+ return ok;
987
+ };
988
+ const onResetGuestContact = () => {
989
+ setWelcomeDone(false);
990
+ resetGuestContact();
991
+ };
992
+ const backToTopics = () => {
993
+ setWelcomeDone(false);
994
+ setThreadHintExpanded(false);
995
+ };
996
+ const canReturnToTopics = welcomeDone && !showUnavailable;
997
+ const welcomeArgs = {
998
+ widgetConfig,
999
+ welcomeMessage: headerTagline,
1000
+ title: headerTitle,
1001
+ locale,
1002
+ labels: t,
1003
+ brandBtnClass: brandBtn,
1004
+ brandStyle,
1005
+ topics,
1006
+ selectedTopicId,
1007
+ setSelectedTopicId,
1008
+ needsGuestFields,
1009
+ isAuthenticated,
1010
+ guestEmail,
1011
+ setGuestEmail,
1012
+ guestName,
1013
+ setGuestName,
1014
+ error,
1015
+ confirmGuestContact: enterFromWelcome
1016
+ };
1017
+ const rootClass = [
1018
+ mailooChatLauncherRootClassNames(position),
1019
+ className ?? ""
1020
+ ].filter(Boolean).join(" ");
1021
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1022
+ "div",
1023
+ {
1024
+ className: rootClass,
1025
+ "data-clarity-mask": "true",
1026
+ style: {
1027
+ ...brandStyle,
1028
+ ...zIndex != null ? { zIndex } : {}
1029
+ },
1030
+ children: [
1031
+ open && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1032
+ "div",
1033
+ {
1034
+ className: `mailoo-chat-panel ${panelClassName ?? ""}`,
1035
+ style: brandStyle,
1036
+ children: [
1037
+ showDefaultHeader && (renderHeader ? renderHeader({
1038
+ welcomeMessage: headerTagline,
1039
+ title: headerTitle
1040
+ }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-start gap-1 border-b border-gray-200 px-2 py-2 dark:border-gray-700", children: [
1041
+ canReturnToTopics ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1042
+ "button",
1043
+ {
1044
+ type: "button",
1045
+ onClick: backToTopics,
1046
+ className: "mt-0.5 shrink-0 rounded p-1 text-gray-500 hover:bg-gray-100 hover:text-gray-800 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-100",
1047
+ "aria-label": t.backToTopics,
1048
+ title: t.backToTopics,
1049
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_outline.ChevronLeftIcon, { className: "h-5 w-5", "aria-hidden": true })
1050
+ }
1051
+ ) : null,
1052
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "min-w-0 flex-1 px-1", children: [
1053
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "text-sm font-medium text-gray-900 dark:text-white", children: headerTitle }),
1054
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: headerTagline })
1055
+ ] })
1056
+ ] })),
1057
+ !isAuthenticated && !guestProfileHydrated && !showUnavailable && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "border-b border-gray-200 p-3 text-sm text-gray-500 dark:border-gray-700 dark:text-gray-400", children: t.loadingProfile }),
1058
+ showUnavailable && unavailableArgs ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex min-h-0 flex-1 flex-col overflow-hidden", children: [
1059
+ messages.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "min-h-0 max-h-[40%] space-y-2 overflow-y-auto border-b border-gray-200 p-3 text-sm dark:border-gray-700", children: messages.map((m) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1060
+ "div",
1061
+ {
1062
+ className: m.messageType === "OUTBOUND" ? "mailoo-chat-bubble--outbound ml-6 rounded-md p-2" : "mailoo-chat-bubble--inbound mr-6 rounded-md p-2",
1063
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "whitespace-pre-wrap text-gray-900 dark:text-gray-100", children: m.content })
1064
+ },
1065
+ m.id
1066
+ )) }) : null,
1067
+ renderUnavailable ? renderUnavailable(unavailableArgs) : MailooChatUnavailablePanel(unavailableArgs)
1068
+ ] }) : null,
1069
+ showWelcomeSkin && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex min-h-0 flex-1 flex-col overflow-hidden", children: renderWelcome(welcomeArgs) }),
1070
+ showBuiltinEntry && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "space-y-3 border-b border-gray-200 p-3 text-sm dark:border-gray-700", children: [
1071
+ topics.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex flex-wrap gap-2", children: topics.map(
1072
+ (topic) => topic.action === "external_url" && topic.externalUrl ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1073
+ "a",
1074
+ {
1075
+ href: topic.externalUrl,
1076
+ target: "_blank",
1077
+ rel: "noopener noreferrer",
1078
+ className: `px-2 py-1 text-xs font-medium ${brandBtn}`,
1079
+ children: topic.label
1080
+ },
1081
+ topic.id
1082
+ ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1083
+ "button",
1084
+ {
1085
+ type: "button",
1086
+ onClick: () => setSelectedTopicId(
1087
+ (prev) => prev === topic.id ? null : topic.id
1088
+ ),
1089
+ className: `px-2 py-1 text-xs font-medium ${selectedTopicId === topic.id ? brandBtn : "rounded border border-gray-300 text-gray-700 dark:border-gray-600 dark:text-gray-200"}`,
1090
+ children: topic.label
1091
+ },
1092
+ topic.id
1093
+ )
1094
+ ) }),
1095
+ needsGuestFields && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
1096
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "text-gray-700 dark:text-gray-300", children: t.intro }),
1097
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
1098
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1099
+ "label",
1100
+ {
1101
+ htmlFor: "mailoo-chat-guest-email",
1102
+ className: "mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400",
1103
+ children: t.emailLabel
1104
+ }
1105
+ ),
1106
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1107
+ "input",
1108
+ {
1109
+ id: "mailoo-chat-guest-email",
1110
+ type: "email",
1111
+ autoComplete: "email",
1112
+ value: guestEmail,
1113
+ onChange: (e) => setGuestEmail(e.target.value),
1114
+ placeholder: t.emailPlaceholder,
1115
+ className: "w-full rounded border border-gray-300 bg-white px-2 py-1.5 text-sm dark:border-gray-600 dark:bg-gray-800"
1116
+ }
1117
+ )
1118
+ ] }),
1119
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
1120
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1121
+ "label",
1122
+ {
1123
+ htmlFor: "mailoo-chat-guest-name",
1124
+ className: "mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400",
1125
+ children: t.nameLabel
1126
+ }
1127
+ ),
1128
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1129
+ "input",
1130
+ {
1131
+ id: "mailoo-chat-guest-name",
1132
+ type: "text",
1133
+ autoComplete: "name",
1134
+ value: guestName,
1135
+ onChange: (e) => setGuestName(e.target.value),
1136
+ placeholder: t.namePlaceholder,
1137
+ className: "w-full rounded border border-gray-300 bg-white px-2 py-1.5 text-sm dark:border-gray-600 dark:bg-gray-800"
1138
+ }
1139
+ )
1140
+ ] }),
1141
+ error && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "text-xs text-red-600 dark:text-red-400", children: error })
1142
+ ] }),
1143
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1144
+ "button",
1145
+ {
1146
+ type: "button",
1147
+ onClick: () => enterFromWelcome(t.emailInvalid),
1148
+ className: `w-full py-2 text-sm font-medium ${brandBtn}`,
1149
+ children: t.continueCta
1150
+ }
1151
+ )
1152
+ ] }),
1153
+ showComposer && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex min-h-0 flex-1 flex-col", children: [
1154
+ !isAuthenticated && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
1155
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex shrink-0 items-center justify-between gap-2 border-b border-gray-100 px-3 py-1.5 text-[11px] text-gray-500 dark:border-gray-800 dark:text-gray-400", children: [
1156
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "truncate", children: guestEmail }),
1157
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1158
+ "button",
1159
+ {
1160
+ type: "button",
1161
+ onClick: () => onResetGuestContact(),
1162
+ className: "shrink-0 text-[var(--mailoo-chat-brand,#2563eb)] hover:underline",
1163
+ children: t.changeContact
1164
+ }
1165
+ )
1166
+ ] }),
1167
+ threadHintExpanded && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "shrink-0 border-b border-gray-100 px-3 py-2 dark:border-gray-800", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-start gap-2", children: [
1168
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "min-w-0 flex-1 text-[11px] leading-snug text-gray-500 dark:text-gray-400", children: t.threadHint }),
1169
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1170
+ "button",
1171
+ {
1172
+ type: "button",
1173
+ onClick: () => setThreadHintExpanded(false),
1174
+ className: "shrink-0 rounded p-0.5 text-gray-400 hover:bg-gray-100 hover:text-gray-600 dark:hover:bg-gray-700 dark:hover:text-gray-300",
1175
+ "aria-label": t.threadHintCollapse,
1176
+ title: t.threadHintCollapse,
1177
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_outline.ChevronUpIcon, { className: "h-4 w-4", "aria-hidden": true })
1178
+ }
1179
+ )
1180
+ ] }) })
1181
+ ] }),
1182
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "relative min-h-0 w-full min-w-0 flex-1", children: [
1183
+ !isAuthenticated && !threadHintExpanded && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1184
+ "button",
1185
+ {
1186
+ type: "button",
1187
+ onClick: () => setThreadHintExpanded(true),
1188
+ className: "absolute right-3 top-2 z-20 rounded-full bg-white/95 p-1 shadow-md ring-1 ring-gray-200/80 hover:bg-gray-50 dark:bg-gray-800/95 dark:ring-gray-600 dark:hover:bg-gray-700",
1189
+ "aria-expanded": false,
1190
+ "aria-label": t.threadHintIconLabel,
1191
+ title: t.threadHintIconLabel,
1192
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1193
+ import_outline.InformationCircleIcon,
1194
+ {
1195
+ className: "h-5 w-5 text-gray-600 dark:text-gray-300",
1196
+ "aria-hidden": true
1197
+ }
1198
+ )
1199
+ }
1200
+ ),
1201
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1202
+ "div",
1203
+ {
1204
+ ref: messagesScrollRef,
1205
+ onScroll: onMessagesScroll,
1206
+ className: `h-full min-h-0 space-y-2 overflow-y-auto p-3 text-sm ${!isAuthenticated && !threadHintExpanded ? "pr-12 pt-2" : ""}`,
1207
+ children: [
1208
+ messages.length === 0 && (renderEmpty ? renderEmpty({ isAuthenticated }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "text-gray-500 dark:text-gray-400", children: isAuthenticated ? t.emptyThreadAuthenticated : t.emptyThreadGuest })),
1209
+ messages.map((m) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1210
+ "div",
1211
+ {
1212
+ className: m.messageType === "OUTBOUND" ? "mailoo-chat-bubble--outbound ml-6 rounded-md p-2" : "mailoo-chat-bubble--inbound mr-6 rounded-md p-2",
1213
+ children: [
1214
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "whitespace-pre-wrap text-gray-900 dark:text-gray-100", children: m.content }),
1215
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "mt-1 text-[10px] text-gray-400", children: new Date(m.createdAt).toLocaleString() })
1216
+ ]
1217
+ },
1218
+ m.id
1219
+ ))
1220
+ ]
1221
+ }
1222
+ )
1223
+ ] }),
1224
+ error && guestContactOk && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "px-3 text-xs text-red-600 dark:text-red-400", children: error }),
1225
+ widgetConfig?.privacyPolicyUrl && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "px-3 pb-1 text-[10px] text-gray-500 dark:text-gray-400", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1226
+ "a",
1227
+ {
1228
+ href: widgetConfig.privacyPolicyUrl,
1229
+ target: "_blank",
1230
+ rel: "noopener noreferrer",
1231
+ className: "underline hover:text-gray-700 dark:hover:text-gray-300",
1232
+ children: "Privacy"
1233
+ }
1234
+ ) }),
1235
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex shrink-0 gap-2 border-t border-gray-200 p-2 dark:border-gray-700", children: [
1236
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1237
+ "input",
1238
+ {
1239
+ type: "text",
1240
+ value: input,
1241
+ onChange: (e) => setInput(e.target.value),
1242
+ onKeyDown: (e) => {
1243
+ if (e.key === "Enter") void send();
1244
+ },
1245
+ placeholder: t.composerPlaceholder,
1246
+ className: "min-w-0 flex-1 rounded border border-gray-300 bg-white px-2 py-1.5 text-sm dark:border-gray-600 dark:bg-gray-800"
1247
+ }
1248
+ ),
1249
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1250
+ "button",
1251
+ {
1252
+ type: "button",
1253
+ onClick: () => void send(),
1254
+ disabled: sending,
1255
+ className: `px-3 py-1.5 text-sm font-medium disabled:opacity-50 ${brandBtn}`,
1256
+ children: t.send
1257
+ }
1258
+ )
1259
+ ] })
1260
+ ] })
1261
+ ]
1262
+ }
1263
+ ),
1264
+ renderLauncher ? renderLauncher({ open, toggle }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1265
+ "button",
1266
+ {
1267
+ type: "button",
1268
+ onClick: toggle,
1269
+ className: `mailoo-chat-launcher ${launcherClassName ?? ""}`,
1270
+ "aria-label": open ? t.launcherClose : launcherLabel ?? t.launcherOpen,
1271
+ children: [
1272
+ open ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_outline.XMarkIcon, { className: "mailoo-chat-launcher__icon", "aria-hidden": true }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1273
+ import_outline.ChatBubbleLeftRightIcon,
1274
+ {
1275
+ className: "mailoo-chat-launcher__icon",
1276
+ "aria-hidden": true
1277
+ }
1278
+ ),
1279
+ !open && anyOperatorOnline ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "mailoo-chat-launcher__online", "aria-hidden": true }) : null
1280
+ ]
1281
+ }
1282
+ )
1283
+ ]
1284
+ }
1285
+ );
1286
+ }
1287
+
1288
+ // src/components/mailoo-site-chat-shell.tsx
1289
+ var import_react6 = require("react");
1290
+
1291
+ // src/welcome-presets/shared.tsx
1292
+ var import_react4 = require("react");
1293
+ var import_jsx_runtime3 = require("react/jsx-runtime");
1294
+ function operatorInitials(name) {
1295
+ const base = (name ?? "?").trim();
1296
+ const parts = base.split(/\s+/).filter(Boolean);
1297
+ if (parts.length >= 2) {
1298
+ return `${parts[0][0] ?? ""}${parts[1][0] ?? ""}`.toUpperCase();
1299
+ }
1300
+ return base.slice(0, 2).toUpperCase();
1301
+ }
1302
+ function GuestContactFields({
1303
+ args,
1304
+ compact = false
1305
+ }) {
1306
+ const id = (0, import_react4.useId)();
1307
+ const emailId = `${id}-guest-email`;
1308
+ const nameId = `${id}-guest-name`;
1309
+ const {
1310
+ labels,
1311
+ guestEmail,
1312
+ setGuestEmail,
1313
+ guestName,
1314
+ setGuestName,
1315
+ error,
1316
+ needsGuestFields
1317
+ } = args;
1318
+ if (!needsGuestFields) return null;
1319
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: compact ? "space-y-2" : "space-y-3", children: [
1320
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
1321
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1322
+ "label",
1323
+ {
1324
+ htmlFor: emailId,
1325
+ className: "mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400",
1326
+ children: labels.emailLabel
1327
+ }
1328
+ ),
1329
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1330
+ "input",
1331
+ {
1332
+ id: emailId,
1333
+ type: "email",
1334
+ autoComplete: "email",
1335
+ value: guestEmail,
1336
+ onChange: (e) => setGuestEmail(e.target.value),
1337
+ placeholder: labels.emailPlaceholder,
1338
+ className: "w-full rounded border border-gray-300 bg-white px-2 py-1.5 text-sm dark:border-gray-600 dark:bg-gray-800"
1339
+ }
1340
+ )
1341
+ ] }),
1342
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
1343
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1344
+ "label",
1345
+ {
1346
+ htmlFor: nameId,
1347
+ className: "mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400",
1348
+ children: labels.nameLabel
1349
+ }
1350
+ ),
1351
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1352
+ "input",
1353
+ {
1354
+ id: nameId,
1355
+ type: "text",
1356
+ autoComplete: "name",
1357
+ value: guestName,
1358
+ onChange: (e) => setGuestName(e.target.value),
1359
+ placeholder: labels.namePlaceholder,
1360
+ className: "w-full rounded border border-gray-300 bg-white px-2 py-1.5 text-sm dark:border-gray-600 dark:bg-gray-800"
1361
+ }
1362
+ )
1363
+ ] }),
1364
+ error && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "text-xs text-red-600 dark:text-red-400", children: error })
1365
+ ] });
1366
+ }
1367
+ function PrivacyFooter({
1368
+ privacyPolicyUrl,
1369
+ className = ""
1370
+ }) {
1371
+ if (!privacyPolicyUrl) return null;
1372
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1373
+ "p",
1374
+ {
1375
+ className: `text-center text-[10px] leading-snug text-gray-500 dark:text-gray-400 ${className}`,
1376
+ children: [
1377
+ "By continuing, you agree to our",
1378
+ " ",
1379
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1380
+ "a",
1381
+ {
1382
+ href: privacyPolicyUrl,
1383
+ target: "_blank",
1384
+ rel: "noopener noreferrer",
1385
+ className: "underline hover:text-gray-700 dark:hover:text-gray-300",
1386
+ children: "privacy policy"
1387
+ }
1388
+ ),
1389
+ "."
1390
+ ]
1391
+ }
1392
+ );
1393
+ }
1394
+
1395
+ // src/welcome-presets/assistant.tsx
1396
+ var import_jsx_runtime4 = require("react/jsx-runtime");
1397
+ function AssistantWelcomePreset(args) {
1398
+ const {
1399
+ widgetConfig,
1400
+ title,
1401
+ welcomeMessage,
1402
+ brandBtnClass,
1403
+ brandStyle,
1404
+ topics,
1405
+ selectedTopicId,
1406
+ setSelectedTopicId,
1407
+ labels,
1408
+ needsGuestFields,
1409
+ confirmGuestContact
1410
+ } = args;
1411
+ const personaName = widgetConfig?.welcomeTitle?.trim() || title;
1412
+ const heroLine = widgetConfig?.welcomeTagline?.trim() || welcomeMessage;
1413
+ const headerBg = "bg-[var(--mailoo-chat-brand,#5b21b6)] text-[var(--mailoo-chat-brand-on,#ffffff)]";
1414
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1415
+ "div",
1416
+ {
1417
+ className: "flex min-h-0 flex-1 flex-col overflow-hidden",
1418
+ style: brandStyle,
1419
+ children: [
1420
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: `shrink-0 px-4 pb-3 pt-5 ${headerBg}`, children: [
1421
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "mb-3 flex items-center gap-3", children: [
1422
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "flex h-12 w-12 items-center justify-center rounded-full bg-white/20 text-lg font-bold", children: personaName.slice(0, 1).toUpperCase() }),
1423
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { children: [
1424
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "text-xs uppercase tracking-wide opacity-80", children: "Assistant" }),
1425
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h2", { className: "text-base font-semibold", children: personaName })
1426
+ ] })
1427
+ ] }),
1428
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "rounded-2xl rounded-tl-sm bg-white/15 px-3 py-2.5 text-sm leading-relaxed", children: heroLine })
1429
+ ] }),
1430
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex min-h-0 flex-1 flex-col overflow-y-auto bg-white px-4 py-4 text-gray-900 dark:bg-gray-950 dark:text-gray-100", children: [
1431
+ topics.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "mb-4 flex flex-wrap gap-2", children: topics.map(
1432
+ (topic) => topic.action === "external_url" && topic.externalUrl ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1433
+ "a",
1434
+ {
1435
+ href: topic.externalUrl,
1436
+ target: "_blank",
1437
+ rel: "noopener noreferrer",
1438
+ className: `rounded-full px-4 py-2 text-xs font-semibold ${brandBtnClass}`,
1439
+ children: topic.label
1440
+ },
1441
+ topic.id
1442
+ ) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1443
+ "button",
1444
+ {
1445
+ type: "button",
1446
+ onClick: () => setSelectedTopicId(
1447
+ selectedTopicId === topic.id ? null : topic.id
1448
+ ),
1449
+ className: `rounded-full px-4 py-2 text-xs font-semibold ${selectedTopicId === topic.id ? brandBtnClass : "border border-gray-300 text-gray-700 dark:border-gray-600 dark:text-gray-200"}`,
1450
+ children: topic.label
1451
+ },
1452
+ topic.id
1453
+ )
1454
+ ) }),
1455
+ needsGuestFields && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "mb-3 text-sm text-gray-600 dark:text-gray-300", children: labels.intro }),
1456
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(GuestContactFields, { args, compact: true }),
1457
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1458
+ "button",
1459
+ {
1460
+ type: "button",
1461
+ onClick: () => confirmGuestContact(labels.emailInvalid),
1462
+ className: `mt-4 w-full py-2.5 text-sm font-semibold ${brandBtnClass}`,
1463
+ children: labels.continueCta
1464
+ }
1465
+ ),
1466
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1467
+ PrivacyFooter,
1468
+ {
1469
+ privacyPolicyUrl: widgetConfig?.privacyPolicyUrl,
1470
+ className: "mt-4 text-gray-500 dark:text-gray-400"
1471
+ }
1472
+ )
1473
+ ] })
1474
+ ]
1475
+ }
1476
+ );
1477
+ }
1478
+
1479
+ // src/welcome-presets/team.tsx
1480
+ var import_jsx_runtime5 = require("react/jsx-runtime");
1481
+ function TeamWelcomePreset(args) {
1482
+ const {
1483
+ widgetConfig,
1484
+ title,
1485
+ welcomeMessage,
1486
+ brandBtnClass,
1487
+ brandStyle,
1488
+ topics,
1489
+ selectedTopicId,
1490
+ setSelectedTopicId,
1491
+ labels,
1492
+ needsGuestFields,
1493
+ confirmGuestContact
1494
+ } = args;
1495
+ const operators = widgetConfig?.operators ?? [];
1496
+ const headerBg = "bg-[var(--mailoo-chat-brand,#5b21b6)] text-[var(--mailoo-chat-brand-on,#ffffff)]";
1497
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1498
+ "div",
1499
+ {
1500
+ className: "flex min-h-0 flex-1 flex-col overflow-hidden",
1501
+ style: brandStyle,
1502
+ children: [
1503
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: `shrink-0 px-4 pb-4 pt-5 ${headerBg}`, children: [
1504
+ operators.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mb-3 flex -space-x-2", children: operators.slice(0, 5).map((op) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "relative", children: [
1505
+ op.image ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1506
+ "img",
1507
+ {
1508
+ src: op.image,
1509
+ alt: "",
1510
+ className: "h-9 w-9 rounded-full border-2 border-white/30 object-cover"
1511
+ }
1512
+ ) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "flex h-9 w-9 items-center justify-center rounded-full border-2 border-white/30 bg-white/20 text-xs font-semibold", children: operatorInitials(op.name) }),
1513
+ op.online && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1514
+ "span",
1515
+ {
1516
+ className: "absolute bottom-0 right-0 h-2.5 w-2.5 rounded-full border-2 border-[var(--mailoo-chat-brand,#5b21b6)] bg-emerald-400",
1517
+ "aria-hidden": true
1518
+ }
1519
+ )
1520
+ ] }, op.userId)) }),
1521
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h2", { className: "text-lg font-semibold leading-tight", children: title }),
1522
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "mt-1 text-sm opacity-90", children: welcomeMessage })
1523
+ ] }),
1524
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex min-h-0 flex-1 flex-col overflow-y-auto bg-white p-4 dark:bg-gray-900", children: [
1525
+ topics.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mb-4 grid grid-cols-2 gap-2", children: topics.map(
1526
+ (topic) => topic.action === "external_url" && topic.externalUrl ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1527
+ "a",
1528
+ {
1529
+ href: topic.externalUrl,
1530
+ target: "_blank",
1531
+ rel: "noopener noreferrer",
1532
+ className: "rounded-lg border border-gray-200 px-3 py-2.5 text-center text-xs font-medium text-gray-800 hover:border-[var(--mailoo-chat-brand,#5b21b6)] dark:border-gray-600 dark:text-gray-100",
1533
+ children: topic.label
1534
+ },
1535
+ topic.id
1536
+ ) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1537
+ "button",
1538
+ {
1539
+ type: "button",
1540
+ onClick: () => setSelectedTopicId(
1541
+ selectedTopicId === topic.id ? null : topic.id
1542
+ ),
1543
+ className: `rounded-lg border px-3 py-2.5 text-center text-xs font-medium ${selectedTopicId === topic.id ? `${brandBtnClass} border-transparent` : "border-gray-200 text-gray-800 hover:border-[var(--mailoo-chat-brand,#5b21b6)] dark:border-gray-600 dark:text-gray-100"}`,
1544
+ children: topic.label
1545
+ },
1546
+ topic.id
1547
+ )
1548
+ ) }),
1549
+ needsGuestFields && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "mb-3 text-sm text-gray-600 dark:text-gray-300", children: labels.intro }),
1550
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(GuestContactFields, { args, compact: true }),
1551
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1552
+ "button",
1553
+ {
1554
+ type: "button",
1555
+ onClick: () => confirmGuestContact(labels.emailInvalid),
1556
+ className: `mt-4 w-full py-2.5 text-sm font-semibold ${brandBtnClass}`,
1557
+ children: labels.continueCta
1558
+ }
1559
+ ),
1560
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1561
+ PrivacyFooter,
1562
+ {
1563
+ privacyPolicyUrl: widgetConfig?.privacyPolicyUrl,
1564
+ className: "mt-4"
1565
+ }
1566
+ )
1567
+ ] })
1568
+ ]
1569
+ }
1570
+ );
1571
+ }
1572
+
1573
+ // src/welcome-presets/preset-context.tsx
1574
+ var import_react5 = require("react");
1575
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1576
+ var MailooChatWelcomePresetContext = (0, import_react5.createContext)(null);
1577
+ function useOptionalMailooChatWelcomePreset() {
1578
+ return (0, import_react5.useContext)(MailooChatWelcomePresetContext);
1579
+ }
1580
+
1581
+ // src/welcome-presets/preset-switcher.tsx
1582
+ var import_jsx_runtime7 = require("react/jsx-runtime");
1583
+
1584
+ // src/welcome-presets/index.ts
1585
+ function renderMailooChatWelcomePreset(preset, args) {
1586
+ if (preset === "team") return TeamWelcomePreset(args);
1587
+ if (preset === "assistant") return AssistantWelcomePreset(args);
1588
+ return null;
1589
+ }
1590
+ function createMailooChatWelcomePresetRenderer(preset) {
1591
+ if (preset === "builtin") return void 0;
1592
+ return (args) => renderMailooChatWelcomePreset(preset, args);
1593
+ }
1594
+ function mailooChatWelcomePresetPanelClassName(preset) {
1595
+ if (preset === "team" || preset === "assistant") {
1596
+ return "border-0 shadow-2xl";
1597
+ }
1598
+ return void 0;
1599
+ }
1600
+
1601
+ // src/components/mailoo-site-chat-shell.tsx
1602
+ var import_jsx_runtime8 = require("react/jsx-runtime");
1603
+ var MAILOO_SITE_CHAT_OPEN_EVENT = "mailoo-site-chat:open";
1604
+ function MailooSiteChatShell({
1605
+ chat,
1606
+ unavailableAction,
1607
+ renderUnavailable,
1608
+ onError,
1609
+ onFault
1610
+ }) {
1611
+ const welcomePreset = useOptionalMailooChatWelcomePreset();
1612
+ const [open, setOpen] = (0, import_react6.useState)(false);
1613
+ const openChat = (0, import_react6.useCallback)(() => setOpen(true), []);
1614
+ (0, import_react6.useEffect)(() => {
1615
+ const onOpen = () => openChat();
1616
+ window.addEventListener(MAILOO_SITE_CHAT_OPEN_EVENT, onOpen);
1617
+ return () => {
1618
+ window.removeEventListener(MAILOO_SITE_CHAT_OPEN_EVENT, onOpen);
1619
+ };
1620
+ }, [openChat]);
1621
+ const renderWelcome = (0, import_react6.useMemo)(
1622
+ () => welcomePreset ? createMailooChatWelcomePresetRenderer(welcomePreset.preset) : void 0,
1623
+ [welcomePreset]
1624
+ );
1625
+ const panelClassName = welcomePreset ? mailooChatWelcomePresetPanelClassName(welcomePreset.preset) : void 0;
1626
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1627
+ MailooSiteChatWidget,
1628
+ {
1629
+ apiOrigin: chat.apiOrigin,
1630
+ projectUid: chat.projectUid,
1631
+ integrationId: chat.integrationId,
1632
+ welcomeMessage: chat.welcomeMessage,
1633
+ locale: chat.locale,
1634
+ isAuthenticated: chat.isAuthenticated,
1635
+ widgetConfig: chat.widgetConfig,
1636
+ open,
1637
+ onOpenChange: setOpen,
1638
+ renderWelcome,
1639
+ panelClassName,
1640
+ unavailableAction,
1641
+ renderUnavailable,
1642
+ onError,
1643
+ onFault
1644
+ }
1645
+ );
1646
+ }
1647
+ function openMailooSiteChat() {
1648
+ window.dispatchEvent(new Event(MAILOO_SITE_CHAT_OPEN_EVENT));
1649
+ }
1650
+ // Annotate the CommonJS export names for ESM import in node:
1651
+ 0 && (module.exports = {
1652
+ MAILOO_SITE_CHAT_OPEN_EVENT,
1653
+ MailooChatUnavailablePanel,
1654
+ MailooSiteChatShell,
1655
+ MailooSiteChatWidget,
1656
+ openMailooSiteChat
1657
+ });
1658
+ //# sourceMappingURL=index.cjs.map