@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.
@@ -0,0 +1,722 @@
1
+ import { ofetch } from "ofetch";
2
+ //#region src/ofetch-client.ts
3
+ let apiKey = "";
4
+ let baseUrl = "";
5
+ let oidcTokenProvider = null;
6
+ function configureWidgetClient(url, key) {
7
+ baseUrl = url.replace(/\/$/, "");
8
+ apiKey = key;
9
+ }
10
+ function setOidcTokenProvider(provider) {
11
+ oidcTokenProvider = provider;
12
+ }
13
+ const widgetFetch = ofetch.create({
14
+ timeout: 15e3,
15
+ retry: 2,
16
+ retryStatusCodes: [
17
+ 408,
18
+ 429,
19
+ 500,
20
+ 502,
21
+ 503,
22
+ 504
23
+ ],
24
+ async onRequest({ options }) {
25
+ options.baseURL = `${baseUrl}/api/v1`;
26
+ const headers = new Headers(options.headers);
27
+ headers.set("X-API-Key", apiKey);
28
+ if (oidcTokenProvider) try {
29
+ const tokens = await oidcTokenProvider();
30
+ headers.set("Authorization", `Bearer ${tokens.accessToken}`);
31
+ } catch {}
32
+ options.headers = headers;
33
+ },
34
+ onResponseError({ response }) {
35
+ const body = response._data;
36
+ throw {
37
+ code: body?.responseCode ?? `HTTP_${response.status}`,
38
+ message: body?.responseMessage ?? response.statusText
39
+ };
40
+ }
41
+ });
42
+ function uploadWithProgress(path, file, onProgress) {
43
+ return new Promise((resolve, reject) => {
44
+ const xhr = new XMLHttpRequest();
45
+ xhr.open("POST", `${baseUrl}${path}`);
46
+ xhr.setRequestHeader("X-API-Key", apiKey);
47
+ if (oidcTokenProvider) oidcTokenProvider().then((tokens) => {
48
+ xhr.setRequestHeader("Authorization", `Bearer ${tokens.accessToken}`);
49
+ sendXhr();
50
+ }).catch(() => sendXhr());
51
+ else sendXhr();
52
+ function sendXhr() {
53
+ if (onProgress) xhr.upload.addEventListener("progress", (e) => {
54
+ if (e.lengthComputable) onProgress(Math.round(e.loaded / e.total * 100));
55
+ });
56
+ xhr.addEventListener("load", () => {
57
+ if (xhr.status >= 200 && xhr.status < 300) try {
58
+ resolve(JSON.parse(xhr.responseText));
59
+ } catch {
60
+ resolve({});
61
+ }
62
+ else reject({
63
+ code: `HTTP_${xhr.status}`,
64
+ message: xhr.statusText
65
+ });
66
+ });
67
+ xhr.addEventListener("error", () => {
68
+ reject({
69
+ code: "NETWORK_ERROR",
70
+ message: "Network error during upload"
71
+ });
72
+ });
73
+ const form = new FormData();
74
+ form.append("file", file);
75
+ xhr.send(form);
76
+ }
77
+ });
78
+ }
79
+ //#endregion
80
+ //#region src/api-client.ts
81
+ function generateIdempotencyKey() {
82
+ return crypto.randomUUID();
83
+ }
84
+ async function submitTicket(projectId, data) {
85
+ const res = await widgetFetch(`/projects/${projectId}/tickets`, {
86
+ method: "POST",
87
+ body: {
88
+ title: data.title,
89
+ description: data.description,
90
+ priority: data.priority,
91
+ categoryId: data.categoryId,
92
+ email: data.email
93
+ },
94
+ headers: { "Idempotency-Key": generateIdempotencyKey() }
95
+ });
96
+ return {
97
+ id: res.data.id,
98
+ ticketNumber: res.data.attributes.ticketNumber,
99
+ trackingToken: res.meta?.trackingToken,
100
+ status: res.data.attributes.status
101
+ };
102
+ }
103
+ async function trackTicket(token) {
104
+ const res = await widgetFetch(`/tickets/track?token=${encodeURIComponent(token)}`);
105
+ const included = res.included ?? [];
106
+ return {
107
+ id: res.data.id,
108
+ ticketNumber: res.data.attributes.ticketNumber,
109
+ title: res.data.attributes.title,
110
+ status: res.data.attributes.status,
111
+ priority: res.data.attributes.priority,
112
+ createdAt: res.data.attributes.createdAt,
113
+ replies: included.filter((i) => i.type === "public-reply").map((i) => ({
114
+ id: i.id,
115
+ body: i.attributes.body,
116
+ authorName: i.attributes.authorName,
117
+ isStaff: i.attributes.isStaff,
118
+ createdAt: i.attributes.createdAt
119
+ }))
120
+ };
121
+ }
122
+ async function submitTrackingReply(token, body) {
123
+ await widgetFetch("/tickets/track/reply", {
124
+ method: "POST",
125
+ body: {
126
+ trackingToken: token,
127
+ body
128
+ }
129
+ });
130
+ }
131
+ function uploadAttachment(ticketId, file, onProgress) {
132
+ return uploadWithProgress(`/api/v1/tickets/${ticketId}/attachments`, file, onProgress);
133
+ }
134
+ async function getTicketDetail(ticketId, includeMedia = true) {
135
+ const res = await widgetFetch(`/tickets/${ticketId}${includeMedia ? "?include=media" : ""}`);
136
+ const included = res.included ?? [];
137
+ const replies = included.filter((i) => i.type === "ticket-reply").map((i) => ({
138
+ id: i.id,
139
+ body: i.attributes.body,
140
+ authorName: i.attributes.authorName ?? "Anonymous",
141
+ isStaff: i.attributes.isStaff ?? false,
142
+ createdAt: i.attributes.timestamps?.createdAt ?? i.attributes.createdAt ?? ""
143
+ }));
144
+ const attachments = included.filter((i) => i.type === "attachment").map((i) => ({
145
+ id: i.id,
146
+ fileName: i.attributes.fileName,
147
+ contentType: i.attributes.contentType,
148
+ fileSize: i.attributes.fileSize,
149
+ downloadUrl: i.attributes.downloadUrl,
150
+ downloadUrlExpiresAt: i.attributes.downloadUrlExpiresAt
151
+ }));
152
+ return {
153
+ id: res.data.id,
154
+ ticketNumber: res.data.attributes.ticketNumber,
155
+ title: res.data.attributes.title,
156
+ description: res.data.attributes.description,
157
+ status: res.data.attributes.status,
158
+ priority: res.data.attributes.priority,
159
+ createdAt: res.data.attributes.timestamps?.createdAt ?? "",
160
+ replies,
161
+ attachments
162
+ };
163
+ }
164
+ async function submitReply(ticketId, body) {
165
+ return { id: (await widgetFetch(`/tickets/${ticketId}/replies`, {
166
+ method: "POST",
167
+ body: {
168
+ body,
169
+ isInternal: false
170
+ }
171
+ })).data.id };
172
+ }
173
+ async function resolveWidgetUser(projectId, email) {
174
+ try {
175
+ return await widgetFetch(`/projects/${projectId}/widget-users?email=${encodeURIComponent(email)}`);
176
+ } catch {
177
+ return null;
178
+ }
179
+ }
180
+ async function listMyTickets(projectId, userId, page = 1, pageSize = 20) {
181
+ const res = await widgetFetch(`/projects/${projectId}/tickets?${new URLSearchParams({
182
+ "filter[createdBy]": userId,
183
+ "sort": "-created_at",
184
+ "page[number]": String(page),
185
+ "page[size]": String(pageSize)
186
+ })}`);
187
+ return (Array.isArray(res.data) ? res.data : []).map((t) => ({
188
+ id: t.id,
189
+ ticketNumber: t.attributes.ticketNumber,
190
+ title: t.attributes.title,
191
+ status: t.attributes.status,
192
+ priority: t.attributes.priority,
193
+ createdAt: t.attributes.timestamps?.createdAt ?? ""
194
+ }));
195
+ }
196
+ //#endregion
197
+ //#region src/i18n/en.ts
198
+ const en = {
199
+ "widget.title": "Support",
200
+ "widget.newTicket": "New Ticket",
201
+ "widget.trackTicket": "Track Ticket",
202
+ "form.title": "Title",
203
+ "form.titlePlaceholder": "Brief summary of your issue",
204
+ "form.description": "Description",
205
+ "form.descriptionPlaceholder": "Describe your issue in detail...",
206
+ "form.email": "Email",
207
+ "form.emailPlaceholder": "your@email.com",
208
+ "form.priority": "Priority",
209
+ "form.priorityLow": "Low",
210
+ "form.priorityMedium": "Medium",
211
+ "form.priorityHigh": "High",
212
+ "form.priorityUrgent": "Urgent",
213
+ "form.category": "Category",
214
+ "form.categoryNone": "Select a category",
215
+ "form.submit": "Submit Ticket",
216
+ "form.submitting": "Submitting...",
217
+ "form.cancel": "Cancel",
218
+ "form.attachment": "Attach files",
219
+ "success.title": "Ticket Submitted!",
220
+ "success.ticketNumber": "Ticket #",
221
+ "success.trackingToken": "Your tracking token:",
222
+ "success.trackingHint": "Save this token to track your ticket status.",
223
+ "success.copyToken": "Copy Token",
224
+ "success.copied": "Copied!",
225
+ "success.close": "Close",
226
+ "success.trackNow": "Track Ticket",
227
+ "tracker.title": "Track Your Ticket",
228
+ "tracker.tokenPlaceholder": "Enter your tracking token (trk_...)",
229
+ "tracker.submit": "Track",
230
+ "tracker.tracking": "Tracking...",
231
+ "tracker.status": "Status",
232
+ "tracker.priority": "Priority",
233
+ "tracker.created": "Created",
234
+ "tracker.replies": "Replies",
235
+ "tracker.noReplies": "No replies yet.",
236
+ "tracker.replyPlaceholder": "Write a reply...",
237
+ "tracker.sendReply": "Send Reply",
238
+ "tracker.sending": "Sending...",
239
+ "tracker.back": "Back",
240
+ "tracker.staff": "Staff",
241
+ "error.generic": "Something went wrong. Please try again.",
242
+ "error.network": "Network error. Check your connection.",
243
+ "error.tokenInvalid": "Invalid or expired tracking token.",
244
+ "error.required": "This field is required.",
245
+ "error.emailInvalid": "Please enter a valid email address.",
246
+ "error.titleMin": "Title must be at least 5 characters.",
247
+ "portal.title": "Support Portal",
248
+ "portal.myTickets": "My Tickets",
249
+ "portal.noTickets": "No tickets yet.",
250
+ "portal.newTicket": "New Ticket",
251
+ "widget.close": "Close",
252
+ "menu.newTicket": "Submit a Ticket",
253
+ "menu.newTicketDesc": "Create a new support request",
254
+ "menu.myTickets": "My Tickets",
255
+ "menu.myTicketsDesc": "View and manage your tickets",
256
+ "menu.trackTicket": "Track a Ticket",
257
+ "menu.trackTicketDesc": "Check status with your tracking token",
258
+ "menu.knowledgeBase": "Knowledge Base",
259
+ "menu.knowledgeBaseDesc": "Browse help articles and guides",
260
+ "menu.myTicketsPlaceholder": "Sign in to view your tickets.",
261
+ "menu.trackPlaceholder": "Ticket tracking is coming soon.",
262
+ "menu.kbPlaceholder": "Knowledge base is coming soon.",
263
+ "menu.preferences": "Preferences",
264
+ "menu.preferencesDesc": "Language and appearance settings",
265
+ "prefs.title": "Preferences",
266
+ "prefs.language": "Language",
267
+ "prefs.theme": "Theme",
268
+ "prefs.light": "Light",
269
+ "prefs.dark": "Dark",
270
+ "prefs.auto": "System",
271
+ "branding.poweredBy": "Powered by",
272
+ "attach.dropzone": "Drop files here or click to browse",
273
+ "attach.dropzoneActive": "Drop files here",
274
+ "attach.maxFiles": "Maximum {max} files",
275
+ "attach.remove": "Remove",
276
+ "attach.uploading": "Uploading files...",
277
+ "attach.uploadProgress": "Uploading {name}... {percent}%",
278
+ "attach.invalidType": "File type not allowed",
279
+ "attach.tooLarge": "File exceeds maximum size",
280
+ "attach.tooMany": "Maximum number of files reached",
281
+ "attach.download": "Download",
282
+ "mytickets.title": "My Tickets",
283
+ "mytickets.emailPrompt": "Enter your email to view your tickets",
284
+ "mytickets.emailPlaceholder": "your@email.com",
285
+ "mytickets.rememberMe": "Remember me",
286
+ "mytickets.lookup": "Find My Tickets",
287
+ "mytickets.lookingUp": "Looking up...",
288
+ "mytickets.noTickets": "No tickets yet.",
289
+ "mytickets.submitFirst": "Submit your first ticket",
290
+ "mytickets.noAccount": "No tickets found for this email.",
291
+ "detail.description": "Description",
292
+ "detail.attachments": "Attachments",
293
+ "detail.noAttachments": "No attachments",
294
+ "detail.replies": "Conversation",
295
+ "detail.noReplies": "No replies yet.",
296
+ "detail.replyPlaceholder": "Write a reply...",
297
+ "detail.sendReply": "Send",
298
+ "detail.sending": "Sending...",
299
+ "detail.staff": "Staff",
300
+ "detail.you": "You",
301
+ "detail.loading": "Loading ticket...",
302
+ "track.title": "Track a Ticket",
303
+ "track.tokenPlaceholder": "Enter tracking token (trk_...)",
304
+ "track.submit": "Track",
305
+ "track.tracking": "Tracking...",
306
+ "track.recentTickets": "Recent Tickets",
307
+ "track.invalidToken": "Invalid or expired tracking token.",
308
+ "prefs.clearEmail": "Clear saved email",
309
+ "prefs.emailCleared": "Email cleared",
310
+ "auth.login": "Login",
311
+ "auth.logout": "Logout",
312
+ "auth.sessionExpired": "Session expired. Please log in again.",
313
+ "prefs.accentColor": "Accent Color"
314
+ };
315
+ //#endregion
316
+ //#region src/i18n/ar.ts
317
+ const ar = {
318
+ "widget.title": "الدعم",
319
+ "widget.newTicket": "تذكرة جديدة",
320
+ "widget.trackTicket": "تتبع التذكرة",
321
+ "form.title": "العنوان",
322
+ "form.titlePlaceholder": "ملخص موجز لمشكلتك",
323
+ "form.description": "الوصف",
324
+ "form.descriptionPlaceholder": "صف مشكلتك بالتفصيل...",
325
+ "form.email": "البريد الإلكتروني",
326
+ "form.emailPlaceholder": "your@email.com",
327
+ "form.priority": "الأولوية",
328
+ "form.priorityLow": "منخفضة",
329
+ "form.priorityMedium": "متوسطة",
330
+ "form.priorityHigh": "عالية",
331
+ "form.priorityUrgent": "عاجلة",
332
+ "form.category": "الفئة",
333
+ "form.categoryNone": "اختر فئة",
334
+ "form.submit": "إرسال التذكرة",
335
+ "form.submitting": "جارِ الإرسال...",
336
+ "form.cancel": "إلغاء",
337
+ "form.attachment": "إرفاق ملفات",
338
+ "success.title": "تم إرسال التذكرة!",
339
+ "success.ticketNumber": "تذكرة #",
340
+ "success.trackingToken": "رمز التتبع الخاص بك:",
341
+ "success.trackingHint": "احفظ هذا الرمز لتتبع حالة تذكرتك.",
342
+ "success.copyToken": "نسخ الرمز",
343
+ "success.copied": "تم النسخ!",
344
+ "success.close": "إغلاق",
345
+ "success.trackNow": "تتبع التذكرة",
346
+ "tracker.title": "تتبع تذكرتك",
347
+ "tracker.tokenPlaceholder": "أدخل رمز التتبع الخاص بك (trk_...)",
348
+ "tracker.submit": "تتبع",
349
+ "tracker.tracking": "جارِ التتبع...",
350
+ "tracker.status": "الحالة",
351
+ "tracker.priority": "الأولوية",
352
+ "tracker.created": "تاريخ الإنشاء",
353
+ "tracker.replies": "الردود",
354
+ "tracker.noReplies": "لا توجد ردود بعد.",
355
+ "tracker.replyPlaceholder": "اكتب رداً...",
356
+ "tracker.sendReply": "إرسال الرد",
357
+ "tracker.sending": "جارِ الإرسال...",
358
+ "tracker.back": "رجوع",
359
+ "tracker.staff": "فريق الدعم",
360
+ "error.generic": "حدث خطأ ما. يرجى المحاولة مرة أخرى.",
361
+ "error.network": "خطأ في الشبكة. تحقق من اتصالك.",
362
+ "error.tokenInvalid": "رمز التتبع غير صالح أو منتهي الصلاحية.",
363
+ "error.required": "هذا الحقل مطلوب.",
364
+ "error.emailInvalid": "يرجى إدخال بريد إلكتروني صالح.",
365
+ "error.titleMin": "يجب أن يكون العنوان 5 أحرف على الأقل.",
366
+ "portal.title": "بوابة الدعم",
367
+ "portal.myTickets": "تذاكري",
368
+ "portal.noTickets": "لا توجد تذاكر بعد.",
369
+ "portal.newTicket": "تذكرة جديدة",
370
+ "widget.close": "إغلاق",
371
+ "menu.newTicket": "إرسال تذكرة",
372
+ "menu.newTicketDesc": "إنشاء طلب ��عم جديد",
373
+ "menu.myTickets": "تذاكري",
374
+ "menu.myTicketsDesc": "عرض وإدارة تذاكرك",
375
+ "menu.trackTicket": "تتبع تذكرة",
376
+ "menu.trackTicketDesc": "تحقق من الحالة باستخدام رمز التتبع",
377
+ "menu.knowledgeBase": "قاعدة المعرفة",
378
+ "menu.knowledgeBaseDesc": "تصفح مقالات المساعدة والأدلة",
379
+ "menu.myTicketsPlaceholder": "سجّل الدخول ��عرض تذاكرك.",
380
+ "menu.trackPlaceholder": "تتبع التذاكر قريبًا.",
381
+ "menu.kbPlaceholder": "قاعدة المعرفة قريبًا.",
382
+ "menu.preferences": "التفضيلات",
383
+ "menu.preferencesDesc": "إعدادات اللغة والمظهر",
384
+ "prefs.title": "التفضيلات",
385
+ "prefs.language": "اللغة",
386
+ "prefs.theme": "المظهر",
387
+ "prefs.light": "فاتح",
388
+ "prefs.dark": "داكن",
389
+ "prefs.auto": "النظام",
390
+ "branding.poweredBy": "مدعوم من",
391
+ "attach.dropzone": "اسحب الملفات هنا أو انقر للتصفح",
392
+ "attach.dropzoneActive": "أفلت الملفات هنا",
393
+ "attach.maxFiles": "بحد أقصى {max} ملفات",
394
+ "attach.remove": "إزالة",
395
+ "attach.uploading": "جارِ رفع الملفات...",
396
+ "attach.uploadProgress": "جارِ رفع {name}... {percent}%",
397
+ "attach.invalidType": "نوع الملف غير مسموح",
398
+ "attach.tooLarge": "حجم الملف يتجاوز الحد الأقصى",
399
+ "attach.tooMany": "تم الوصول للحد الأقصى لعدد الملفات",
400
+ "attach.download": "تحميل",
401
+ "mytickets.title": "تذاكري",
402
+ "mytickets.emailPrompt": "أدخل بريدك الإلكتروني لعرض تذاكرك",
403
+ "mytickets.emailPlaceholder": "your@email.com",
404
+ "mytickets.rememberMe": "تذكرني",
405
+ "mytickets.lookup": "البحث عن تذاكري",
406
+ "mytickets.lookingUp": "جارِ البحث...",
407
+ "mytickets.noTickets": "لا توجد تذاكر بعد.",
408
+ "mytickets.submitFirst": "أرسل تذكرتك الأولى",
409
+ "mytickets.noAccount": "لم يتم العثور على تذاكر لهذا البريد.",
410
+ "detail.description": "الوصف",
411
+ "detail.attachments": "المرفقات",
412
+ "detail.noAttachments": "لا توجد مرفقات",
413
+ "detail.replies": "المحادثة",
414
+ "detail.noReplies": "لا توجد ردود بعد.",
415
+ "detail.replyPlaceholder": "اكتب رداً...",
416
+ "detail.sendReply": "إرسال",
417
+ "detail.sending": "جارِ الإرسال...",
418
+ "detail.staff": "فريق الدعم",
419
+ "detail.you": "أنت",
420
+ "detail.loading": "جارِ تحميل التذكرة...",
421
+ "track.title": "تتبع تذكرة",
422
+ "track.tokenPlaceholder": "أدخل رمز التتبع (trk_...)",
423
+ "track.submit": "تتبع",
424
+ "track.tracking": "جارِ التتبع...",
425
+ "track.recentTickets": "التذاكر الأخيرة",
426
+ "track.invalidToken": "رمز التتبع غير صالح أو منتهي.",
427
+ "prefs.clearEmail": "مسح البريد المحفوظ",
428
+ "prefs.emailCleared": "تم مسح البريد",
429
+ "auth.login": "تسجيل الدخول",
430
+ "auth.logout": "تسجيل الخروج",
431
+ "auth.sessionExpired": "انتهت الجلسة. يرجى تسجيل الدخول مرة أخرى.",
432
+ "prefs.accentColor": "لون التمييز"
433
+ };
434
+ //#endregion
435
+ //#region src/theme.ts
436
+ const DEFAULT_THEME = {
437
+ primaryColor: "#42b983",
438
+ mode: "light",
439
+ borderRadius: "8px",
440
+ fontFamily: "inherit",
441
+ zIndex: 9999
442
+ };
443
+ function resolveMode(mode) {
444
+ if (mode === "auto") {
445
+ if (typeof window !== "undefined" && window.matchMedia?.("(prefers-color-scheme: dark)").matches) return "dark";
446
+ return "light";
447
+ }
448
+ return mode ?? "light";
449
+ }
450
+ function themeToVars(theme = {}) {
451
+ const t = {
452
+ ...DEFAULT_THEME,
453
+ ...theme,
454
+ mode: resolveMode(theme.mode ?? DEFAULT_THEME.mode)
455
+ };
456
+ const hsl = hexToHsl(t.primaryColor);
457
+ const lightL = Math.min(hsl.l + 30, 95);
458
+ const darkL = Math.max(hsl.l - 15, 10);
459
+ return [
460
+ `--rqd-primary: ${t.primaryColor}`,
461
+ `--rqd-primary-h: ${hsl.h}`,
462
+ `--rqd-primary-s: ${hsl.s}%`,
463
+ `--rqd-primary-l: ${hsl.l}%`,
464
+ `--rqd-primary-light: hsl(${hsl.h}, ${hsl.s}%, ${lightL}%)`,
465
+ `--rqd-primary-dark: hsl(${hsl.h}, ${hsl.s}%, ${darkL}%)`,
466
+ `--rqd-radius: ${t.borderRadius}`,
467
+ `--rqd-font: ${t.fontFamily}`,
468
+ `--rqd-z: ${t.zIndex}`,
469
+ `--rqd-bg: ${t.mode === "dark" ? "#1a1a2e" : "#ffffff"}`,
470
+ `--rqd-bg-secondary: ${t.mode === "dark" ? "#16213e" : "#f8f9fa"}`,
471
+ `--rqd-text: ${t.mode === "dark" ? "#e0e0e0" : "#1a1a2e"}`,
472
+ `--rqd-text-secondary: ${t.mode === "dark" ? "#a0a0b0" : "#6c757d"}`,
473
+ `--rqd-border: ${t.mode === "dark" ? "#2a2a4a" : "#e0e0e0"}`,
474
+ `--rqd-input-bg: ${t.mode === "dark" ? "#0f3460" : "#ffffff"}`,
475
+ `--rqd-shadow: ${t.mode === "dark" ? "0 8px 32px rgba(0,0,0,0.4)" : "0 8px 32px rgba(0,0,0,0.12)"}`
476
+ ].join("; ");
477
+ }
478
+ function hexToHsl(hex) {
479
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
480
+ if (!result) return {
481
+ h: 160,
482
+ s: 51,
483
+ l: 49
484
+ };
485
+ const r = parseInt(result[1], 16) / 255;
486
+ const g = parseInt(result[2], 16) / 255;
487
+ const b = parseInt(result[3], 16) / 255;
488
+ const max = Math.max(r, g, b);
489
+ const min = Math.min(r, g, b);
490
+ let h = 0;
491
+ let s = 0;
492
+ const l = (max + min) / 2;
493
+ if (max !== min) {
494
+ const d = max - min;
495
+ s = l > .5 ? d / (2 - max - min) : d / (max + min);
496
+ if (max === r) h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
497
+ else if (max === g) h = ((b - r) / d + 2) / 6;
498
+ else h = ((r - g) / d + 4) / 6;
499
+ }
500
+ return {
501
+ h: Math.round(h * 360),
502
+ s: Math.round(s * 100),
503
+ l: Math.round(l * 100)
504
+ };
505
+ }
506
+ function getWidgetStyles() {
507
+ return WIDGET_CSS;
508
+ }
509
+ const WIDGET_CSS = `:host { all: initial; }
510
+ .rqd-root { font-family: var(--rqd-font, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif); font-size: 14px; line-height: 1.5; color: var(--rqd-text); -webkit-font-smoothing: antialiased; }
511
+ .rqd-fab { position: fixed; bottom: 20px; width: 56px; height: 56px; border-radius: 50%; background: var(--rqd-primary); color: #fff; border: none; cursor: pointer; display: flex; align-items: center; justify-content: center; box-shadow: 0 4px 16px rgba(0,0,0,0.2); transition: transform 0.2s, box-shadow 0.2s; pointer-events: auto; z-index: var(--rqd-z, 9999); }
512
+ .rqd-fab:hover { transform: scale(1.08); box-shadow: 0 6px 20px rgba(0,0,0,0.25); }
513
+ .rqd-fab.rqd-bottom-right { right: 20px; }
514
+ .rqd-fab.rqd-bottom-left { left: 20px; }
515
+ .rqd-fab svg { width: 24px; height: 24px; fill: currentColor; }
516
+ .rqd-panel { position: fixed; bottom: 88px; width: 380px; max-height: calc(100vh - 120px); background: var(--rqd-bg); border-radius: var(--rqd-radius); box-shadow: var(--rqd-shadow); overflow: hidden; display: flex; flex-direction: column; pointer-events: auto; animation: rqd-slide-up 0.25s ease-out; border: 1px solid var(--rqd-border); }
517
+ .rqd-panel.rqd-bottom-right { right: 20px; }
518
+ .rqd-panel.rqd-bottom-left { left: 20px; }
519
+ .rqd-panel.rqd-hidden { display: none; }
520
+ @keyframes rqd-slide-up { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: translateY(0); } }
521
+ .rqd-header { display: flex; align-items: center; justify-content: space-between; padding: 16px; background: var(--rqd-primary); color: #fff; }
522
+ .rqd-header-title { font-size: 16px; font-weight: 600; }
523
+ .rqd-header-close { background: none; border: none; color: #fff; cursor: pointer; width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; border-radius: 50%; transition: background 0.15s; }
524
+ .rqd-header-close:hover { background: rgba(255,255,255,0.2); }
525
+ .rqd-tabs { display: flex; border-bottom: 1px solid var(--rqd-border); }
526
+ .rqd-tab { flex: 1; padding: 10px; text-align: center; background: none; border: none; cursor: pointer; font-size: 13px; font-weight: 500; color: var(--rqd-text-secondary); border-bottom: 2px solid transparent; transition: color 0.15s, border-color 0.15s; }
527
+ .rqd-tab.rqd-active { color: var(--rqd-primary); border-bottom-color: var(--rqd-primary); }
528
+ .rqd-body { flex: 1; overflow-y: auto; padding: 16px; }
529
+ .rqd-form-group { margin-bottom: 14px; }
530
+ .rqd-label { display: block; font-size: 13px; font-weight: 500; margin-bottom: 4px; color: var(--rqd-text); }
531
+ .rqd-input, .rqd-textarea, .rqd-select { width: 100%; padding: 8px 12px; border: 1px solid var(--rqd-border); border-radius: calc(var(--rqd-radius, 8px) / 2); font-size: 14px; font-family: inherit; background: var(--rqd-input-bg); color: var(--rqd-text); outline: none; transition: border-color 0.15s; box-sizing: border-box; }
532
+ .rqd-input:focus, .rqd-textarea:focus, .rqd-select:focus { border-color: var(--rqd-primary); }
533
+ .rqd-textarea { resize: vertical; min-height: 80px; }
534
+ .rqd-error-text { color: #e74c3c; font-size: 12px; margin-top: 2px; }
535
+ .rqd-btn { width: 100%; padding: 10px; border: none; border-radius: calc(var(--rqd-radius, 8px) / 2); font-size: 14px; font-weight: 600; cursor: pointer; transition: opacity 0.15s; }
536
+ .rqd-btn:disabled { opacity: 0.6; cursor: not-allowed; }
537
+ .rqd-btn-primary { background: var(--rqd-primary); color: #fff; }
538
+ .rqd-btn-primary:hover:not(:disabled) { opacity: 0.9; }
539
+ .rqd-btn-secondary { background: transparent; color: var(--rqd-primary); border: 1px solid var(--rqd-primary); }
540
+ .rqd-success { text-align: center; padding: 24px 0; }
541
+ .rqd-success-icon { font-size: 48px; margin-bottom: 12px; }
542
+ .rqd-success h3 { margin: 0 0 8px; font-size: 18px; color: var(--rqd-text); }
543
+ .rqd-token-box { background: var(--rqd-bg-secondary); border: 1px solid var(--rqd-border); border-radius: calc(var(--rqd-radius, 8px) / 2); padding: 8px 12px; font-family: monospace; font-size: 12px; word-break: break-all; margin: 8px 0; color: var(--rqd-text); }
544
+ .rqd-ticket-info { background: var(--rqd-bg-secondary); border-radius: calc(var(--rqd-radius, 8px) / 2); padding: 12px; margin-bottom: 12px; }
545
+ .rqd-ticket-row { display: flex; justify-content: space-between; margin-bottom: 4px; font-size: 13px; }
546
+ .rqd-ticket-label { color: var(--rqd-text-secondary); }
547
+ .rqd-badge { display: inline-block; padding: 2px 8px; border-radius: 12px; font-size: 11px; font-weight: 600; background: var(--rqd-primary-light); color: var(--rqd-primary-dark); }
548
+ .rqd-reply { padding: 10px 0; border-bottom: 1px solid var(--rqd-border); }
549
+ .rqd-reply:last-child { border-bottom: none; }
550
+ .rqd-reply-header { display: flex; justify-content: space-between; font-size: 12px; color: var(--rqd-text-secondary); margin-bottom: 4px; }
551
+ .rqd-reply-staff { color: var(--rqd-primary); font-weight: 600; }
552
+ .rqd-reply-body { font-size: 14px; white-space: pre-wrap; }
553
+ .rqd-inline { pointer-events: auto; background: var(--rqd-bg); border: 1px solid var(--rqd-border); border-radius: var(--rqd-radius); overflow: hidden; }
554
+ .rqd-inline .rqd-body { max-height: none; }
555
+ :host([dir="rtl"]) .rqd-root { direction: rtl; text-align: right; }
556
+ :host([dir="rtl"]) .rqd-fab.rqd-bottom-right { right: auto; left: 20px; }
557
+ :host([dir="rtl"]) .rqd-fab.rqd-bottom-left { left: auto; right: 20px; }
558
+ :host([dir="rtl"]) .rqd-panel.rqd-bottom-right { right: auto; left: 20px; }
559
+ :host([dir="rtl"]) .rqd-panel.rqd-bottom-left { left: auto; right: 20px; }
560
+ .rqd-menu { display: flex; flex-direction: column; gap: 6px; }
561
+ .rqd-menu-item { display: flex; align-items: center; gap: 14px; width: 100%; padding: 14px 12px; background: var(--rqd-bg-secondary); border: 1px solid transparent; border-radius: calc(var(--rqd-radius, 8px) / 1.5); cursor: pointer; text-align: start; transition: border-color 0.15s, background 0.15s, transform 0.1s; font-family: inherit; color: var(--rqd-text); }
562
+ .rqd-menu-item:hover { border-color: var(--rqd-primary); background: var(--rqd-bg); transform: translateY(-1px); }
563
+ .rqd-menu-icon { width: 42px; height: 42px; display: flex; align-items: center; justify-content: center; border-radius: calc(var(--rqd-radius, 8px) / 2); background: var(--rqd-primary-light); color: var(--rqd-primary-dark); flex-shrink: 0; }
564
+ .rqd-menu-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
565
+ .rqd-menu-label { font-size: 14px; font-weight: 600; line-height: 1.3; }
566
+ .rqd-menu-desc { font-size: 12px; color: var(--rqd-text-secondary); line-height: 1.3; }
567
+ .rqd-placeholder { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px; padding: 40px 16px; color: var(--rqd-text-secondary); text-align: center; }
568
+ .rqd-placeholder p { margin: 0; font-size: 14px; }
569
+ .rqd-header-brand { display: flex; align-items: center; gap: 8px; min-width: 0; }
570
+ .rqd-header-logo { width: 24px; height: 24px; border-radius: 4px; object-fit: contain; flex-shrink: 0; }
571
+ .rqd-footer { display: flex; align-items: center; justify-content: center; gap: 4px; padding: 8px 16px; border-top: 1px solid var(--rqd-border); font-size: 11px; color: var(--rqd-text-secondary); }
572
+ .rqd-footer a { color: var(--rqd-text-secondary); text-decoration: none; font-weight: 600; transition: color 0.15s; }
573
+ .rqd-footer a:hover { color: var(--rqd-primary); }
574
+ .rqd-prefs { display: flex; flex-direction: column; gap: 16px; }
575
+ .rqd-prefs-group { display: flex; flex-direction: column; gap: 6px; }
576
+ .rqd-prefs-label { font-size: 13px; font-weight: 600; color: var(--rqd-text); }
577
+ .rqd-prefs-options { display: flex; gap: 6px; }
578
+ .rqd-prefs-option { flex: 1; padding: 8px 12px; background: var(--rqd-bg-secondary); border: 1px solid var(--rqd-border); border-radius: calc(var(--rqd-radius, 8px) / 2); cursor: pointer; text-align: center; font-size: 13px; font-family: inherit; color: var(--rqd-text); transition: border-color 0.15s, background 0.15s; }
579
+ .rqd-prefs-option:hover { border-color: var(--rqd-primary); }
580
+ .rqd-prefs-option.rqd-active { border-color: var(--rqd-primary); background: var(--rqd-primary-light); color: var(--rqd-primary-dark); font-weight: 600; }
581
+ .rqd-dropzone { border: 2px dashed var(--rqd-border); border-radius: calc(var(--rqd-radius, 8px) / 2); padding: 16px; text-align: center; cursor: pointer; transition: border-color 0.15s, background 0.15s; color: var(--rqd-text-secondary); font-size: 13px; margin-bottom: 14px; }
582
+ .rqd-dropzone:hover { border-color: var(--rqd-primary); }
583
+ .rqd-dropzone.rqd-dropzone-active { border-color: var(--rqd-primary); background: var(--rqd-primary-light); color: var(--rqd-primary-dark); }
584
+ .rqd-file-list { display: flex; flex-direction: column; gap: 6px; margin-bottom: 14px; }
585
+ .rqd-file-item { display: flex; align-items: center; justify-content: space-between; padding: 8px 10px; background: var(--rqd-bg-secondary); border-radius: calc(var(--rqd-radius, 8px) / 2); font-size: 13px; }
586
+ .rqd-file-item-info { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
587
+ .rqd-file-item-name { font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
588
+ .rqd-file-item-size { font-size: 11px; color: var(--rqd-text-secondary); }
589
+ .rqd-file-remove { background: none; border: none; color: var(--rqd-text-secondary); cursor: pointer; padding: 4px; font-size: 16px; line-height: 1; flex-shrink: 0; }
590
+ .rqd-file-remove:hover { color: #e74c3c; }
591
+ .rqd-progress { width: 100%; height: 6px; background: var(--rqd-bg-secondary); border-radius: 3px; overflow: hidden; margin-top: 6px; }
592
+ .rqd-progress-bar { height: 100%; background: var(--rqd-primary); border-radius: 3px; transition: width 0.2s ease; }
593
+ .rqd-upload-status { font-size: 12px; color: var(--rqd-text-secondary); margin-top: 4px; }
594
+ .rqd-attachment-list { display: flex; flex-direction: column; gap: 6px; margin-bottom: 12px; }
595
+ .rqd-attachment-item { display: flex; align-items: center; justify-content: space-between; padding: 8px 10px; background: var(--rqd-bg-secondary); border-radius: calc(var(--rqd-radius, 8px) / 2); font-size: 13px; }
596
+ .rqd-attachment-item a { color: var(--rqd-primary); text-decoration: none; font-weight: 500; font-size: 12px; flex-shrink: 0; }
597
+ .rqd-attachment-item a:hover { text-decoration: underline; }
598
+ .rqd-ticket-header { margin-bottom: 12px; }
599
+ .rqd-ticket-header h3 { margin: 0 0 6px; font-size: 16px; font-weight: 600; color: var(--rqd-text); }
600
+ .rqd-ticket-header-meta { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
601
+ .rqd-ticket-desc { font-size: 14px; color: var(--rqd-text); white-space: pre-wrap; margin-bottom: 16px; padding: 10px; background: var(--rqd-bg-secondary); border-radius: calc(var(--rqd-radius, 8px) / 2); }
602
+ .rqd-section-title { font-size: 13px; font-weight: 600; color: var(--rqd-text); margin-bottom: 8px; }
603
+ .rqd-email-form { display: flex; flex-direction: column; gap: 12px; padding: 24px 0; }
604
+ .rqd-email-form p { margin: 0; font-size: 14px; color: var(--rqd-text-secondary); text-align: center; }
605
+ .rqd-checkbox-label { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--rqd-text-secondary); cursor: pointer; }
606
+ .rqd-checkbox-label input[type="checkbox"] { accent-color: var(--rqd-primary); width: 16px; height: 16px; cursor: pointer; }
607
+ .rqd-ticket-card { display: flex; flex-direction: column; gap: 4px; padding: 12px; background: var(--rqd-bg-secondary); border: 1px solid transparent; border-radius: calc(var(--rqd-radius, 8px) / 1.5); cursor: pointer; transition: border-color 0.15s, transform 0.1s; }
608
+ .rqd-ticket-card:hover { border-color: var(--rqd-primary); transform: translateY(-1px); }
609
+ .rqd-ticket-card-top { display: flex; justify-content: space-between; align-items: center; }
610
+ .rqd-ticket-card-number { font-size: 12px; font-weight: 600; color: var(--rqd-text-secondary); }
611
+ .rqd-ticket-card-title { font-size: 14px; font-weight: 500; color: var(--rqd-text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
612
+ .rqd-ticket-card-date { font-size: 11px; color: var(--rqd-text-secondary); }
613
+ .rqd-ticket-list { display: flex; flex-direction: column; gap: 6px; }
614
+ .rqd-loading { text-align: center; padding: 24px 0; color: var(--rqd-text-secondary); font-size: 14px; }
615
+ .rqd-reply-compose { margin-top: 12px; display: flex; flex-direction: column; gap: 8px; }
616
+ .rqd-auth-btn { background: rgba(255,255,255,0.2); border: 1px solid rgba(255,255,255,0.4); color: #fff; cursor: pointer; padding: 4px 12px; border-radius: 12px; font-size: 12px; font-weight: 500; font-family: inherit; transition: background 0.15s; white-space: nowrap; }
617
+ .rqd-auth-btn:hover { background: rgba(255,255,255,0.3); }
618
+ .rqd-color-presets { display: flex; gap: 8px; flex-wrap: wrap; }
619
+ .rqd-color-preset { width: 28px; height: 28px; border-radius: 50%; border: 2px solid transparent; cursor: pointer; transition: transform 0.15s, box-shadow 0.15s; padding: 0; }
620
+ .rqd-color-preset:hover { transform: scale(1.15); }
621
+ .rqd-color-preset.rqd-active { box-shadow: 0 0 0 3px var(--rqd-bg), 0 0 0 5px currentColor; }
622
+ .rqd-contained.rqd-fab { position: absolute; }
623
+ .rqd-contained.rqd-panel { position: absolute; }
624
+ @media (max-width: 440px) { .rqd-panel { width: calc(100vw - 24px); right: 12px !important; left: 12px !important; bottom: 76px; } .rqd-fab { bottom: 12px; } .rqd-fab.rqd-bottom-right { right: 12px; } .rqd-fab.rqd-bottom-left { left: 12px; } }
625
+ `;
626
+ //#endregion
627
+ //#region src/storage.ts
628
+ const STORAGE_PREFIX = "reqdesk_";
629
+ function getStorage() {
630
+ try {
631
+ const s = window.localStorage;
632
+ s.setItem("__test__", "1");
633
+ s.removeItem("__test__");
634
+ return s;
635
+ } catch {
636
+ try {
637
+ return window.sessionStorage;
638
+ } catch {
639
+ return null;
640
+ }
641
+ }
642
+ }
643
+ function saveTrackingToken(projectSlug, token) {
644
+ const storage = getStorage();
645
+ if (!storage) return;
646
+ const key = `${STORAGE_PREFIX}tokens_${projectSlug}`;
647
+ try {
648
+ const existing = JSON.parse(storage.getItem(key) ?? "[]");
649
+ if (!existing.includes(token)) {
650
+ existing.push(token);
651
+ storage.setItem(key, JSON.stringify(existing));
652
+ }
653
+ } catch {
654
+ storage.setItem(key, JSON.stringify([token]));
655
+ }
656
+ }
657
+ function getTrackingTokens(projectSlug) {
658
+ const storage = getStorage();
659
+ if (!storage) return [];
660
+ const key = `${STORAGE_PREFIX}tokens_${projectSlug}`;
661
+ try {
662
+ return JSON.parse(storage.getItem(key) ?? "[]");
663
+ } catch {
664
+ return [];
665
+ }
666
+ }
667
+ function saveWidgetConfig(apiKey, config) {
668
+ const storage = getStorage();
669
+ if (!storage) return;
670
+ const key = `${STORAGE_PREFIX}config_${apiKey}`;
671
+ try {
672
+ const existing = loadWidgetConfig(apiKey);
673
+ const merged = {
674
+ ...existing,
675
+ ...config
676
+ };
677
+ if (config.theme && existing?.theme) merged.theme = {
678
+ ...existing.theme,
679
+ ...config.theme
680
+ };
681
+ storage.setItem(key, JSON.stringify(merged));
682
+ } catch {}
683
+ }
684
+ function saveWidgetEmail(apiKey, email) {
685
+ const storage = getStorage();
686
+ if (!storage) return;
687
+ try {
688
+ storage.setItem(`${STORAGE_PREFIX}email_${apiKey}`, email);
689
+ } catch {}
690
+ }
691
+ function loadWidgetEmail(apiKey) {
692
+ const storage = getStorage();
693
+ if (!storage) return null;
694
+ try {
695
+ return storage.getItem(`${STORAGE_PREFIX}email_${apiKey}`);
696
+ } catch {
697
+ return null;
698
+ }
699
+ }
700
+ function clearWidgetEmail(apiKey) {
701
+ const storage = getStorage();
702
+ if (!storage) return;
703
+ try {
704
+ storage.removeItem(`${STORAGE_PREFIX}email_${apiKey}`);
705
+ } catch {}
706
+ }
707
+ function loadWidgetConfig(apiKey) {
708
+ const storage = getStorage();
709
+ if (!storage) return null;
710
+ const key = `${STORAGE_PREFIX}config_${apiKey}`;
711
+ try {
712
+ const raw = storage.getItem(key);
713
+ if (!raw) return null;
714
+ return JSON.parse(raw);
715
+ } catch {
716
+ return null;
717
+ }
718
+ }
719
+ //#endregion
720
+ export { submitTrackingReply as _, saveTrackingToken as a, configureWidgetClient as b, getWidgetStyles as c, en as d, getTicketDetail as f, submitTicket as g, submitReply as h, loadWidgetEmail as i, themeToVars as l, resolveWidgetUser as m, getTrackingTokens as n, saveWidgetConfig as o, listMyTickets as p, loadWidgetConfig as r, saveWidgetEmail as s, clearWidgetEmail as t, ar as u, trackTicket as v, setOidcTokenProvider as x, uploadAttachment as y };
721
+
722
+ //# sourceMappingURL=storage-CC5BCsxP.js.map