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