@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/react.js ADDED
@@ -0,0 +1,1637 @@
1
+ import { _ as submitTrackingReply, a as saveTrackingToken, b as configureWidgetClient, c as getWidgetStyles, d as en, f as getTicketDetail, g as submitTicket, h as submitReply, i as loadWidgetEmail, l as themeToVars, m as resolveWidgetUser, n as getTrackingTokens, o as saveWidgetConfig, p as listMyTickets, r as loadWidgetConfig, s as saveWidgetEmail, t as clearWidgetEmail, u as ar, v as trackTicket, x as setOidcTokenProvider, y as uploadAttachment } from "./storage-CC5BCsxP.js";
2
+ import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
3
+ import { QueryClient, QueryClientProvider, keepPreviousData, queryOptions, useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
4
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
+ import { createPortal } from "react-dom";
6
+ //#region src/auth/widget-auth.ts
7
+ let authState = {
8
+ isAuthenticated: false,
9
+ isLoading: false
10
+ };
11
+ let listeners = [];
12
+ let oidcInstance = null;
13
+ function notify() {
14
+ for (const listener of listeners) try {
15
+ listener(authState);
16
+ } catch {}
17
+ }
18
+ function setState(update) {
19
+ authState = {
20
+ ...authState,
21
+ ...update
22
+ };
23
+ notify();
24
+ }
25
+ function getAuthState() {
26
+ return authState;
27
+ }
28
+ function onAuthStateChange(listener) {
29
+ listeners.push(listener);
30
+ return () => {
31
+ listeners = listeners.filter((l) => l !== listener);
32
+ };
33
+ }
34
+ async function initWidgetAuth(config) {
35
+ setState({ isLoading: true });
36
+ try {
37
+ const { createOidc } = await import("oidc-spa/core");
38
+ const oidc = await createOidc({
39
+ issuerUri: config.issuerUri,
40
+ clientId: config.clientId
41
+ });
42
+ oidcInstance = oidc;
43
+ if (oidc.isUserLoggedIn) {
44
+ setOidcTokenProvider(() => oidc.getTokens());
45
+ const payload = decodeJwtPayload((await oidc.getTokens()).accessToken);
46
+ setState({
47
+ isAuthenticated: true,
48
+ isLoading: false,
49
+ userEmail: payload?.email,
50
+ userName: payload?.name ?? payload?.preferred_username
51
+ });
52
+ oidc.subscribeToTokensChange(async (newTokens) => {
53
+ const p = decodeJwtPayload(newTokens.accessToken);
54
+ setState({
55
+ userEmail: p?.email ?? authState.userEmail,
56
+ userName: p?.name ?? p?.preferred_username ?? authState.userName
57
+ });
58
+ });
59
+ } else setState({
60
+ isAuthenticated: false,
61
+ isLoading: false
62
+ });
63
+ } catch (err) {
64
+ console.warn("[reqdesk-widget] oidc-spa initialization failed:", err);
65
+ setState({
66
+ isAuthenticated: false,
67
+ isLoading: false
68
+ });
69
+ }
70
+ }
71
+ async function login() {
72
+ if (!oidcInstance) return;
73
+ await oidcInstance.login({ doesCurrentHrefRequiresAuth: false });
74
+ }
75
+ async function logout() {
76
+ if (!oidcInstance) return;
77
+ setOidcTokenProvider(null);
78
+ setState({
79
+ isAuthenticated: false,
80
+ userEmail: void 0,
81
+ userName: void 0
82
+ });
83
+ await oidcInstance.logout({ redirectTo: "current page" });
84
+ }
85
+ function isAuthConfigured() {
86
+ return oidcInstance !== null;
87
+ }
88
+ function decodeJwtPayload(token) {
89
+ try {
90
+ const parts = token.split(".");
91
+ if (parts.length !== 3) return null;
92
+ const payload = atob(parts[1].replace(/-/g, "+").replace(/_/g, "/"));
93
+ return JSON.parse(payload);
94
+ } catch {
95
+ return null;
96
+ }
97
+ }
98
+ //#endregion
99
+ //#region src/react/ReqdeskProvider.tsx
100
+ const ReqdeskContext = createContext(null);
101
+ function useReqdeskContext() {
102
+ const ctx = useContext(ReqdeskContext);
103
+ if (!ctx) throw new Error("useReqdesk must be used within a <ReqdeskProvider>");
104
+ return ctx;
105
+ }
106
+ const widgetQueryClient = new QueryClient({ defaultOptions: {
107
+ queries: {
108
+ staleTime: 300 * 1e3,
109
+ gcTime: 600 * 1e3,
110
+ retry: (failureCount, error) => {
111
+ const status = error?.status ?? error?.statusCode;
112
+ if (status && status >= 400 && status < 500 && status !== 408) return false;
113
+ if (status && status >= 500) return false;
114
+ return failureCount < 2;
115
+ },
116
+ retryDelay: (attemptIndex) => Math.min(1e3 * 2 ** attemptIndex, 3e4),
117
+ refetchOnWindowFocus: false
118
+ },
119
+ mutations: { retry: false }
120
+ } });
121
+ function ReqdeskProvider({ apiKey, auth, theme, language, customer, translations, children }) {
122
+ const initialized = useRef(false);
123
+ const [authState, setAuthState] = useState(getAuthState);
124
+ const saved = initialized.current ? null : loadWidgetConfig(apiKey);
125
+ const resolvedLanguage = language ?? saved?.language ?? "en";
126
+ const resolvedTheme = theme ?? saved?.theme;
127
+ useEffect(() => {
128
+ if (!initialized.current) {
129
+ configureWidgetClient(window.location.origin, apiKey);
130
+ initialized.current = true;
131
+ if (auth) initWidgetAuth(auth);
132
+ }
133
+ }, [apiKey, auth]);
134
+ useEffect(() => {
135
+ return onAuthStateChange(setAuthState);
136
+ }, []);
137
+ useEffect(() => {
138
+ saveWidgetConfig(apiKey, {
139
+ language: resolvedLanguage,
140
+ theme: resolvedTheme
141
+ });
142
+ }, [
143
+ apiKey,
144
+ resolvedLanguage,
145
+ resolvedTheme
146
+ ]);
147
+ const value = useMemo(() => ({
148
+ apiKey,
149
+ auth,
150
+ theme: resolvedTheme,
151
+ language: resolvedLanguage,
152
+ customer,
153
+ translations,
154
+ isAuthenticated: authState.isAuthenticated,
155
+ userEmail: authState.userEmail,
156
+ userName: authState.userName
157
+ }), [
158
+ apiKey,
159
+ auth,
160
+ resolvedTheme,
161
+ resolvedLanguage,
162
+ customer,
163
+ translations,
164
+ authState
165
+ ]);
166
+ return /* @__PURE__ */ jsx(ReqdeskContext.Provider, {
167
+ value,
168
+ children: /* @__PURE__ */ jsx(QueryClientProvider, {
169
+ client: widgetQueryClient,
170
+ children
171
+ })
172
+ });
173
+ }
174
+ //#endregion
175
+ //#region src/react/useReqdesk.ts
176
+ function useReqdesk() {
177
+ useReqdeskContext();
178
+ const [isLoading, setIsLoading] = useState(false);
179
+ const [error, setError] = useState(null);
180
+ return {
181
+ submitTicket: useCallback(async (data) => {
182
+ setIsLoading(true);
183
+ setError(null);
184
+ try {
185
+ return await submitTicket("_current", data);
186
+ } catch (err) {
187
+ const widgetError = err;
188
+ setError(widgetError);
189
+ throw widgetError;
190
+ } finally {
191
+ setIsLoading(false);
192
+ }
193
+ }, []),
194
+ trackTicket: useCallback(async (token) => {
195
+ setIsLoading(true);
196
+ setError(null);
197
+ try {
198
+ return await trackTicket(token);
199
+ } catch (err) {
200
+ const widgetError = err;
201
+ setError(widgetError);
202
+ throw widgetError;
203
+ } finally {
204
+ setIsLoading(false);
205
+ }
206
+ }, []),
207
+ submitTrackingReply: useCallback(async (token, body) => {
208
+ setIsLoading(true);
209
+ setError(null);
210
+ try {
211
+ await submitTrackingReply(token, body);
212
+ } catch (err) {
213
+ const widgetError = err;
214
+ setError(widgetError);
215
+ throw widgetError;
216
+ } finally {
217
+ setIsLoading(false);
218
+ }
219
+ }, []),
220
+ isLoading,
221
+ error
222
+ };
223
+ }
224
+ //#endregion
225
+ //#region src/react/shadow-root.tsx
226
+ function ShadowRoot({ children }) {
227
+ const hostRef = useRef(null);
228
+ const [mountPoint, setMountPoint] = useState(null);
229
+ useEffect(() => {
230
+ const host = hostRef.current;
231
+ if (!host || host.shadowRoot) return;
232
+ const shadow = host.attachShadow({ mode: "open" });
233
+ const style = document.createElement("style");
234
+ style.textContent = getWidgetStyles();
235
+ shadow.appendChild(style);
236
+ const mount = document.createElement("div");
237
+ mount.className = "rqd-root";
238
+ shadow.appendChild(mount);
239
+ setMountPoint(mount);
240
+ }, []);
241
+ return /* @__PURE__ */ jsx("div", {
242
+ ref: hostRef,
243
+ children: mountPoint && createPortal(children, mountPoint)
244
+ });
245
+ }
246
+ //#endregion
247
+ //#region src/react/TicketForm.tsx
248
+ const translations$5 = {
249
+ en,
250
+ ar
251
+ };
252
+ function TicketForm({ mode = "inline", onTicketCreated, onError, className, style }) {
253
+ const ctx = useReqdeskContext();
254
+ const { submitTicket, isLoading } = useReqdesk();
255
+ const [success, setSuccess] = useState(null);
256
+ const [errors, setErrors] = useState({});
257
+ const t = useCallback((key) => {
258
+ if (ctx.translations?.[key]) return ctx.translations[key];
259
+ return (translations$5[ctx.language] ?? translations$5.en)[key] ?? key;
260
+ }, [ctx.language, ctx.translations]);
261
+ const handleSubmit = useCallback(async (e) => {
262
+ e.preventDefault();
263
+ const form = e.currentTarget;
264
+ const formData = new FormData(form);
265
+ const title = formData.get("title")?.trim() ?? "";
266
+ const email = formData.get("email")?.trim() ?? "";
267
+ const newErrors = {};
268
+ if (!title) newErrors.title = t("error.required");
269
+ else if (title.length < 5) newErrors.title = t("error.titleMin");
270
+ if (!email) newErrors.email = t("error.required");
271
+ else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) newErrors.email = t("error.emailInvalid");
272
+ if (Object.keys(newErrors).length > 0) {
273
+ setErrors(newErrors);
274
+ return;
275
+ }
276
+ setErrors({});
277
+ const data = {
278
+ title,
279
+ description: formData.get("description")?.trim() || void 0,
280
+ email,
281
+ priority: formData.get("priority") ?? "medium"
282
+ };
283
+ try {
284
+ const result = await submitTicket(data);
285
+ if (result.trackingToken) saveTrackingToken(ctx.apiKey, result.trackingToken);
286
+ setSuccess(result);
287
+ onTicketCreated?.(result);
288
+ } catch (err) {
289
+ onError?.(err);
290
+ }
291
+ }, [
292
+ submitTicket,
293
+ ctx.apiKey,
294
+ ctx.translations,
295
+ ctx.language,
296
+ onTicketCreated,
297
+ onError,
298
+ t
299
+ ]);
300
+ const cssVars = themeToVars(ctx.theme);
301
+ const content = success ? /* @__PURE__ */ jsxs("div", {
302
+ className: "rqd-success",
303
+ style: {
304
+ textAlign: "center",
305
+ padding: "24px 0"
306
+ },
307
+ children: [
308
+ /* @__PURE__ */ jsx("div", {
309
+ style: {
310
+ fontSize: 48,
311
+ marginBottom: 12
312
+ },
313
+ children: "✅"
314
+ }),
315
+ /* @__PURE__ */ jsx("h3", {
316
+ style: {
317
+ margin: "0 0 8px",
318
+ fontSize: 18
319
+ },
320
+ children: t("success.title")
321
+ }),
322
+ /* @__PURE__ */ jsxs("p", { children: [t("success.ticketNumber"), success.ticketNumber] }),
323
+ success.trackingToken && /* @__PURE__ */ jsxs(Fragment, { children: [
324
+ /* @__PURE__ */ jsx("p", {
325
+ style: {
326
+ fontSize: 13,
327
+ color: "var(--rqd-text-secondary)"
328
+ },
329
+ children: t("success.trackingHint")
330
+ }),
331
+ /* @__PURE__ */ jsx("div", {
332
+ className: "rqd-token-box",
333
+ children: success.trackingToken
334
+ }),
335
+ /* @__PURE__ */ jsx("button", {
336
+ className: "rqd-btn rqd-btn-secondary",
337
+ onClick: () => navigator.clipboard.writeText(success.trackingToken),
338
+ children: t("success.copyToken")
339
+ })
340
+ ] })
341
+ ]
342
+ }) : /* @__PURE__ */ jsxs("form", {
343
+ className: "rqd-form",
344
+ onSubmit: handleSubmit,
345
+ noValidate: true,
346
+ children: [
347
+ /* @__PURE__ */ jsxs("div", {
348
+ className: "rqd-form-group",
349
+ children: [
350
+ /* @__PURE__ */ jsx("label", {
351
+ className: "rqd-label",
352
+ children: t("form.title")
353
+ }),
354
+ /* @__PURE__ */ jsx("input", {
355
+ className: "rqd-input",
356
+ name: "title",
357
+ placeholder: t("form.titlePlaceholder"),
358
+ required: true
359
+ }),
360
+ errors.title && /* @__PURE__ */ jsx("div", {
361
+ className: "rqd-error-text",
362
+ children: errors.title
363
+ })
364
+ ]
365
+ }),
366
+ /* @__PURE__ */ jsxs("div", {
367
+ className: "rqd-form-group",
368
+ children: [/* @__PURE__ */ jsx("label", {
369
+ className: "rqd-label",
370
+ children: t("form.description")
371
+ }), /* @__PURE__ */ jsx("textarea", {
372
+ className: "rqd-textarea",
373
+ name: "description",
374
+ placeholder: t("form.descriptionPlaceholder")
375
+ })]
376
+ }),
377
+ /* @__PURE__ */ jsxs("div", {
378
+ className: "rqd-form-group",
379
+ children: [
380
+ /* @__PURE__ */ jsx("label", {
381
+ className: "rqd-label",
382
+ children: t("form.email")
383
+ }),
384
+ /* @__PURE__ */ jsx("input", {
385
+ className: "rqd-input",
386
+ name: "email",
387
+ type: "email",
388
+ placeholder: t("form.emailPlaceholder"),
389
+ required: true
390
+ }),
391
+ errors.email && /* @__PURE__ */ jsx("div", {
392
+ className: "rqd-error-text",
393
+ children: errors.email
394
+ })
395
+ ]
396
+ }),
397
+ /* @__PURE__ */ jsxs("div", {
398
+ className: "rqd-form-group",
399
+ children: [/* @__PURE__ */ jsx("label", {
400
+ className: "rqd-label",
401
+ children: t("form.priority")
402
+ }), /* @__PURE__ */ jsxs("select", {
403
+ className: "rqd-select",
404
+ name: "priority",
405
+ defaultValue: "medium",
406
+ children: [
407
+ /* @__PURE__ */ jsx("option", {
408
+ value: "low",
409
+ children: t("form.priorityLow")
410
+ }),
411
+ /* @__PURE__ */ jsx("option", {
412
+ value: "medium",
413
+ children: t("form.priorityMedium")
414
+ }),
415
+ /* @__PURE__ */ jsx("option", {
416
+ value: "high",
417
+ children: t("form.priorityHigh")
418
+ }),
419
+ /* @__PURE__ */ jsx("option", {
420
+ value: "urgent",
421
+ children: t("form.priorityUrgent")
422
+ })
423
+ ]
424
+ })]
425
+ }),
426
+ /* @__PURE__ */ jsx("button", {
427
+ className: "rqd-btn rqd-btn-primary",
428
+ type: "submit",
429
+ disabled: isLoading,
430
+ children: isLoading ? t("form.submitting") : t("form.submit")
431
+ })
432
+ ]
433
+ });
434
+ if (mode === "floating") return /* @__PURE__ */ jsx(ShadowRoot, { children: /* @__PURE__ */ jsx("div", {
435
+ className,
436
+ style: {
437
+ ...style,
438
+ cssText: cssVars
439
+ },
440
+ children: /* @__PURE__ */ jsx("div", {
441
+ className: "rqd-body",
442
+ children: content
443
+ })
444
+ }) });
445
+ return /* @__PURE__ */ jsx(ShadowRoot, { children: /* @__PURE__ */ jsx("div", {
446
+ className: `rqd-inline ${className ?? ""}`,
447
+ style: { cssText: cssVars },
448
+ children: /* @__PURE__ */ jsx("div", {
449
+ className: "rqd-body",
450
+ children: content
451
+ })
452
+ }) });
453
+ }
454
+ //#endregion
455
+ //#region src/react/SupportPortal.tsx
456
+ function SupportPortal({ className }) {
457
+ const ctx = useReqdeskContext();
458
+ const { isLoading } = useReqdesk();
459
+ const cssVars = themeToVars(ctx.theme);
460
+ return /* @__PURE__ */ jsx(ShadowRoot, { children: /* @__PURE__ */ jsx("div", {
461
+ className: `rqd-inline ${className ?? ""}`,
462
+ style: { cssText: cssVars },
463
+ children: /* @__PURE__ */ jsx("div", {
464
+ className: "rqd-body",
465
+ children: isLoading ? /* @__PURE__ */ jsx("p", {
466
+ style: {
467
+ textAlign: "center",
468
+ color: "var(--rqd-text-secondary)"
469
+ },
470
+ children: "Loading..."
471
+ }) : /* @__PURE__ */ jsx("p", {
472
+ style: {
473
+ textAlign: "center",
474
+ color: "var(--rqd-text-secondary)",
475
+ padding: "24px 0"
476
+ },
477
+ children: "Support Portal — coming in a future update."
478
+ })
479
+ })
480
+ }) });
481
+ }
482
+ //#endregion
483
+ //#region src/react/views/SubmitTicketView.tsx
484
+ const translations$4 = {
485
+ en,
486
+ ar
487
+ };
488
+ const ALLOWED_MIME_TYPES = new Set([
489
+ "image/jpeg",
490
+ "image/png",
491
+ "image/gif",
492
+ "image/webp",
493
+ "application/pdf",
494
+ "text/plain",
495
+ "text/csv",
496
+ "application/zip",
497
+ "application/x-zip-compressed",
498
+ "application/msword",
499
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
500
+ "application/vnd.ms-excel",
501
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
502
+ "application/vnd.ms-powerpoint",
503
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation"
504
+ ]);
505
+ const BLOCKED_EXTENSIONS = new Set([
506
+ ".exe",
507
+ ".bat",
508
+ ".cmd",
509
+ ".sh",
510
+ ".ps1",
511
+ ".msi",
512
+ ".dll",
513
+ ".scr"
514
+ ]);
515
+ const MAX_FILES = 5;
516
+ const MAX_SIZE_MB = 10;
517
+ function formatFileSize$1(bytes) {
518
+ if (bytes < 1024) return `${bytes} B`;
519
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
520
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
521
+ }
522
+ function SubmitTicketView({ projectId, onSuccess, onError, isAuthenticated, userEmail }) {
523
+ const ctx = useReqdeskContext();
524
+ const fileInputRef = useRef(null);
525
+ const savedEmail = loadWidgetEmail(ctx.apiKey);
526
+ const [errors, setErrors] = useState({});
527
+ const [files, setFiles] = useState([]);
528
+ const [isDragOver, setIsDragOver] = useState(false);
529
+ const [uploadProgress, setUploadProgress] = useState(null);
530
+ const [success, setSuccess] = useState(null);
531
+ const [rememberEmail, setRememberEmail] = useState(!!savedEmail);
532
+ const t = useCallback((key) => {
533
+ if (ctx.translations?.[key]) return ctx.translations[key];
534
+ return (translations$4[ctx.language] ?? translations$4.en)[key] ?? key;
535
+ }, [ctx.language, ctx.translations]);
536
+ const submitMutation = useMutation({
537
+ mutationFn: async ({ data, validFiles }) => {
538
+ const result = await submitTicket(projectId, data);
539
+ if (result.trackingToken) saveTrackingToken(ctx.apiKey, result.trackingToken);
540
+ for (const queued of validFiles) {
541
+ setUploadProgress({
542
+ fileId: queued.id,
543
+ fileName: queued.file.name,
544
+ percent: 0
545
+ });
546
+ await uploadAttachment(result.id, queued.file, (percent) => {
547
+ setUploadProgress({
548
+ fileId: queued.id,
549
+ fileName: queued.file.name,
550
+ percent
551
+ });
552
+ });
553
+ }
554
+ setUploadProgress(null);
555
+ return result;
556
+ },
557
+ onSuccess: (result) => {
558
+ setSuccess(result);
559
+ onSuccess?.(result);
560
+ },
561
+ onError: (err) => {
562
+ setUploadProgress(null);
563
+ onError?.(err);
564
+ }
565
+ });
566
+ function validateFile(file) {
567
+ const ext = "." + file.name.split(".").pop()?.toLowerCase();
568
+ if (BLOCKED_EXTENSIONS.has(ext)) return t("attach.invalidType");
569
+ if (!ALLOWED_MIME_TYPES.has(file.type) && !file.type.startsWith("application/vnd.openxmlformats-officedocument.")) return t("attach.invalidType");
570
+ if (file.size > MAX_SIZE_MB * 1024 * 1024) return t("attach.tooLarge");
571
+ return null;
572
+ }
573
+ function addFiles(newFiles) {
574
+ const toAdd = [];
575
+ for (const file of Array.from(newFiles)) {
576
+ if (files.length + toAdd.length >= MAX_FILES) break;
577
+ const error = validateFile(file);
578
+ toAdd.push({
579
+ file,
580
+ id: crypto.randomUUID(),
581
+ error: error ?? void 0
582
+ });
583
+ }
584
+ setFiles((prev) => [...prev, ...toAdd]);
585
+ }
586
+ function removeFile(id) {
587
+ setFiles((prev) => prev.filter((f) => f.id !== id));
588
+ }
589
+ function handleDragOver(e) {
590
+ e.preventDefault();
591
+ setIsDragOver(true);
592
+ }
593
+ function handleDragLeave(e) {
594
+ e.preventDefault();
595
+ setIsDragOver(false);
596
+ }
597
+ function handleDrop(e) {
598
+ e.preventDefault();
599
+ setIsDragOver(false);
600
+ if (e.dataTransfer.files.length > 0) addFiles(e.dataTransfer.files);
601
+ }
602
+ function handleSubmit(e) {
603
+ e.preventDefault();
604
+ const form = e.currentTarget;
605
+ const formData = new FormData(form);
606
+ const title = formData.get("title")?.trim() ?? "";
607
+ const email = formData.get("email")?.trim() ?? userEmail ?? "";
608
+ const newErrors = {};
609
+ if (!title) newErrors.title = t("error.required");
610
+ else if (title.length < 5) newErrors.title = t("error.titleMin");
611
+ if (!isAuthenticated && email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) newErrors.email = t("error.emailInvalid");
612
+ if (Object.keys(newErrors).length > 0) {
613
+ setErrors(newErrors);
614
+ return;
615
+ }
616
+ setErrors({});
617
+ if (rememberEmail && email) saveWidgetEmail(ctx.apiKey, email);
618
+ const validFiles = files.filter((f) => !f.error);
619
+ const data = {
620
+ title,
621
+ description: formData.get("description")?.trim() || void 0,
622
+ email,
623
+ priority: formData.get("priority") ?? "medium"
624
+ };
625
+ submitMutation.mutate({
626
+ data,
627
+ validFiles
628
+ });
629
+ }
630
+ if (success) return /* @__PURE__ */ jsxs("div", {
631
+ className: "rqd-success",
632
+ style: {
633
+ textAlign: "center",
634
+ padding: "24px 0"
635
+ },
636
+ children: [
637
+ /* @__PURE__ */ jsx("div", {
638
+ style: {
639
+ fontSize: 48,
640
+ marginBottom: 12
641
+ },
642
+ children: "✅"
643
+ }),
644
+ /* @__PURE__ */ jsx("h3", {
645
+ style: {
646
+ margin: "0 0 8px",
647
+ fontSize: 18
648
+ },
649
+ children: t("success.title")
650
+ }),
651
+ /* @__PURE__ */ jsxs("p", { children: [t("success.ticketNumber"), success.ticketNumber] }),
652
+ success.trackingToken && /* @__PURE__ */ jsxs(Fragment, { children: [
653
+ /* @__PURE__ */ jsx("p", {
654
+ style: {
655
+ fontSize: 13,
656
+ color: "var(--rqd-text-secondary)"
657
+ },
658
+ children: t("success.trackingHint")
659
+ }),
660
+ /* @__PURE__ */ jsx("div", {
661
+ className: "rqd-token-box",
662
+ children: success.trackingToken
663
+ }),
664
+ /* @__PURE__ */ jsx("button", {
665
+ className: "rqd-btn rqd-btn-secondary",
666
+ onClick: () => navigator.clipboard.writeText(success.trackingToken),
667
+ children: t("success.copyToken")
668
+ })
669
+ ] })
670
+ ]
671
+ });
672
+ if (submitMutation.isPending && uploadProgress) return /* @__PURE__ */ jsxs("div", {
673
+ style: {
674
+ padding: "24px 0",
675
+ textAlign: "center"
676
+ },
677
+ children: [
678
+ /* @__PURE__ */ jsx("p", {
679
+ style: {
680
+ marginBottom: 12,
681
+ fontWeight: 500
682
+ },
683
+ children: t("attach.uploading")
684
+ }),
685
+ /* @__PURE__ */ jsxs("div", {
686
+ className: "rqd-file-item",
687
+ style: { marginBottom: 8 },
688
+ children: [/* @__PURE__ */ jsx("div", {
689
+ className: "rqd-file-item-info",
690
+ children: /* @__PURE__ */ jsx("span", {
691
+ className: "rqd-file-item-name",
692
+ children: uploadProgress.fileName
693
+ })
694
+ }), /* @__PURE__ */ jsxs("span", {
695
+ style: {
696
+ fontSize: 12,
697
+ color: "var(--rqd-text-secondary)"
698
+ },
699
+ children: [uploadProgress.percent, "%"]
700
+ })]
701
+ }),
702
+ /* @__PURE__ */ jsx("div", {
703
+ className: "rqd-progress",
704
+ children: /* @__PURE__ */ jsx("div", {
705
+ className: "rqd-progress-bar",
706
+ style: { width: `${uploadProgress.percent}%` }
707
+ })
708
+ })
709
+ ]
710
+ });
711
+ return /* @__PURE__ */ jsxs("form", {
712
+ className: "rqd-form",
713
+ onSubmit: handleSubmit,
714
+ noValidate: true,
715
+ children: [
716
+ /* @__PURE__ */ jsxs("div", {
717
+ className: "rqd-form-group",
718
+ children: [
719
+ /* @__PURE__ */ jsx("label", {
720
+ className: "rqd-label",
721
+ children: t("form.title")
722
+ }),
723
+ /* @__PURE__ */ jsx("input", {
724
+ className: "rqd-input",
725
+ name: "title",
726
+ placeholder: t("form.titlePlaceholder"),
727
+ required: true
728
+ }),
729
+ errors.title && /* @__PURE__ */ jsx("div", {
730
+ className: "rqd-error-text",
731
+ children: errors.title
732
+ })
733
+ ]
734
+ }),
735
+ /* @__PURE__ */ jsxs("div", {
736
+ className: "rqd-form-group",
737
+ children: [/* @__PURE__ */ jsx("label", {
738
+ className: "rqd-label",
739
+ children: t("form.description")
740
+ }), /* @__PURE__ */ jsx("textarea", {
741
+ className: "rqd-textarea",
742
+ name: "description",
743
+ placeholder: t("form.descriptionPlaceholder")
744
+ })]
745
+ }),
746
+ !isAuthenticated && /* @__PURE__ */ jsxs("div", {
747
+ className: "rqd-form-group",
748
+ children: [
749
+ /* @__PURE__ */ jsx("label", {
750
+ className: "rqd-label",
751
+ children: t("form.email")
752
+ }),
753
+ /* @__PURE__ */ jsx("input", {
754
+ className: "rqd-input",
755
+ name: "email",
756
+ type: "email",
757
+ placeholder: t("form.emailPlaceholder"),
758
+ defaultValue: userEmail ?? savedEmail ?? ""
759
+ }),
760
+ errors.email && /* @__PURE__ */ jsx("div", {
761
+ className: "rqd-error-text",
762
+ children: errors.email
763
+ }),
764
+ /* @__PURE__ */ jsxs("label", {
765
+ className: "rqd-checkbox-label",
766
+ style: { marginTop: 6 },
767
+ children: [/* @__PURE__ */ jsx("input", {
768
+ type: "checkbox",
769
+ checked: rememberEmail,
770
+ onChange: (e) => setRememberEmail(e.target.checked)
771
+ }), t("mytickets.rememberMe")]
772
+ })
773
+ ]
774
+ }),
775
+ /* @__PURE__ */ jsxs("div", {
776
+ className: "rqd-form-group",
777
+ children: [/* @__PURE__ */ jsx("label", {
778
+ className: "rqd-label",
779
+ children: t("form.priority")
780
+ }), /* @__PURE__ */ jsxs("select", {
781
+ className: "rqd-select",
782
+ name: "priority",
783
+ defaultValue: "medium",
784
+ children: [
785
+ /* @__PURE__ */ jsx("option", {
786
+ value: "low",
787
+ children: t("form.priorityLow")
788
+ }),
789
+ /* @__PURE__ */ jsx("option", {
790
+ value: "medium",
791
+ children: t("form.priorityMedium")
792
+ }),
793
+ /* @__PURE__ */ jsx("option", {
794
+ value: "high",
795
+ children: t("form.priorityHigh")
796
+ }),
797
+ /* @__PURE__ */ jsx("option", {
798
+ value: "urgent",
799
+ children: t("form.priorityUrgent")
800
+ })
801
+ ]
802
+ })]
803
+ }),
804
+ /* @__PURE__ */ jsxs("div", {
805
+ className: "rqd-form-group",
806
+ children: [
807
+ /* @__PURE__ */ jsx("label", {
808
+ className: "rqd-label",
809
+ children: t("form.attachment")
810
+ }),
811
+ /* @__PURE__ */ jsxs("div", {
812
+ className: `rqd-dropzone${isDragOver ? " rqd-dropzone-active" : ""}`,
813
+ onDragOver: handleDragOver,
814
+ onDragLeave: handleDragLeave,
815
+ onDrop: handleDrop,
816
+ onClick: () => fileInputRef.current?.click(),
817
+ children: [isDragOver ? t("attach.dropzoneActive") : t("attach.dropzone"), /* @__PURE__ */ jsx("input", {
818
+ ref: fileInputRef,
819
+ type: "file",
820
+ multiple: true,
821
+ style: { display: "none" },
822
+ onChange: (e) => {
823
+ if (e.target.files) addFiles(e.target.files);
824
+ e.target.value = "";
825
+ }
826
+ })]
827
+ }),
828
+ files.length > 0 && /* @__PURE__ */ jsx("div", {
829
+ className: "rqd-file-list",
830
+ children: files.map((f) => /* @__PURE__ */ jsxs("div", {
831
+ className: "rqd-file-item",
832
+ children: [/* @__PURE__ */ jsxs("div", {
833
+ className: "rqd-file-item-info",
834
+ children: [/* @__PURE__ */ jsx("span", {
835
+ className: "rqd-file-item-name",
836
+ children: f.file.name
837
+ }), /* @__PURE__ */ jsxs("span", {
838
+ className: "rqd-file-item-size",
839
+ children: [formatFileSize$1(f.file.size), f.error && /* @__PURE__ */ jsx("span", {
840
+ style: {
841
+ color: "#e74c3c",
842
+ marginLeft: 6
843
+ },
844
+ children: f.error
845
+ })]
846
+ })]
847
+ }), /* @__PURE__ */ jsx("button", {
848
+ className: "rqd-file-remove",
849
+ onClick: () => removeFile(f.id),
850
+ type: "button",
851
+ children: "×"
852
+ })]
853
+ }, f.id))
854
+ })
855
+ ]
856
+ }),
857
+ /* @__PURE__ */ jsx("button", {
858
+ className: "rqd-btn rqd-btn-primary",
859
+ type: "submit",
860
+ disabled: submitMutation.isPending,
861
+ children: submitMutation.isPending ? t("form.submitting") : t("form.submit")
862
+ })
863
+ ]
864
+ });
865
+ }
866
+ //#endregion
867
+ //#region src/react/queries.ts
868
+ const widgetTicketDetailOptions = (ticketId) => queryOptions({
869
+ queryKey: ["widget-ticket", ticketId],
870
+ queryFn: () => getTicketDetail(ticketId),
871
+ staleTime: 6e4,
872
+ enabled: !!ticketId
873
+ });
874
+ const widgetMyTicketsOptions = (projectId, userId) => queryOptions({
875
+ queryKey: [
876
+ "widget-tickets",
877
+ projectId,
878
+ userId
879
+ ],
880
+ queryFn: () => listMyTickets(projectId, userId),
881
+ staleTime: 3e4,
882
+ placeholderData: keepPreviousData,
883
+ enabled: !!userId
884
+ });
885
+ const widgetUserOptions = (projectId, email) => queryOptions({
886
+ queryKey: [
887
+ "widget-user",
888
+ projectId,
889
+ email
890
+ ],
891
+ queryFn: () => resolveWidgetUser(projectId, email),
892
+ staleTime: 5 * 6e4,
893
+ enabled: !!email
894
+ });
895
+ //#endregion
896
+ //#region src/react/views/MyTicketsView.tsx
897
+ const translations$3 = {
898
+ en,
899
+ ar
900
+ };
901
+ function MyTicketsView({ projectId, onSelectTicket, isAuthenticated, userEmail }) {
902
+ const ctx = useReqdeskContext();
903
+ const savedEmail = loadWidgetEmail(ctx.apiKey);
904
+ const [email, setEmail] = useState(userEmail ?? savedEmail ?? "");
905
+ const [rememberMe, setRememberMe] = useState(!!savedEmail);
906
+ const [submittedEmail, setSubmittedEmail] = useState(isAuthenticated && userEmail ? userEmail : savedEmail);
907
+ const [tokenTickets, setTokenTickets] = useState([]);
908
+ const t = useCallback((key) => {
909
+ if (ctx.translations?.[key]) return ctx.translations[key];
910
+ return (translations$3[ctx.language] ?? translations$3.en)[key] ?? key;
911
+ }, [ctx.language, ctx.translations]);
912
+ const { data: widgetUser } = useQuery(widgetUserOptions(projectId, submittedEmail ?? ""));
913
+ const { data: emailTickets = [], isLoading } = useQuery(widgetMyTicketsOptions(projectId, widgetUser?.userId ?? ""));
914
+ useEffect(() => {
915
+ const tokens = getTrackingTokens(ctx.apiKey);
916
+ if (tokens.length === 0) return;
917
+ Promise.allSettled(tokens.slice(0, 5).map((tk) => trackTicket(tk))).then((results) => {
918
+ const tracked = [];
919
+ for (const r of results) if (r.status === "fulfilled") {
920
+ const t = r.value;
921
+ if (!emailTickets.some((et) => et.id === t.id)) tracked.push({
922
+ id: t.id,
923
+ ticketNumber: t.ticketNumber,
924
+ title: t.title,
925
+ status: t.status,
926
+ priority: t.priority,
927
+ createdAt: t.createdAt
928
+ });
929
+ }
930
+ setTokenTickets(tracked);
931
+ });
932
+ }, [ctx.apiKey, emailTickets]);
933
+ const allTickets = [...emailTickets, ...tokenTickets];
934
+ function handleSubmit(e) {
935
+ e.preventDefault();
936
+ const trimmed = email.trim();
937
+ if (!trimmed || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmed)) return;
938
+ if (rememberMe) saveWidgetEmail(ctx.apiKey, trimmed);
939
+ setSubmittedEmail(trimmed);
940
+ }
941
+ function formatDate(iso) {
942
+ try {
943
+ return new Date(iso).toLocaleDateString();
944
+ } catch {
945
+ return iso;
946
+ }
947
+ }
948
+ if (!submittedEmail && !isAuthenticated) return /* @__PURE__ */ jsxs("div", {
949
+ className: "rqd-email-form",
950
+ children: [/* @__PURE__ */ jsx("p", { children: t("mytickets.emailPrompt") }), /* @__PURE__ */ jsxs("form", {
951
+ onSubmit: handleSubmit,
952
+ children: [
953
+ /* @__PURE__ */ jsx("div", {
954
+ className: "rqd-form-group",
955
+ children: /* @__PURE__ */ jsx("input", {
956
+ className: "rqd-input",
957
+ type: "email",
958
+ value: email,
959
+ onChange: (e) => setEmail(e.target.value),
960
+ placeholder: t("mytickets.emailPlaceholder"),
961
+ required: true
962
+ })
963
+ }),
964
+ /* @__PURE__ */ jsxs("label", {
965
+ className: "rqd-checkbox-label",
966
+ children: [/* @__PURE__ */ jsx("input", {
967
+ type: "checkbox",
968
+ checked: rememberMe,
969
+ onChange: (e) => setRememberMe(e.target.checked)
970
+ }), t("mytickets.rememberMe")]
971
+ }),
972
+ /* @__PURE__ */ jsx("button", {
973
+ className: "rqd-btn rqd-btn-primary",
974
+ type: "submit",
975
+ style: { marginTop: 12 },
976
+ children: t("mytickets.lookup")
977
+ })
978
+ ]
979
+ })]
980
+ });
981
+ if (isLoading) return /* @__PURE__ */ jsx("div", {
982
+ className: "rqd-loading",
983
+ children: t("mytickets.lookingUp")
984
+ });
985
+ if (allTickets.length === 0) return /* @__PURE__ */ jsxs("div", {
986
+ className: "rqd-placeholder",
987
+ children: [/* @__PURE__ */ jsx("p", { children: submittedEmail && !widgetUser ? t("mytickets.noAccount") : t("mytickets.noTickets") }), submittedEmail && !isAuthenticated && /* @__PURE__ */ jsx("button", {
988
+ className: "rqd-btn rqd-btn-secondary",
989
+ style: {
990
+ width: "auto",
991
+ padding: "8px 20px"
992
+ },
993
+ onClick: () => setSubmittedEmail(null),
994
+ children: t("tracker.back")
995
+ })]
996
+ });
997
+ return /* @__PURE__ */ jsx("div", {
998
+ className: "rqd-ticket-list",
999
+ children: allTickets.map((ticket) => /* @__PURE__ */ jsxs("div", {
1000
+ className: "rqd-ticket-card",
1001
+ onClick: () => onSelectTicket(ticket.id),
1002
+ children: [
1003
+ /* @__PURE__ */ jsxs("div", {
1004
+ className: "rqd-ticket-card-top",
1005
+ children: [/* @__PURE__ */ jsx("span", {
1006
+ className: "rqd-ticket-card-number",
1007
+ children: ticket.ticketNumber
1008
+ }), /* @__PURE__ */ jsx("span", {
1009
+ className: "rqd-badge",
1010
+ children: ticket.status
1011
+ })]
1012
+ }),
1013
+ /* @__PURE__ */ jsx("span", {
1014
+ className: "rqd-ticket-card-title",
1015
+ children: ticket.title
1016
+ }),
1017
+ /* @__PURE__ */ jsx("span", {
1018
+ className: "rqd-ticket-card-date",
1019
+ children: formatDate(ticket.createdAt)
1020
+ })
1021
+ ]
1022
+ }, ticket.id))
1023
+ });
1024
+ }
1025
+ //#endregion
1026
+ //#region src/react/views/TicketDetailView.tsx
1027
+ const translations$2 = {
1028
+ en,
1029
+ ar
1030
+ };
1031
+ function formatDate(iso) {
1032
+ try {
1033
+ return new Date(iso).toLocaleString();
1034
+ } catch {
1035
+ return iso;
1036
+ }
1037
+ }
1038
+ function formatFileSize(bytes) {
1039
+ if (bytes < 1024) return `${bytes} B`;
1040
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
1041
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
1042
+ }
1043
+ function TicketDetailView({ ticketId, onBack }) {
1044
+ const ctx = useReqdeskContext();
1045
+ const queryClient = useQueryClient();
1046
+ const [replyBody, setReplyBody] = useState("");
1047
+ const t = useCallback((key) => {
1048
+ if (ctx.translations?.[key]) return ctx.translations[key];
1049
+ return (translations$2[ctx.language] ?? translations$2.en)[key] ?? key;
1050
+ }, [ctx.language, ctx.translations]);
1051
+ const { data: ticket, isLoading, error } = useQuery(widgetTicketDetailOptions(ticketId));
1052
+ const replyMutation = useMutation({
1053
+ mutationFn: (body) => submitReply(ticketId, body),
1054
+ onMutate: async (body) => {
1055
+ await queryClient.cancelQueries({ queryKey: ["widget-ticket", ticketId] });
1056
+ const previous = queryClient.getQueryData(["widget-ticket", ticketId]);
1057
+ if (previous) queryClient.setQueryData(["widget-ticket", ticketId], {
1058
+ ...previous,
1059
+ replies: [...previous.replies, {
1060
+ id: `optimistic-${Date.now()}`,
1061
+ body,
1062
+ authorName: ctx.userName ?? ctx.userEmail ?? "You",
1063
+ isStaff: false,
1064
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
1065
+ }]
1066
+ });
1067
+ return { previous };
1068
+ },
1069
+ onSuccess: () => {
1070
+ setReplyBody("");
1071
+ queryClient.invalidateQueries({ queryKey: ["widget-ticket", ticketId] });
1072
+ },
1073
+ onError: (_err, _vars, context) => {
1074
+ if (context?.previous) queryClient.setQueryData(["widget-ticket", ticketId], context.previous);
1075
+ }
1076
+ });
1077
+ if (isLoading) return /* @__PURE__ */ jsx("div", {
1078
+ className: "rqd-loading",
1079
+ children: t("detail.loading")
1080
+ });
1081
+ if (error || !ticket) return /* @__PURE__ */ jsxs("div", {
1082
+ className: "rqd-placeholder",
1083
+ children: [/* @__PURE__ */ jsx("p", { children: t("error.generic") }), /* @__PURE__ */ jsx("button", {
1084
+ className: "rqd-btn rqd-btn-secondary",
1085
+ style: {
1086
+ width: "auto",
1087
+ padding: "8px 20px"
1088
+ },
1089
+ onClick: onBack,
1090
+ children: t("tracker.back")
1091
+ })]
1092
+ });
1093
+ return /* @__PURE__ */ jsxs("div", { children: [
1094
+ /* @__PURE__ */ jsxs("div", {
1095
+ className: "rqd-ticket-header",
1096
+ children: [/* @__PURE__ */ jsx("h3", { children: ticket.title }), /* @__PURE__ */ jsxs("div", {
1097
+ className: "rqd-ticket-header-meta",
1098
+ children: [
1099
+ /* @__PURE__ */ jsx("span", {
1100
+ className: "rqd-ticket-card-number",
1101
+ children: ticket.ticketNumber
1102
+ }),
1103
+ /* @__PURE__ */ jsx("span", {
1104
+ className: "rqd-badge",
1105
+ children: ticket.status
1106
+ }),
1107
+ /* @__PURE__ */ jsx("span", {
1108
+ className: "rqd-badge",
1109
+ style: {
1110
+ background: "var(--rqd-bg-secondary)",
1111
+ color: "var(--rqd-text-secondary)"
1112
+ },
1113
+ children: ticket.priority
1114
+ }),
1115
+ /* @__PURE__ */ jsx("span", {
1116
+ className: "rqd-ticket-card-date",
1117
+ children: formatDate(ticket.createdAt)
1118
+ })
1119
+ ]
1120
+ })]
1121
+ }),
1122
+ ticket.description && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
1123
+ className: "rqd-section-title",
1124
+ children: t("detail.description")
1125
+ }), /* @__PURE__ */ jsx("div", {
1126
+ className: "rqd-ticket-desc",
1127
+ children: ticket.description
1128
+ })] }),
1129
+ ticket.attachments.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
1130
+ className: "rqd-section-title",
1131
+ children: t("detail.attachments")
1132
+ }), /* @__PURE__ */ jsx("div", {
1133
+ className: "rqd-attachment-list",
1134
+ children: ticket.attachments.map((a) => /* @__PURE__ */ jsxs("div", {
1135
+ className: "rqd-attachment-item",
1136
+ children: [/* @__PURE__ */ jsxs("div", {
1137
+ className: "rqd-file-item-info",
1138
+ children: [/* @__PURE__ */ jsx("span", {
1139
+ className: "rqd-file-item-name",
1140
+ children: a.fileName
1141
+ }), /* @__PURE__ */ jsx("span", {
1142
+ className: "rqd-file-item-size",
1143
+ children: formatFileSize(a.fileSize)
1144
+ })]
1145
+ }), a.downloadUrl && /* @__PURE__ */ jsx("a", {
1146
+ href: a.downloadUrl,
1147
+ target: "_blank",
1148
+ rel: "noopener noreferrer",
1149
+ children: t("attach.download")
1150
+ })]
1151
+ }, a.id))
1152
+ })] }),
1153
+ /* @__PURE__ */ jsx("div", {
1154
+ className: "rqd-section-title",
1155
+ children: t("detail.replies")
1156
+ }),
1157
+ ticket.replies.length === 0 ? /* @__PURE__ */ jsx("p", {
1158
+ style: {
1159
+ color: "var(--rqd-text-secondary)",
1160
+ fontSize: 13,
1161
+ margin: "0 0 12px"
1162
+ },
1163
+ children: t("detail.noReplies")
1164
+ }) : /* @__PURE__ */ jsx("div", { children: ticket.replies.map((reply) => /* @__PURE__ */ jsxs("div", {
1165
+ className: "rqd-reply",
1166
+ children: [/* @__PURE__ */ jsxs("div", {
1167
+ className: "rqd-reply-header",
1168
+ children: [/* @__PURE__ */ jsx("span", {
1169
+ className: reply.isStaff ? "rqd-reply-staff" : "",
1170
+ children: reply.isStaff ? `${reply.authorName} \u00b7 ${t("detail.staff")}` : reply.authorName
1171
+ }), /* @__PURE__ */ jsx("span", { children: formatDate(reply.createdAt) })]
1172
+ }), /* @__PURE__ */ jsx("div", {
1173
+ className: "rqd-reply-body",
1174
+ children: reply.body
1175
+ })]
1176
+ }, reply.id)) }),
1177
+ /* @__PURE__ */ jsxs("div", {
1178
+ className: "rqd-reply-compose",
1179
+ children: [/* @__PURE__ */ jsx("textarea", {
1180
+ className: "rqd-textarea",
1181
+ value: replyBody,
1182
+ onChange: (e) => setReplyBody(e.target.value),
1183
+ placeholder: t("detail.replyPlaceholder"),
1184
+ rows: 3
1185
+ }), /* @__PURE__ */ jsx("button", {
1186
+ className: "rqd-btn rqd-btn-primary",
1187
+ onClick: () => replyMutation.mutate(replyBody.trim()),
1188
+ disabled: !replyBody.trim() || replyMutation.isPending,
1189
+ children: replyMutation.isPending ? t("detail.sending") : t("detail.sendReply")
1190
+ })]
1191
+ })
1192
+ ] });
1193
+ }
1194
+ //#endregion
1195
+ //#region src/react/views/TrackTicketView.tsx
1196
+ const translations$1 = {
1197
+ en,
1198
+ ar
1199
+ };
1200
+ function TrackTicketView({ onTrackSuccess }) {
1201
+ const ctx = useReqdeskContext();
1202
+ const [token, setToken] = useState("");
1203
+ const savedTokens = getTrackingTokens(ctx.apiKey);
1204
+ const t = useCallback((key) => {
1205
+ if (ctx.translations?.[key]) return ctx.translations[key];
1206
+ return (translations$1[ctx.language] ?? translations$1.en)[key] ?? key;
1207
+ }, [ctx.language, ctx.translations]);
1208
+ const trackMutation = useMutation({
1209
+ mutationFn: (trackToken) => trackTicket(trackToken.trim()),
1210
+ onSuccess: (result) => onTrackSuccess(result.id)
1211
+ });
1212
+ function handleSubmit(e) {
1213
+ e.preventDefault();
1214
+ if (token.trim()) trackMutation.mutate(token);
1215
+ }
1216
+ return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("form", {
1217
+ onSubmit: handleSubmit,
1218
+ children: [/* @__PURE__ */ jsxs("div", {
1219
+ className: "rqd-form-group",
1220
+ children: [/* @__PURE__ */ jsx("input", {
1221
+ className: "rqd-input",
1222
+ value: token,
1223
+ onChange: (e) => setToken(e.target.value),
1224
+ placeholder: t("track.tokenPlaceholder"),
1225
+ required: true
1226
+ }), trackMutation.isError && /* @__PURE__ */ jsx("div", {
1227
+ className: "rqd-error-text",
1228
+ children: t("track.invalidToken")
1229
+ })]
1230
+ }), /* @__PURE__ */ jsx("button", {
1231
+ className: "rqd-btn rqd-btn-primary",
1232
+ type: "submit",
1233
+ disabled: trackMutation.isPending || !token.trim(),
1234
+ children: trackMutation.isPending ? t("track.tracking") : t("track.submit")
1235
+ })]
1236
+ }), savedTokens.length > 0 && /* @__PURE__ */ jsxs("div", {
1237
+ style: { marginTop: 20 },
1238
+ children: [/* @__PURE__ */ jsx("div", {
1239
+ className: "rqd-section-title",
1240
+ children: t("track.recentTickets")
1241
+ }), /* @__PURE__ */ jsx("div", {
1242
+ className: "rqd-ticket-list",
1243
+ children: savedTokens.slice(0, 5).map((tk) => /* @__PURE__ */ jsx("button", {
1244
+ className: "rqd-ticket-card",
1245
+ style: {
1246
+ border: "none",
1247
+ textAlign: "start",
1248
+ width: "100%"
1249
+ },
1250
+ onClick: () => trackMutation.mutate(tk),
1251
+ children: /* @__PURE__ */ jsxs("span", {
1252
+ className: "rqd-ticket-card-number",
1253
+ style: {
1254
+ fontFamily: "monospace",
1255
+ fontSize: 12
1256
+ },
1257
+ children: [tk.slice(0, 20), "..."]
1258
+ })
1259
+ }, tk))
1260
+ })]
1261
+ })] });
1262
+ }
1263
+ //#endregion
1264
+ //#region src/react/FloatingWidget.tsx
1265
+ const translations = {
1266
+ en,
1267
+ ar
1268
+ };
1269
+ const ICONS = {
1270
+ chat: "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",
1271
+ close: "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",
1272
+ newTicket: "M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z",
1273
+ tickets: "M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm4 18H6V4h7v5h5v11zM8 15.01l1.41 1.41L11 14.84V19h2v-4.16l1.59 1.59L16 15.01 12.01 11z",
1274
+ track: "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z",
1275
+ kb: "M21 5c-1.11-.35-2.33-.5-3.5-.5-1.95 0-4.05.4-5.5 1.5-1.45-1.1-3.55-1.5-5.5-1.5S2.45 4.9 1 6v14.65c0 .25.25.5.5.5.1 0 .15-.05.25-.05C3.1 20.45 5.05 20 6.5 20c1.95 0 4.05.4 5.5 1.5 1.35-.85 3.8-1.5 5.5-1.5 1.65 0 3.35.3 4.75 1.05.1.05.15.05.25.05.25 0 .5-.25.5-.5V6c-.6-.45-1.25-.75-2-1zm0 13.5c-1.1-.35-2.3-.5-3.5-.5-1.7 0-4.15.65-5.5 1.5V8c1.35-.85 3.8-1.5 5.5-1.5 1.2 0 2.4.15 3.5.5v11.5z",
1276
+ back: "M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z",
1277
+ backRtl: "M4 11h12.17l-5.59-5.59L12 4l8 8-8 8-1.41-1.41L16.17 13H4v-2z",
1278
+ settings: "M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58a.49.49 0 00.12-.61l-1.92-3.32a.49.49 0 00-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54a.484.484 0 00-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96a.49.49 0 00-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.07.62-.07.94s.02.64.07.94l-2.03 1.58a.49.49 0 00-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6A3.6 3.6 0 1115.6 12 3.6 3.6 0 0112 15.6z"
1279
+ };
1280
+ function SvgIcon({ path, size = 20 }) {
1281
+ return /* @__PURE__ */ jsx("svg", {
1282
+ width: size,
1283
+ height: size,
1284
+ viewBox: "0 0 24 24",
1285
+ fill: "currentColor",
1286
+ style: {
1287
+ display: "block",
1288
+ flexShrink: 0
1289
+ },
1290
+ children: /* @__PURE__ */ jsx("path", { d: path })
1291
+ });
1292
+ }
1293
+ function FloatingWidget({ position = "bottom-right", contained = false, onTicketCreated, onError }) {
1294
+ const ctx = useReqdeskContext();
1295
+ const [isOpen, setIsOpen] = useState(false);
1296
+ const [view, setView] = useState("home");
1297
+ const [selectedTicketId, setSelectedTicketId] = useState(null);
1298
+ const [emailCleared, setEmailCleared] = useState(false);
1299
+ const savedPrefs = loadWidgetConfig(ctx.apiKey);
1300
+ const [userLang, setUserLang] = useState(savedPrefs?.language ?? null);
1301
+ const [userThemeMode, setUserThemeMode] = useState(savedPrefs?.theme?.mode ?? null);
1302
+ const [userColor, setUserColor] = useState(savedPrefs?.theme?.primaryColor ?? null);
1303
+ const activeLang = userLang ?? ctx.language ?? "en";
1304
+ const activeThemeMode = userThemeMode ?? ctx.theme?.mode ?? "light";
1305
+ const activeColor = userColor ?? ctx.theme?.primaryColor ?? "#42b983";
1306
+ const activeTheme = {
1307
+ ...ctx.theme,
1308
+ mode: activeThemeMode,
1309
+ primaryColor: activeColor
1310
+ };
1311
+ const t = useCallback((key) => {
1312
+ if (ctx.translations?.[key]) return ctx.translations[key];
1313
+ return (translations[activeLang] ?? translations.en)[key] ?? key;
1314
+ }, [activeLang, ctx.translations]);
1315
+ const isRtl = activeLang === "ar";
1316
+ const cssVars = themeToVars(activeTheme);
1317
+ const posClass = `rqd-${position}`;
1318
+ const containedClass = contained ? " rqd-contained" : "";
1319
+ const brandName = ctx.theme?.brandName;
1320
+ const brandLogo = ctx.theme?.logo;
1321
+ const hideBranding = ctx.theme?.hideBranding === true;
1322
+ const projectId = "_current";
1323
+ function toggleOpen() {
1324
+ setIsOpen((prev) => !prev);
1325
+ if (!isOpen) {
1326
+ setView("home");
1327
+ setSelectedTicketId(null);
1328
+ }
1329
+ }
1330
+ function goHome() {
1331
+ setView("home");
1332
+ setSelectedTicketId(null);
1333
+ setEmailCleared(false);
1334
+ }
1335
+ function handleLangChange(lang) {
1336
+ setUserLang(lang);
1337
+ saveWidgetConfig(ctx.apiKey, { language: lang });
1338
+ }
1339
+ function handleThemeChange(mode) {
1340
+ setUserThemeMode(mode);
1341
+ saveWidgetConfig(ctx.apiKey, { theme: {
1342
+ ...ctx.theme,
1343
+ mode,
1344
+ primaryColor: activeColor
1345
+ } });
1346
+ }
1347
+ function handleColorChange(color) {
1348
+ setUserColor(color);
1349
+ saveWidgetConfig(ctx.apiKey, { theme: {
1350
+ ...ctx.theme,
1351
+ mode: activeThemeMode,
1352
+ primaryColor: color
1353
+ } });
1354
+ }
1355
+ const COLOR_PRESETS = [
1356
+ {
1357
+ color: "#42b983",
1358
+ label: "Green"
1359
+ },
1360
+ {
1361
+ color: "#3b82f6",
1362
+ label: "Blue"
1363
+ },
1364
+ {
1365
+ color: "#8b5cf6",
1366
+ label: "Purple"
1367
+ },
1368
+ {
1369
+ color: "#f59e0b",
1370
+ label: "Orange"
1371
+ },
1372
+ {
1373
+ color: "#ef4444",
1374
+ label: "Red"
1375
+ }
1376
+ ];
1377
+ function handleClearEmail() {
1378
+ clearWidgetEmail(ctx.apiKey);
1379
+ setEmailCleared(true);
1380
+ setTimeout(() => setEmailCleared(false), 2e3);
1381
+ }
1382
+ function openTicketDetail(ticketId) {
1383
+ setSelectedTicketId(ticketId);
1384
+ setView("ticket-detail");
1385
+ }
1386
+ const menuItems = [
1387
+ {
1388
+ key: "new-ticket",
1389
+ icon: ICONS.newTicket,
1390
+ label: t("menu.newTicket"),
1391
+ desc: t("menu.newTicketDesc")
1392
+ },
1393
+ {
1394
+ key: "my-tickets",
1395
+ icon: ICONS.tickets,
1396
+ label: t("menu.myTickets"),
1397
+ desc: t("menu.myTicketsDesc")
1398
+ },
1399
+ {
1400
+ key: "track",
1401
+ icon: ICONS.track,
1402
+ label: t("menu.trackTicket"),
1403
+ desc: t("menu.trackTicketDesc")
1404
+ },
1405
+ {
1406
+ key: "kb",
1407
+ icon: ICONS.kb,
1408
+ label: t("menu.knowledgeBase"),
1409
+ desc: t("menu.knowledgeBaseDesc")
1410
+ },
1411
+ {
1412
+ key: "preferences",
1413
+ icon: ICONS.settings,
1414
+ label: t("menu.preferences"),
1415
+ desc: t("menu.preferencesDesc")
1416
+ }
1417
+ ];
1418
+ function renderViewContent() {
1419
+ switch (view) {
1420
+ case "home": return /* @__PURE__ */ jsx("div", {
1421
+ className: "rqd-menu",
1422
+ children: menuItems.map((item) => /* @__PURE__ */ jsxs("button", {
1423
+ className: "rqd-menu-item",
1424
+ onClick: () => setView(item.key),
1425
+ children: [/* @__PURE__ */ jsx("div", {
1426
+ className: "rqd-menu-icon",
1427
+ children: /* @__PURE__ */ jsx(SvgIcon, {
1428
+ path: item.icon,
1429
+ size: 22
1430
+ })
1431
+ }), /* @__PURE__ */ jsxs("div", {
1432
+ className: "rqd-menu-text",
1433
+ children: [/* @__PURE__ */ jsx("span", {
1434
+ className: "rqd-menu-label",
1435
+ children: item.label
1436
+ }), /* @__PURE__ */ jsx("span", {
1437
+ className: "rqd-menu-desc",
1438
+ children: item.desc
1439
+ })]
1440
+ })]
1441
+ }, item.key))
1442
+ });
1443
+ case "new-ticket": return /* @__PURE__ */ jsx(SubmitTicketView, {
1444
+ projectId,
1445
+ onSuccess: (result) => {
1446
+ onTicketCreated?.(result);
1447
+ },
1448
+ onError,
1449
+ isAuthenticated: ctx.isAuthenticated,
1450
+ userEmail: ctx.userEmail
1451
+ });
1452
+ case "my-tickets": return /* @__PURE__ */ jsx(MyTicketsView, {
1453
+ projectId,
1454
+ onSelectTicket: openTicketDetail,
1455
+ isAuthenticated: ctx.isAuthenticated,
1456
+ userEmail: ctx.userEmail
1457
+ });
1458
+ case "ticket-detail":
1459
+ if (!selectedTicketId) return null;
1460
+ return /* @__PURE__ */ jsx(TicketDetailView, {
1461
+ ticketId: selectedTicketId,
1462
+ onBack: () => setView("my-tickets")
1463
+ });
1464
+ case "track": return /* @__PURE__ */ jsx(TrackTicketView, { onTrackSuccess: openTicketDetail });
1465
+ case "kb": return /* @__PURE__ */ jsxs("div", {
1466
+ className: "rqd-placeholder",
1467
+ children: [/* @__PURE__ */ jsx(SvgIcon, {
1468
+ path: ICONS.kb,
1469
+ size: 40
1470
+ }), /* @__PURE__ */ jsx("p", { children: t("menu.kbPlaceholder") })]
1471
+ });
1472
+ case "preferences": return /* @__PURE__ */ jsxs("div", {
1473
+ className: "rqd-prefs",
1474
+ children: [
1475
+ /* @__PURE__ */ jsxs("div", {
1476
+ className: "rqd-prefs-group",
1477
+ children: [/* @__PURE__ */ jsx("span", {
1478
+ className: "rqd-prefs-label",
1479
+ children: t("prefs.language")
1480
+ }), /* @__PURE__ */ jsxs("div", {
1481
+ className: "rqd-prefs-options",
1482
+ children: [/* @__PURE__ */ jsx("button", {
1483
+ className: `rqd-prefs-option${activeLang === "en" ? " rqd-active" : ""}`,
1484
+ onClick: () => handleLangChange("en"),
1485
+ children: "English"
1486
+ }), /* @__PURE__ */ jsx("button", {
1487
+ className: `rqd-prefs-option${activeLang === "ar" ? " rqd-active" : ""}`,
1488
+ onClick: () => handleLangChange("ar"),
1489
+ children: "العربية"
1490
+ })]
1491
+ })]
1492
+ }),
1493
+ /* @__PURE__ */ jsxs("div", {
1494
+ className: "rqd-prefs-group",
1495
+ children: [/* @__PURE__ */ jsx("span", {
1496
+ className: "rqd-prefs-label",
1497
+ children: t("prefs.theme")
1498
+ }), /* @__PURE__ */ jsx("div", {
1499
+ className: "rqd-prefs-options",
1500
+ children: [
1501
+ "light",
1502
+ "dark",
1503
+ "auto"
1504
+ ].map((mode) => /* @__PURE__ */ jsx("button", {
1505
+ className: `rqd-prefs-option${activeThemeMode === mode ? " rqd-active" : ""}`,
1506
+ onClick: () => handleThemeChange(mode),
1507
+ children: t(`prefs.${mode}`)
1508
+ }, mode))
1509
+ })]
1510
+ }),
1511
+ /* @__PURE__ */ jsxs("div", {
1512
+ className: "rqd-prefs-group",
1513
+ children: [/* @__PURE__ */ jsx("span", {
1514
+ className: "rqd-prefs-label",
1515
+ children: t("prefs.accentColor")
1516
+ }), /* @__PURE__ */ jsx("div", {
1517
+ className: "rqd-color-presets",
1518
+ children: COLOR_PRESETS.map((preset) => /* @__PURE__ */ jsx("button", {
1519
+ className: `rqd-color-preset${activeColor === preset.color ? " rqd-active" : ""}`,
1520
+ style: {
1521
+ background: preset.color,
1522
+ color: preset.color
1523
+ },
1524
+ onClick: () => handleColorChange(preset.color),
1525
+ "aria-label": preset.label,
1526
+ title: preset.label
1527
+ }, preset.color))
1528
+ })]
1529
+ }),
1530
+ loadWidgetEmail(ctx.apiKey) && /* @__PURE__ */ jsx("div", {
1531
+ className: "rqd-prefs-group",
1532
+ children: /* @__PURE__ */ jsx("button", {
1533
+ className: "rqd-btn rqd-btn-secondary",
1534
+ onClick: handleClearEmail,
1535
+ style: {
1536
+ width: "auto",
1537
+ padding: "8px 16px"
1538
+ },
1539
+ children: emailCleared ? t("prefs.emailCleared") : t("prefs.clearEmail")
1540
+ })
1541
+ })
1542
+ ]
1543
+ });
1544
+ }
1545
+ }
1546
+ const viewTitle = {
1547
+ home: brandName ?? t("widget.title"),
1548
+ "new-ticket": t("widget.newTicket"),
1549
+ "my-tickets": t("menu.myTickets"),
1550
+ "ticket-detail": t("menu.myTickets"),
1551
+ track: t("widget.trackTicket"),
1552
+ kb: t("menu.knowledgeBase"),
1553
+ preferences: t("prefs.title")
1554
+ };
1555
+ const canGoBack = view !== "home";
1556
+ const goBackTarget = view === "ticket-detail" ? "my-tickets" : "home";
1557
+ return /* @__PURE__ */ jsx(ShadowRoot, { children: /* @__PURE__ */ jsxs("div", {
1558
+ style: { cssText: cssVars },
1559
+ ...isRtl ? { dir: "rtl" } : {},
1560
+ children: [/* @__PURE__ */ jsx("button", {
1561
+ className: `rqd-fab ${posClass}${containedClass}`,
1562
+ onClick: toggleOpen,
1563
+ "aria-label": isOpen ? t("widget.close") : t("widget.title"),
1564
+ children: /* @__PURE__ */ jsx(SvgIcon, {
1565
+ path: isOpen ? ICONS.close : ICONS.chat,
1566
+ size: 24
1567
+ })
1568
+ }), /* @__PURE__ */ jsxs("div", {
1569
+ className: `rqd-panel ${posClass}${containedClass}${isOpen ? "" : " rqd-hidden"}`,
1570
+ children: [
1571
+ /* @__PURE__ */ jsxs("div", {
1572
+ className: "rqd-header",
1573
+ children: [/* @__PURE__ */ jsxs("div", {
1574
+ className: "rqd-header-brand",
1575
+ children: [
1576
+ canGoBack && /* @__PURE__ */ jsx("button", {
1577
+ className: "rqd-header-close",
1578
+ onClick: () => goBackTarget === "home" ? goHome() : setView(goBackTarget),
1579
+ "aria-label": t("tracker.back"),
1580
+ children: /* @__PURE__ */ jsx(SvgIcon, {
1581
+ path: isRtl ? ICONS.backRtl : ICONS.back,
1582
+ size: 18
1583
+ })
1584
+ }),
1585
+ view === "home" && brandLogo && /* @__PURE__ */ jsx("img", {
1586
+ src: brandLogo,
1587
+ alt: "",
1588
+ className: "rqd-header-logo"
1589
+ }),
1590
+ /* @__PURE__ */ jsx("span", {
1591
+ className: "rqd-header-title",
1592
+ children: viewTitle[view]
1593
+ })
1594
+ ]
1595
+ }), /* @__PURE__ */ jsxs("div", {
1596
+ style: {
1597
+ display: "flex",
1598
+ alignItems: "center",
1599
+ gap: 4
1600
+ },
1601
+ children: [(ctx.auth || isAuthConfigured()) && view === "home" && /* @__PURE__ */ jsx("button", {
1602
+ className: "rqd-auth-btn",
1603
+ onClick: () => ctx.isAuthenticated ? logout() : login(),
1604
+ "aria-label": ctx.isAuthenticated ? t("auth.logout") : t("auth.login"),
1605
+ children: ctx.isAuthenticated ? ctx.userName ?? t("auth.logout") : t("auth.login")
1606
+ }), /* @__PURE__ */ jsx("button", {
1607
+ className: "rqd-header-close",
1608
+ onClick: toggleOpen,
1609
+ "aria-label": t("widget.close"),
1610
+ children: /* @__PURE__ */ jsx(SvgIcon, {
1611
+ path: ICONS.close,
1612
+ size: 18
1613
+ })
1614
+ })]
1615
+ })]
1616
+ }),
1617
+ /* @__PURE__ */ jsx("div", {
1618
+ className: "rqd-body",
1619
+ children: renderViewContent()
1620
+ }),
1621
+ !hideBranding && /* @__PURE__ */ jsxs("div", {
1622
+ className: "rqd-footer",
1623
+ children: [/* @__PURE__ */ jsx("span", { children: t("branding.poweredBy") }), /* @__PURE__ */ jsx("a", {
1624
+ href: "https://reqdesk.com",
1625
+ target: "_blank",
1626
+ rel: "noopener noreferrer",
1627
+ children: "Reqdesk"
1628
+ })]
1629
+ })
1630
+ ]
1631
+ })]
1632
+ }) });
1633
+ }
1634
+ //#endregion
1635
+ export { FloatingWidget, ReqdeskProvider, ShadowRoot, SupportPortal, TicketForm, loadWidgetConfig as getWidgetDefaults, useReqdesk };
1636
+
1637
+ //# sourceMappingURL=react.js.map