glitchgrab 1.11.0 → 1.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -4,8 +4,8 @@
4
4
  import {
5
5
  createContext,
6
6
  useContext,
7
- useRef,
8
- useEffect,
7
+ useRef as useRef2,
8
+ useEffect as useEffect2,
9
9
  useCallback
10
10
  } from "react";
11
11
 
@@ -316,244 +316,10 @@ var GlitchgrabErrorBoundary = class extends React.Component {
316
316
  }
317
317
  };
318
318
 
319
- // src/provider.tsx
320
- import { Fragment, jsx } from "react/jsx-runtime";
321
- var DEFAULT_BASE_URL2 = "https://www.glitchgrab.dev";
322
- var GlitchgrabContext = createContext(null);
323
- function useGlitchgrab() {
324
- const ctx = useContext(GlitchgrabContext);
325
- if (!ctx) {
326
- throw new Error("useGlitchgrab must be used within a GlitchgrabProvider");
327
- }
328
- return ctx;
329
- }
330
- function GlitchgrabProviderInner({
331
- token,
332
- session,
333
- baseUrl,
334
- onError,
335
- onReportSent,
336
- breadcrumbs: enableBreadcrumbs = true,
337
- maxBreadcrumbs: maxBreadcrumbs2 = 50,
338
- children,
339
- fallback
340
- }) {
341
- const visitedPagesRef = useRef([]);
342
- useEffect(() => {
343
- if (enableBreadcrumbs) {
344
- initBreadcrumbs(maxBreadcrumbs2);
345
- }
346
- }, [enableBreadcrumbs, maxBreadcrumbs2]);
347
- useEffect(() => {
348
- try {
349
- if (typeof window === "undefined") return;
350
- const trackPage = () => {
351
- try {
352
- const sanitized = sanitizeUrl(window.location.href);
353
- const pages = visitedPagesRef.current;
354
- if (pages[pages.length - 1] !== sanitized) {
355
- pages.push(sanitized);
356
- if (pages.length > 20) {
357
- pages.splice(0, pages.length - 20);
358
- }
359
- }
360
- } catch (e) {
361
- }
362
- };
363
- trackPage();
364
- const handlePopState = () => trackPage();
365
- window.addEventListener("popstate", handlePopState);
366
- const origPushState = history.pushState.bind(history);
367
- const origReplaceState = history.replaceState.bind(history);
368
- history.pushState = function(...args) {
369
- origPushState(...args);
370
- trackPage();
371
- };
372
- history.replaceState = function(...args) {
373
- origReplaceState(...args);
374
- trackPage();
375
- };
376
- return () => {
377
- window.removeEventListener("popstate", handlePopState);
378
- history.pushState = origPushState;
379
- history.replaceState = origReplaceState;
380
- };
381
- } catch (e) {
382
- }
383
- }, []);
384
- useEffect(() => {
385
- try {
386
- if (typeof window === "undefined") return;
387
- if (process.env.NODE_ENV === "development") return;
388
- const handleError = (event) => {
389
- var _a, _b, _c, _d, _e;
390
- try {
391
- const context = captureContext(visitedPagesRef.current);
392
- const payload = {
393
- token,
394
- source: "SDK_AUTO",
395
- type: "BUG",
396
- errorMessage: event.message,
397
- errorStack: (_a = event.error) == null ? void 0 : _a.stack,
398
- pageUrl: context.url,
399
- userAgent: context.userAgent,
400
- breadcrumbs: context.breadcrumbs,
401
- deviceInfo: (_b = context.deviceInfo) != null ? _b : void 0,
402
- metadata: {
403
- timestamp: context.timestamp,
404
- visitedPages: JSON.stringify(context.visitedPages),
405
- filename: (_c = event.filename) != null ? _c : "",
406
- lineno: String((_d = event.lineno) != null ? _d : ""),
407
- colno: String((_e = event.colno) != null ? _e : ""),
408
- ...(session == null ? void 0 : session.userId) ? { sessionUserId: session.userId } : {},
409
- ...(session == null ? void 0 : session.name) ? { sessionUserName: String(session.name) } : {},
410
- ...(session == null ? void 0 : session.email) ? { sessionUserEmail: String(session.email) } : {}
411
- }
412
- };
413
- sendReport(payload, baseUrl).then((result) => {
414
- if (result && onReportSent) onReportSent(result);
415
- });
416
- if (onError && event.error) onError(event.error);
417
- } catch (e) {
418
- }
419
- };
420
- const handleRejection = (event) => {
421
- var _a;
422
- try {
423
- const context = captureContext(visitedPagesRef.current);
424
- const reason = event.reason;
425
- const payload = {
426
- token,
427
- source: "SDK_AUTO",
428
- type: "BUG",
429
- errorMessage: reason instanceof Error ? reason.message : String(reason),
430
- errorStack: reason instanceof Error ? reason.stack : void 0,
431
- pageUrl: context.url,
432
- userAgent: context.userAgent,
433
- breadcrumbs: context.breadcrumbs,
434
- deviceInfo: (_a = context.deviceInfo) != null ? _a : void 0,
435
- metadata: {
436
- timestamp: context.timestamp,
437
- visitedPages: JSON.stringify(context.visitedPages),
438
- type: "unhandledrejection",
439
- ...(session == null ? void 0 : session.userId) ? { sessionUserId: session.userId } : {},
440
- ...(session == null ? void 0 : session.name) ? { sessionUserName: String(session.name) } : {},
441
- ...(session == null ? void 0 : session.email) ? { sessionUserEmail: String(session.email) } : {}
442
- }
443
- };
444
- sendReport(payload, baseUrl).then((result) => {
445
- if (result && onReportSent) onReportSent(result);
446
- });
447
- if (onError && reason instanceof Error) onError(reason);
448
- } catch (e) {
449
- }
450
- };
451
- window.addEventListener("error", handleError);
452
- window.addEventListener("unhandledrejection", handleRejection);
453
- return () => {
454
- window.removeEventListener("error", handleError);
455
- window.removeEventListener("unhandledrejection", handleRejection);
456
- };
457
- } catch (e) {
458
- }
459
- }, [token, baseUrl, onError, onReportSent]);
460
- const report = useCallback(
461
- async (type, description, metadata) => {
462
- var _a;
463
- try {
464
- const context = captureContext(visitedPagesRef.current);
465
- const payload = {
466
- token,
467
- source: "SDK_USER_REPORT",
468
- type,
469
- description,
470
- pageUrl: context.url,
471
- userAgent: context.userAgent,
472
- breadcrumbs: context.breadcrumbs,
473
- deviceInfo: (_a = context.deviceInfo) != null ? _a : void 0,
474
- metadata: {
475
- timestamp: context.timestamp,
476
- visitedPages: JSON.stringify(context.visitedPages),
477
- ...(session == null ? void 0 : session.userId) ? { sessionUserId: session.userId } : {},
478
- ...(session == null ? void 0 : session.name) ? { sessionUserName: String(session.name) } : {},
479
- ...(session == null ? void 0 : session.email) ? { sessionUserEmail: String(session.email) } : {},
480
- ...(session == null ? void 0 : session.phone) ? { sessionUserPhone: String(session.phone) } : {},
481
- ...metadata
482
- }
483
- };
484
- const result = await sendReport(payload, baseUrl);
485
- if (result && onReportSent) onReportSent(result);
486
- return result;
487
- } catch (e) {
488
- return null;
489
- }
490
- },
491
- [token, baseUrl, onReportSent, session]
492
- );
493
- const reportBug = useCallback(
494
- (description, metadata) => report("BUG", description, metadata),
495
- [report]
496
- );
497
- const addBreadcrumb2 = useCallback(
498
- (message, data) => {
499
- addBreadcrumb("custom", message, data);
500
- },
501
- []
502
- );
503
- const openReportDialog = useCallback((options) => {
504
- if (typeof window !== "undefined") {
505
- window.dispatchEvent(new CustomEvent("glitchgrab:open-report", { detail: options }));
506
- }
507
- }, []);
508
- return /* @__PURE__ */ jsx(
509
- GlitchgrabContext.Provider,
510
- {
511
- value: {
512
- token,
513
- baseUrl: baseUrl != null ? baseUrl : DEFAULT_BASE_URL2,
514
- reportBug,
515
- report,
516
- addBreadcrumb: addBreadcrumb2,
517
- openReportDialog
518
- },
519
- children: /* @__PURE__ */ jsx(
520
- GlitchgrabErrorBoundary,
521
- {
522
- token,
523
- baseUrl,
524
- onError,
525
- fallback,
526
- visitedPages: visitedPagesRef.current,
527
- children
528
- }
529
- )
530
- }
531
- );
532
- }
533
- function GlitchgrabProvider(props) {
534
- if (!props.token) return /* @__PURE__ */ jsx(Fragment, { children: props.children });
535
- const resolvedProps = {
536
- ...props,
537
- baseUrl: props.baseUrl || DEFAULT_BASE_URL2
538
- };
539
- try {
540
- return /* @__PURE__ */ jsx(GlitchgrabProviderInner, { ...resolvedProps });
541
- } catch (e) {
542
- return /* @__PURE__ */ jsx(Fragment, { children: props.children });
543
- }
544
- }
545
-
546
- // src/report-button.tsx
547
- import { useState as useState2, useRef as useRef2, useEffect as useEffect2 } from "react";
319
+ // src/report-dialog.tsx
320
+ import { useState, useRef, useEffect } from "react";
548
321
  import { createPortal } from "react-dom";
549
- import { Fragment as Fragment2, jsx as jsx2, jsxs } from "react/jsx-runtime";
550
- function useGlitchgrabSafe() {
551
- try {
552
- return useGlitchgrab();
553
- } catch (e) {
554
- return null;
555
- }
556
- }
322
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
557
323
  function useIsDark() {
558
324
  if (typeof window === "undefined") return true;
559
325
  try {
@@ -571,30 +337,28 @@ function useIsDark() {
571
337
  function getTheme(dark) {
572
338
  return dark ? { bg: "#1c1c1e", bgSecondary: "#27272a", border: "#2c2c2e", text: "#fafafa", textMuted: "#a1a1aa", accent: "#22d3ee", accentText: "#09090b", inputBg: "#27272a", inputBorder: "#3f3f46" } : { bg: "#ffffff", bgSecondary: "#f4f4f5", border: "#e4e4e7", text: "#18181b", textMuted: "#71717a", accent: "#0891b2", accentText: "#ffffff", inputBg: "#fafafa", inputBorder: "#d4d4d8" };
573
339
  }
574
- function ReportButton({
575
- position = "bottom-right",
576
- label = "Report Bug",
577
- className,
578
- children
340
+ function ReportDialog({
341
+ report,
342
+ types,
343
+ showSeverity = true
579
344
  }) {
580
- var _a, _b;
581
- const [capturing, setCapturing] = useState2(false);
582
- const [isOpen, setIsOpen] = useState2(false);
583
- const [description, setDescription] = useState2("");
584
- const [isSubmitting, setIsSubmitting] = useState2(false);
585
- const [submitted, setSubmitted] = useState2(false);
586
- const [screenshot, setScreenshot] = useState2(null);
587
- const [previewOpen, setPreviewOpen] = useState2(false);
588
- const fileInputRef = useRef2(null);
589
- const modalRef = useRef2(null);
590
- const glitchgrab = useGlitchgrabSafe();
591
- if (!glitchgrab) return null;
592
- const { reportBug } = glitchgrab;
345
+ const [isOpen, setIsOpen] = useState(false);
346
+ const [description, setDescription] = useState("");
347
+ const [isSubmitting, setIsSubmitting] = useState(false);
348
+ const [submitted, setSubmitted] = useState(false);
349
+ const [screenshot, setScreenshot] = useState(null);
350
+ const [previewOpen, setPreviewOpen] = useState(false);
351
+ const fileInputRef = useRef(null);
352
+ const modalRef = useRef(null);
353
+ const [step, setStep] = useState(1);
354
+ const [reportType, setReportType] = useState("BUG");
355
+ const [severity, setSeverity] = useState("medium");
356
+ const availableTypes = types != null ? types : ["BUG", "FEATURE_REQUEST", "QUESTION", "OTHER"];
593
357
  const isDark = useIsDark();
594
358
  const t = getTheme(isDark);
595
359
  const handleFileUpload = (e) => {
596
- var _a2;
597
- const file = (_a2 = e.target.files) == null ? void 0 : _a2[0];
360
+ var _a;
361
+ const file = (_a = e.target.files) == null ? void 0 : _a[0];
598
362
  if (!file) return;
599
363
  const reader = new FileReader();
600
364
  reader.onload = () => {
@@ -622,8 +386,11 @@ function ReportButton({
622
386
  setIsOpen(true);
623
387
  };
624
388
  const handleOpen = async () => {
625
- setCapturing(true);
626
389
  setSubmitted(false);
390
+ if (availableTypes.length === 1) {
391
+ setReportType(availableTypes[0]);
392
+ setStep(2);
393
+ }
627
394
  try {
628
395
  const { default: html2canvas } = await import("html2canvas-pro");
629
396
  const canvas = await html2canvas(document.body, {
@@ -635,42 +402,56 @@ function ReportButton({
635
402
  setScreenshot(canvas.toDataURL("image/jpeg", 0.6));
636
403
  } catch (e) {
637
404
  }
638
- setCapturing(false);
639
405
  setIsOpen(true);
640
406
  };
641
- useEffect2(() => {
407
+ useEffect(() => {
642
408
  const handler = (e) => {
643
409
  if (isOpen) return;
644
410
  const detail = e.detail;
645
411
  if (detail == null ? void 0 : detail.description) setDescription(detail.description);
412
+ if (detail == null ? void 0 : detail.type) {
413
+ setReportType(detail.type);
414
+ setStep(2);
415
+ }
646
416
  handleOpen();
647
417
  };
648
418
  window.addEventListener("glitchgrab:open-report", handler);
649
419
  return () => window.removeEventListener("glitchgrab:open-report", handler);
650
420
  }, [isOpen, handleOpen]);
651
- useEffect2(() => {
421
+ useEffect(() => {
652
422
  if (!isOpen) return;
653
423
  const handleEsc = (e) => {
654
424
  if (e.key === "Escape") {
655
425
  if (previewOpen) setPreviewOpen(false);
656
- else setIsOpen(false);
426
+ else handleClose();
657
427
  }
658
428
  };
659
429
  document.addEventListener("keydown", handleEsc);
660
430
  return () => document.removeEventListener("keydown", handleEsc);
661
431
  }, [isOpen, previewOpen]);
432
+ const handleClose = () => {
433
+ setIsOpen(false);
434
+ setStep(1);
435
+ setReportType("BUG");
436
+ setSeverity("medium");
437
+ };
662
438
  const handleSubmit = async () => {
663
439
  try {
664
440
  if (!description.trim() || isSubmitting) return;
665
441
  setIsSubmitting(true);
666
- const result = await reportBug(description.trim(), screenshot ? { screenshot } : void 0);
442
+ const metadata = {};
443
+ if (screenshot) metadata.screenshot = screenshot;
444
+ if (showSeverity && reportType === "BUG") {
445
+ metadata.severity = severity;
446
+ }
447
+ const result = await report(reportType, description.trim(), Object.keys(metadata).length > 0 ? metadata : void 0);
667
448
  if (result) {
668
449
  setSubmitted(true);
669
450
  setDescription("");
670
451
  setScreenshot(null);
671
452
  setTimeout(() => {
672
453
  setSubmitted(false);
673
- setIsOpen(false);
454
+ handleClose();
674
455
  }, 2e3);
675
456
  }
676
457
  setIsSubmitting(false);
@@ -678,27 +459,10 @@ function ReportButton({
678
459
  setIsSubmitting(false);
679
460
  }
680
461
  };
681
- const isTop = (_a = position == null ? void 0 : position.startsWith("top")) != null ? _a : false;
682
- const isLeft = (_b = position == null ? void 0 : position.endsWith("left")) != null ? _b : false;
683
- const modalPositionStyles = children ? {} : {
684
- ...isLeft ? { left: "16px" } : { right: "16px" },
685
- ...isTop ? { top: "64px" } : { bottom: "64px" }
686
- };
687
- return /* @__PURE__ */ jsxs(Fragment2, { children: [
688
- children ? children({ onClick: handleOpen, capturing }) : /* @__PURE__ */ jsx2(
689
- DefaultTrigger,
690
- {
691
- onClick: handleOpen,
692
- capturing,
693
- label,
694
- className,
695
- position,
696
- isLeft,
697
- isTop
698
- }
699
- ),
700
- isOpen && typeof document !== "undefined" && createPortal(
701
- /* @__PURE__ */ jsx2(
462
+ if (typeof document === "undefined") return null;
463
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
464
+ isOpen && createPortal(
465
+ /* @__PURE__ */ jsx(
702
466
  "div",
703
467
  {
704
468
  style: {
@@ -712,7 +476,7 @@ function ReportButton({
712
476
  fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
713
477
  },
714
478
  onClick: () => {
715
- if (!previewOpen) setIsOpen(false);
479
+ if (!previewOpen) handleClose();
716
480
  },
717
481
  children: /* @__PURE__ */ jsxs(
718
482
  "div",
@@ -720,8 +484,7 @@ function ReportButton({
720
484
  ref: modalRef,
721
485
  onClick: (e) => e.stopPropagation(),
722
486
  style: {
723
- position: children ? "relative" : "fixed",
724
- ...modalPositionStyles,
487
+ position: "relative",
725
488
  zIndex: 2147483647,
726
489
  width: "340px",
727
490
  maxWidth: "calc(100% - 32px)",
@@ -734,59 +497,95 @@ function ReportButton({
734
497
  isolation: "isolate"
735
498
  },
736
499
  children: [
737
- /* @__PURE__ */ jsx2(
500
+ /* @__PURE__ */ jsxs(
738
501
  "div",
739
502
  {
740
503
  style: {
741
504
  padding: "16px 16px 12px",
742
505
  borderBottom: `1px solid ${t.border}`
743
506
  },
744
- children: /* @__PURE__ */ jsxs(
745
- "div",
746
- {
747
- style: {
748
- display: "flex",
749
- justifyContent: "space-between",
750
- alignItems: "center"
751
- },
752
- children: [
753
- /* @__PURE__ */ jsx2(
754
- "span",
755
- {
756
- style: {
757
- fontSize: "15px",
758
- fontWeight: 600,
759
- color: t.text
760
- },
761
- children: "Report a Bug"
762
- }
763
- ),
764
- /* @__PURE__ */ jsx2(
765
- "button",
766
- {
767
- type: "button",
768
- onClick: () => setIsOpen(false),
769
- style: {
770
- background: "none",
771
- border: "none",
772
- cursor: "pointer",
773
- padding: "4px",
774
- display: "flex",
775
- alignItems: "center",
776
- justifyContent: "center"
777
- },
778
- "aria-label": "Close",
779
- children: /* @__PURE__ */ jsx2("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", style: { display: "block" }, children: /* @__PURE__ */ jsx2("path", { d: "M4 4L12 12M12 4L4 12", stroke: t.textMuted, strokeWidth: "2", strokeLinecap: "round" }) })
780
- }
781
- )
782
- ]
783
- }
784
- )
785
- }
786
- ),
787
- /* @__PURE__ */ jsx2("div", { style: { padding: "16px" }, children: submitted ? /* @__PURE__ */ jsx2(
788
- "div",
789
- {
507
+ children: [
508
+ /* @__PURE__ */ jsxs(
509
+ "div",
510
+ {
511
+ style: {
512
+ display: "flex",
513
+ justifyContent: "space-between",
514
+ alignItems: "center"
515
+ },
516
+ children: [
517
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
518
+ step > 1 && /* @__PURE__ */ jsx(
519
+ "button",
520
+ {
521
+ type: "button",
522
+ onClick: () => setStep((s) => s - 1),
523
+ style: {
524
+ background: "none",
525
+ border: "none",
526
+ cursor: "pointer",
527
+ padding: "2px",
528
+ display: "flex",
529
+ alignItems: "center"
530
+ },
531
+ "aria-label": "Back",
532
+ children: /* @__PURE__ */ jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: /* @__PURE__ */ jsx("path", { d: "M10 3L5 8L10 13", stroke: t.textMuted, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) })
533
+ }
534
+ ),
535
+ /* @__PURE__ */ jsx(
536
+ "span",
537
+ {
538
+ style: {
539
+ fontSize: "15px",
540
+ fontWeight: 600,
541
+ color: t.text
542
+ },
543
+ children: step === 1 ? "What's on your mind?" : step === 2 ? "Tell us more" : "Review & Send"
544
+ }
545
+ )
546
+ ] }),
547
+ /* @__PURE__ */ jsx(
548
+ "button",
549
+ {
550
+ type: "button",
551
+ onClick: handleClose,
552
+ style: {
553
+ background: "none",
554
+ border: "none",
555
+ cursor: "pointer",
556
+ padding: "4px",
557
+ display: "flex",
558
+ alignItems: "center",
559
+ justifyContent: "center"
560
+ },
561
+ "aria-label": "Close",
562
+ children: /* @__PURE__ */ jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", style: { display: "block" }, children: /* @__PURE__ */ jsx("path", { d: "M4 4L12 12M12 4L4 12", stroke: t.textMuted, strokeWidth: "2", strokeLinecap: "round" }) })
563
+ }
564
+ )
565
+ ]
566
+ }
567
+ ),
568
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", alignItems: "center", justifyContent: "center", gap: "0", marginTop: "12px" }, children: [1, 2, 3].map((s, i) => /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center" }, children: [
569
+ /* @__PURE__ */ jsx("div", { style: {
570
+ width: "8px",
571
+ height: "8px",
572
+ borderRadius: "50%",
573
+ backgroundColor: s <= step ? t.accent : t.inputBorder,
574
+ transition: "background-color 0.2s ease"
575
+ } }),
576
+ i < 2 && /* @__PURE__ */ jsx("div", { style: {
577
+ width: "40px",
578
+ height: "2px",
579
+ backgroundColor: s < step ? t.accent : t.inputBorder,
580
+ transition: "background-color 0.2s ease"
581
+ } })
582
+ ] }, s)) })
583
+ ]
584
+ }
585
+ ),
586
+ /* @__PURE__ */ jsx("div", { style: { padding: "16px" }, children: submitted ? /* @__PURE__ */ jsxs(
587
+ "div",
588
+ {
790
589
  style: {
791
590
  textAlign: "center",
792
591
  padding: "20px 0",
@@ -794,202 +593,280 @@ function ReportButton({
794
593
  fontSize: "14px",
795
594
  fontWeight: 500
796
595
  },
797
- children: "Bug report sent. Thank you!"
596
+ children: [
597
+ getTypeLabel(reportType),
598
+ " sent. Thank you!"
599
+ ]
798
600
  }
799
- ) : /* @__PURE__ */ jsxs(Fragment2, { children: [
800
- screenshot && /* @__PURE__ */ jsxs("div", { style: { marginBottom: "10px", position: "relative" }, children: [
801
- /* @__PURE__ */ jsx2(
802
- "img",
601
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
602
+ step === 1 && /* @__PURE__ */ jsx("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "8px" }, children: availableTypes.map((type) => /* @__PURE__ */ jsxs(
603
+ "button",
604
+ {
605
+ type: "button",
606
+ onClick: () => {
607
+ setReportType(type);
608
+ setStep(2);
609
+ },
610
+ style: {
611
+ display: "flex",
612
+ flexDirection: "column",
613
+ alignItems: "center",
614
+ gap: "8px",
615
+ padding: "16px 8px",
616
+ borderRadius: "8px",
617
+ border: `1px solid ${t.inputBorder}`,
618
+ background: t.inputBg,
619
+ cursor: "pointer",
620
+ color: t.text,
621
+ fontFamily: "inherit",
622
+ transition: "border-color 0.15s ease"
623
+ },
624
+ onMouseEnter: (e) => {
625
+ e.currentTarget.style.borderColor = t.accent;
626
+ },
627
+ onMouseLeave: (e) => {
628
+ e.currentTarget.style.borderColor = t.inputBorder;
629
+ },
630
+ children: [
631
+ getTypeIcon(type, t.accent),
632
+ /* @__PURE__ */ jsx("span", { style: { fontSize: "13px", fontWeight: 600 }, children: getTypeLabel(type) }),
633
+ /* @__PURE__ */ jsx("span", { style: { fontSize: "11px", color: t.textMuted, lineHeight: "1.3" }, children: getTypeSubtitle(type) })
634
+ ]
635
+ },
636
+ type
637
+ )) }),
638
+ step === 2 && /* @__PURE__ */ jsxs(Fragment, { children: [
639
+ /* @__PURE__ */ jsx(
640
+ "textarea",
803
641
  {
804
- src: screenshot,
805
- alt: "Page screenshot",
806
- onClick: () => setPreviewOpen(true),
642
+ value: description,
643
+ onChange: (e) => setDescription(e.target.value),
644
+ placeholder: getPlaceholder(reportType),
807
645
  style: {
808
646
  width: "100%",
809
- borderRadius: "6px",
810
- border: `1px solid ${t.border}`,
811
- maxHeight: "120px",
812
- objectFit: "cover",
813
- objectPosition: "top",
814
- cursor: "pointer"
815
- }
647
+ minHeight: "100px",
648
+ padding: "10px 12px",
649
+ borderRadius: "8px",
650
+ border: `1px solid ${t.inputBorder}`,
651
+ fontSize: "14px",
652
+ fontFamily: "inherit",
653
+ resize: "vertical",
654
+ outline: "none",
655
+ boxSizing: "border-box",
656
+ color: t.text,
657
+ backgroundColor: t.inputBg
658
+ },
659
+ onFocus: (e) => {
660
+ e.target.style.borderColor = t.accent;
661
+ },
662
+ onBlur: (e) => {
663
+ e.target.style.borderColor = t.inputBorder;
664
+ },
665
+ autoFocus: true
816
666
  }
817
667
  ),
818
- /* @__PURE__ */ jsx2(
668
+ showSeverity && reportType === "BUG" && /* @__PURE__ */ jsxs("div", { style: { marginTop: "10px" }, children: [
669
+ /* @__PURE__ */ jsx("span", { style: { fontSize: "12px", color: t.textMuted, marginBottom: "6px", display: "block" }, children: "Severity" }),
670
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: "6px" }, children: ["low", "medium", "high"].map((s) => /* @__PURE__ */ jsx(
671
+ "button",
672
+ {
673
+ type: "button",
674
+ onClick: () => setSeverity(s),
675
+ style: {
676
+ flex: 1,
677
+ padding: "6px 0",
678
+ borderRadius: "6px",
679
+ border: `1px solid ${severity === s ? t.accent : t.inputBorder}`,
680
+ background: severity === s ? t.accent : "transparent",
681
+ color: severity === s ? t.accentText : t.text,
682
+ fontSize: "12px",
683
+ fontWeight: 600,
684
+ cursor: "pointer",
685
+ fontFamily: "inherit",
686
+ textTransform: "capitalize",
687
+ transition: "all 0.15s ease"
688
+ },
689
+ children: s
690
+ },
691
+ s
692
+ )) })
693
+ ] }),
694
+ /* @__PURE__ */ jsx(
819
695
  "button",
820
696
  {
821
697
  type: "button",
822
- onClick: () => setScreenshot(null),
698
+ onClick: () => setStep(3),
699
+ disabled: !description.trim(),
823
700
  style: {
824
- position: "absolute",
825
- top: "4px",
826
- right: "4px",
827
- background: "rgba(0,0,0,0.6)",
701
+ marginTop: "12px",
702
+ width: "100%",
703
+ padding: "10px",
704
+ borderRadius: "8px",
828
705
  border: "none",
829
- borderRadius: "50%",
830
- width: "20px",
831
- height: "20px",
832
- cursor: "pointer",
833
- display: "flex",
834
- alignItems: "center",
835
- justifyContent: "center"
706
+ backgroundColor: !description.trim() ? t.bgSecondary : t.accent,
707
+ color: !description.trim() ? t.textMuted : t.accentText,
708
+ fontSize: "14px",
709
+ fontWeight: 600,
710
+ cursor: !description.trim() ? "not-allowed" : "pointer",
711
+ fontFamily: "inherit",
712
+ transition: "background-color 0.15s ease"
836
713
  },
837
- "aria-label": "Remove screenshot",
838
- children: /* @__PURE__ */ jsx2("svg", { width: "10", height: "10", viewBox: "0 0 10 10", fill: "none", children: /* @__PURE__ */ jsx2("path", { d: "M1 1L9 9M9 1L1 9", stroke: "#fff", strokeWidth: "1.5", strokeLinecap: "round" }) })
714
+ children: "Next"
839
715
  }
840
- ),
841
- /* @__PURE__ */ jsxs(
842
- "div",
716
+ )
717
+ ] }),
718
+ step === 3 && /* @__PURE__ */ jsxs(Fragment, { children: [
719
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "6px", flexWrap: "wrap", marginBottom: "12px" }, children: [
720
+ /* @__PURE__ */ jsxs("span", { style: {
721
+ display: "inline-flex",
722
+ alignItems: "center",
723
+ gap: "4px",
724
+ padding: "4px 10px",
725
+ borderRadius: "12px",
726
+ fontSize: "12px",
727
+ fontWeight: 600,
728
+ backgroundColor: t.bgSecondary,
729
+ color: t.text,
730
+ border: `1px solid ${t.border}`
731
+ }, children: [
732
+ getTypeIcon(reportType, t.accent, 12),
733
+ getTypeLabel(reportType)
734
+ ] }),
735
+ showSeverity && reportType === "BUG" && /* @__PURE__ */ jsx("span", { style: {
736
+ display: "inline-flex",
737
+ alignItems: "center",
738
+ padding: "4px 10px",
739
+ borderRadius: "12px",
740
+ fontSize: "12px",
741
+ fontWeight: 600,
742
+ backgroundColor: severity === "high" ? "#fef2f2" : severity === "medium" ? "#fffbeb" : "#f0fdf4",
743
+ color: severity === "high" ? "#dc2626" : severity === "medium" ? "#d97706" : "#16a34a",
744
+ border: `1px solid ${severity === "high" ? "#fecaca" : severity === "medium" ? "#fde68a" : "#bbf7d0"}`,
745
+ textTransform: "capitalize"
746
+ }, children: severity })
747
+ ] }),
748
+ /* @__PURE__ */ jsx("div", { style: {
749
+ padding: "10px 12px",
750
+ borderRadius: "8px",
751
+ border: `1px solid ${t.inputBorder}`,
752
+ backgroundColor: t.inputBg,
753
+ fontSize: "13px",
754
+ color: t.text,
755
+ marginBottom: "10px",
756
+ maxHeight: "80px",
757
+ overflow: "hidden",
758
+ lineHeight: "1.4"
759
+ }, children: description.length > 200 ? description.slice(0, 200) + "..." : description }),
760
+ screenshot && /* @__PURE__ */ jsxs("div", { style: { marginBottom: "10px", position: "relative" }, children: [
761
+ /* @__PURE__ */ jsx(
762
+ "img",
763
+ {
764
+ src: screenshot,
765
+ alt: "Page screenshot",
766
+ onClick: () => setPreviewOpen(true),
767
+ style: {
768
+ width: "100%",
769
+ borderRadius: "6px",
770
+ border: `1px solid ${t.border}`,
771
+ maxHeight: "80px",
772
+ objectFit: "cover",
773
+ objectPosition: "top",
774
+ cursor: "pointer"
775
+ }
776
+ }
777
+ ),
778
+ /* @__PURE__ */ jsx(
779
+ "button",
780
+ {
781
+ type: "button",
782
+ onClick: () => setScreenshot(null),
783
+ style: {
784
+ position: "absolute",
785
+ top: "4px",
786
+ right: "4px",
787
+ background: "rgba(0,0,0,0.6)",
788
+ border: "none",
789
+ borderRadius: "50%",
790
+ width: "20px",
791
+ height: "20px",
792
+ cursor: "pointer",
793
+ display: "flex",
794
+ alignItems: "center",
795
+ justifyContent: "center"
796
+ },
797
+ "aria-label": "Remove screenshot",
798
+ children: /* @__PURE__ */ jsx("svg", { width: "10", height: "10", viewBox: "0 0 10 10", fill: "none", children: /* @__PURE__ */ jsx("path", { d: "M1 1L9 9M9 1L1 9", stroke: "#fff", strokeWidth: "1.5", strokeLinecap: "round" }) })
799
+ }
800
+ )
801
+ ] }),
802
+ !screenshot && /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "6px", marginBottom: "10px" }, children: [
803
+ /* @__PURE__ */ jsx(
804
+ "button",
805
+ {
806
+ type: "button",
807
+ onClick: () => {
808
+ var _a;
809
+ return (_a = fileInputRef.current) == null ? void 0 : _a.click();
810
+ },
811
+ style: {
812
+ flex: 1,
813
+ padding: "8px",
814
+ borderRadius: "6px",
815
+ border: `1px dashed ${t.inputBorder}`,
816
+ textAlign: "center",
817
+ fontSize: "11px",
818
+ color: t.textMuted,
819
+ background: "none",
820
+ cursor: "pointer"
821
+ },
822
+ children: "Upload screenshot"
823
+ }
824
+ ),
825
+ /* @__PURE__ */ jsx(
826
+ "button",
827
+ {
828
+ type: "button",
829
+ onClick: retakeScreenshot,
830
+ style: {
831
+ flex: 1,
832
+ padding: "8px",
833
+ borderRadius: "6px",
834
+ border: `1px dashed ${t.inputBorder}`,
835
+ textAlign: "center",
836
+ fontSize: "11px",
837
+ color: t.textMuted,
838
+ background: "none",
839
+ cursor: "pointer"
840
+ },
841
+ children: "Retake screenshot"
842
+ }
843
+ )
844
+ ] }),
845
+ /* @__PURE__ */ jsx(
846
+ "button",
843
847
  {
848
+ type: "button",
849
+ onClick: handleSubmit,
850
+ disabled: isSubmitting,
844
851
  style: {
845
- position: "absolute",
846
- bottom: "4px",
847
- left: "6px",
848
- right: "6px",
849
- display: "flex",
850
- justifyContent: "space-between",
851
- alignItems: "center"
852
+ width: "100%",
853
+ padding: "10px",
854
+ borderRadius: "8px",
855
+ border: "none",
856
+ backgroundColor: isSubmitting ? t.bgSecondary : t.accent,
857
+ color: isSubmitting ? t.textMuted : t.accentText,
858
+ fontSize: "14px",
859
+ fontWeight: 600,
860
+ cursor: isSubmitting ? "not-allowed" : "pointer",
861
+ fontFamily: "inherit",
862
+ transition: "background-color 0.15s ease"
852
863
  },
853
- children: [
854
- /* @__PURE__ */ jsxs(
855
- "span",
856
- {
857
- style: {
858
- background: "rgba(0,0,0,0.6)",
859
- color: "#fff",
860
- fontSize: "10px",
861
- padding: "2px 6px",
862
- borderRadius: "4px"
863
- },
864
- children: [
865
- "Screenshot (",
866
- Math.round(screenshot.length * 0.75 / 1024),
867
- "KB) \u2014 click to preview"
868
- ]
869
- }
870
- ),
871
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "4px" }, children: [
872
- /* @__PURE__ */ jsx2(
873
- "button",
874
- {
875
- type: "button",
876
- onClick: () => {
877
- var _a2;
878
- return (_a2 = fileInputRef.current) == null ? void 0 : _a2.click();
879
- },
880
- style: {
881
- background: t.accent,
882
- color: t.accentText,
883
- fontSize: "10px",
884
- padding: "3px 8px",
885
- borderRadius: "4px",
886
- border: "none",
887
- cursor: "pointer",
888
- fontWeight: 600
889
- },
890
- children: "Upload"
891
- }
892
- ),
893
- /* @__PURE__ */ jsx2(
894
- "button",
895
- {
896
- type: "button",
897
- onClick: retakeScreenshot,
898
- style: {
899
- background: t.accent,
900
- color: t.accentText,
901
- fontSize: "10px",
902
- padding: "3px 8px",
903
- borderRadius: "4px",
904
- border: "none",
905
- cursor: "pointer",
906
- fontWeight: 600
907
- },
908
- children: "Retake"
909
- }
910
- )
911
- ] })
912
- ]
864
+ children: isSubmitting ? "Sending..." : "Send Report"
913
865
  }
914
866
  )
915
- ] }),
916
- !screenshot && !submitted && /* @__PURE__ */ jsx2(
917
- "button",
918
- {
919
- type: "button",
920
- onClick: () => {
921
- var _a2;
922
- return (_a2 = fileInputRef.current) == null ? void 0 : _a2.click();
923
- },
924
- style: {
925
- marginBottom: "10px",
926
- padding: "10px",
927
- borderRadius: "6px",
928
- border: `1px dashed ${t.inputBorder}`,
929
- textAlign: "center",
930
- fontSize: "11px",
931
- color: t.textMuted,
932
- width: "100%",
933
- background: "none",
934
- cursor: "pointer"
935
- },
936
- children: "Click to upload a screenshot"
937
- }
938
- ),
939
- /* @__PURE__ */ jsx2(
940
- "textarea",
941
- {
942
- value: description,
943
- onChange: (e) => setDescription(e.target.value),
944
- placeholder: "Describe what went wrong...",
945
- style: {
946
- width: "100%",
947
- minHeight: "100px",
948
- padding: "10px 12px",
949
- borderRadius: "8px",
950
- border: `1px solid ${t.inputBorder}`,
951
- fontSize: "14px",
952
- fontFamily: "inherit",
953
- resize: "vertical",
954
- outline: "none",
955
- boxSizing: "border-box",
956
- color: t.text,
957
- backgroundColor: t.inputBg
958
- },
959
- onFocus: (e) => {
960
- e.target.style.borderColor = t.accent;
961
- },
962
- onBlur: (e) => {
963
- e.target.style.borderColor = t.inputBorder;
964
- },
965
- autoFocus: true
966
- }
967
- ),
968
- /* @__PURE__ */ jsx2(
969
- "button",
970
- {
971
- type: "button",
972
- onClick: handleSubmit,
973
- disabled: !description.trim() || isSubmitting,
974
- style: {
975
- marginTop: "12px",
976
- width: "100%",
977
- padding: "10px",
978
- borderRadius: "8px",
979
- border: "none",
980
- backgroundColor: !description.trim() || isSubmitting ? t.bgSecondary : t.accent,
981
- color: !description.trim() || isSubmitting ? t.textMuted : t.accentText,
982
- fontSize: "14px",
983
- fontWeight: 600,
984
- cursor: !description.trim() || isSubmitting ? "not-allowed" : "pointer",
985
- fontFamily: "inherit",
986
- transition: "background-color 0.15s ease"
987
- },
988
- children: isSubmitting ? "Sending..." : "Send Report"
989
- }
990
- )
867
+ ] })
991
868
  ] }) }),
992
- /* @__PURE__ */ jsx2(
869
+ /* @__PURE__ */ jsx(
993
870
  "div",
994
871
  {
995
872
  style: {
@@ -997,7 +874,7 @@ function ReportButton({
997
874
  borderTop: `1px solid ${t.border}`,
998
875
  textAlign: "center"
999
876
  },
1000
- children: /* @__PURE__ */ jsx2("span", { style: { fontSize: "11px", color: t.textMuted }, children: "Powered by Glitchgrab" })
877
+ children: /* @__PURE__ */ jsx("span", { style: { fontSize: "11px", color: t.textMuted }, children: "Powered by Glitchgrab" })
1001
878
  }
1002
879
  )
1003
880
  ]
@@ -1007,7 +884,7 @@ function ReportButton({
1007
884
  ),
1008
885
  document.body
1009
886
  ),
1010
- /* @__PURE__ */ jsx2(
887
+ /* @__PURE__ */ jsx(
1011
888
  "input",
1012
889
  {
1013
890
  ref: fileInputRef,
@@ -1017,7 +894,7 @@ function ReportButton({
1017
894
  style: { display: "none" }
1018
895
  }
1019
896
  ),
1020
- previewOpen && screenshot && typeof document !== "undefined" && createPortal(
897
+ previewOpen && screenshot && createPortal(
1021
898
  /* @__PURE__ */ jsxs(
1022
899
  "div",
1023
900
  {
@@ -1039,7 +916,7 @@ function ReportButton({
1039
916
  },
1040
917
  onMouseDown: (e) => e.stopPropagation(),
1041
918
  children: [
1042
- /* @__PURE__ */ jsx2(
919
+ /* @__PURE__ */ jsx(
1043
920
  "img",
1044
921
  {
1045
922
  src: screenshot,
@@ -1054,7 +931,7 @@ function ReportButton({
1054
931
  }
1055
932
  }
1056
933
  ),
1057
- /* @__PURE__ */ jsx2(
934
+ /* @__PURE__ */ jsx(
1058
935
  "span",
1059
936
  {
1060
937
  style: {
@@ -1072,24 +949,330 @@ function ReportButton({
1072
949
  )
1073
950
  ] });
1074
951
  }
1075
- function DefaultTrigger({
1076
- onClick,
1077
- capturing,
1078
- label,
952
+ function getTypeLabel(type) {
953
+ switch (type) {
954
+ case "BUG":
955
+ return "Bug Report";
956
+ case "FEATURE_REQUEST":
957
+ return "Feature Request";
958
+ case "QUESTION":
959
+ return "Question";
960
+ case "OTHER":
961
+ return "Other";
962
+ }
963
+ }
964
+ function getTypeSubtitle(type) {
965
+ switch (type) {
966
+ case "BUG":
967
+ return "Something isn't working";
968
+ case "FEATURE_REQUEST":
969
+ return "Suggest an improvement";
970
+ case "QUESTION":
971
+ return "Ask a question";
972
+ case "OTHER":
973
+ return "General feedback";
974
+ }
975
+ }
976
+ function getPlaceholder(type) {
977
+ switch (type) {
978
+ case "BUG":
979
+ return "What went wrong?";
980
+ case "FEATURE_REQUEST":
981
+ return "Describe the feature you'd like...";
982
+ case "QUESTION":
983
+ return "What would you like to know?";
984
+ case "OTHER":
985
+ return "Tell us what's on your mind...";
986
+ }
987
+ }
988
+ function getTypeIcon(type, color, size = 24) {
989
+ const props = { width: size, height: size, viewBox: "0 0 24 24", fill: "none", style: { flexShrink: 0 } };
990
+ switch (type) {
991
+ case "BUG":
992
+ return /* @__PURE__ */ jsx("svg", { ...props, children: /* @__PURE__ */ jsx("path", { d: "M8 2L6.5 3.5M16 2L17.5 3.5M3 9H7M17 9H21M12 2a5 5 0 015 5v4a5 5 0 01-10 0V7a5 5 0 015-5zM7 16l-2 3M17 16l2 3", stroke: color, strokeWidth: "1.5", strokeLinecap: "round" }) });
993
+ case "FEATURE_REQUEST":
994
+ return /* @__PURE__ */ jsx("svg", { ...props, children: /* @__PURE__ */ jsx("path", { d: "M12 2L15.09 8.26L22 9.27L17 14.14L18.18 21.02L12 17.77L5.82 21.02L7 14.14L2 9.27L8.91 8.26L12 2z", stroke: color, strokeWidth: "1.5", strokeLinejoin: "round" }) });
995
+ case "QUESTION":
996
+ return /* @__PURE__ */ jsxs("svg", { ...props, children: [
997
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10", stroke: color, strokeWidth: "1.5" }),
998
+ /* @__PURE__ */ jsx("path", { d: "M9.09 9a3 3 0 015.83 1c0 2-3 3-3 3M12 17h.01", stroke: color, strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" })
999
+ ] });
1000
+ case "OTHER":
1001
+ return /* @__PURE__ */ jsx("svg", { ...props, children: /* @__PURE__ */ jsx("path", { d: "M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2v10z", stroke: color, strokeWidth: "1.5", strokeLinejoin: "round" }) });
1002
+ }
1003
+ }
1004
+
1005
+ // src/provider.tsx
1006
+ import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
1007
+ var DEFAULT_BASE_URL2 = "https://www.glitchgrab.dev";
1008
+ var GlitchgrabContext = createContext(null);
1009
+ function useGlitchgrab() {
1010
+ const ctx = useContext(GlitchgrabContext);
1011
+ if (!ctx) {
1012
+ throw new Error("useGlitchgrab must be used within a GlitchgrabProvider");
1013
+ }
1014
+ return ctx;
1015
+ }
1016
+ function GlitchgrabProviderInner({
1017
+ token,
1018
+ session,
1019
+ baseUrl,
1020
+ onError,
1021
+ onReportSent,
1022
+ breadcrumbs: enableBreadcrumbs = true,
1023
+ maxBreadcrumbs: maxBreadcrumbs2 = 50,
1024
+ children,
1025
+ fallback,
1026
+ types,
1027
+ showSeverity
1028
+ }) {
1029
+ const visitedPagesRef = useRef2([]);
1030
+ useEffect2(() => {
1031
+ if (enableBreadcrumbs) {
1032
+ initBreadcrumbs(maxBreadcrumbs2);
1033
+ }
1034
+ }, [enableBreadcrumbs, maxBreadcrumbs2]);
1035
+ useEffect2(() => {
1036
+ try {
1037
+ if (typeof window === "undefined") return;
1038
+ const trackPage = () => {
1039
+ try {
1040
+ const sanitized = sanitizeUrl(window.location.href);
1041
+ const pages = visitedPagesRef.current;
1042
+ if (pages[pages.length - 1] !== sanitized) {
1043
+ pages.push(sanitized);
1044
+ if (pages.length > 20) {
1045
+ pages.splice(0, pages.length - 20);
1046
+ }
1047
+ }
1048
+ } catch (e) {
1049
+ }
1050
+ };
1051
+ trackPage();
1052
+ const handlePopState = () => trackPage();
1053
+ window.addEventListener("popstate", handlePopState);
1054
+ const origPushState = history.pushState.bind(history);
1055
+ const origReplaceState = history.replaceState.bind(history);
1056
+ history.pushState = function(...args) {
1057
+ origPushState(...args);
1058
+ trackPage();
1059
+ };
1060
+ history.replaceState = function(...args) {
1061
+ origReplaceState(...args);
1062
+ trackPage();
1063
+ };
1064
+ return () => {
1065
+ window.removeEventListener("popstate", handlePopState);
1066
+ history.pushState = origPushState;
1067
+ history.replaceState = origReplaceState;
1068
+ };
1069
+ } catch (e) {
1070
+ }
1071
+ }, []);
1072
+ useEffect2(() => {
1073
+ try {
1074
+ if (typeof window === "undefined") return;
1075
+ if (process.env.NODE_ENV === "development") return;
1076
+ const handleError = (event) => {
1077
+ var _a, _b, _c, _d, _e;
1078
+ try {
1079
+ const context = captureContext(visitedPagesRef.current);
1080
+ const payload = {
1081
+ token,
1082
+ source: "SDK_AUTO",
1083
+ type: "BUG",
1084
+ errorMessage: event.message,
1085
+ errorStack: (_a = event.error) == null ? void 0 : _a.stack,
1086
+ pageUrl: context.url,
1087
+ userAgent: context.userAgent,
1088
+ breadcrumbs: context.breadcrumbs,
1089
+ deviceInfo: (_b = context.deviceInfo) != null ? _b : void 0,
1090
+ metadata: {
1091
+ timestamp: context.timestamp,
1092
+ visitedPages: JSON.stringify(context.visitedPages),
1093
+ filename: (_c = event.filename) != null ? _c : "",
1094
+ lineno: String((_d = event.lineno) != null ? _d : ""),
1095
+ colno: String((_e = event.colno) != null ? _e : ""),
1096
+ ...(session == null ? void 0 : session.userId) ? { sessionUserId: session.userId } : {},
1097
+ ...(session == null ? void 0 : session.name) ? { sessionUserName: String(session.name) } : {},
1098
+ ...(session == null ? void 0 : session.email) ? { sessionUserEmail: String(session.email) } : {}
1099
+ }
1100
+ };
1101
+ sendReport(payload, baseUrl).then((result) => {
1102
+ if (result && onReportSent) onReportSent(result);
1103
+ });
1104
+ if (onError && event.error) onError(event.error);
1105
+ } catch (e) {
1106
+ }
1107
+ };
1108
+ const handleRejection = (event) => {
1109
+ var _a;
1110
+ try {
1111
+ const context = captureContext(visitedPagesRef.current);
1112
+ const reason = event.reason;
1113
+ const payload = {
1114
+ token,
1115
+ source: "SDK_AUTO",
1116
+ type: "BUG",
1117
+ errorMessage: reason instanceof Error ? reason.message : String(reason),
1118
+ errorStack: reason instanceof Error ? reason.stack : void 0,
1119
+ pageUrl: context.url,
1120
+ userAgent: context.userAgent,
1121
+ breadcrumbs: context.breadcrumbs,
1122
+ deviceInfo: (_a = context.deviceInfo) != null ? _a : void 0,
1123
+ metadata: {
1124
+ timestamp: context.timestamp,
1125
+ visitedPages: JSON.stringify(context.visitedPages),
1126
+ type: "unhandledrejection",
1127
+ ...(session == null ? void 0 : session.userId) ? { sessionUserId: session.userId } : {},
1128
+ ...(session == null ? void 0 : session.name) ? { sessionUserName: String(session.name) } : {},
1129
+ ...(session == null ? void 0 : session.email) ? { sessionUserEmail: String(session.email) } : {}
1130
+ }
1131
+ };
1132
+ sendReport(payload, baseUrl).then((result) => {
1133
+ if (result && onReportSent) onReportSent(result);
1134
+ });
1135
+ if (onError && reason instanceof Error) onError(reason);
1136
+ } catch (e) {
1137
+ }
1138
+ };
1139
+ window.addEventListener("error", handleError);
1140
+ window.addEventListener("unhandledrejection", handleRejection);
1141
+ return () => {
1142
+ window.removeEventListener("error", handleError);
1143
+ window.removeEventListener("unhandledrejection", handleRejection);
1144
+ };
1145
+ } catch (e) {
1146
+ }
1147
+ }, [token, baseUrl, onError, onReportSent]);
1148
+ const report = useCallback(
1149
+ async (type, description, metadata) => {
1150
+ var _a;
1151
+ try {
1152
+ const context = captureContext(visitedPagesRef.current);
1153
+ const payload = {
1154
+ token,
1155
+ source: "SDK_USER_REPORT",
1156
+ type,
1157
+ description,
1158
+ pageUrl: context.url,
1159
+ userAgent: context.userAgent,
1160
+ breadcrumbs: context.breadcrumbs,
1161
+ deviceInfo: (_a = context.deviceInfo) != null ? _a : void 0,
1162
+ metadata: {
1163
+ timestamp: context.timestamp,
1164
+ visitedPages: JSON.stringify(context.visitedPages),
1165
+ ...(session == null ? void 0 : session.userId) ? { sessionUserId: session.userId } : {},
1166
+ ...(session == null ? void 0 : session.name) ? { sessionUserName: String(session.name) } : {},
1167
+ ...(session == null ? void 0 : session.email) ? { sessionUserEmail: String(session.email) } : {},
1168
+ ...(session == null ? void 0 : session.phone) ? { sessionUserPhone: String(session.phone) } : {},
1169
+ ...metadata
1170
+ }
1171
+ };
1172
+ const result = await sendReport(payload, baseUrl);
1173
+ if (result && onReportSent) onReportSent(result);
1174
+ return result;
1175
+ } catch (e) {
1176
+ return null;
1177
+ }
1178
+ },
1179
+ [token, baseUrl, onReportSent, session]
1180
+ );
1181
+ const reportBug = useCallback(
1182
+ (description, metadata) => report("BUG", description, metadata),
1183
+ [report]
1184
+ );
1185
+ const addBreadcrumb2 = useCallback(
1186
+ (message, data) => {
1187
+ addBreadcrumb("custom", message, data);
1188
+ },
1189
+ []
1190
+ );
1191
+ const openReportDialog = useCallback((options) => {
1192
+ if (typeof window !== "undefined") {
1193
+ window.dispatchEvent(new CustomEvent("glitchgrab:open-report", { detail: options }));
1194
+ }
1195
+ }, []);
1196
+ return /* @__PURE__ */ jsxs2(
1197
+ GlitchgrabContext.Provider,
1198
+ {
1199
+ value: {
1200
+ token,
1201
+ baseUrl: baseUrl != null ? baseUrl : DEFAULT_BASE_URL2,
1202
+ reportBug,
1203
+ report,
1204
+ addBreadcrumb: addBreadcrumb2,
1205
+ openReportDialog
1206
+ },
1207
+ children: [
1208
+ /* @__PURE__ */ jsx2(
1209
+ GlitchgrabErrorBoundary,
1210
+ {
1211
+ token,
1212
+ baseUrl,
1213
+ onError,
1214
+ fallback,
1215
+ visitedPages: visitedPagesRef.current,
1216
+ children
1217
+ }
1218
+ ),
1219
+ /* @__PURE__ */ jsx2(ReportDialog, { report, types, showSeverity })
1220
+ ]
1221
+ }
1222
+ );
1223
+ }
1224
+ function GlitchgrabProvider(props) {
1225
+ if (!props.token) return /* @__PURE__ */ jsx2(Fragment2, { children: props.children });
1226
+ const resolvedProps = {
1227
+ ...props,
1228
+ baseUrl: props.baseUrl || DEFAULT_BASE_URL2
1229
+ };
1230
+ try {
1231
+ return /* @__PURE__ */ jsx2(GlitchgrabProviderInner, { ...resolvedProps });
1232
+ } catch (e) {
1233
+ return /* @__PURE__ */ jsx2(Fragment2, { children: props.children });
1234
+ }
1235
+ }
1236
+
1237
+ // src/report-button.tsx
1238
+ import { useState as useState3 } from "react";
1239
+ import { Fragment as Fragment3, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1240
+ function useGlitchgrabSafe() {
1241
+ try {
1242
+ return useGlitchgrab();
1243
+ } catch (e) {
1244
+ return null;
1245
+ }
1246
+ }
1247
+ function ReportButton({
1248
+ position = "bottom-right",
1249
+ label = "Report Bug",
1079
1250
  className,
1080
- position,
1081
- isLeft,
1082
- isTop
1251
+ children
1083
1252
  }) {
1253
+ var _a, _b;
1254
+ const [capturing, setCapturing] = useState3(false);
1255
+ const glitchgrab = useGlitchgrabSafe();
1256
+ if (!glitchgrab) return null;
1257
+ const handleClick = async () => {
1258
+ setCapturing(true);
1259
+ glitchgrab.openReportDialog();
1260
+ setCapturing(false);
1261
+ };
1262
+ if (children) {
1263
+ return /* @__PURE__ */ jsx3(Fragment3, { children: children({ onClick: handleClick, capturing }) });
1264
+ }
1265
+ const isTop = (_a = position == null ? void 0 : position.startsWith("top")) != null ? _a : false;
1266
+ const isLeft = (_b = position == null ? void 0 : position.endsWith("left")) != null ? _b : false;
1084
1267
  const positionStyles = {
1085
1268
  ...isLeft ? { left: "16px" } : { right: "16px" },
1086
1269
  ...isTop ? { top: "16px" } : { bottom: "16px" }
1087
1270
  };
1088
- return /* @__PURE__ */ jsxs(
1271
+ return /* @__PURE__ */ jsxs3(
1089
1272
  "button",
1090
1273
  {
1091
1274
  type: "button",
1092
- onClick,
1275
+ onClick: handleClick,
1093
1276
  disabled: capturing,
1094
1277
  className,
1095
1278
  style: {
@@ -1120,7 +1303,7 @@ function DefaultTrigger({
1120
1303
  },
1121
1304
  "aria-label": label,
1122
1305
  children: [
1123
- /* @__PURE__ */ jsx2(
1306
+ /* @__PURE__ */ jsx3(
1124
1307
  "svg",
1125
1308
  {
1126
1309
  width: "16",
@@ -1128,7 +1311,7 @@ function DefaultTrigger({
1128
1311
  viewBox: "0 0 16 16",
1129
1312
  fill: "none",
1130
1313
  style: { flexShrink: 0 },
1131
- children: /* @__PURE__ */ jsx2(
1314
+ children: /* @__PURE__ */ jsx3(
1132
1315
  "path",
1133
1316
  {
1134
1317
  d: "M8 1C4.134 1 1 4.134 1 8s3.134 7 7 7 7-3.134 7-7-3.134-7-7-7zm0 12.5a.75.75 0 110-1.5.75.75 0 010 1.5zm.75-3.5a.75.75 0 01-1.5 0V5a.75.75 0 011.5 0v5z",
@@ -1144,17 +1327,17 @@ function DefaultTrigger({
1144
1327
  }
1145
1328
 
1146
1329
  // src/use-reports.ts
1147
- import { useState as useState3, useEffect as useEffect3, useCallback as useCallback2 } from "react";
1330
+ import { useState as useState4, useEffect as useEffect3, useCallback as useCallback2 } from "react";
1148
1331
  var BASE_URL = "https://www.glitchgrab.dev";
1149
1332
  function useGlitchgrabReports({
1150
1333
  token,
1151
1334
  userId,
1152
1335
  limit = 100
1153
1336
  }) {
1154
- const [reports, setReports] = useState3([]);
1155
- const [isLoading, setIsLoading] = useState3(true);
1156
- const [isFetching, setIsFetching] = useState3(false);
1157
- const [error, setError] = useState3(null);
1337
+ const [reports, setReports] = useState4([]);
1338
+ const [isLoading, setIsLoading] = useState4(true);
1339
+ const [isFetching, setIsFetching] = useState4(false);
1340
+ const [error, setError] = useState4(null);
1158
1341
  const fetchReports = useCallback2(async () => {
1159
1342
  var _a, _b;
1160
1343
  if (!token || !userId) {
@@ -1218,8 +1401,8 @@ function useGlitchgrabActions({
1218
1401
  onSuccess,
1219
1402
  onError
1220
1403
  }) {
1221
- const [isPending, setIsPending] = useState3(false);
1222
- const [error, setError] = useState3(null);
1404
+ const [isPending, setIsPending] = useState4(false);
1405
+ const [error, setError] = useState4(null);
1223
1406
  const run = useCallback2(
1224
1407
  async (reportId, body) => {
1225
1408
  setIsPending(true);