glitchgrab 1.58.3 → 1.59.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -711,12 +711,21 @@ function signedFields(identity) {
711
711
  return { userId: identity.userId, signature: identity.signature };
712
712
  }
713
713
  async function enhanceText(text, token, baseUrl, screenshot, context, identity) {
714
+ try {
715
+ return await enhanceTextOrThrow(text, token, baseUrl, screenshot, context, identity);
716
+ } catch (e) {
717
+ return text;
718
+ }
719
+ }
720
+ async function enhanceTextOrThrow(text, token, baseUrl, screenshot, context, identity) {
714
721
  var _a;
722
+ const trimmed = text.trim();
723
+ if (!trimmed) return text;
724
+ const unavailable = "AI enhance isn't available right now \u2014 send your report as written.";
725
+ let response;
715
726
  try {
716
- const trimmed = text.trim();
717
- if (!trimmed) return text;
718
727
  const url = `${baseUrl != null ? baseUrl : DEFAULT_BASE_URL}/api/v1/ai/enhance-text`;
719
- const response = await fetch(url, {
728
+ response = await fetch(url, {
720
729
  method: "POST",
721
730
  headers: {
722
731
  "Content-Type": "application/json",
@@ -729,15 +738,20 @@ async function enhanceText(text, token, baseUrl, screenshot, context, identity)
729
738
  ...signedFields(identity)
730
739
  })
731
740
  });
732
- if (!response.ok) return text;
733
- const envelope = await response.json();
734
- if ((envelope == null ? void 0 : envelope.success) && typeof ((_a = envelope.data) == null ? void 0 : _a.text) === "string") {
735
- return envelope.data.text;
736
- }
737
- return text;
738
741
  } catch (e) {
739
- return text;
742
+ throw new Error(unavailable);
743
+ }
744
+ if (response.status === 429) {
745
+ throw new Error("Too many AI enhance tries \u2014 wait a while, or send your report as written.");
740
746
  }
747
+ const envelope = await response.json().catch(() => null);
748
+ if (response.ok && (envelope == null ? void 0 : envelope.success) && typeof ((_a = envelope.data) == null ? void 0 : _a.text) === "string") {
749
+ return envelope.data.text;
750
+ }
751
+ if (response.status === 400 && typeof (envelope == null ? void 0 : envelope.error) === "string") {
752
+ throw new Error(envelope.error);
753
+ }
754
+ throw new Error(unavailable);
741
755
  }
742
756
  async function findSimilarIssues(description, token, baseUrl, identity, files) {
743
757
  var _a;
@@ -5494,6 +5508,7 @@ function ReportDialog({
5494
5508
  const [aiConversationId, setAiConversationId] = useState4(null);
5495
5509
  const [issueTitle, setIssueTitle] = useState4("");
5496
5510
  const [isEnhanced, setIsEnhanced] = useState4(false);
5511
+ const [enhanceError, setEnhanceError] = useState4(null);
5497
5512
  const [originalDescription, setOriginalDescription] = useState4(
5498
5513
  null
5499
5514
  );
@@ -5948,6 +5963,7 @@ function ReportDialog({
5948
5963
  setSelfReportFrom(null);
5949
5964
  setVoiceError(null);
5950
5965
  setIsEnhanced(false);
5966
+ setEnhanceError(null);
5951
5967
  setOriginalDescription(null);
5952
5968
  setAssistOpen(false);
5953
5969
  setAssistUsed(false);
@@ -6013,6 +6029,7 @@ function ReportDialog({
6013
6029
  const sep = base.trim() ? " " : "";
6014
6030
  setDescriptionFromVoice(base + sep + text.trim());
6015
6031
  setValidationError(null);
6032
+ setEnhanceError(null);
6016
6033
  setIsEnhanced(false);
6017
6034
  setOriginalDescription(null);
6018
6035
  }
@@ -7108,6 +7125,7 @@ function ReportDialog({
7108
7125
  notePushToTalkEdit();
7109
7126
  setDescription(e.target.value);
7110
7127
  if (validationError) setValidationError(null);
7128
+ if (enhanceError) setEnhanceError(null);
7111
7129
  if (isEnhanced) {
7112
7130
  setIsEnhanced(false);
7113
7131
  setOriginalDescription(null);
@@ -7229,6 +7247,7 @@ function ReportDialog({
7229
7247
  var _a2;
7230
7248
  if (!description.trim() || isEnhancing) return;
7231
7249
  setIsEnhancing(true);
7250
+ setEnhanceError(null);
7232
7251
  try {
7233
7252
  const polished = await enhanceText2(
7234
7253
  description,
@@ -7241,6 +7260,10 @@ function ReportDialog({
7241
7260
  if (validationError)
7242
7261
  setValidationError(null);
7243
7262
  }
7263
+ } catch (err) {
7264
+ setEnhanceError(
7265
+ err instanceof Error && err.message ? err.message : "AI enhance isn't available right now \u2014 send your report as written."
7266
+ );
7244
7267
  } finally {
7245
7268
  setIsEnhancing(false);
7246
7269
  }
@@ -7499,6 +7522,20 @@ function ReportDialog({
7499
7522
  children: voiceError
7500
7523
  }
7501
7524
  ),
7525
+ enhanceError && /* @__PURE__ */ jsx5(
7526
+ "p",
7527
+ {
7528
+ role: "alert",
7529
+ style: {
7530
+ color: "#ef4444",
7531
+ fontSize: "11px",
7532
+ lineHeight: 1.5,
7533
+ marginTop: "4px",
7534
+ marginBottom: 0
7535
+ },
7536
+ children: enhanceError
7537
+ }
7538
+ ),
7502
7539
  assistNotice && /* @__PURE__ */ jsx5(
7503
7540
  "p",
7504
7541
  {
@@ -8521,6 +8558,69 @@ function getTypeIcon(type, color, size = 24) {
8521
8558
 
8522
8559
  // src/booking-dialog.tsx
8523
8560
  import { useCallback as useCallback2, useEffect as useEffect5, useMemo as useMemo3, useState as useState5 } from "react";
8561
+
8562
+ // src/phone.ts
8563
+ var COUNTRIES = [
8564
+ { iso: "IN", name: "India", dial: "91" },
8565
+ { iso: "AE", name: "UAE", dial: "971" },
8566
+ { iso: "US", name: "US / Canada", dial: "1" },
8567
+ { iso: "GB", name: "UK", dial: "44" },
8568
+ { iso: "SG", name: "Singapore", dial: "65" },
8569
+ { iso: "AU", name: "Australia", dial: "61" },
8570
+ { iso: "SA", name: "Saudi Arabia", dial: "966" },
8571
+ { iso: "QA", name: "Qatar", dial: "974" },
8572
+ { iso: "NP", name: "Nepal", dial: "977" },
8573
+ { iso: "BD", name: "Bangladesh", dial: "880" },
8574
+ { iso: "LK", name: "Sri Lanka", dial: "94" },
8575
+ { iso: "DE", name: "Germany", dial: "49" }
8576
+ ];
8577
+ var ZONE_COUNTRY = {
8578
+ "Asia/Kolkata": "91",
8579
+ "Asia/Calcutta": "91",
8580
+ "Asia/Dubai": "971",
8581
+ "Europe/London": "44",
8582
+ "Asia/Singapore": "65",
8583
+ "Asia/Riyadh": "966",
8584
+ "Asia/Qatar": "974",
8585
+ "Asia/Kathmandu": "977",
8586
+ "Asia/Katmandu": "977",
8587
+ "Asia/Dhaka": "880",
8588
+ "Asia/Colombo": "94",
8589
+ "Europe/Berlin": "49"
8590
+ };
8591
+ function defaultDialForZone(zone) {
8592
+ if (ZONE_COUNTRY[zone]) return ZONE_COUNTRY[zone];
8593
+ if (zone.startsWith("America/")) return "1";
8594
+ if (zone.startsWith("Australia/")) return "61";
8595
+ return "91";
8596
+ }
8597
+ function splitTypedPhone(typed, dial) {
8598
+ const trimmed = typed.trim();
8599
+ const digits = trimmed.replace(/\D/g, "");
8600
+ if (trimmed.startsWith("+")) {
8601
+ const match = [...COUNTRIES].sort((a, b) => b.dial.length - a.dial.length).find((c) => digits.startsWith(c.dial));
8602
+ if (match) return { dial: match.dial, national: digits.slice(match.dial.length) };
8603
+ }
8604
+ if (dial === "91" && digits.length === 12 && digits.startsWith("91")) return { dial, national: digits.slice(2) };
8605
+ if (dial === "91" && digits.length === 11 && digits.startsWith("0")) return { dial, national: digits.slice(1) };
8606
+ return { dial, national: digits };
8607
+ }
8608
+ function phoneProblem(dial, national) {
8609
+ const digits = national.replace(/\D/g, "");
8610
+ if (!digits) return "Enter your mobile number";
8611
+ if (dial === "91") {
8612
+ if (digits.length !== 10) return `An Indian mobile has 10 digits \u2014 this has ${digits.length}`;
8613
+ if (!/^[6-9]/.test(digits)) return "An Indian mobile starts with 6, 7, 8 or 9";
8614
+ return null;
8615
+ }
8616
+ if (digits.length < 6 || digits.length > 12) return "Check the number \u2014 it looks too short or too long";
8617
+ return null;
8618
+ }
8619
+ function toE164(dial, national) {
8620
+ return `+${dial}${national.replace(/\D/g, "")}`;
8621
+ }
8622
+
8623
+ // src/booking-dialog.tsx
8524
8624
  import { Fragment as Fragment5, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
8525
8625
  function readQuestions(value) {
8526
8626
  if (!Array.isArray(value)) return [];
@@ -8663,7 +8763,9 @@ function BookingDialog({
8663
8763
  open,
8664
8764
  onClose,
8665
8765
  preview = false,
8666
- availabilityUrl
8766
+ availabilityUrl,
8767
+ intro,
8768
+ onBooked
8667
8769
  }) {
8668
8770
  var _a, _b, _c, _d, _e;
8669
8771
  const wide = useMediaQuery(WIDE_QUERY, true);
@@ -8686,6 +8788,8 @@ function BookingDialog({
8686
8788
  const [name, setName] = useState5("");
8687
8789
  const [email, setEmail] = useState5("");
8688
8790
  const [phone, setPhone] = useState5("");
8791
+ const [dial, setDial] = useState5(() => defaultDialForZone(localZone()));
8792
+ const [phoneTouched, setPhoneTouched] = useState5(false);
8689
8793
  const [note, setNote] = useState5("");
8690
8794
  const [questions, setQuestions] = useState5([]);
8691
8795
  const [answers, setAnswers] = useState5({});
@@ -8796,8 +8900,12 @@ function BookingDialog({
8796
8900
  const view = step !== "slots" ? step : !pageLoaded ? "loading" : questions.length > 0 && !asked ? "questions" : "slots";
8797
8901
  const roomy = step === "slots";
8798
8902
  async function startBooking() {
8799
- var _a2, _b2;
8903
+ var _a2, _b2, _c2;
8800
8904
  if (!selected) return;
8905
+ if (phoneError) {
8906
+ setPhoneTouched(true);
8907
+ return;
8908
+ }
8801
8909
  if (preview) {
8802
8910
  setPreviewEnded(true);
8803
8911
  return;
@@ -8811,7 +8919,7 @@ function BookingDialog({
8811
8919
  body: JSON.stringify({
8812
8920
  name,
8813
8921
  email,
8814
- phone,
8922
+ phone: toE164(dial, phone),
8815
8923
  note,
8816
8924
  answers: Object.fromEntries(given.map((q) => [q.id, q.answer])),
8817
8925
  startsAt: selected.startsAt,
@@ -8830,6 +8938,7 @@ function BookingDialog({
8830
8938
  if (json.data.confirmed) {
8831
8939
  setMeetUrl((_b2 = json.data.meetUrl) != null ? _b2 : null);
8832
8940
  setStep("done");
8941
+ reportBooked(json.data.bookingId, (_c2 = json.data.meetUrl) != null ? _c2 : null);
8833
8942
  return;
8834
8943
  }
8835
8944
  setBookingId(json.data.bookingId);
@@ -8840,8 +8949,16 @@ function BookingDialog({
8840
8949
  setBusy(false);
8841
8950
  }
8842
8951
  }
8952
+ function reportBooked(id, meet) {
8953
+ if (!onBooked || !id || !selected) return;
8954
+ try {
8955
+ onBooked({ bookingId: id, startsAt: selected.startsAt, name, email, phone: toE164(dial, phone), meetUrl: meet });
8956
+ } catch (e) {
8957
+ console.error("[glitchgrab] onBooked threw", e);
8958
+ }
8959
+ }
8843
8960
  async function confirmBooking() {
8844
- var _a2;
8961
+ var _a2, _b2;
8845
8962
  if (!bookingId) return;
8846
8963
  setBusy(true);
8847
8964
  setError(null);
@@ -8858,13 +8975,15 @@ function BookingDialog({
8858
8975
  }
8859
8976
  setMeetUrl(json.data.meetUrl);
8860
8977
  setStep("done");
8978
+ reportBooked(bookingId, (_b2 = json.data.meetUrl) != null ? _b2 : null);
8861
8979
  } catch (e) {
8862
8980
  setError("Couldn't reach the booking service");
8863
8981
  } finally {
8864
8982
  setBusy(false);
8865
8983
  }
8866
8984
  }
8867
- const canSubmitDetails = name.trim() && email.includes("@") && phone.replace(/\D/g, "").length >= 8;
8985
+ const phoneError = phoneProblem(dial, phone);
8986
+ const canSubmitDetails = Boolean(name.trim() && email.includes("@"));
8868
8987
  const selectedEnd = selected ? (_e = selected.endsAt) != null ? _e : new Date(new Date(selected.startsAt).getTime() + page.slotMinutes * 6e4).toISOString() : null;
8869
8988
  const monthDate = new Date(keyToUtc(`${month}-01`));
8870
8989
  const muted = { color: t.muted };
@@ -9011,6 +9130,14 @@ function BookingDialog({
9011
9130
  children: "Preview \u2014 nothing is booked"
9012
9131
  }
9013
9132
  ),
9133
+ intro && /* @__PURE__ */ jsx6(
9134
+ "div",
9135
+ {
9136
+ "data-glitchgrab-booking-intro": true,
9137
+ style: { fontSize: 13, fontWeight: 600, lineHeight: 1.45, paddingRight: 32, color: t.ink, overflowWrap: "anywhere" },
9138
+ children: intro
9139
+ }
9140
+ ),
9014
9141
  /* @__PURE__ */ jsx6("div", { style: { fontSize: 20, fontWeight: 600, lineHeight: 1.25, paddingRight: 32, overflowWrap: "anywhere" }, children: page.title }),
9015
9142
  page.description && /* @__PURE__ */ jsx6("div", { style: { fontSize: 13, lineHeight: 1.5, overflowWrap: "anywhere", ...muted }, children: page.description }),
9016
9143
  /* @__PURE__ */ jsxs6("div", { style: { display: "flex", flexDirection: "column", gap: 10, marginTop: 4, fontSize: 14 }, children: [
@@ -9453,14 +9580,17 @@ function BookingDialog({
9453
9580
  /* @__PURE__ */ jsx6(Field, { t, label: "Your name", value: name, onChange: setName, autoComplete: "name" }),
9454
9581
  /* @__PURE__ */ jsx6(Field, { t, label: "Email", value: email, onChange: setEmail, type: "email", autoComplete: "email" }),
9455
9582
  /* @__PURE__ */ jsx6(
9456
- Field,
9583
+ PhoneField,
9457
9584
  {
9458
9585
  t,
9459
- label: "Mobile number",
9460
- value: phone,
9461
- onChange: setPhone,
9462
- type: "tel",
9463
- autoComplete: "tel",
9586
+ dial,
9587
+ national: phone,
9588
+ onChange: (nextDial, national) => {
9589
+ setDial(nextDial);
9590
+ setPhone(national);
9591
+ },
9592
+ onBlur: () => setPhoneTouched(true),
9593
+ error: phoneTouched ? phoneError : null,
9464
9594
  hint: requireOtp ? "We send a code here to confirm, then a reminder before the call." : "We send a reminder here before the call."
9465
9595
  }
9466
9596
  ),
@@ -9748,6 +9878,64 @@ function Field({
9748
9878
  hint && /* @__PURE__ */ jsx6("div", { style: { marginTop: 6, fontSize: 12, fontWeight: 400, color: t.muted }, children: hint })
9749
9879
  ] });
9750
9880
  }
9881
+ function PhoneField({
9882
+ t,
9883
+ dial,
9884
+ national,
9885
+ onChange,
9886
+ onBlur,
9887
+ error,
9888
+ hint
9889
+ }) {
9890
+ const box = {
9891
+ boxSizing: "border-box",
9892
+ padding: "9px 12px",
9893
+ background: t.input,
9894
+ color: t.text,
9895
+ border: `1px solid ${error ? t.danger : t.border}`,
9896
+ borderRadius: 8,
9897
+ fontFamily: "inherit",
9898
+ fontSize: 14,
9899
+ outline: "none"
9900
+ };
9901
+ return /* @__PURE__ */ jsxs6("label", { style: { display: "block", fontSize: 13, fontWeight: 500, color: t.text }, children: [
9902
+ "Mobile number",
9903
+ /* @__PURE__ */ jsxs6("div", { style: { display: "flex", gap: 8, marginTop: 6 }, children: [
9904
+ /* @__PURE__ */ jsx6(
9905
+ "select",
9906
+ {
9907
+ "aria-label": "Country code",
9908
+ value: dial,
9909
+ onChange: (e) => onChange(e.target.value, national),
9910
+ style: { ...box, flex: "0 0 auto", paddingRight: 8, cursor: "pointer" },
9911
+ children: COUNTRIES.map((c) => /* @__PURE__ */ jsxs6("option", { value: c.dial, children: [
9912
+ c.iso,
9913
+ " +",
9914
+ c.dial
9915
+ ] }, c.iso))
9916
+ }
9917
+ ),
9918
+ /* @__PURE__ */ jsx6(
9919
+ "input",
9920
+ {
9921
+ type: "tel",
9922
+ autoComplete: "tel",
9923
+ inputMode: "numeric",
9924
+ placeholder: dial === "91" ? "98765 43210" : "Mobile number",
9925
+ value: national,
9926
+ "aria-invalid": Boolean(error),
9927
+ onChange: (e) => {
9928
+ const next = splitTypedPhone(e.target.value, dial);
9929
+ onChange(next.dial, next.national);
9930
+ },
9931
+ onBlur,
9932
+ style: { ...box, flex: "1 1 auto", minWidth: 0, width: "100%" }
9933
+ }
9934
+ )
9935
+ ] }),
9936
+ error ? /* @__PURE__ */ jsx6("div", { role: "alert", style: { marginTop: 6, fontSize: 12, fontWeight: 500, color: t.danger }, children: error }) : /* @__PURE__ */ jsx6("div", { style: { marginTop: 6, fontSize: 12, fontWeight: 400, color: t.muted }, children: hint })
9937
+ ] });
9938
+ }
9751
9939
 
9752
9940
  // src/rejection.ts
9753
9941
  var DETAILS_MAX_LENGTH = 1e3;
@@ -10252,21 +10440,27 @@ function GlitchgrabProviderInner({
10252
10440
  () => ({ userId: signedUserId != null ? signedUserId : null, signature }),
10253
10441
  [signedUserId, signature]
10254
10442
  );
10443
+ const enhanceForDialog = useCallback3(
10444
+ async (text, screenshot) => {
10445
+ const ctx = captureContext(visitedPagesRef.current);
10446
+ const context = {
10447
+ url: ctx.url,
10448
+ visitedPages: ctx.visitedPages.slice(-5),
10449
+ breadcrumbs: ctx.breadcrumbs.slice(-10).map((b) => ({ type: b.type, message: b.message }))
10450
+ };
10451
+ return await enhanceTextOrThrow(text, token, baseUrl, screenshot, context, identity);
10452
+ },
10453
+ [token, baseUrl, identity]
10454
+ );
10255
10455
  const enhance = useCallback3(
10256
10456
  async (text, screenshot) => {
10257
10457
  try {
10258
- const ctx = captureContext(visitedPagesRef.current);
10259
- const context = {
10260
- url: ctx.url,
10261
- visitedPages: ctx.visitedPages.slice(-5),
10262
- breadcrumbs: ctx.breadcrumbs.slice(-10).map((b) => ({ type: b.type, message: b.message }))
10263
- };
10264
- return await enhanceText(text, token, baseUrl, screenshot, context, identity);
10458
+ return await enhanceForDialog(text, screenshot);
10265
10459
  } catch (e) {
10266
10460
  return text;
10267
10461
  }
10268
10462
  },
10269
- [token, baseUrl, identity]
10463
+ [enhanceForDialog]
10270
10464
  );
10271
10465
  const assist = useCallback3(
10272
10466
  async (params) => {
@@ -10332,8 +10526,18 @@ function GlitchgrabProviderInner({
10332
10526
  const [signedUsers, setSignedUsers] = useState6("OFF");
10333
10527
  const aiBlockedUnsigned = signedUsers === "ENFORCE" && !signature;
10334
10528
  const [booking, setBooking] = useState6("closed");
10335
- const openBookingDialog = useCallback3(() => setBooking("live"), []);
10336
- const openBookingPreview = useCallback3(() => setBooking("preview"), []);
10529
+ const [bookingOptions, setBookingOptions] = useState6({});
10530
+ const openBookingDialog = useCallback3((options) => {
10531
+ setBookingOptions({
10532
+ intro: typeof (options == null ? void 0 : options.intro) === "string" ? options.intro : void 0,
10533
+ onBooked: typeof (options == null ? void 0 : options.onBooked) === "function" ? options.onBooked : void 0
10534
+ });
10535
+ setBooking("live");
10536
+ }, []);
10537
+ const openBookingPreview = useCallback3(() => {
10538
+ setBookingOptions({});
10539
+ setBooking("preview");
10540
+ }, []);
10337
10541
  useEffect6(() => {
10338
10542
  let cancelled = false;
10339
10543
  fetch(`${baseUrl}/api/v1/sdk/project`, {
@@ -10432,6 +10636,8 @@ function GlitchgrabProviderInner({
10432
10636
  baseUrl: baseUrl != null ? baseUrl : DEFAULT_BASE_URL2,
10433
10637
  open: booking !== "closed",
10434
10638
  preview: booking === "preview",
10639
+ intro: bookingOptions.intro,
10640
+ onBooked: bookingOptions.onBooked,
10435
10641
  onClose: () => setBooking("closed")
10436
10642
  }
10437
10643
  ),
@@ -10440,7 +10646,7 @@ function GlitchgrabProviderInner({
10440
10646
  {
10441
10647
  report,
10442
10648
  sendFeedback: feedback,
10443
- enhanceText: aiAssist && !aiBlockedUnsigned ? enhance : void 0,
10649
+ enhanceText: aiAssist && !aiBlockedUnsigned ? enhanceForDialog : void 0,
10444
10650
  assist: aiAssist && !aiBlockedUnsigned ? assist : void 0,
10445
10651
  findSimilarIssues: aiAssist && !aiBlockedUnsigned ? findSimilar : void 0,
10446
10652
  readFile: aiAssist && !aiBlockedUnsigned ? readFile : void 0,
@@ -10573,12 +10779,13 @@ function BookDemoButton({
10573
10779
  children
10574
10780
  }) {
10575
10781
  const { openBookingDialog } = useGlitchgrab();
10576
- if (children) return /* @__PURE__ */ jsx9(Fragment8, { children: children({ onClick: openBookingDialog }) });
10782
+ const open = () => openBookingDialog();
10783
+ if (children) return /* @__PURE__ */ jsx9(Fragment8, { children: children({ onClick: open }) });
10577
10784
  return /* @__PURE__ */ jsx9(
10578
10785
  "button",
10579
10786
  {
10580
10787
  type: "button",
10581
- onClick: openBookingDialog,
10788
+ onClick: open,
10582
10789
  className,
10583
10790
  style: className ? void 0 : {
10584
10791
  padding: "10px 18px",
@@ -10785,6 +10992,87 @@ function useGlitchgrabActions({
10785
10992
  error
10786
10993
  };
10787
10994
  }
10995
+ async function fetchGlitchgrabReportThread({
10996
+ token,
10997
+ reportId
10998
+ }) {
10999
+ var _a, _b, _c;
11000
+ const res = await fetch(`${BASE_URL}/api/v1/sdk/reports/${encodeURIComponent(reportId)}`, {
11001
+ headers: { Authorization: `Bearer ${token}` }
11002
+ });
11003
+ const data = await res.json().catch(() => ({}));
11004
+ if (!res.ok || !data.success) throw new Error((_a = data.error) != null ? _a : "Failed to fetch report");
11005
+ return { ...data.data, comments: (_c = (_b = data.data) == null ? void 0 : _b.comments) != null ? _c : [] };
11006
+ }
11007
+ async function postGlitchgrabReportComment({
11008
+ token,
11009
+ reportId,
11010
+ message,
11011
+ reporterName,
11012
+ reporterEmail
11013
+ }) {
11014
+ var _a;
11015
+ const res = await fetch(`${BASE_URL}/api/v1/sdk/reports/${encodeURIComponent(reportId)}/comments`, {
11016
+ method: "POST",
11017
+ headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
11018
+ body: JSON.stringify({ message, reporterName, reporterEmail })
11019
+ });
11020
+ const data = await res.json().catch(() => ({}));
11021
+ if (!res.ok || !data.success) throw new Error((_a = data.error) != null ? _a : "Failed to post comment");
11022
+ return data.data;
11023
+ }
11024
+ function useGlitchgrabReportThread({
11025
+ token,
11026
+ reportId,
11027
+ reporterName,
11028
+ reporterEmail
11029
+ }) {
11030
+ var _a;
11031
+ const [report, setReport] = useState8(null);
11032
+ const [isLoading, setIsLoading] = useState8(!!reportId);
11033
+ const [isFetching, setIsFetching] = useState8(false);
11034
+ const [isPosting, setIsPosting] = useState8(false);
11035
+ const [error, setError] = useState8(null);
11036
+ const refetch = useCallback4(async () => {
11037
+ if (!token || !reportId) {
11038
+ setReport(null);
11039
+ setIsLoading(false);
11040
+ return;
11041
+ }
11042
+ setIsFetching(true);
11043
+ setError(null);
11044
+ try {
11045
+ setReport(await fetchGlitchgrabReportThread({ token, reportId }));
11046
+ } catch (e) {
11047
+ setError(e instanceof Error ? e.message : "Network error");
11048
+ } finally {
11049
+ setIsLoading(false);
11050
+ setIsFetching(false);
11051
+ }
11052
+ }, [token, reportId]);
11053
+ useEffect7(() => {
11054
+ refetch();
11055
+ }, [refetch]);
11056
+ const addComment = useCallback4(
11057
+ async (message) => {
11058
+ if (!token || !reportId || !message.trim()) return false;
11059
+ setIsPosting(true);
11060
+ setError(null);
11061
+ try {
11062
+ await postGlitchgrabReportComment({ token, reportId, message: message.trim(), reporterName, reporterEmail });
11063
+ await refetch();
11064
+ return true;
11065
+ } catch (e) {
11066
+ setError(e instanceof Error ? e.message : "Failed to post comment");
11067
+ return false;
11068
+ } finally {
11069
+ setIsPosting(false);
11070
+ }
11071
+ },
11072
+ [token, reportId, reporterName, reporterEmail, refetch]
11073
+ );
11074
+ return { report, comments: (_a = report == null ? void 0 : report.comments) != null ? _a : [], isLoading, isFetching, isPosting, error, refetch, addComment };
11075
+ }
10788
11076
 
10789
11077
  // src/use-feedback.ts
10790
11078
  import { useState as useState9, useEffect as useEffect8, useCallback as useCallback5 } from "react";
@@ -10865,12 +11153,14 @@ export {
10865
11153
  computeSignature,
10866
11154
  enhanceText,
10867
11155
  fetchGlitchgrabFeedback,
11156
+ fetchGlitchgrabReportThread,
10868
11157
  fetchGlitchgrabReports,
10869
11158
  getAppContext,
10870
11159
  getBreadcrumbs,
10871
11160
  getShortcutLabel2 as getShortcutLabel,
10872
11161
  initBreadcrumbs,
10873
11162
  matchesShortcut,
11163
+ postGlitchgrabReportComment,
10874
11164
  sanitizeUrl,
10875
11165
  sendFeedback,
10876
11166
  sendLead,
@@ -10881,6 +11171,7 @@ export {
10881
11171
  useGlitchgrab,
10882
11172
  useGlitchgrabActions,
10883
11173
  useGlitchgrabFeedback,
11174
+ useGlitchgrabReportThread,
10884
11175
  useGlitchgrabReports
10885
11176
  };
10886
11177
  //# sourceMappingURL=index.mjs.map