@reqdesk/widget 0.1.0 → 0.2.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 CHANGED
@@ -1,4 +1,4 @@
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";
1
+ import { C as setOidcTokenProvider, S as configureWidgetClient, _ as submitReply, a as saveTrackingToken, b as trackTicket, c as getWidgetStyles, d as en, f as closeTicket, g as resolveWidgetUser, h as listMyTickets, i as loadWidgetEmail, l as themeToVars, m as getTicketDetail, n as getTrackingTokens, o as saveWidgetConfig, p as getCategories, r as loadWidgetConfig, s as saveWidgetEmail, t as clearWidgetEmail, u as ar, v as submitTicket, x as uploadAttachment, y as submitTrackingReply } from "./storage-PjDHb5v7.js";
2
2
  import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
3
3
  import { QueryClient, QueryClientProvider, keepPreviousData, queryOptions, useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
4
4
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
@@ -480,6 +480,132 @@ function SupportPortal({ className }) {
480
480
  }) });
481
481
  }
482
482
  //#endregion
483
+ //#region src/react/queries.ts
484
+ const widgetTicketDetailOptions = (ticketId) => queryOptions({
485
+ queryKey: ["widget-ticket", ticketId],
486
+ queryFn: () => getTicketDetail(ticketId),
487
+ staleTime: 6e4,
488
+ enabled: !!ticketId
489
+ });
490
+ const widgetMyTicketsOptions = (projectId, userId) => queryOptions({
491
+ queryKey: [
492
+ "widget-tickets",
493
+ projectId,
494
+ userId
495
+ ],
496
+ queryFn: () => listMyTickets(projectId, userId),
497
+ staleTime: 3e4,
498
+ placeholderData: keepPreviousData,
499
+ enabled: !!userId
500
+ });
501
+ const widgetUserOptions = (projectId, email) => queryOptions({
502
+ queryKey: [
503
+ "widget-user",
504
+ projectId,
505
+ email
506
+ ],
507
+ queryFn: () => resolveWidgetUser(projectId, email),
508
+ staleTime: 5 * 6e4,
509
+ enabled: !!email
510
+ });
511
+ const widgetCategoriesOptions = (projectId, parentId) => queryOptions({
512
+ queryKey: [
513
+ "widget-categories",
514
+ projectId,
515
+ parentId ?? "root"
516
+ ],
517
+ queryFn: () => getCategories(projectId, parentId),
518
+ staleTime: 5 * 6e4
519
+ });
520
+ //#endregion
521
+ //#region src/client-metadata.ts
522
+ const STORAGE_PREFIX = "reqdesk_diag_";
523
+ const DEFAULT_PREFS = {
524
+ screenResolution: false,
525
+ deviceType: false,
526
+ timezone: false,
527
+ referrerUrl: false,
528
+ language: false,
529
+ platform: false
530
+ };
531
+ /** Always collected — minimal, non-sensitive */
532
+ function collectMinimalMetadata() {
533
+ const meta = {};
534
+ try {
535
+ meta.pageUrl = window.location.href;
536
+ meta.userAgent = navigator.userAgent;
537
+ } catch {}
538
+ return meta;
539
+ }
540
+ /** Full diagnostic set — only included for opted-in fields */
541
+ function collectDiagnosticMetadata(prefs) {
542
+ const meta = {};
543
+ try {
544
+ if (prefs.screenResolution) meta.screenResolution = `${screen.width}x${screen.height}`;
545
+ if (prefs.deviceType) meta.deviceType = detectDeviceType();
546
+ if (prefs.timezone) meta.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
547
+ if (prefs.referrerUrl && document.referrer) meta.referrerUrl = document.referrer;
548
+ if (prefs.language) meta.language = navigator.language;
549
+ if (prefs.platform) meta.platform = navigator.platform;
550
+ } catch {}
551
+ return meta;
552
+ }
553
+ /** Combine minimal + opted-in diagnostic metadata */
554
+ function collectAllMetadata(apiKey) {
555
+ const prefs = getMetadataPreferences(apiKey);
556
+ return {
557
+ ...collectMinimalMetadata(),
558
+ ...collectDiagnosticMetadata(prefs)
559
+ };
560
+ }
561
+ function getMetadataPreferences(apiKey) {
562
+ try {
563
+ const raw = localStorage.getItem(`${STORAGE_PREFIX}${apiKey}`);
564
+ if (raw) return {
565
+ ...DEFAULT_PREFS,
566
+ ...JSON.parse(raw)
567
+ };
568
+ } catch {}
569
+ return { ...DEFAULT_PREFS };
570
+ }
571
+ function saveMetadataPreferences(apiKey, prefs) {
572
+ try {
573
+ localStorage.setItem(`${STORAGE_PREFIX}${apiKey}`, JSON.stringify(prefs));
574
+ } catch {}
575
+ }
576
+ function detectDeviceType() {
577
+ const ua = navigator.userAgent.toLowerCase();
578
+ if (/tablet|ipad|playbook|silk/i.test(ua)) return "tablet";
579
+ if (/mobile|iphone|ipod|android.*mobile|windows phone/i.test(ua)) return "mobile";
580
+ return "desktop";
581
+ }
582
+ const DIAGNOSTIC_FIELDS = [
583
+ {
584
+ key: "screenResolution",
585
+ labelKey: "diag.screenResolution"
586
+ },
587
+ {
588
+ key: "deviceType",
589
+ labelKey: "diag.deviceType"
590
+ },
591
+ {
592
+ key: "timezone",
593
+ labelKey: "diag.timezone"
594
+ },
595
+ {
596
+ key: "referrerUrl",
597
+ labelKey: "diag.referrerUrl"
598
+ },
599
+ {
600
+ key: "language",
601
+ labelKey: "diag.language"
602
+ },
603
+ {
604
+ key: "platform",
605
+ labelKey: "diag.platform"
606
+ }
607
+ ];
608
+ //#endregion
483
609
  //#region src/react/views/SubmitTicketView.tsx
484
610
  const translations$4 = {
485
611
  en,
@@ -529,6 +655,19 @@ function SubmitTicketView({ projectId, onSuccess, onError, isAuthenticated, user
529
655
  const [uploadProgress, setUploadProgress] = useState(null);
530
656
  const [success, setSuccess] = useState(null);
531
657
  const [rememberEmail, setRememberEmail] = useState(!!savedEmail);
658
+ const [categoryPath, setCategoryPath] = useState([]);
659
+ const [selectedCategory, setSelectedCategory] = useState(null);
660
+ const { data: categories = [] } = useQuery(widgetCategoriesOptions(projectId, categoryPath.length > 0 ? categoryPath[categoryPath.length - 1].id : null));
661
+ const [diagOpen, setDiagOpen] = useState(false);
662
+ const [diagPrefs, setDiagPrefs] = useState(getMetadataPreferences(ctx.apiKey));
663
+ const diagValues = collectDiagnosticMetadata({
664
+ screenResolution: true,
665
+ deviceType: true,
666
+ timezone: true,
667
+ referrerUrl: true,
668
+ language: true,
669
+ platform: true
670
+ });
532
671
  const t = useCallback((key) => {
533
672
  if (ctx.translations?.[key]) return ctx.translations[key];
534
673
  return (translations$4[ctx.language] ?? translations$4.en)[key] ?? key;
@@ -615,12 +754,15 @@ function SubmitTicketView({ projectId, onSuccess, onError, isAuthenticated, user
615
754
  }
616
755
  setErrors({});
617
756
  if (rememberEmail && email) saveWidgetEmail(ctx.apiKey, email);
757
+ saveMetadataPreferences(ctx.apiKey, diagPrefs);
618
758
  const validFiles = files.filter((f) => !f.error);
619
759
  const data = {
620
760
  title,
621
761
  description: formData.get("description")?.trim() || void 0,
622
762
  email,
623
- priority: formData.get("priority") ?? "medium"
763
+ priority: formData.get("priority") ?? "medium",
764
+ categoryId: selectedCategory?.id,
765
+ clientMetadata: collectAllMetadata(ctx.apiKey)
624
766
  };
625
767
  submitMutation.mutate({
626
768
  data,
@@ -801,6 +943,97 @@ function SubmitTicketView({ projectId, onSuccess, onError, isAuthenticated, user
801
943
  ]
802
944
  })]
803
945
  }),
946
+ /* @__PURE__ */ jsxs("div", {
947
+ className: "rqd-form-group",
948
+ children: [/* @__PURE__ */ jsx("label", {
949
+ className: "rqd-label",
950
+ children: t("form.categorySelected")
951
+ }), selectedCategory ? /* @__PURE__ */ jsxs("div", {
952
+ className: "rqd-category-selected",
953
+ children: [/* @__PURE__ */ jsxs("span", { children: [
954
+ categoryPath.map((c) => c.name).join(" > "),
955
+ categoryPath.length > 0 ? " > " : "",
956
+ selectedCategory.name
957
+ ] }), /* @__PURE__ */ jsx("button", {
958
+ type: "button",
959
+ onClick: () => {
960
+ setSelectedCategory(null);
961
+ setCategoryPath([]);
962
+ },
963
+ children: "×"
964
+ })]
965
+ }) : /* @__PURE__ */ jsxs(Fragment, { children: [categoryPath.length > 0 && /* @__PURE__ */ jsxs("div", {
966
+ className: "rqd-category-breadcrumb",
967
+ children: [/* @__PURE__ */ jsx("button", {
968
+ type: "button",
969
+ onClick: () => setCategoryPath([]),
970
+ children: t("form.categoryPlaceholder")
971
+ }), categoryPath.map((c, i) => /* @__PURE__ */ jsxs("span", { children: [/* @__PURE__ */ jsx("span", {
972
+ className: "rqd-category-breadcrumb-sep",
973
+ children: "›"
974
+ }), /* @__PURE__ */ jsx("button", {
975
+ type: "button",
976
+ onClick: () => setCategoryPath((prev) => prev.slice(0, i + 1)),
977
+ children: c.name
978
+ })] }, c.id))]
979
+ }), /* @__PURE__ */ jsxs("div", {
980
+ className: "rqd-category-list",
981
+ children: [categories.map((cat) => /* @__PURE__ */ jsxs("button", {
982
+ type: "button",
983
+ className: "rqd-category-item",
984
+ onClick: () => {
985
+ if (cat.hasChildren) setCategoryPath((prev) => [...prev, cat]);
986
+ else setSelectedCategory(cat);
987
+ },
988
+ children: [cat.name, cat.hasChildren && /* @__PURE__ */ jsx("span", {
989
+ className: "rqd-category-item-chevron",
990
+ children: "›"
991
+ })]
992
+ }, cat.id)), categories.length === 0 && categoryPath.length === 0 && /* @__PURE__ */ jsx("span", {
993
+ style: {
994
+ fontSize: 13,
995
+ color: "var(--rqd-text-secondary)"
996
+ },
997
+ children: t("form.categoryPlaceholder")
998
+ })]
999
+ })] })]
1000
+ }),
1001
+ /* @__PURE__ */ jsxs("div", {
1002
+ className: "rqd-form-group",
1003
+ children: [/* @__PURE__ */ jsxs("button", {
1004
+ type: "button",
1005
+ className: "rqd-diag-toggle",
1006
+ onClick: () => setDiagOpen((prev) => !prev),
1007
+ children: [/* @__PURE__ */ jsx("span", { children: t("diag.title") }), /* @__PURE__ */ jsx("span", { children: diagOpen ? "▲" : "▼" })]
1008
+ }), diagOpen && /* @__PURE__ */ jsxs("div", {
1009
+ className: "rqd-diag-panel",
1010
+ children: [/* @__PURE__ */ jsx("p", {
1011
+ style: {
1012
+ fontSize: 12,
1013
+ color: "var(--rqd-text-secondary)",
1014
+ margin: "0 0 6px"
1015
+ },
1016
+ children: t("diag.hint")
1017
+ }), DIAGNOSTIC_FIELDS.map((field) => /* @__PURE__ */ jsxs("label", {
1018
+ className: "rqd-diag-item",
1019
+ children: [
1020
+ /* @__PURE__ */ jsx("input", {
1021
+ type: "checkbox",
1022
+ checked: diagPrefs[field.key],
1023
+ onChange: (e) => setDiagPrefs((prev) => ({
1024
+ ...prev,
1025
+ [field.key]: e.target.checked
1026
+ }))
1027
+ }),
1028
+ /* @__PURE__ */ jsx("span", { children: t(field.labelKey) }),
1029
+ /* @__PURE__ */ jsx("span", {
1030
+ className: "rqd-diag-item-value",
1031
+ children: diagValues[field.key] ?? "—"
1032
+ })
1033
+ ]
1034
+ }, field.key))]
1035
+ })]
1036
+ }),
804
1037
  /* @__PURE__ */ jsxs("div", {
805
1038
  className: "rqd-form-group",
806
1039
  children: [
@@ -864,35 +1097,6 @@ function SubmitTicketView({ projectId, onSuccess, onError, isAuthenticated, user
864
1097
  });
865
1098
  }
866
1099
  //#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
1100
  //#region src/react/views/MyTicketsView.tsx
897
1101
  const translations$3 = {
898
1102
  en,
@@ -1074,6 +1278,24 @@ function TicketDetailView({ ticketId, onBack }) {
1074
1278
  if (context?.previous) queryClient.setQueryData(["widget-ticket", ticketId], context.previous);
1075
1279
  }
1076
1280
  });
1281
+ const resolveMutation = useMutation({
1282
+ mutationFn: () => closeTicket(ticketId),
1283
+ onMutate: async () => {
1284
+ await queryClient.cancelQueries({ queryKey: ["widget-ticket", ticketId] });
1285
+ const previous = queryClient.getQueryData(["widget-ticket", ticketId]);
1286
+ if (previous) queryClient.setQueryData(["widget-ticket", ticketId], {
1287
+ ...previous,
1288
+ status: "resolved"
1289
+ });
1290
+ return { previous };
1291
+ },
1292
+ onSuccess: () => {
1293
+ queryClient.invalidateQueries({ queryKey: ["widget-ticket", ticketId] });
1294
+ },
1295
+ onError: (_err, _vars, context) => {
1296
+ if (context?.previous) queryClient.setQueryData(["widget-ticket", ticketId], context.previous);
1297
+ }
1298
+ });
1077
1299
  if (isLoading) return /* @__PURE__ */ jsx("div", {
1078
1300
  className: "rqd-loading",
1079
1301
  children: t("detail.loading")
@@ -1182,11 +1404,28 @@ function TicketDetailView({ ticketId, onBack }) {
1182
1404
  onChange: (e) => setReplyBody(e.target.value),
1183
1405
  placeholder: t("detail.replyPlaceholder"),
1184
1406
  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")
1407
+ }), /* @__PURE__ */ jsxs("div", {
1408
+ style: {
1409
+ display: "flex",
1410
+ gap: 8
1411
+ },
1412
+ children: [/* @__PURE__ */ jsx("button", {
1413
+ className: "rqd-btn rqd-btn-primary",
1414
+ style: { flex: 1 },
1415
+ onClick: () => replyMutation.mutate(replyBody.trim()),
1416
+ disabled: !replyBody.trim() || replyMutation.isPending,
1417
+ children: replyMutation.isPending ? t("detail.sending") : t("detail.sendReply")
1418
+ }), ticket.status !== "resolved" && ticket.status !== "closed" && /* @__PURE__ */ jsx("button", {
1419
+ className: "rqd-btn rqd-btn-secondary",
1420
+ style: {
1421
+ flex: 0,
1422
+ whiteSpace: "nowrap",
1423
+ padding: "10px 16px"
1424
+ },
1425
+ onClick: () => resolveMutation.mutate(),
1426
+ disabled: resolveMutation.isPending,
1427
+ children: resolveMutation.isPending ? t("detail.resolving") : t("detail.resolve")
1428
+ })]
1190
1429
  })]
1191
1430
  })
1192
1431
  ] });