@reqdesk/widget 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/dist/index.js ADDED
@@ -0,0 +1,912 @@
1
+ import { _ as submitTrackingReply, a as saveTrackingToken, b as configureWidgetClient, c as getWidgetStyles, d as en, g as submitTicket, l as themeToVars, n as getTrackingTokens, o as saveWidgetConfig, r as loadWidgetConfig, u as ar, v as trackTicket } from "./storage-CC5BCsxP.js";
2
+ //#region src/shadow-dom.ts
3
+ let shadowRoot = null;
4
+ let hostEl = null;
5
+ function mountWidget(config) {
6
+ if (shadowRoot && hostEl) {
7
+ const mount = shadowRoot.querySelector(".rqd-root");
8
+ return {
9
+ shadow: shadowRoot,
10
+ host: hostEl,
11
+ mount
12
+ };
13
+ }
14
+ hostEl = document.createElement("div");
15
+ hostEl.id = "reqdesk-widget";
16
+ hostEl.style.position = "fixed";
17
+ hostEl.style.zIndex = String(config.zIndex ?? 9999);
18
+ hostEl.style.inset = "0";
19
+ hostEl.style.pointerEvents = "none";
20
+ document.body.appendChild(hostEl);
21
+ shadowRoot = hostEl.attachShadow({ mode: "open" });
22
+ const style = document.createElement("style");
23
+ style.textContent = getWidgetStyles();
24
+ shadowRoot.appendChild(style);
25
+ const mount = document.createElement("div");
26
+ mount.className = "rqd-root";
27
+ shadowRoot.appendChild(mount);
28
+ return {
29
+ shadow: shadowRoot,
30
+ host: hostEl,
31
+ mount
32
+ };
33
+ }
34
+ function unmountWidget() {
35
+ if (hostEl) {
36
+ hostEl.remove();
37
+ hostEl = null;
38
+ shadowRoot = null;
39
+ }
40
+ }
41
+ function getShadowRoot() {
42
+ return shadowRoot;
43
+ }
44
+ //#endregion
45
+ //#region src/ui/fab.ts
46
+ function createSvg(path) {
47
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
48
+ svg.setAttribute("viewBox", "0 0 24 24");
49
+ const p = document.createElementNS("http://www.w3.org/2000/svg", "path");
50
+ p.setAttribute("d", path);
51
+ svg.appendChild(p);
52
+ return svg;
53
+ }
54
+ const CHAT_PATH = "M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z";
55
+ const CLOSE_PATH = "M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z";
56
+ function createFab(position, onClick) {
57
+ const btn = document.createElement("button");
58
+ btn.className = `rqd-fab rqd-${position}`;
59
+ btn.appendChild(createSvg(CHAT_PATH));
60
+ btn.setAttribute("aria-label", "Open support widget");
61
+ btn.addEventListener("click", onClick);
62
+ return btn;
63
+ }
64
+ function setFabOpen(fab, isOpen) {
65
+ while (fab.firstChild) fab.removeChild(fab.firstChild);
66
+ fab.appendChild(createSvg(isOpen ? CLOSE_PATH : CHAT_PATH));
67
+ fab.setAttribute("aria-label", isOpen ? "Close support widget" : "Open support widget");
68
+ }
69
+ //#endregion
70
+ //#region src/ui/ticket-form.ts
71
+ function createTicketForm(projectId, t, onSuccess, onError) {
72
+ const form = document.createElement("form");
73
+ form.className = "rqd-form";
74
+ form.setAttribute("novalidate", "");
75
+ const fields = {};
76
+ const errors = {};
77
+ function addField(name, type, label, opts) {
78
+ const group = document.createElement("div");
79
+ group.className = "rqd-form-group";
80
+ const lbl = document.createElement("label");
81
+ lbl.className = "rqd-label";
82
+ lbl.textContent = label;
83
+ group.appendChild(lbl);
84
+ let el;
85
+ if (type === "textarea") {
86
+ el = document.createElement("textarea");
87
+ el.className = "rqd-textarea";
88
+ } else if (type === "select") {
89
+ el = document.createElement("select");
90
+ el.className = "rqd-select";
91
+ if (opts?.options) for (const opt of opts.options) {
92
+ const option = document.createElement("option");
93
+ option.value = opt.value;
94
+ option.textContent = opt.label;
95
+ el.appendChild(option);
96
+ }
97
+ } else {
98
+ el = document.createElement("input");
99
+ el.className = "rqd-input";
100
+ el.type = name === "email" ? "email" : "text";
101
+ }
102
+ if (opts?.placeholder && "placeholder" in el) el.placeholder = opts.placeholder;
103
+ if (opts?.required) el.required = true;
104
+ el.name = name;
105
+ group.appendChild(el);
106
+ const err = document.createElement("div");
107
+ err.className = "rqd-error-text";
108
+ err.style.display = "none";
109
+ group.appendChild(err);
110
+ fields[name] = el;
111
+ errors[name] = err;
112
+ form.appendChild(group);
113
+ }
114
+ let hasContent = false;
115
+ function onBeforeUnload(e) {
116
+ if (hasContent) e.preventDefault();
117
+ }
118
+ window.addEventListener("beforeunload", onBeforeUnload);
119
+ form.addEventListener("input", () => {
120
+ hasContent = Object.values(fields).some((f) => f.value.trim().length > 0);
121
+ });
122
+ addField("title", "input", t("form.title"), {
123
+ placeholder: t("form.titlePlaceholder"),
124
+ required: true
125
+ });
126
+ addField("description", "textarea", t("form.description"), { placeholder: t("form.descriptionPlaceholder") });
127
+ addField("email", "input", t("form.email"), {
128
+ placeholder: t("form.emailPlaceholder"),
129
+ required: true
130
+ });
131
+ addField("priority", "select", t("form.priority"), { options: [
132
+ {
133
+ value: "medium",
134
+ label: t("form.priorityMedium")
135
+ },
136
+ {
137
+ value: "low",
138
+ label: t("form.priorityLow")
139
+ },
140
+ {
141
+ value: "high",
142
+ label: t("form.priorityHigh")
143
+ },
144
+ {
145
+ value: "urgent",
146
+ label: t("form.priorityUrgent")
147
+ }
148
+ ] });
149
+ const submitBtn = document.createElement("button");
150
+ submitBtn.type = "submit";
151
+ submitBtn.className = "rqd-btn rqd-btn-primary";
152
+ submitBtn.textContent = t("form.submit");
153
+ form.appendChild(submitBtn);
154
+ function validate() {
155
+ let valid = true;
156
+ const title = fields.title.value.trim();
157
+ const email = fields.email.value.trim();
158
+ for (const err of Object.values(errors)) {
159
+ err.style.display = "none";
160
+ err.textContent = "";
161
+ }
162
+ if (!title) {
163
+ errors.title.textContent = t("error.required");
164
+ errors.title.style.display = "block";
165
+ valid = false;
166
+ } else if (title.length < 5) {
167
+ errors.title.textContent = t("error.titleMin");
168
+ errors.title.style.display = "block";
169
+ valid = false;
170
+ }
171
+ if (!email) {
172
+ errors.email.textContent = t("error.required");
173
+ errors.email.style.display = "block";
174
+ valid = false;
175
+ } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
176
+ errors.email.textContent = t("error.emailInvalid");
177
+ errors.email.style.display = "block";
178
+ valid = false;
179
+ }
180
+ return valid;
181
+ }
182
+ form.addEventListener("submit", async (e) => {
183
+ e.preventDefault();
184
+ if (!validate()) return;
185
+ submitBtn.disabled = true;
186
+ submitBtn.textContent = t("form.submitting");
187
+ const data = {
188
+ title: fields.title.value.trim(),
189
+ description: fields.description.value.trim() || void 0,
190
+ email: fields.email.value.trim(),
191
+ priority: fields.priority.value
192
+ };
193
+ try {
194
+ const result = await submitTicket(projectId, data);
195
+ if (result.trackingToken) saveTrackingToken(projectId, result.trackingToken);
196
+ hasContent = false;
197
+ window.removeEventListener("beforeunload", onBeforeUnload);
198
+ onSuccess(result);
199
+ } catch (err) {
200
+ onError(err);
201
+ submitBtn.disabled = false;
202
+ submitBtn.textContent = t("form.submit");
203
+ }
204
+ });
205
+ return form;
206
+ }
207
+ function createSuccessView(result, t, onClose, onTrack) {
208
+ const div = document.createElement("div");
209
+ div.className = "rqd-success";
210
+ const icon = document.createElement("div");
211
+ icon.className = "rqd-success-icon";
212
+ icon.textContent = "✅";
213
+ div.appendChild(icon);
214
+ const heading = document.createElement("h3");
215
+ heading.textContent = t("success.title");
216
+ div.appendChild(heading);
217
+ const num = document.createElement("p");
218
+ num.textContent = t("success.ticketNumber") + result.ticketNumber;
219
+ div.appendChild(num);
220
+ if (result.trackingToken) {
221
+ const hint = document.createElement("p");
222
+ hint.textContent = t("success.trackingHint");
223
+ hint.style.fontSize = "13px";
224
+ hint.style.color = "var(--rqd-text-secondary)";
225
+ div.appendChild(hint);
226
+ const tokenBox = document.createElement("div");
227
+ tokenBox.className = "rqd-token-box";
228
+ tokenBox.textContent = result.trackingToken;
229
+ div.appendChild(tokenBox);
230
+ const copyBtn = document.createElement("button");
231
+ copyBtn.className = "rqd-btn rqd-btn-secondary";
232
+ copyBtn.textContent = t("success.copyToken");
233
+ copyBtn.style.marginBottom = "8px";
234
+ copyBtn.addEventListener("click", () => {
235
+ navigator.clipboard.writeText(result.trackingToken).then(() => {
236
+ copyBtn.textContent = t("success.copied");
237
+ setTimeout(() => {
238
+ copyBtn.textContent = t("success.copyToken");
239
+ }, 2e3);
240
+ });
241
+ });
242
+ div.appendChild(copyBtn);
243
+ if (onTrack) {
244
+ const trackBtn = document.createElement("button");
245
+ trackBtn.className = "rqd-btn rqd-btn-primary";
246
+ trackBtn.textContent = t("success.trackNow");
247
+ trackBtn.style.marginTop = "4px";
248
+ trackBtn.addEventListener("click", onTrack);
249
+ div.appendChild(trackBtn);
250
+ }
251
+ }
252
+ const closeBtn = document.createElement("button");
253
+ closeBtn.className = "rqd-btn rqd-btn-secondary";
254
+ closeBtn.textContent = t("success.close");
255
+ closeBtn.style.marginTop = "8px";
256
+ closeBtn.addEventListener("click", onClose);
257
+ div.appendChild(closeBtn);
258
+ return div;
259
+ }
260
+ //#endregion
261
+ //#region src/ui/tracker.ts
262
+ function createTrackerView(t, onTracked, onReplySent, onError, onBack, prefillToken) {
263
+ const container = document.createElement("div");
264
+ container.className = "rqd-tracker";
265
+ if (prefillToken) {
266
+ renderLoading(container, t);
267
+ doTrack(prefillToken, container, t, onTracked, onReplySent, onError, onBack);
268
+ } else renderTokenInput(container, t, onTracked, onReplySent, onError, onBack);
269
+ return container;
270
+ }
271
+ function renderTokenInput(container, t, onTracked, onReplySent, onError, onBack) {
272
+ while (container.firstChild) container.removeChild(container.firstChild);
273
+ const group = document.createElement("div");
274
+ group.className = "rqd-form-group";
275
+ const label = document.createElement("label");
276
+ label.className = "rqd-label";
277
+ label.textContent = t("tracker.title");
278
+ group.appendChild(label);
279
+ const input = document.createElement("input");
280
+ input.className = "rqd-input";
281
+ input.placeholder = t("tracker.tokenPlaceholder");
282
+ input.type = "text";
283
+ group.appendChild(input);
284
+ const err = document.createElement("div");
285
+ err.className = "rqd-error-text";
286
+ err.style.display = "none";
287
+ group.appendChild(err);
288
+ container.appendChild(group);
289
+ const btn = document.createElement("button");
290
+ btn.className = "rqd-btn rqd-btn-primary";
291
+ btn.textContent = t("tracker.submit");
292
+ btn.addEventListener("click", () => {
293
+ const token = input.value.trim();
294
+ if (!token) {
295
+ err.textContent = t("error.required");
296
+ err.style.display = "block";
297
+ return;
298
+ }
299
+ err.style.display = "none";
300
+ btn.disabled = true;
301
+ btn.textContent = t("tracker.tracking");
302
+ doTrack(token, container, t, onTracked, onReplySent, onError, onBack);
303
+ });
304
+ container.appendChild(btn);
305
+ }
306
+ function renderLoading(container, t) {
307
+ while (container.firstChild) container.removeChild(container.firstChild);
308
+ const p = document.createElement("p");
309
+ p.textContent = t("tracker.tracking");
310
+ p.style.textAlign = "center";
311
+ p.style.color = "var(--rqd-text-secondary)";
312
+ container.appendChild(p);
313
+ }
314
+ async function doTrack(token, container, t, onTracked, onReplySent, onError, onBack) {
315
+ try {
316
+ const result = await trackTicket(token);
317
+ onTracked(result);
318
+ renderTicketDetail(container, token, result, t, onReplySent, onError, onBack);
319
+ } catch (err) {
320
+ onError(err);
321
+ renderTokenInput(container, t, onTracked, onReplySent, onError, onBack);
322
+ }
323
+ }
324
+ function renderTicketDetail(container, token, ticket, t, onReplySent, onError, onBack) {
325
+ while (container.firstChild) container.removeChild(container.firstChild);
326
+ if (onBack) {
327
+ const backBtn = document.createElement("button");
328
+ backBtn.className = "rqd-btn rqd-btn-secondary";
329
+ backBtn.textContent = t("tracker.back");
330
+ backBtn.style.marginBottom = "12px";
331
+ backBtn.addEventListener("click", onBack);
332
+ container.appendChild(backBtn);
333
+ }
334
+ const info = document.createElement("div");
335
+ info.className = "rqd-ticket-info";
336
+ const titleRow = document.createElement("div");
337
+ titleRow.style.fontWeight = "600";
338
+ titleRow.style.marginBottom = "8px";
339
+ titleRow.textContent = `${ticket.ticketNumber} — ${ticket.title}`;
340
+ info.appendChild(titleRow);
341
+ for (const [label, value] of [
342
+ [t("tracker.status"), ticket.status],
343
+ [t("tracker.priority"), ticket.priority],
344
+ [t("tracker.created"), new Date(ticket.createdAt).toLocaleDateString()]
345
+ ]) {
346
+ const row = document.createElement("div");
347
+ row.className = "rqd-ticket-row";
348
+ const lbl = document.createElement("span");
349
+ lbl.className = "rqd-ticket-label";
350
+ lbl.textContent = label;
351
+ row.appendChild(lbl);
352
+ const badge = document.createElement("span");
353
+ badge.className = "rqd-badge";
354
+ badge.textContent = value;
355
+ row.appendChild(badge);
356
+ info.appendChild(row);
357
+ }
358
+ container.appendChild(info);
359
+ const repliesLabel = document.createElement("div");
360
+ repliesLabel.className = "rqd-label";
361
+ repliesLabel.textContent = t("tracker.replies");
362
+ repliesLabel.style.marginBottom = "8px";
363
+ container.appendChild(repliesLabel);
364
+ if (ticket.replies.length === 0) {
365
+ const noReplies = document.createElement("p");
366
+ noReplies.textContent = t("tracker.noReplies");
367
+ noReplies.style.color = "var(--rqd-text-secondary)";
368
+ noReplies.style.fontSize = "13px";
369
+ container.appendChild(noReplies);
370
+ } else for (const reply of ticket.replies) {
371
+ const replyEl = document.createElement("div");
372
+ replyEl.className = "rqd-reply";
373
+ const header = document.createElement("div");
374
+ header.className = "rqd-reply-header";
375
+ const authorSpan = document.createElement("span");
376
+ authorSpan.textContent = reply.authorName;
377
+ if (reply.isStaff) authorSpan.className = "rqd-reply-staff";
378
+ header.appendChild(authorSpan);
379
+ const dateSpan = document.createElement("span");
380
+ dateSpan.textContent = new Date(reply.createdAt).toLocaleString();
381
+ header.appendChild(dateSpan);
382
+ replyEl.appendChild(header);
383
+ const body = document.createElement("div");
384
+ body.className = "rqd-reply-body";
385
+ body.textContent = reply.body;
386
+ replyEl.appendChild(body);
387
+ container.appendChild(replyEl);
388
+ }
389
+ const replyGroup = document.createElement("div");
390
+ replyGroup.className = "rqd-form-group";
391
+ replyGroup.style.marginTop = "12px";
392
+ const textarea = document.createElement("textarea");
393
+ textarea.className = "rqd-textarea";
394
+ textarea.placeholder = t("tracker.replyPlaceholder");
395
+ textarea.rows = 3;
396
+ replyGroup.appendChild(textarea);
397
+ container.appendChild(replyGroup);
398
+ const sendBtn = document.createElement("button");
399
+ sendBtn.className = "rqd-btn rqd-btn-primary";
400
+ sendBtn.textContent = t("tracker.sendReply");
401
+ sendBtn.addEventListener("click", async () => {
402
+ const body = textarea.value.trim();
403
+ if (!body) return;
404
+ sendBtn.disabled = true;
405
+ sendBtn.textContent = t("tracker.sending");
406
+ try {
407
+ await submitTrackingReply(token, body);
408
+ onReplySent({
409
+ token,
410
+ body
411
+ });
412
+ renderTicketDetail(container, token, await trackTicket(token), t, onReplySent, onError, onBack);
413
+ } catch (err) {
414
+ onError(err);
415
+ sendBtn.disabled = false;
416
+ sendBtn.textContent = t("tracker.sendReply");
417
+ }
418
+ });
419
+ container.appendChild(sendBtn);
420
+ }
421
+ //#endregion
422
+ //#region src/ui/portal.ts
423
+ function createPortalView(t, fetchTickets, fetchTicketDetail, submitReply, projectId, onTicketCreated, onError, pollingInterval) {
424
+ const container = document.createElement("div");
425
+ container.className = "rqd-portal";
426
+ let currentView = "list";
427
+ let pollTimer;
428
+ function renderList() {
429
+ currentView = "list";
430
+ while (container.firstChild) container.removeChild(container.firstChild);
431
+ const header = document.createElement("div");
432
+ header.style.display = "flex";
433
+ header.style.justifyContent = "space-between";
434
+ header.style.alignItems = "center";
435
+ header.style.marginBottom = "12px";
436
+ const title = document.createElement("span");
437
+ title.style.fontWeight = "600";
438
+ title.textContent = t("portal.myTickets");
439
+ header.appendChild(title);
440
+ const newBtn = document.createElement("button");
441
+ newBtn.className = "rqd-btn rqd-btn-primary";
442
+ newBtn.style.width = "auto";
443
+ newBtn.style.padding = "6px 14px";
444
+ newBtn.style.fontSize = "13px";
445
+ newBtn.textContent = t("portal.newTicket");
446
+ newBtn.addEventListener("click", renderNewTicketForm);
447
+ header.appendChild(newBtn);
448
+ container.appendChild(header);
449
+ const loading = document.createElement("p");
450
+ loading.textContent = "...";
451
+ loading.style.textAlign = "center";
452
+ loading.style.color = "var(--rqd-text-secondary)";
453
+ container.appendChild(loading);
454
+ fetchTickets().then((tickets) => {
455
+ loading.remove();
456
+ if (tickets.length === 0) {
457
+ const empty = document.createElement("p");
458
+ empty.textContent = t("portal.noTickets");
459
+ empty.style.textAlign = "center";
460
+ empty.style.color = "var(--rqd-text-secondary)";
461
+ empty.style.padding = "24px 0";
462
+ container.appendChild(empty);
463
+ return;
464
+ }
465
+ for (const ticket of tickets) {
466
+ const row = document.createElement("div");
467
+ row.className = "rqd-ticket-info";
468
+ row.style.cursor = "pointer";
469
+ row.style.marginBottom = "8px";
470
+ const top = document.createElement("div");
471
+ top.style.display = "flex";
472
+ top.style.justifyContent = "space-between";
473
+ top.style.marginBottom = "4px";
474
+ const num = document.createElement("span");
475
+ num.style.fontWeight = "600";
476
+ num.style.fontSize = "13px";
477
+ num.textContent = ticket.ticketNumber;
478
+ top.appendChild(num);
479
+ const badge = document.createElement("span");
480
+ badge.className = "rqd-badge";
481
+ badge.textContent = ticket.status;
482
+ top.appendChild(badge);
483
+ row.appendChild(top);
484
+ const titleEl = document.createElement("div");
485
+ titleEl.style.fontSize = "14px";
486
+ titleEl.textContent = ticket.title;
487
+ row.appendChild(titleEl);
488
+ const date = document.createElement("div");
489
+ date.style.fontSize = "12px";
490
+ date.style.color = "var(--rqd-text-secondary)";
491
+ date.style.marginTop = "4px";
492
+ date.textContent = new Date(ticket.createdAt).toLocaleDateString();
493
+ row.appendChild(date);
494
+ row.addEventListener("click", () => renderDetail(ticket.id));
495
+ container.appendChild(row);
496
+ }
497
+ }).catch((err) => {
498
+ loading.remove();
499
+ onError(err);
500
+ });
501
+ if (pollingInterval && pollingInterval > 0) {
502
+ stopPolling();
503
+ pollTimer = setInterval(() => {
504
+ if (currentView === "list") renderList();
505
+ }, pollingInterval);
506
+ }
507
+ }
508
+ function renderDetail(ticketId) {
509
+ currentView = "detail";
510
+ stopPolling();
511
+ while (container.firstChild) container.removeChild(container.firstChild);
512
+ const backBtn = document.createElement("button");
513
+ backBtn.className = "rqd-btn rqd-btn-secondary";
514
+ backBtn.textContent = t("tracker.back");
515
+ backBtn.style.marginBottom = "12px";
516
+ backBtn.addEventListener("click", renderList);
517
+ container.appendChild(backBtn);
518
+ const loading = document.createElement("p");
519
+ loading.textContent = "...";
520
+ loading.style.textAlign = "center";
521
+ container.appendChild(loading);
522
+ fetchTicketDetail(ticketId).then((ticket) => {
523
+ loading.remove();
524
+ const info = document.createElement("div");
525
+ info.className = "rqd-ticket-info";
526
+ const titleRow = document.createElement("div");
527
+ titleRow.style.fontWeight = "600";
528
+ titleRow.style.marginBottom = "8px";
529
+ titleRow.textContent = `${ticket.ticketNumber} — ${ticket.title}`;
530
+ info.appendChild(titleRow);
531
+ for (const [label, value] of [[t("tracker.status"), ticket.status], [t("tracker.priority"), ticket.priority]]) {
532
+ const row = document.createElement("div");
533
+ row.className = "rqd-ticket-row";
534
+ const lbl = document.createElement("span");
535
+ lbl.className = "rqd-ticket-label";
536
+ lbl.textContent = label;
537
+ row.appendChild(lbl);
538
+ const badge = document.createElement("span");
539
+ badge.className = "rqd-badge";
540
+ badge.textContent = value;
541
+ row.appendChild(badge);
542
+ info.appendChild(row);
543
+ }
544
+ container.appendChild(info);
545
+ const repliesLabel = document.createElement("div");
546
+ repliesLabel.className = "rqd-label";
547
+ repliesLabel.textContent = t("tracker.replies");
548
+ repliesLabel.style.marginBottom = "8px";
549
+ container.appendChild(repliesLabel);
550
+ if (ticket.replies.length === 0) {
551
+ const noReplies = document.createElement("p");
552
+ noReplies.textContent = t("tracker.noReplies");
553
+ noReplies.style.color = "var(--rqd-text-secondary)";
554
+ noReplies.style.fontSize = "13px";
555
+ container.appendChild(noReplies);
556
+ } else for (const reply of ticket.replies) {
557
+ const replyEl = document.createElement("div");
558
+ replyEl.className = "rqd-reply";
559
+ const header = document.createElement("div");
560
+ header.className = "rqd-reply-header";
561
+ const authorSpan = document.createElement("span");
562
+ authorSpan.textContent = reply.authorName;
563
+ if (reply.isStaff) authorSpan.className = "rqd-reply-staff";
564
+ header.appendChild(authorSpan);
565
+ const dateSpan = document.createElement("span");
566
+ dateSpan.textContent = new Date(reply.createdAt).toLocaleString();
567
+ header.appendChild(dateSpan);
568
+ replyEl.appendChild(header);
569
+ const body = document.createElement("div");
570
+ body.className = "rqd-reply-body";
571
+ body.textContent = reply.body;
572
+ replyEl.appendChild(body);
573
+ container.appendChild(replyEl);
574
+ }
575
+ const replyGroup = document.createElement("div");
576
+ replyGroup.className = "rqd-form-group";
577
+ replyGroup.style.marginTop = "12px";
578
+ const textarea = document.createElement("textarea");
579
+ textarea.className = "rqd-textarea";
580
+ textarea.placeholder = t("tracker.replyPlaceholder");
581
+ textarea.rows = 3;
582
+ replyGroup.appendChild(textarea);
583
+ container.appendChild(replyGroup);
584
+ const sendBtn = document.createElement("button");
585
+ sendBtn.className = "rqd-btn rqd-btn-primary";
586
+ sendBtn.textContent = t("tracker.sendReply");
587
+ sendBtn.addEventListener("click", async () => {
588
+ const body = textarea.value.trim();
589
+ if (!body) return;
590
+ sendBtn.disabled = true;
591
+ sendBtn.textContent = t("tracker.sending");
592
+ try {
593
+ await submitReply(ticketId, body);
594
+ renderDetail(ticketId);
595
+ } catch (err) {
596
+ onError(err);
597
+ sendBtn.disabled = false;
598
+ sendBtn.textContent = t("tracker.sendReply");
599
+ }
600
+ });
601
+ container.appendChild(sendBtn);
602
+ }).catch((err) => {
603
+ loading.remove();
604
+ onError(err);
605
+ });
606
+ }
607
+ function renderNewTicketForm() {
608
+ currentView = "new";
609
+ stopPolling();
610
+ while (container.firstChild) container.removeChild(container.firstChild);
611
+ const backBtn = document.createElement("button");
612
+ backBtn.className = "rqd-btn rqd-btn-secondary";
613
+ backBtn.textContent = t("tracker.back");
614
+ backBtn.style.marginBottom = "12px";
615
+ backBtn.addEventListener("click", renderList);
616
+ container.appendChild(backBtn);
617
+ const fields = {};
618
+ function addField(name, type, label, placeholder) {
619
+ const group = document.createElement("div");
620
+ group.className = "rqd-form-group";
621
+ const lbl = document.createElement("label");
622
+ lbl.className = "rqd-label";
623
+ lbl.textContent = label;
624
+ group.appendChild(lbl);
625
+ const el = type === "textarea" ? document.createElement("textarea") : document.createElement("input");
626
+ el.className = type === "textarea" ? "rqd-textarea" : "rqd-input";
627
+ el.placeholder = placeholder;
628
+ el.name = name;
629
+ group.appendChild(el);
630
+ fields[name] = el;
631
+ container.appendChild(group);
632
+ }
633
+ addField("title", "input", t("form.title"), t("form.titlePlaceholder"));
634
+ addField("description", "textarea", t("form.description"), t("form.descriptionPlaceholder"));
635
+ const submitBtn = document.createElement("button");
636
+ submitBtn.className = "rqd-btn rqd-btn-primary";
637
+ submitBtn.textContent = t("form.submit");
638
+ submitBtn.addEventListener("click", async () => {
639
+ const title = fields.title.value.trim();
640
+ if (!title || title.length < 5) return;
641
+ submitBtn.disabled = true;
642
+ submitBtn.textContent = t("form.submitting");
643
+ const data = {
644
+ title,
645
+ description: fields.description.value.trim() || void 0,
646
+ email: "",
647
+ priority: "medium"
648
+ };
649
+ try {
650
+ onTicketCreated(await submitTicket(projectId, data));
651
+ renderList();
652
+ } catch (err) {
653
+ onError(err);
654
+ submitBtn.disabled = false;
655
+ submitBtn.textContent = t("form.submit");
656
+ }
657
+ });
658
+ container.appendChild(submitBtn);
659
+ }
660
+ function stopPolling() {
661
+ if (pollTimer) {
662
+ clearInterval(pollTimer);
663
+ pollTimer = void 0;
664
+ }
665
+ }
666
+ renderList();
667
+ return container;
668
+ }
669
+ //#endregion
670
+ //#region src/index.ts
671
+ const translations = {
672
+ en,
673
+ ar
674
+ };
675
+ const listeners = {};
676
+ let config = null;
677
+ let isOpen = false;
678
+ let currentLang = "en";
679
+ let fab = null;
680
+ let panel = null;
681
+ let mount = null;
682
+ let currentTab = "form";
683
+ let pendingTrackToken;
684
+ function t(key) {
685
+ const overrides = config?.translations;
686
+ if (overrides?.[key]) return overrides[key];
687
+ return (translations[currentLang] ?? translations.en)[key] ?? key;
688
+ }
689
+ function emit(event, data) {
690
+ for (const cb of listeners[event] ?? []) try {
691
+ cb(data);
692
+ } catch {}
693
+ }
694
+ function deriveApiUrl() {
695
+ return window.location.origin;
696
+ }
697
+ function extractProjectId() {
698
+ return "_current";
699
+ }
700
+ function renderPanel() {
701
+ if (!mount || !config) return;
702
+ while (mount.firstChild) mount.removeChild(mount.firstChild);
703
+ const position = config.position ?? "bottom-right";
704
+ if (!config.inline) {
705
+ panel = document.createElement("div");
706
+ panel.className = `rqd-panel rqd-${position}`;
707
+ panel.style.cssText = themeToVars(config.theme);
708
+ const header = document.createElement("div");
709
+ header.className = "rqd-header";
710
+ const title = document.createElement("span");
711
+ title.className = "rqd-header-title";
712
+ title.textContent = t("widget.title");
713
+ header.appendChild(title);
714
+ const closeBtn = document.createElement("button");
715
+ closeBtn.className = "rqd-header-close";
716
+ closeBtn.textContent = "✕";
717
+ closeBtn.addEventListener("click", close);
718
+ header.appendChild(closeBtn);
719
+ panel.appendChild(header);
720
+ const body = document.createElement("div");
721
+ body.className = "rqd-body";
722
+ if (config.widget === "support-portal" && config.customer?.email) renderPortal(body);
723
+ else {
724
+ const tabs = document.createElement("div");
725
+ tabs.className = "rqd-tabs";
726
+ const formTab = document.createElement("button");
727
+ formTab.className = `rqd-tab${currentTab === "form" ? " rqd-active" : ""}`;
728
+ formTab.textContent = t("widget.newTicket");
729
+ formTab.addEventListener("click", () => switchTab("form", body, tabs));
730
+ const trackerTab = document.createElement("button");
731
+ trackerTab.className = `rqd-tab${currentTab === "tracker" ? " rqd-active" : ""}`;
732
+ trackerTab.textContent = t("widget.trackTicket");
733
+ trackerTab.addEventListener("click", () => switchTab("tracker", body, tabs));
734
+ tabs.appendChild(formTab);
735
+ tabs.appendChild(trackerTab);
736
+ panel.appendChild(tabs);
737
+ if (currentTab === "form") renderForm(body);
738
+ else renderTracker(body);
739
+ }
740
+ panel.appendChild(body);
741
+ mount.appendChild(panel);
742
+ } else {
743
+ const container = typeof config.container === "string" ? document.querySelector(config.container) : config.container;
744
+ if (container) {
745
+ const inline = document.createElement("div");
746
+ inline.className = "rqd-inline";
747
+ inline.style.cssText = themeToVars(config.theme);
748
+ const body = document.createElement("div");
749
+ body.className = "rqd-body";
750
+ renderForm(body);
751
+ inline.appendChild(body);
752
+ container.appendChild(inline);
753
+ return;
754
+ }
755
+ }
756
+ }
757
+ function renderPortal(container) {
758
+ if (!config) return;
759
+ const portalView = createPortalView(t, async () => {
760
+ return [];
761
+ }, async (id) => {
762
+ return {
763
+ id,
764
+ ticketNumber: "",
765
+ title: "",
766
+ status: "",
767
+ priority: "",
768
+ createdAt: "",
769
+ replies: []
770
+ };
771
+ }, async (_ticketId, _body) => {}, extractProjectId(), (data) => emit("ticket:created", data), (err) => emit("error", err), 3e4);
772
+ container.appendChild(portalView);
773
+ }
774
+ function switchTab(tab, body, tabs) {
775
+ currentTab = tab;
776
+ pendingTrackToken = void 0;
777
+ tabs.querySelectorAll(".rqd-tab").forEach((btn, i) => {
778
+ btn.classList.toggle("rqd-active", i === 0 && tab === "form" || i === 1 && tab === "tracker");
779
+ });
780
+ while (body.firstChild) body.removeChild(body.firstChild);
781
+ if (tab === "form") renderForm(body);
782
+ else renderTracker(body);
783
+ }
784
+ function renderForm(container) {
785
+ if (!config) return;
786
+ const form = createTicketForm(extractProjectId(), t, (result) => {
787
+ emit("ticket:created", result);
788
+ renderSuccess(container, result);
789
+ }, (err) => {
790
+ emit("error", err);
791
+ });
792
+ container.appendChild(form);
793
+ }
794
+ function renderTracker(container) {
795
+ const view = createTrackerView(t, (data) => emit("ticket:tracked", data), (data) => emit("reply:sent", data), (err) => emit("error", err), void 0, pendingTrackToken);
796
+ container.appendChild(view);
797
+ }
798
+ function renderSuccess(container, result) {
799
+ while (container.firstChild) container.removeChild(container.firstChild);
800
+ const view = createSuccessView(result, t, () => close(), result.trackingToken ? () => {
801
+ pendingTrackToken = result.trackingToken;
802
+ if (panel) {
803
+ const body = panel.querySelector(".rqd-body");
804
+ const tabs = panel.querySelector(".rqd-tabs");
805
+ if (body && tabs) switchTab("tracker", body, tabs);
806
+ }
807
+ } : void 0);
808
+ container.appendChild(view);
809
+ }
810
+ function init(cfg) {
811
+ if (config) return;
812
+ const saved = loadWidgetConfig(cfg.apiKey);
813
+ config = {
814
+ ...cfg,
815
+ position: cfg.position ?? saved?.position ?? "bottom-right",
816
+ language: cfg.language ?? saved?.language ?? "en",
817
+ widget: cfg.widget ?? saved?.widget ?? "ticket-form",
818
+ theme: {
819
+ ...saved?.theme,
820
+ ...cfg.theme
821
+ }
822
+ };
823
+ currentLang = config.language ?? "en";
824
+ saveWidgetConfig(cfg.apiKey, {
825
+ position: config.position,
826
+ language: config.language,
827
+ widget: config.widget,
828
+ theme: config.theme
829
+ });
830
+ configureWidgetClient(deriveApiUrl(), cfg.apiKey);
831
+ if (cfg.inline) {
832
+ renderPanel();
833
+ return;
834
+ }
835
+ const { mount: m } = mountWidget({ zIndex: cfg.theme?.zIndex });
836
+ mount = m;
837
+ const shadow = getShadowRoot();
838
+ if (shadow) {
839
+ const root = shadow.querySelector(".rqd-root");
840
+ if (root) root.style.cssText = themeToVars(cfg.theme);
841
+ }
842
+ const host = document.getElementById("reqdesk-widget");
843
+ if (host && currentLang === "ar") host.setAttribute("dir", "rtl");
844
+ if (getTrackingTokens(cfg.apiKey).length > 0) currentTab = "tracker";
845
+ fab = createFab(cfg.position ?? "bottom-right", toggle);
846
+ mount.appendChild(fab);
847
+ }
848
+ function open() {
849
+ if (!config || isOpen) return;
850
+ isOpen = true;
851
+ if (fab) setFabOpen(fab, true);
852
+ renderPanel();
853
+ emit("open");
854
+ }
855
+ function close() {
856
+ if (!isOpen) return;
857
+ isOpen = false;
858
+ if (fab) setFabOpen(fab, false);
859
+ if (panel) {
860
+ panel.remove();
861
+ panel = null;
862
+ }
863
+ emit("close");
864
+ }
865
+ function toggle() {
866
+ if (isOpen) close();
867
+ else open();
868
+ }
869
+ function setLanguage(lang) {
870
+ currentLang = lang;
871
+ if (config) {
872
+ config.language = lang;
873
+ saveWidgetConfig(config.apiKey, { language: lang });
874
+ }
875
+ const host = document.getElementById("reqdesk-widget");
876
+ if (host) host.setAttribute("dir", lang === "ar" ? "rtl" : "ltr");
877
+ if (isOpen && panel && mount) {
878
+ panel.remove();
879
+ panel = null;
880
+ renderPanel();
881
+ }
882
+ }
883
+ function setTheme(theme) {
884
+ if (!config) return;
885
+ config.theme = {
886
+ ...config.theme,
887
+ ...theme
888
+ };
889
+ saveWidgetConfig(config.apiKey, { theme: config.theme });
890
+ if (panel) panel.style.cssText = themeToVars(config.theme);
891
+ }
892
+ function on(event, callback) {
893
+ if (!listeners[event]) listeners[event] = [];
894
+ listeners[event].push(callback);
895
+ }
896
+ function identify(customer) {
897
+ if (config) config.customer = customer;
898
+ }
899
+ function destroy() {
900
+ close();
901
+ unmountWidget();
902
+ config = null;
903
+ fab = null;
904
+ panel = null;
905
+ mount = null;
906
+ isOpen = false;
907
+ for (const key of Object.keys(listeners)) delete listeners[key];
908
+ }
909
+ //#endregion
910
+ export { close, destroy, identify, init, on, open, setLanguage, setTheme, toggle };
911
+
912
+ //# sourceMappingURL=index.js.map