@jarve/bug-reporter 0.1.1 → 0.3.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/README.md +8 -7
- package/dist/index.d.mts +5 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +299 -152
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +299 -152
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -34,16 +34,21 @@ function cn(...inputs) {
|
|
|
34
34
|
|
|
35
35
|
// src/floating-button.tsx
|
|
36
36
|
import { jsx } from "react/jsx-runtime";
|
|
37
|
-
function FloatingButton({ isActive, onClick }) {
|
|
37
|
+
function FloatingButton({ isActive, onClick, position = "right" }) {
|
|
38
|
+
const sideClasses = position === "left" ? "left-4 md:left-6" : "right-4 md:right-6";
|
|
38
39
|
return /* @__PURE__ */ jsx(
|
|
39
40
|
"button",
|
|
40
41
|
{
|
|
41
42
|
onClick,
|
|
42
43
|
className: cn(
|
|
43
|
-
"fixed
|
|
44
|
-
"hover:scale-110 focus:
|
|
45
|
-
|
|
46
|
-
"bottom-4
|
|
44
|
+
"fixed z-[9999] flex items-center justify-center rounded-full shadow-lg transition-all duration-200",
|
|
45
|
+
"hover:scale-110 focus:ring-2 focus:ring-offset-2 focus:outline-none",
|
|
46
|
+
// size + vertical position
|
|
47
|
+
"bottom-4 h-11 w-11 md:bottom-6 md:h-12 md:w-12",
|
|
48
|
+
// horizontal side
|
|
49
|
+
sideClasses,
|
|
50
|
+
// active vs idle colors
|
|
51
|
+
isActive ? "animate-pulse bg-red-500 text-white focus:ring-red-400" : "bg-indigo-600 text-white hover:bg-indigo-700 focus:ring-indigo-400"
|
|
47
52
|
),
|
|
48
53
|
title: isActive ? "Cancel bug capture" : "Report a bug",
|
|
49
54
|
"aria-label": isActive ? "Cancel bug capture" : "Report a bug",
|
|
@@ -111,7 +116,18 @@ function buildSelectorPath(element, stopAt) {
|
|
|
111
116
|
}
|
|
112
117
|
return parts.join(" > ");
|
|
113
118
|
}
|
|
114
|
-
function
|
|
119
|
+
function isTouchCapable() {
|
|
120
|
+
return "ontouchstart" in window || navigator.maxTouchPoints > 0;
|
|
121
|
+
}
|
|
122
|
+
function extractCoordinates(e) {
|
|
123
|
+
return {
|
|
124
|
+
pageX: e.pageX,
|
|
125
|
+
pageY: e.pageY,
|
|
126
|
+
clientX: e.clientX,
|
|
127
|
+
clientY: e.clientY
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
function collectElementInfo(target, section, coords) {
|
|
115
131
|
const sectionRect = section.getBoundingClientRect();
|
|
116
132
|
const dataAttributes = {};
|
|
117
133
|
for (const attr of Array.from(target.attributes)) {
|
|
@@ -127,10 +143,10 @@ function collectElementInfo(target, section, event) {
|
|
|
127
143
|
ariaLabel: target.getAttribute("aria-label") || null,
|
|
128
144
|
dataAttributes,
|
|
129
145
|
selectorPath: buildSelectorPath(target, section),
|
|
130
|
-
clickX:
|
|
131
|
-
clickY:
|
|
132
|
-
relativeClickX:
|
|
133
|
-
relativeClickY:
|
|
146
|
+
clickX: coords.pageX,
|
|
147
|
+
clickY: coords.pageY,
|
|
148
|
+
relativeClickX: coords.clientX - sectionRect.left,
|
|
149
|
+
relativeClickY: coords.clientY - sectionRect.top
|
|
134
150
|
};
|
|
135
151
|
}
|
|
136
152
|
function collectMetadata(sectionElement, siteId, reporterName, reporterEmail, clickedElement) {
|
|
@@ -162,8 +178,7 @@ var errorListener = null;
|
|
|
162
178
|
var rejectionListener = null;
|
|
163
179
|
function startCapturing() {
|
|
164
180
|
if (isCapturing) return;
|
|
165
|
-
if (console.error.__bugReporterPatched)
|
|
166
|
-
return;
|
|
181
|
+
if (console.error.__bugReporterPatched) return;
|
|
167
182
|
isCapturing = true;
|
|
168
183
|
capturedErrors = [];
|
|
169
184
|
originalConsoleError = console.error;
|
|
@@ -355,6 +370,15 @@ function clearCapturedNetworkErrors() {
|
|
|
355
370
|
|
|
356
371
|
// src/capture-overlay.tsx
|
|
357
372
|
import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
373
|
+
function dataUrlToBlob(dataUrl) {
|
|
374
|
+
var _a;
|
|
375
|
+
const [header, base64] = dataUrl.split(",");
|
|
376
|
+
const mime = ((_a = header.match(/:(.*?);/)) == null ? void 0 : _a[1]) || "image/png";
|
|
377
|
+
const bytes = atob(base64);
|
|
378
|
+
const arr = new Uint8Array(bytes.length);
|
|
379
|
+
for (let i = 0; i < bytes.length; i++) arr[i] = bytes.charCodeAt(i);
|
|
380
|
+
return new Blob([arr], { type: mime });
|
|
381
|
+
}
|
|
358
382
|
function CaptureOverlay({
|
|
359
383
|
isActive,
|
|
360
384
|
siteId,
|
|
@@ -366,48 +390,30 @@ function CaptureOverlay({
|
|
|
366
390
|
const [hoveredElement, setHoveredElement] = useState(null);
|
|
367
391
|
const [hoveredRect, setHoveredRect] = useState(null);
|
|
368
392
|
const [isCapturing3, setIsCapturing] = useState(false);
|
|
393
|
+
const [isTouchMode, setIsTouchMode] = useState(false);
|
|
394
|
+
const [selectedSection, setSelectedSection] = useState(null);
|
|
395
|
+
const [selectedRect, setSelectedRect] = useState(null);
|
|
396
|
+
const [selectedTarget, setSelectedTarget] = useState(null);
|
|
369
397
|
const overlayRef = useRef(null);
|
|
370
398
|
const hoveredElementRef = useRef(null);
|
|
371
399
|
const rafRef = useRef(null);
|
|
372
|
-
const
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
setHoveredElement(null);
|
|
382
|
-
setHoveredRect(null);
|
|
383
|
-
hoveredElementRef.current = null;
|
|
384
|
-
return;
|
|
385
|
-
}
|
|
386
|
-
const section = getNearestSection(target);
|
|
387
|
-
setHoveredElement(section);
|
|
388
|
-
hoveredElementRef.current = section;
|
|
389
|
-
setHoveredRect(section ? section.getBoundingClientRect() : null);
|
|
390
|
-
});
|
|
391
|
-
},
|
|
392
|
-
[isActive, isCapturing3]
|
|
393
|
-
);
|
|
394
|
-
const handleClick = useCallback(
|
|
395
|
-
async (e) => {
|
|
396
|
-
var _a, _b;
|
|
397
|
-
if (!isActive || isCapturing3) return;
|
|
398
|
-
const target = e.target;
|
|
399
|
-
if (!(target instanceof HTMLElement)) return;
|
|
400
|
-
if (target.closest("[data-bug-reporter]")) return;
|
|
401
|
-
e.preventDefault();
|
|
402
|
-
e.stopPropagation();
|
|
403
|
-
const section = getNearestSection(target);
|
|
404
|
-
if (!section) return;
|
|
405
|
-
const elementInfo = collectElementInfo(target, section, e);
|
|
400
|
+
const touchCoordsRef = useRef(null);
|
|
401
|
+
useEffect(() => {
|
|
402
|
+
if (isActive) {
|
|
403
|
+
setIsTouchMode(isTouchCapable());
|
|
404
|
+
}
|
|
405
|
+
}, [isActive]);
|
|
406
|
+
const captureScreenshot = useCallback(
|
|
407
|
+
async (section, target, coords) => {
|
|
408
|
+
const elementInfo = collectElementInfo(target, section, coords);
|
|
406
409
|
setIsCapturing(true);
|
|
407
410
|
try {
|
|
408
411
|
setHoveredElement(null);
|
|
409
412
|
setHoveredRect(null);
|
|
410
413
|
hoveredElementRef.current = null;
|
|
414
|
+
setSelectedSection(null);
|
|
415
|
+
setSelectedRect(null);
|
|
416
|
+
setSelectedTarget(null);
|
|
411
417
|
await new Promise((r) => setTimeout(r, 50));
|
|
412
418
|
const MAX_DIMENSION = 2e3;
|
|
413
419
|
const sectionRect = section.getBoundingClientRect();
|
|
@@ -417,12 +423,7 @@ function CaptureOverlay({
|
|
|
417
423
|
pixelRatio,
|
|
418
424
|
skipFonts: true
|
|
419
425
|
});
|
|
420
|
-
const
|
|
421
|
-
const mime = ((_a = header.match(/:(.*?);/)) == null ? void 0 : _a[1]) || "image/png";
|
|
422
|
-
const bytes = atob(base64);
|
|
423
|
-
const arr = new Uint8Array(bytes.length);
|
|
424
|
-
for (let i = 0; i < bytes.length; i++) arr[i] = bytes.charCodeAt(i);
|
|
425
|
-
const blob = new Blob([arr], { type: mime });
|
|
426
|
+
const blob = dataUrlToBlob(dataUrl);
|
|
426
427
|
const metadata = collectMetadata(
|
|
427
428
|
section,
|
|
428
429
|
siteId,
|
|
@@ -434,7 +435,10 @@ function CaptureOverlay({
|
|
|
434
435
|
const networkErrors = getCapturedNetworkErrors();
|
|
435
436
|
onCapture({ screenshot: blob, metadata, consoleErrors, networkErrors });
|
|
436
437
|
} catch (err) {
|
|
437
|
-
console.warn(
|
|
438
|
+
console.warn(
|
|
439
|
+
"Bug reporter: first capture attempt failed, retrying with simpler settings",
|
|
440
|
+
err
|
|
441
|
+
);
|
|
438
442
|
try {
|
|
439
443
|
const dataUrl = await toPng(section, {
|
|
440
444
|
quality: 0.6,
|
|
@@ -442,12 +446,7 @@ function CaptureOverlay({
|
|
|
442
446
|
skipFonts: true,
|
|
443
447
|
cacheBust: true
|
|
444
448
|
});
|
|
445
|
-
const
|
|
446
|
-
const mime = ((_b = header.match(/:(.*?);/)) == null ? void 0 : _b[1]) || "image/png";
|
|
447
|
-
const bytes = atob(base64);
|
|
448
|
-
const arr = new Uint8Array(bytes.length);
|
|
449
|
-
for (let i = 0; i < bytes.length; i++) arr[i] = bytes.charCodeAt(i);
|
|
450
|
-
const retryBlob = new Blob([arr], { type: mime });
|
|
449
|
+
const retryBlob = dataUrlToBlob(dataUrl);
|
|
451
450
|
const metadata = collectMetadata(
|
|
452
451
|
section,
|
|
453
452
|
siteId,
|
|
@@ -458,7 +457,7 @@ function CaptureOverlay({
|
|
|
458
457
|
const consoleErrors = getCapturedErrors();
|
|
459
458
|
const networkErrors = getCapturedNetworkErrors();
|
|
460
459
|
onCapture({ screenshot: retryBlob, metadata, consoleErrors, networkErrors });
|
|
461
|
-
} catch (
|
|
460
|
+
} catch (e) {
|
|
462
461
|
console.error("Bug reporter: screenshot capture failed after retry");
|
|
463
462
|
const metadata = collectMetadata(
|
|
464
463
|
section,
|
|
@@ -480,7 +479,75 @@ function CaptureOverlay({
|
|
|
480
479
|
setIsCapturing(false);
|
|
481
480
|
}
|
|
482
481
|
},
|
|
483
|
-
[
|
|
482
|
+
[siteId, reporterName, reporterEmail, onCapture]
|
|
483
|
+
);
|
|
484
|
+
const handleMouseMove = useCallback(
|
|
485
|
+
(e) => {
|
|
486
|
+
if (!isActive || isCapturing3 || isTouchMode) return;
|
|
487
|
+
if (rafRef.current) return;
|
|
488
|
+
rafRef.current = requestAnimationFrame(() => {
|
|
489
|
+
rafRef.current = null;
|
|
490
|
+
const target = e.target;
|
|
491
|
+
if (!(target instanceof HTMLElement)) return;
|
|
492
|
+
if (target.closest("[data-bug-reporter]")) {
|
|
493
|
+
setHoveredElement(null);
|
|
494
|
+
setHoveredRect(null);
|
|
495
|
+
hoveredElementRef.current = null;
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
const section = getNearestSection(target);
|
|
499
|
+
setHoveredElement(section);
|
|
500
|
+
hoveredElementRef.current = section;
|
|
501
|
+
setHoveredRect(section ? section.getBoundingClientRect() : null);
|
|
502
|
+
});
|
|
503
|
+
},
|
|
504
|
+
[isActive, isCapturing3, isTouchMode]
|
|
505
|
+
);
|
|
506
|
+
const handleClick = useCallback(
|
|
507
|
+
async (e) => {
|
|
508
|
+
if (!isActive || isCapturing3 || isTouchMode) return;
|
|
509
|
+
const target = e.target;
|
|
510
|
+
if (!(target instanceof HTMLElement)) return;
|
|
511
|
+
if (target.closest("[data-bug-reporter]")) return;
|
|
512
|
+
e.preventDefault();
|
|
513
|
+
e.stopPropagation();
|
|
514
|
+
const section = getNearestSection(target);
|
|
515
|
+
if (!section) return;
|
|
516
|
+
await captureScreenshot(section, target, extractCoordinates(e));
|
|
517
|
+
},
|
|
518
|
+
[isActive, isCapturing3, isTouchMode, captureScreenshot]
|
|
519
|
+
);
|
|
520
|
+
const handleTouchEnd = useCallback(
|
|
521
|
+
(e) => {
|
|
522
|
+
if (!isActive || isCapturing3) return;
|
|
523
|
+
const touch = e.changedTouches[0];
|
|
524
|
+
if (!touch) return;
|
|
525
|
+
const target = document.elementFromPoint(touch.clientX, touch.clientY);
|
|
526
|
+
if (!(target instanceof HTMLElement)) return;
|
|
527
|
+
if (target.closest("[data-bug-reporter]")) return;
|
|
528
|
+
const section = getNearestSection(target);
|
|
529
|
+
if (!section) return;
|
|
530
|
+
setSelectedSection(section);
|
|
531
|
+
setSelectedRect(section.getBoundingClientRect());
|
|
532
|
+
setSelectedTarget(target);
|
|
533
|
+
touchCoordsRef.current = extractCoordinates(touch);
|
|
534
|
+
},
|
|
535
|
+
[isActive, isCapturing3]
|
|
536
|
+
);
|
|
537
|
+
const handleConfirmCapture = useCallback(async () => {
|
|
538
|
+
if (!selectedSection || !selectedTarget || !touchCoordsRef.current) return;
|
|
539
|
+
await captureScreenshot(selectedSection, selectedTarget, touchCoordsRef.current);
|
|
540
|
+
}, [selectedSection, selectedTarget, captureScreenshot]);
|
|
541
|
+
const handlePointerDown = useCallback(
|
|
542
|
+
(e) => {
|
|
543
|
+
if (!isActive) return;
|
|
544
|
+
if (e.pointerType === "touch") {
|
|
545
|
+
setIsTouchMode(true);
|
|
546
|
+
} else if (e.pointerType === "mouse") {
|
|
547
|
+
setIsTouchMode(false);
|
|
548
|
+
}
|
|
549
|
+
},
|
|
550
|
+
[isActive]
|
|
484
551
|
);
|
|
485
552
|
const handleKeyDown = useCallback(
|
|
486
553
|
(e) => {
|
|
@@ -493,60 +560,131 @@ function CaptureOverlay({
|
|
|
493
560
|
[isActive, onCancel]
|
|
494
561
|
);
|
|
495
562
|
const handleScroll = useCallback(() => {
|
|
496
|
-
if (
|
|
497
|
-
|
|
498
|
-
|
|
563
|
+
if (hoveredElementRef.current) {
|
|
564
|
+
setHoveredRect(hoveredElementRef.current.getBoundingClientRect());
|
|
565
|
+
}
|
|
566
|
+
if (selectedSection) {
|
|
567
|
+
setSelectedRect(selectedSection.getBoundingClientRect());
|
|
568
|
+
}
|
|
569
|
+
}, [selectedSection]);
|
|
499
570
|
useEffect(() => {
|
|
500
571
|
if (!isActive) {
|
|
501
572
|
setHoveredElement(null);
|
|
502
573
|
setHoveredRect(null);
|
|
503
574
|
hoveredElementRef.current = null;
|
|
575
|
+
setSelectedSection(null);
|
|
576
|
+
setSelectedRect(null);
|
|
577
|
+
setSelectedTarget(null);
|
|
578
|
+
touchCoordsRef.current = null;
|
|
504
579
|
return;
|
|
505
580
|
}
|
|
506
|
-
document.addEventListener("
|
|
507
|
-
document.addEventListener("click", handleClick, true);
|
|
581
|
+
document.addEventListener("pointerdown", handlePointerDown);
|
|
508
582
|
document.addEventListener("keydown", handleKeyDown);
|
|
509
583
|
window.addEventListener("scroll", handleScroll, { passive: true });
|
|
584
|
+
if (isTouchMode) {
|
|
585
|
+
document.addEventListener("touchend", handleTouchEnd, { passive: true });
|
|
586
|
+
} else {
|
|
587
|
+
document.addEventListener("mousemove", handleMouseMove, true);
|
|
588
|
+
document.addEventListener("click", handleClick, true);
|
|
589
|
+
}
|
|
510
590
|
return () => {
|
|
511
|
-
document.removeEventListener("
|
|
512
|
-
document.removeEventListener("click", handleClick, true);
|
|
591
|
+
document.removeEventListener("pointerdown", handlePointerDown);
|
|
513
592
|
document.removeEventListener("keydown", handleKeyDown);
|
|
514
593
|
window.removeEventListener("scroll", handleScroll);
|
|
594
|
+
document.removeEventListener("touchend", handleTouchEnd);
|
|
595
|
+
document.removeEventListener("mousemove", handleMouseMove, true);
|
|
596
|
+
document.removeEventListener("click", handleClick, true);
|
|
515
597
|
if (rafRef.current) cancelAnimationFrame(rafRef.current);
|
|
516
598
|
};
|
|
517
|
-
}, [
|
|
518
|
-
|
|
599
|
+
}, [
|
|
600
|
+
isActive,
|
|
601
|
+
isTouchMode,
|
|
602
|
+
handleMouseMove,
|
|
603
|
+
handleClick,
|
|
604
|
+
handleTouchEnd,
|
|
605
|
+
handlePointerDown,
|
|
606
|
+
handleKeyDown,
|
|
607
|
+
handleScroll
|
|
608
|
+
]);
|
|
609
|
+
const highlightRect = isTouchMode ? selectedRect : hoveredRect;
|
|
610
|
+
const showHighlight = isTouchMode ? !!selectedSection : !!hoveredElement && !!hoveredRect;
|
|
611
|
+
if (!isActive) return null;
|
|
519
612
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
520
|
-
/* @__PURE__ */
|
|
613
|
+
/* @__PURE__ */ jsx2(
|
|
521
614
|
"div",
|
|
522
615
|
{
|
|
523
616
|
"data-bug-reporter": true,
|
|
524
617
|
role: "alert",
|
|
525
618
|
"aria-live": "assertive",
|
|
526
|
-
className: "fixed top-0
|
|
527
|
-
children: [
|
|
528
|
-
"
|
|
529
|
-
/* @__PURE__ */ jsx2(
|
|
619
|
+
className: "fixed top-0 right-0 left-0 z-[10000] flex items-center justify-center gap-3 bg-indigo-600 px-4 py-2 text-center text-sm font-medium text-white",
|
|
620
|
+
children: isTouchMode ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
621
|
+
/* @__PURE__ */ jsx2("span", { children: "Tap the section with the bug" }),
|
|
622
|
+
/* @__PURE__ */ jsx2(
|
|
623
|
+
"button",
|
|
624
|
+
{
|
|
625
|
+
onClick: onCancel,
|
|
626
|
+
className: "min-h-[44px] rounded-md bg-white/20 px-3 py-1 text-sm font-medium",
|
|
627
|
+
children: "Cancel"
|
|
628
|
+
}
|
|
629
|
+
)
|
|
630
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
631
|
+
"Click on the section with the bug. Press",
|
|
632
|
+
" ",
|
|
633
|
+
/* @__PURE__ */ jsx2("kbd", { className: "mx-1 rounded bg-indigo-800 px-1.5 py-0.5 text-xs", children: "Esc" }),
|
|
530
634
|
" to cancel."
|
|
531
|
-
]
|
|
635
|
+
] })
|
|
532
636
|
}
|
|
533
637
|
),
|
|
534
|
-
/* @__PURE__ */ jsx2(
|
|
638
|
+
showHighlight && highlightRect && /* @__PURE__ */ jsx2(
|
|
535
639
|
"div",
|
|
536
640
|
{
|
|
537
641
|
ref: overlayRef,
|
|
538
642
|
"data-bug-reporter": true,
|
|
539
|
-
className: "
|
|
643
|
+
className: "pointer-events-none fixed z-[9998] rounded-sm border-2 border-indigo-500 transition-all duration-150 ease-out",
|
|
540
644
|
style: {
|
|
541
|
-
top:
|
|
542
|
-
left:
|
|
543
|
-
width:
|
|
544
|
-
height:
|
|
645
|
+
top: highlightRect.top - 2,
|
|
646
|
+
left: highlightRect.left - 2,
|
|
647
|
+
width: highlightRect.width + 4,
|
|
648
|
+
height: highlightRect.height + 4,
|
|
545
649
|
backgroundColor: "rgba(99, 102, 241, 0.08)"
|
|
546
650
|
}
|
|
547
651
|
}
|
|
548
652
|
),
|
|
549
|
-
|
|
653
|
+
isTouchMode && selectedSection && !isCapturing3 && /* @__PURE__ */ jsx2(
|
|
654
|
+
"div",
|
|
655
|
+
{
|
|
656
|
+
"data-bug-reporter": true,
|
|
657
|
+
className: "fixed right-0 bottom-0 left-0 z-[10000] border-t border-gray-200 bg-white shadow-lg",
|
|
658
|
+
style: { paddingBottom: "env(safe-area-inset-bottom, 0px)" },
|
|
659
|
+
children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3 px-4 py-3", children: [
|
|
660
|
+
/* @__PURE__ */ jsx2("span", { className: "truncate text-sm font-medium text-gray-900", children: "Capture this section?" }),
|
|
661
|
+
/* @__PURE__ */ jsxs("div", { className: "flex shrink-0 gap-2", children: [
|
|
662
|
+
/* @__PURE__ */ jsx2(
|
|
663
|
+
"button",
|
|
664
|
+
{
|
|
665
|
+
onClick: () => {
|
|
666
|
+
setSelectedSection(null);
|
|
667
|
+
setSelectedRect(null);
|
|
668
|
+
setSelectedTarget(null);
|
|
669
|
+
touchCoordsRef.current = null;
|
|
670
|
+
},
|
|
671
|
+
className: "min-h-[44px] rounded-md border border-gray-300 px-4 text-sm font-medium text-gray-700",
|
|
672
|
+
children: "Cancel"
|
|
673
|
+
}
|
|
674
|
+
),
|
|
675
|
+
/* @__PURE__ */ jsx2(
|
|
676
|
+
"button",
|
|
677
|
+
{
|
|
678
|
+
onClick: handleConfirmCapture,
|
|
679
|
+
className: "min-h-[44px] rounded-md bg-indigo-600 px-4 text-sm font-medium text-white",
|
|
680
|
+
children: "Capture"
|
|
681
|
+
}
|
|
682
|
+
)
|
|
683
|
+
] })
|
|
684
|
+
] })
|
|
685
|
+
}
|
|
686
|
+
),
|
|
687
|
+
!isTouchMode && /* @__PURE__ */ jsx2("style", { children: `* { cursor: crosshair !important; }` })
|
|
550
688
|
] });
|
|
551
689
|
}
|
|
552
690
|
|
|
@@ -612,7 +750,9 @@ function ReportModal({
|
|
|
612
750
|
})
|
|
613
751
|
});
|
|
614
752
|
if (response.status === 401) {
|
|
615
|
-
console.error(
|
|
753
|
+
console.error(
|
|
754
|
+
"Bug reporter: invalid or missing API key. Check your BugReporter apiKey prop."
|
|
755
|
+
);
|
|
616
756
|
setMessages([
|
|
617
757
|
{
|
|
618
758
|
role: "assistant",
|
|
@@ -709,9 +849,7 @@ function ReportModal({
|
|
|
709
849
|
setModalState("submitted");
|
|
710
850
|
} catch (err) {
|
|
711
851
|
console.error("Bug reporter: failed to submit report", err);
|
|
712
|
-
setErrorMessage(
|
|
713
|
-
err instanceof Error ? err.message : "Failed to submit report"
|
|
714
|
-
);
|
|
852
|
+
setErrorMessage(err instanceof Error ? err.message : "Failed to submit report");
|
|
715
853
|
setModalState("error");
|
|
716
854
|
}
|
|
717
855
|
},
|
|
@@ -724,10 +862,7 @@ function ReportModal({
|
|
|
724
862
|
if (!input.trim() || isLoading || !captureResult) return;
|
|
725
863
|
const userMessage = input.trim();
|
|
726
864
|
setInput("");
|
|
727
|
-
const newMessages = [
|
|
728
|
-
...messages,
|
|
729
|
-
{ role: "user", content: userMessage }
|
|
730
|
-
];
|
|
865
|
+
const newMessages = [...messages, { role: "user", content: userMessage }];
|
|
731
866
|
setMessages(newMessages);
|
|
732
867
|
setIsLoading(true);
|
|
733
868
|
try {
|
|
@@ -743,7 +878,9 @@ function ReportModal({
|
|
|
743
878
|
})
|
|
744
879
|
});
|
|
745
880
|
if (response.status === 401) {
|
|
746
|
-
console.error(
|
|
881
|
+
console.error(
|
|
882
|
+
"Bug reporter: invalid or missing API key. Check your BugReporter apiKey prop."
|
|
883
|
+
);
|
|
747
884
|
setMessages([
|
|
748
885
|
...newMessages,
|
|
749
886
|
{
|
|
@@ -755,10 +892,7 @@ function ReportModal({
|
|
|
755
892
|
}
|
|
756
893
|
if (!response.ok) throw new Error("Failed to get AI response");
|
|
757
894
|
const data = await response.json();
|
|
758
|
-
setMessages([
|
|
759
|
-
...newMessages,
|
|
760
|
-
{ role: "assistant", content: data.message }
|
|
761
|
-
]);
|
|
895
|
+
setMessages([...newMessages, { role: "assistant", content: data.message }]);
|
|
762
896
|
if (data.readyToSubmit && data.structuredReport) {
|
|
763
897
|
await submitReport(
|
|
764
898
|
[...newMessages, { role: "assistant", content: data.message }],
|
|
@@ -791,7 +925,7 @@ function ReportModal({
|
|
|
791
925
|
onClose();
|
|
792
926
|
}
|
|
793
927
|
function handleKeyDown(e) {
|
|
794
|
-
if (e.key === "Enter" && !e.shiftKey) {
|
|
928
|
+
if (e.key === "Enter" && !e.shiftKey && !isTouchCapable()) {
|
|
795
929
|
e.preventDefault();
|
|
796
930
|
sendMessage();
|
|
797
931
|
}
|
|
@@ -809,65 +943,66 @@ function ReportModal({
|
|
|
809
943
|
"div",
|
|
810
944
|
{
|
|
811
945
|
className: cn(
|
|
812
|
-
"
|
|
813
|
-
"w-full max-w-lg
|
|
814
|
-
"max-[768px]:mx-0 max-[768px]:
|
|
946
|
+
"flex flex-col overflow-hidden rounded-xl border border-gray-200 bg-white shadow-2xl dark:border-gray-800 dark:bg-gray-950",
|
|
947
|
+
"mx-4 w-full max-w-lg",
|
|
948
|
+
"max-[768px]:mx-0 max-[768px]:h-full max-[768px]:max-w-none max-[768px]:rounded-none",
|
|
815
949
|
"min-[769px]:max-h-[85vh]"
|
|
816
950
|
),
|
|
817
951
|
children: [
|
|
818
|
-
/* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between
|
|
952
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between border-b border-gray-200 bg-gray-50/30 px-4 py-3 dark:border-gray-800 dark:bg-gray-900/30", children: [
|
|
819
953
|
/* @__PURE__ */ jsxs2("div", { children: [
|
|
820
|
-
/* @__PURE__ */ jsx3("h2", { className: "font-semibold text-
|
|
954
|
+
/* @__PURE__ */ jsx3("h2", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100", children: "Bug Report" }),
|
|
821
955
|
/* @__PURE__ */ jsx3("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: siteId })
|
|
822
956
|
] }),
|
|
823
957
|
/* @__PURE__ */ jsx3(
|
|
824
958
|
"button",
|
|
825
959
|
{
|
|
826
960
|
onClick: handleClose,
|
|
827
|
-
className: "p-1.5
|
|
961
|
+
className: "rounded-md p-1.5 transition-colors hover:bg-gray-100 dark:hover:bg-gray-800",
|
|
828
962
|
"aria-label": "Close",
|
|
829
963
|
children: /* @__PURE__ */ jsx3(X, { className: "h-4 w-4 text-gray-600 dark:text-gray-400" })
|
|
830
964
|
}
|
|
831
965
|
)
|
|
832
966
|
] }),
|
|
833
|
-
screenshotUrl && /* @__PURE__ */ jsx3("div", { className: "
|
|
967
|
+
screenshotUrl && /* @__PURE__ */ jsx3("div", { className: "border-b border-gray-200 bg-gray-50/10 px-4 py-3 dark:border-gray-800 dark:bg-gray-900/10", children: /* @__PURE__ */ jsx3(
|
|
834
968
|
"img",
|
|
835
969
|
{
|
|
836
970
|
src: screenshotUrl,
|
|
837
971
|
alt: "Captured section",
|
|
838
|
-
className: "
|
|
972
|
+
className: "max-h-40 w-full rounded-md border border-gray-200 object-contain dark:border-gray-700"
|
|
839
973
|
}
|
|
840
974
|
) }),
|
|
841
|
-
/* @__PURE__ */ jsx3("div", { className: "flex-1 overflow-y-auto px-4 py-3
|
|
842
|
-
/* @__PURE__ */ jsx3(CheckCircle2, { className: "h-12 w-12 text-green-500
|
|
843
|
-
/* @__PURE__ */ jsx3("h3", { className: "font-semibold text-
|
|
844
|
-
/* @__PURE__ */ jsxs2("p", { className: "text-sm text-gray-500 dark:text-gray-400
|
|
845
|
-
"Reference:
|
|
846
|
-
|
|
975
|
+
/* @__PURE__ */ jsx3("div", { className: "min-h-0 flex-1 space-y-3 overflow-y-auto px-4 py-3", children: modalState === "submitted" ? /* @__PURE__ */ jsxs2("div", { className: "flex flex-col items-center justify-center py-8 text-center", children: [
|
|
976
|
+
/* @__PURE__ */ jsx3(CheckCircle2, { className: "mb-3 h-12 w-12 text-green-500" }),
|
|
977
|
+
/* @__PURE__ */ jsx3("h3", { className: "text-lg font-semibold text-gray-900 dark:text-gray-100", children: "Report Submitted" }),
|
|
978
|
+
/* @__PURE__ */ jsxs2("p", { className: "mt-1 text-sm text-gray-500 dark:text-gray-400", children: [
|
|
979
|
+
"Reference:",
|
|
980
|
+
" ",
|
|
981
|
+
/* @__PURE__ */ jsx3("code", { className: "rounded bg-gray-100 px-1.5 py-0.5 text-xs dark:bg-gray-800", children: reportId == null ? void 0 : reportId.slice(0, 8) })
|
|
847
982
|
] }),
|
|
848
|
-
/* @__PURE__ */ jsx3("p", { className: "text-sm text-gray-500 dark:text-gray-400
|
|
983
|
+
/* @__PURE__ */ jsx3("p", { className: "mt-2 text-sm text-gray-500 dark:text-gray-400", children: "Thanks for the report \u2014 we'll look into it." }),
|
|
849
984
|
/* @__PURE__ */ jsx3(
|
|
850
985
|
"button",
|
|
851
986
|
{
|
|
852
987
|
onClick: handleClose,
|
|
853
|
-
className: "mt-4 px-4 py-2
|
|
988
|
+
className: "mt-4 rounded-md bg-indigo-600 px-4 py-2 text-sm text-white transition-colors hover:bg-indigo-700",
|
|
854
989
|
children: "Done"
|
|
855
990
|
}
|
|
856
991
|
)
|
|
857
992
|
] }) : modalState === "error" ? /* @__PURE__ */ jsxs2("div", { className: "flex flex-col items-center justify-center py-8 text-center", children: [
|
|
858
|
-
/* @__PURE__ */ jsx3(X, { className: "h-12 w-12 text-red-500
|
|
859
|
-
/* @__PURE__ */ jsx3("h3", { className: "font-semibold text-
|
|
860
|
-
/* @__PURE__ */ jsx3("p", { className: "text-sm text-gray-500 dark:text-gray-400
|
|
993
|
+
/* @__PURE__ */ jsx3(X, { className: "mb-3 h-12 w-12 text-red-500" }),
|
|
994
|
+
/* @__PURE__ */ jsx3("h3", { className: "text-lg font-semibold text-gray-900 dark:text-gray-100", children: "Submission Failed" }),
|
|
995
|
+
/* @__PURE__ */ jsx3("p", { className: "mt-1 text-sm text-gray-500 dark:text-gray-400", children: errorMessage || "Something went wrong. Please try again." }),
|
|
861
996
|
/* @__PURE__ */ jsx3(
|
|
862
997
|
"button",
|
|
863
998
|
{
|
|
864
999
|
onClick: () => setModalState("chatting"),
|
|
865
|
-
className: "mt-4 px-4 py-2
|
|
1000
|
+
className: "mt-4 rounded-md bg-indigo-600 px-4 py-2 text-sm text-white transition-colors hover:bg-indigo-700",
|
|
866
1001
|
children: "Try Again"
|
|
867
1002
|
}
|
|
868
1003
|
)
|
|
869
1004
|
] }) : modalState === "submitting" ? /* @__PURE__ */ jsxs2("div", { className: "flex flex-col items-center justify-center py-8", children: [
|
|
870
|
-
/* @__PURE__ */ jsx3(Loader2, { className: "h-8 w-8 animate-spin text-indigo-500
|
|
1005
|
+
/* @__PURE__ */ jsx3(Loader2, { className: "mb-3 h-8 w-8 animate-spin text-indigo-500" }),
|
|
871
1006
|
/* @__PURE__ */ jsx3("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: "Submitting your report..." })
|
|
872
1007
|
] }) : /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
873
1008
|
(captureResult == null ? void 0 : captureResult.screenshot.size) === 0 && /* @__PURE__ */ jsx3("p", { className: "text-xs text-amber-600", children: "Screenshot could not be captured. Please describe the visual issue in detail." }),
|
|
@@ -876,42 +1011,53 @@ function ReportModal({
|
|
|
876
1011
|
{
|
|
877
1012
|
className: cn(
|
|
878
1013
|
"text-sm leading-relaxed",
|
|
879
|
-
msg.role === "assistant" ? "
|
|
1014
|
+
msg.role === "assistant" ? "rounded-lg bg-gray-100/50 p-3 text-gray-900 dark:bg-gray-800/50 dark:text-gray-100" : "ml-8 rounded-lg bg-indigo-600 p-3 text-white"
|
|
880
1015
|
),
|
|
881
1016
|
children: msg.content
|
|
882
1017
|
},
|
|
883
1018
|
i
|
|
884
1019
|
)),
|
|
885
|
-
isLoading && /* @__PURE__ */ jsxs2("div", { className: "bg-gray-100/50 dark:bg-gray-800/50
|
|
1020
|
+
isLoading && /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2 rounded-lg bg-gray-100/50 p-3 dark:bg-gray-800/50", children: [
|
|
886
1021
|
/* @__PURE__ */ jsx3(Loader2, { className: "h-3.5 w-3.5 animate-spin text-gray-500" }),
|
|
887
1022
|
/* @__PURE__ */ jsx3("span", { className: "text-sm text-gray-500 dark:text-gray-400", children: "Thinking..." })
|
|
888
1023
|
] }),
|
|
889
1024
|
/* @__PURE__ */ jsx3("div", { ref: chatEndRef })
|
|
890
1025
|
] }) }),
|
|
891
|
-
modalState === "chatting" && /* @__PURE__ */
|
|
892
|
-
/* @__PURE__ */
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
/* @__PURE__ */ jsxs2("
|
|
907
|
-
|
|
1026
|
+
modalState === "chatting" && /* @__PURE__ */ jsx3("div", { className: "border-t border-gray-200 px-4 py-3 dark:border-gray-800", children: /* @__PURE__ */ jsxs2("div", { className: "flex flex-col gap-2", children: [
|
|
1027
|
+
/* @__PURE__ */ jsx3(
|
|
1028
|
+
"textarea",
|
|
1029
|
+
{
|
|
1030
|
+
ref: inputRef,
|
|
1031
|
+
value: input,
|
|
1032
|
+
onChange: (e) => setInput(e.target.value),
|
|
1033
|
+
onKeyDown: handleKeyDown,
|
|
1034
|
+
placeholder: "Describe what's wrong...",
|
|
1035
|
+
rows: 2,
|
|
1036
|
+
className: "w-full resize-none rounded-md border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 focus:border-transparent focus:ring-2 focus:ring-indigo-400 focus:outline-none dark:border-gray-700 dark:bg-gray-950 dark:text-gray-100",
|
|
1037
|
+
disabled: isLoading
|
|
1038
|
+
}
|
|
1039
|
+
),
|
|
1040
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex flex-col gap-2", children: [
|
|
1041
|
+
captureResult && (captureResult.consoleErrors.length > 0 || captureResult.networkErrors.length > 0) && /* @__PURE__ */ jsxs2("p", { className: "flex-1 text-xs text-amber-600", children: [
|
|
1042
|
+
[
|
|
1043
|
+
captureResult.consoleErrors.length > 0 ? `${captureResult.consoleErrors.length} console error${captureResult.consoleErrors.length !== 1 ? "s" : ""}` : null,
|
|
1044
|
+
captureResult.networkErrors.length > 0 ? `${captureResult.networkErrors.length} failed request${captureResult.networkErrors.length !== 1 ? "s" : ""}` : null
|
|
1045
|
+
].filter(Boolean).join(" + "),
|
|
1046
|
+
" ",
|
|
1047
|
+
"captured \u2014 these will be included in the report."
|
|
1048
|
+
] }),
|
|
1049
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex justify-end gap-2", children: [
|
|
1050
|
+
/* @__PURE__ */ jsxs2(
|
|
908
1051
|
"button",
|
|
909
1052
|
{
|
|
910
1053
|
onClick: sendMessage,
|
|
911
1054
|
disabled: !input.trim() || isLoading,
|
|
912
|
-
className: "
|
|
1055
|
+
className: "flex items-center gap-1 rounded-md bg-indigo-600 px-3 py-2 text-nowrap text-white transition-colors hover:bg-indigo-700 disabled:cursor-not-allowed disabled:opacity-50",
|
|
913
1056
|
title: "Send message",
|
|
914
|
-
children:
|
|
1057
|
+
children: [
|
|
1058
|
+
/* @__PURE__ */ jsx3(Send, { className: "h-4 w-4" }),
|
|
1059
|
+
/* @__PURE__ */ jsx3("span", { className: "text-sm font-medium", children: "Send message" })
|
|
1060
|
+
]
|
|
915
1061
|
}
|
|
916
1062
|
),
|
|
917
1063
|
messages.length >= 2 && /* @__PURE__ */ jsx3(
|
|
@@ -919,22 +1065,14 @@ function ReportModal({
|
|
|
919
1065
|
{
|
|
920
1066
|
onClick: handleManualSubmit,
|
|
921
1067
|
disabled: isLoading,
|
|
922
|
-
className: "px-
|
|
1068
|
+
className: "rounded-md bg-green-600 px-3 py-2 text-xs font-medium text-nowrap text-white transition-colors hover:bg-green-700 disabled:opacity-50",
|
|
923
1069
|
title: "Submit report now",
|
|
924
|
-
children: "Submit"
|
|
1070
|
+
children: /* @__PURE__ */ jsx3("span", { className: "text-sm font-medium", children: "Submit report" })
|
|
925
1071
|
}
|
|
926
1072
|
)
|
|
927
1073
|
] })
|
|
928
|
-
] }),
|
|
929
|
-
captureResult && (captureResult.consoleErrors.length > 0 || captureResult.networkErrors.length > 0) && /* @__PURE__ */ jsxs2("p", { className: "text-xs text-amber-600 mt-1.5", children: [
|
|
930
|
-
[
|
|
931
|
-
captureResult.consoleErrors.length > 0 ? `${captureResult.consoleErrors.length} console error${captureResult.consoleErrors.length !== 1 ? "s" : ""}` : null,
|
|
932
|
-
captureResult.networkErrors.length > 0 ? `${captureResult.networkErrors.length} failed request${captureResult.networkErrors.length !== 1 ? "s" : ""}` : null
|
|
933
|
-
].filter(Boolean).join(" + "),
|
|
934
|
-
" ",
|
|
935
|
-
"captured \u2014 these will be included in the report."
|
|
936
1074
|
] })
|
|
937
|
-
] })
|
|
1075
|
+
] }) })
|
|
938
1076
|
]
|
|
939
1077
|
}
|
|
940
1078
|
)
|
|
@@ -948,8 +1086,10 @@ function JarveBugReporter({
|
|
|
948
1086
|
apiUrl,
|
|
949
1087
|
apiKey,
|
|
950
1088
|
user,
|
|
1089
|
+
buttonPosition,
|
|
951
1090
|
children
|
|
952
1091
|
}) {
|
|
1092
|
+
const safeApiKey = apiKey || "";
|
|
953
1093
|
const [captureMode, setCaptureMode] = useState3(false);
|
|
954
1094
|
const [captureResult, setCaptureResult] = useState3(null);
|
|
955
1095
|
const [showModal, setShowModal] = useState3(false);
|
|
@@ -978,12 +1118,19 @@ function JarveBugReporter({
|
|
|
978
1118
|
clearCapturedErrors();
|
|
979
1119
|
clearCapturedNetworkErrors();
|
|
980
1120
|
}, []);
|
|
981
|
-
const siteId =
|
|
1121
|
+
const siteId = safeApiKey.startsWith("brk_") ? safeApiKey.slice(4, 12) : "external";
|
|
982
1122
|
const reporterName = (user == null ? void 0 : user.name) || "Anonymous";
|
|
983
1123
|
const reporterEmail = (user == null ? void 0 : user.email) || "unknown@external";
|
|
984
1124
|
return /* @__PURE__ */ jsxs3(Fragment3, { children: [
|
|
985
1125
|
children,
|
|
986
|
-
/* @__PURE__ */ jsx4(
|
|
1126
|
+
/* @__PURE__ */ jsx4(
|
|
1127
|
+
FloatingButton,
|
|
1128
|
+
{
|
|
1129
|
+
isActive: captureMode,
|
|
1130
|
+
onClick: toggleCaptureMode,
|
|
1131
|
+
position: buttonPosition
|
|
1132
|
+
}
|
|
1133
|
+
),
|
|
987
1134
|
/* @__PURE__ */ jsx4(
|
|
988
1135
|
CaptureOverlay,
|
|
989
1136
|
{
|