glitchgrab 0.1.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 ADDED
@@ -0,0 +1,787 @@
1
+ "use client";
2
+
3
+ // src/provider.tsx
4
+ import {
5
+ createContext,
6
+ useContext,
7
+ useRef,
8
+ useEffect,
9
+ useCallback
10
+ } from "react";
11
+
12
+ // src/error-boundary.tsx
13
+ import React from "react";
14
+
15
+ // src/breadcrumbs.ts
16
+ var MAX_DEFAULT = 50;
17
+ var breadcrumbs = [];
18
+ var maxBreadcrumbs = MAX_DEFAULT;
19
+ var initialized = false;
20
+ function initBreadcrumbs(max) {
21
+ if (initialized) return;
22
+ maxBreadcrumbs = max != null ? max : MAX_DEFAULT;
23
+ try {
24
+ if (typeof window === "undefined") return;
25
+ interceptConsole();
26
+ interceptFetch();
27
+ interceptNavigation();
28
+ interceptClicks();
29
+ initialized = true;
30
+ } catch (e) {
31
+ }
32
+ }
33
+ function addBreadcrumb(type, message, data) {
34
+ try {
35
+ breadcrumbs.push({
36
+ type,
37
+ message: message.slice(0, 200),
38
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
39
+ data
40
+ });
41
+ if (breadcrumbs.length > maxBreadcrumbs) {
42
+ breadcrumbs = breadcrumbs.slice(-maxBreadcrumbs);
43
+ }
44
+ } catch (e) {
45
+ }
46
+ }
47
+ function getBreadcrumbs() {
48
+ return [...breadcrumbs];
49
+ }
50
+ function clearBreadcrumbs() {
51
+ breadcrumbs = [];
52
+ }
53
+ function interceptConsole() {
54
+ const origLog = console.log;
55
+ const origWarn = console.warn;
56
+ const origError = console.error;
57
+ console.log = function(...args) {
58
+ addBreadcrumb("console", `[log] ${argsToString(args)}`);
59
+ origLog.apply(console, args);
60
+ };
61
+ console.warn = function(...args) {
62
+ addBreadcrumb("console", `[warn] ${argsToString(args)}`);
63
+ origWarn.apply(console, args);
64
+ };
65
+ console.error = function(...args) {
66
+ addBreadcrumb("console", `[error] ${argsToString(args)}`);
67
+ origError.apply(console, args);
68
+ };
69
+ }
70
+ function argsToString(args) {
71
+ try {
72
+ return args.map((a) => {
73
+ if (typeof a === "string") return a;
74
+ if (a instanceof Error) return a.message;
75
+ try {
76
+ return JSON.stringify(a);
77
+ } catch (e) {
78
+ return String(a);
79
+ }
80
+ }).join(" ").slice(0, 200);
81
+ } catch (e) {
82
+ return "[unknown]";
83
+ }
84
+ }
85
+ function interceptFetch() {
86
+ const origFetch = window.fetch;
87
+ window.fetch = async function(input, init) {
88
+ var _a;
89
+ const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input instanceof Request ? input.url : String(input);
90
+ const method = (_a = init == null ? void 0 : init.method) != null ? _a : "GET";
91
+ const start = Date.now();
92
+ try {
93
+ const response = await origFetch.apply(window, [input, init]);
94
+ addBreadcrumb("api", `${method} ${url.slice(0, 100)} \u2192 ${response.status}`, {
95
+ method,
96
+ status: String(response.status),
97
+ duration: `${Date.now() - start}ms`
98
+ });
99
+ return response;
100
+ } catch (err) {
101
+ addBreadcrumb("api", `${method} ${url.slice(0, 100)} \u2192 FAILED`, {
102
+ method,
103
+ error: err instanceof Error ? err.message : "unknown",
104
+ duration: `${Date.now() - start}ms`
105
+ });
106
+ throw err;
107
+ }
108
+ };
109
+ }
110
+ function interceptNavigation() {
111
+ const origPush = history.pushState.bind(history);
112
+ const origReplace = history.replaceState.bind(history);
113
+ history.pushState = function(...args) {
114
+ origPush(...args);
115
+ addBreadcrumb("navigation", `Navigate to ${window.location.pathname}`);
116
+ };
117
+ history.replaceState = function(...args) {
118
+ origReplace(...args);
119
+ addBreadcrumb("navigation", `Replace to ${window.location.pathname}`);
120
+ };
121
+ window.addEventListener("popstate", () => {
122
+ addBreadcrumb("navigation", `Back/Forward to ${window.location.pathname}`);
123
+ });
124
+ }
125
+ function interceptClicks() {
126
+ document.addEventListener(
127
+ "click",
128
+ (e) => {
129
+ var _a, _b, _c;
130
+ try {
131
+ const target = e.target;
132
+ const tag = (_a = target.tagName) == null ? void 0 : _a.toLowerCase();
133
+ const text = (_c = (_b = target.textContent) == null ? void 0 : _b.trim().slice(0, 50)) != null ? _c : "";
134
+ const id = target.id ? `#${target.id}` : "";
135
+ const cls = target.className && typeof target.className === "string" ? `.${target.className.split(" ")[0]}` : "";
136
+ addBreadcrumb("click", `Click ${tag}${id}${cls} "${text}"`);
137
+ } catch (e2) {
138
+ }
139
+ },
140
+ { capture: true }
141
+ );
142
+ }
143
+
144
+ // src/utils.ts
145
+ var SENSITIVE_PARAMS = [
146
+ "token",
147
+ "key",
148
+ "secret",
149
+ "password",
150
+ "passwd",
151
+ "auth",
152
+ "authorization",
153
+ "session",
154
+ "sessionid",
155
+ "session_id",
156
+ "api_key",
157
+ "apikey",
158
+ "access_token",
159
+ "refresh_token",
160
+ "client_secret",
161
+ "code",
162
+ "state",
163
+ "nonce",
164
+ "credential",
165
+ "private"
166
+ ];
167
+ function sanitizeUrl(url) {
168
+ var _a;
169
+ try {
170
+ const parsed = new URL(url);
171
+ const params = new URLSearchParams(parsed.search);
172
+ let modified = false;
173
+ for (const key of Array.from(params.keys())) {
174
+ if (SENSITIVE_PARAMS.some((s) => key.toLowerCase().includes(s))) {
175
+ params.set(key, "[REDACTED]");
176
+ modified = true;
177
+ }
178
+ }
179
+ if (modified) parsed.search = params.toString();
180
+ return parsed.toString();
181
+ } catch (e) {
182
+ return (_a = url.split("?")[0]) != null ? _a : url;
183
+ }
184
+ }
185
+ function captureDeviceInfo() {
186
+ var _a, _b, _c;
187
+ try {
188
+ if (typeof window === "undefined") return null;
189
+ return {
190
+ screenWidth: screen.width,
191
+ screenHeight: screen.height,
192
+ viewportWidth: window.innerWidth,
193
+ viewportHeight: window.innerHeight,
194
+ platform: (_a = navigator.platform) != null ? _a : "unknown",
195
+ language: (_b = navigator.language) != null ? _b : "unknown",
196
+ online: navigator.onLine,
197
+ colorScheme: window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light",
198
+ devicePixelRatio: (_c = window.devicePixelRatio) != null ? _c : 1
199
+ };
200
+ } catch (e) {
201
+ return null;
202
+ }
203
+ }
204
+ function captureContext(visitedPages) {
205
+ try {
206
+ return {
207
+ url: sanitizeUrl(typeof window !== "undefined" ? window.location.href : ""),
208
+ userAgent: typeof navigator !== "undefined" ? navigator.userAgent : "",
209
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
210
+ visitedPages: visitedPages.map(sanitizeUrl),
211
+ breadcrumbs: getBreadcrumbs(),
212
+ deviceInfo: captureDeviceInfo()
213
+ };
214
+ } catch (e) {
215
+ return {
216
+ url: "",
217
+ userAgent: "",
218
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
219
+ visitedPages: [],
220
+ breadcrumbs: [],
221
+ deviceInfo: null
222
+ };
223
+ }
224
+ }
225
+ var DEFAULT_BASE_URL = "https://glitchgrab.dev";
226
+ var MAX_RETRIES = 3;
227
+ var RETRY_BASE_MS = 500;
228
+ async function sendReport(payload, baseUrl) {
229
+ var _a;
230
+ try {
231
+ const url = `${baseUrl != null ? baseUrl : DEFAULT_BASE_URL}/api/v1/sdk/report`;
232
+ const body = JSON.stringify(payload);
233
+ const headers = {
234
+ "Content-Type": "application/json",
235
+ Authorization: `Bearer ${payload.token}`
236
+ };
237
+ for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
238
+ try {
239
+ const response = await fetch(url, {
240
+ method: "POST",
241
+ headers,
242
+ body,
243
+ keepalive: true
244
+ });
245
+ if (response.ok) {
246
+ const data = await response.json();
247
+ return (_a = data.data) != null ? _a : { success: data.success };
248
+ }
249
+ if (response.status >= 400 && response.status < 500 && response.status !== 429) {
250
+ return null;
251
+ }
252
+ } catch (e) {
253
+ }
254
+ if (attempt < MAX_RETRIES - 1) {
255
+ await new Promise(
256
+ (resolve) => setTimeout(resolve, RETRY_BASE_MS * Math.pow(2, attempt))
257
+ );
258
+ }
259
+ }
260
+ if (typeof navigator !== "undefined" && navigator.sendBeacon) {
261
+ try {
262
+ navigator.sendBeacon(url, new Blob([body], { type: "application/json" }));
263
+ } catch (e) {
264
+ }
265
+ }
266
+ return null;
267
+ } catch (e) {
268
+ return null;
269
+ }
270
+ }
271
+
272
+ // src/error-boundary.tsx
273
+ var GlitchgrabErrorBoundary = class extends React.Component {
274
+ constructor(props) {
275
+ super(props);
276
+ this.state = { hasError: false };
277
+ }
278
+ static getDerivedStateFromError() {
279
+ return { hasError: true };
280
+ }
281
+ componentDidCatch(error, errorInfo) {
282
+ var _a, _b;
283
+ try {
284
+ const context = captureContext(this.props.visitedPages);
285
+ const payload = {
286
+ token: this.props.token,
287
+ source: "SDK_AUTO",
288
+ type: "BUG",
289
+ errorMessage: error.message,
290
+ errorStack: error.stack,
291
+ componentStack: (_a = errorInfo.componentStack) != null ? _a : void 0,
292
+ pageUrl: context.url,
293
+ userAgent: context.userAgent,
294
+ breadcrumbs: context.breadcrumbs,
295
+ deviceInfo: (_b = context.deviceInfo) != null ? _b : void 0,
296
+ metadata: {
297
+ timestamp: context.timestamp,
298
+ visitedPages: JSON.stringify(context.visitedPages)
299
+ }
300
+ };
301
+ sendReport(payload, this.props.baseUrl);
302
+ if (this.props.onError) {
303
+ this.props.onError(error);
304
+ }
305
+ } catch (e) {
306
+ }
307
+ }
308
+ render() {
309
+ if (this.state.hasError) {
310
+ if (this.props.fallback) {
311
+ return this.props.fallback;
312
+ }
313
+ return this.props.children;
314
+ }
315
+ return this.props.children;
316
+ }
317
+ };
318
+
319
+ // src/provider.tsx
320
+ import { Fragment, jsx } from "react/jsx-runtime";
321
+ var DEFAULT_BASE_URL2 = "https://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
+ baseUrl,
333
+ onError,
334
+ onReportSent,
335
+ breadcrumbs: enableBreadcrumbs = true,
336
+ maxBreadcrumbs: maxBreadcrumbs2 = 50,
337
+ children,
338
+ fallback
339
+ }) {
340
+ const visitedPagesRef = useRef([]);
341
+ useEffect(() => {
342
+ if (enableBreadcrumbs) {
343
+ initBreadcrumbs(maxBreadcrumbs2);
344
+ }
345
+ }, [enableBreadcrumbs, maxBreadcrumbs2]);
346
+ useEffect(() => {
347
+ try {
348
+ if (typeof window === "undefined") return;
349
+ const trackPage = () => {
350
+ try {
351
+ const sanitized = sanitizeUrl(window.location.href);
352
+ const pages = visitedPagesRef.current;
353
+ if (pages[pages.length - 1] !== sanitized) {
354
+ pages.push(sanitized);
355
+ if (pages.length > 20) {
356
+ pages.splice(0, pages.length - 20);
357
+ }
358
+ }
359
+ } catch (e) {
360
+ }
361
+ };
362
+ trackPage();
363
+ const handlePopState = () => trackPage();
364
+ window.addEventListener("popstate", handlePopState);
365
+ const origPushState = history.pushState.bind(history);
366
+ const origReplaceState = history.replaceState.bind(history);
367
+ history.pushState = function(...args) {
368
+ origPushState(...args);
369
+ trackPage();
370
+ };
371
+ history.replaceState = function(...args) {
372
+ origReplaceState(...args);
373
+ trackPage();
374
+ };
375
+ return () => {
376
+ window.removeEventListener("popstate", handlePopState);
377
+ history.pushState = origPushState;
378
+ history.replaceState = origReplaceState;
379
+ };
380
+ } catch (e) {
381
+ }
382
+ }, []);
383
+ useEffect(() => {
384
+ try {
385
+ if (typeof window === "undefined") return;
386
+ const handleError = (event) => {
387
+ var _a, _b, _c, _d, _e;
388
+ try {
389
+ const context = captureContext(visitedPagesRef.current);
390
+ const payload = {
391
+ token,
392
+ source: "SDK_AUTO",
393
+ type: "BUG",
394
+ errorMessage: event.message,
395
+ errorStack: (_a = event.error) == null ? void 0 : _a.stack,
396
+ pageUrl: context.url,
397
+ userAgent: context.userAgent,
398
+ breadcrumbs: context.breadcrumbs,
399
+ deviceInfo: (_b = context.deviceInfo) != null ? _b : void 0,
400
+ metadata: {
401
+ timestamp: context.timestamp,
402
+ visitedPages: JSON.stringify(context.visitedPages),
403
+ filename: (_c = event.filename) != null ? _c : "",
404
+ lineno: String((_d = event.lineno) != null ? _d : ""),
405
+ colno: String((_e = event.colno) != null ? _e : "")
406
+ }
407
+ };
408
+ sendReport(payload, baseUrl).then((result) => {
409
+ if (result && onReportSent) onReportSent(result);
410
+ });
411
+ if (onError && event.error) onError(event.error);
412
+ } catch (e) {
413
+ }
414
+ };
415
+ const handleRejection = (event) => {
416
+ var _a;
417
+ try {
418
+ const context = captureContext(visitedPagesRef.current);
419
+ const reason = event.reason;
420
+ const payload = {
421
+ token,
422
+ source: "SDK_AUTO",
423
+ type: "BUG",
424
+ errorMessage: reason instanceof Error ? reason.message : String(reason),
425
+ errorStack: reason instanceof Error ? reason.stack : void 0,
426
+ pageUrl: context.url,
427
+ userAgent: context.userAgent,
428
+ breadcrumbs: context.breadcrumbs,
429
+ deviceInfo: (_a = context.deviceInfo) != null ? _a : void 0,
430
+ metadata: {
431
+ timestamp: context.timestamp,
432
+ visitedPages: JSON.stringify(context.visitedPages),
433
+ type: "unhandledrejection"
434
+ }
435
+ };
436
+ sendReport(payload, baseUrl).then((result) => {
437
+ if (result && onReportSent) onReportSent(result);
438
+ });
439
+ if (onError && reason instanceof Error) onError(reason);
440
+ } catch (e) {
441
+ }
442
+ };
443
+ window.addEventListener("error", handleError);
444
+ window.addEventListener("unhandledrejection", handleRejection);
445
+ return () => {
446
+ window.removeEventListener("error", handleError);
447
+ window.removeEventListener("unhandledrejection", handleRejection);
448
+ };
449
+ } catch (e) {
450
+ }
451
+ }, [token, baseUrl, onError, onReportSent]);
452
+ const report = useCallback(
453
+ async (type, description, metadata) => {
454
+ var _a;
455
+ try {
456
+ const context = captureContext(visitedPagesRef.current);
457
+ const payload = {
458
+ token,
459
+ source: "SDK_USER_REPORT",
460
+ type,
461
+ description,
462
+ pageUrl: context.url,
463
+ userAgent: context.userAgent,
464
+ breadcrumbs: context.breadcrumbs,
465
+ deviceInfo: (_a = context.deviceInfo) != null ? _a : void 0,
466
+ metadata: {
467
+ timestamp: context.timestamp,
468
+ visitedPages: JSON.stringify(context.visitedPages),
469
+ ...metadata
470
+ }
471
+ };
472
+ const result = await sendReport(payload, baseUrl);
473
+ if (result && onReportSent) onReportSent(result);
474
+ return result;
475
+ } catch (e) {
476
+ return null;
477
+ }
478
+ },
479
+ [token, baseUrl, onReportSent]
480
+ );
481
+ const reportBug = useCallback(
482
+ (description, metadata) => report("BUG", description, metadata),
483
+ [report]
484
+ );
485
+ const addBreadcrumb2 = useCallback(
486
+ (message, data) => {
487
+ addBreadcrumb("custom", message, data);
488
+ },
489
+ []
490
+ );
491
+ return /* @__PURE__ */ jsx(
492
+ GlitchgrabContext.Provider,
493
+ {
494
+ value: {
495
+ token,
496
+ baseUrl: baseUrl != null ? baseUrl : DEFAULT_BASE_URL2,
497
+ reportBug,
498
+ report,
499
+ addBreadcrumb: addBreadcrumb2
500
+ },
501
+ children: /* @__PURE__ */ jsx(
502
+ GlitchgrabErrorBoundary,
503
+ {
504
+ token,
505
+ baseUrl,
506
+ onError,
507
+ fallback,
508
+ visitedPages: visitedPagesRef.current,
509
+ children
510
+ }
511
+ )
512
+ }
513
+ );
514
+ }
515
+ function GlitchgrabProvider(props) {
516
+ try {
517
+ return /* @__PURE__ */ jsx(GlitchgrabProviderInner, { ...props });
518
+ } catch (e) {
519
+ return /* @__PURE__ */ jsx(Fragment, { children: props.children });
520
+ }
521
+ }
522
+
523
+ // src/report-button.tsx
524
+ import { useState, useRef as useRef2, useEffect as useEffect2 } from "react";
525
+ import { Fragment as Fragment2, jsx as jsx2, jsxs } from "react/jsx-runtime";
526
+ function ReportButton({
527
+ position = "bottom-right",
528
+ label = "Report Bug",
529
+ className
530
+ }) {
531
+ const [isOpen, setIsOpen] = useState(false);
532
+ const [description, setDescription] = useState("");
533
+ const [isSubmitting, setIsSubmitting] = useState(false);
534
+ const [submitted, setSubmitted] = useState(false);
535
+ const modalRef = useRef2(null);
536
+ const { reportBug } = useGlitchgrab();
537
+ useEffect2(() => {
538
+ try {
539
+ if (!isOpen) return;
540
+ const handleClickOutside = (event) => {
541
+ if (modalRef.current && !modalRef.current.contains(event.target)) {
542
+ setIsOpen(false);
543
+ }
544
+ };
545
+ document.addEventListener("mousedown", handleClickOutside);
546
+ return () => document.removeEventListener("mousedown", handleClickOutside);
547
+ } catch (e) {
548
+ }
549
+ }, [isOpen]);
550
+ const handleSubmit = () => {
551
+ try {
552
+ if (!description.trim() || isSubmitting) return;
553
+ setIsSubmitting(true);
554
+ reportBug(description.trim());
555
+ setSubmitted(true);
556
+ setDescription("");
557
+ setIsSubmitting(false);
558
+ setTimeout(() => {
559
+ setSubmitted(false);
560
+ setIsOpen(false);
561
+ }, 2e3);
562
+ } catch (e) {
563
+ setIsSubmitting(false);
564
+ }
565
+ };
566
+ const positionStyles = position === "bottom-left" ? { left: "16px", bottom: "16px" } : { right: "16px", bottom: "16px" };
567
+ const modalPositionStyles = position === "bottom-left" ? { left: "16px", bottom: "64px" } : { right: "16px", bottom: "64px" };
568
+ return /* @__PURE__ */ jsxs(Fragment2, { children: [
569
+ /* @__PURE__ */ jsxs(
570
+ "button",
571
+ {
572
+ type: "button",
573
+ onClick: () => {
574
+ setIsOpen(!isOpen);
575
+ setSubmitted(false);
576
+ },
577
+ className,
578
+ style: {
579
+ position: "fixed",
580
+ ...positionStyles,
581
+ zIndex: 99999,
582
+ padding: "10px 18px",
583
+ borderRadius: "24px",
584
+ border: "none",
585
+ backgroundColor: "#18181b",
586
+ color: "#fafafa",
587
+ fontSize: "14px",
588
+ fontWeight: 500,
589
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
590
+ cursor: "pointer",
591
+ boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15), 0 1px 3px rgba(0, 0, 0, 0.1)",
592
+ transition: "transform 0.15s ease, box-shadow 0.15s ease",
593
+ display: "flex",
594
+ alignItems: "center",
595
+ gap: "6px"
596
+ },
597
+ onMouseEnter: (e) => {
598
+ e.target.style.transform = "scale(1.05)";
599
+ },
600
+ onMouseLeave: (e) => {
601
+ e.target.style.transform = "scale(1)";
602
+ },
603
+ "aria-label": label,
604
+ children: [
605
+ /* @__PURE__ */ jsx2(
606
+ "svg",
607
+ {
608
+ width: "16",
609
+ height: "16",
610
+ viewBox: "0 0 16 16",
611
+ fill: "none",
612
+ style: { flexShrink: 0 },
613
+ children: /* @__PURE__ */ jsx2(
614
+ "path",
615
+ {
616
+ 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",
617
+ fill: "currentColor"
618
+ }
619
+ )
620
+ }
621
+ ),
622
+ label
623
+ ]
624
+ }
625
+ ),
626
+ isOpen && /* @__PURE__ */ jsxs(
627
+ "div",
628
+ {
629
+ ref: modalRef,
630
+ style: {
631
+ position: "fixed",
632
+ ...modalPositionStyles,
633
+ zIndex: 1e5,
634
+ width: "320px",
635
+ backgroundColor: "#ffffff",
636
+ borderRadius: "12px",
637
+ boxShadow: "0 20px 60px rgba(0, 0, 0, 0.15), 0 4px 16px rgba(0, 0, 0, 0.1)",
638
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
639
+ overflow: "hidden"
640
+ },
641
+ children: [
642
+ /* @__PURE__ */ jsx2(
643
+ "div",
644
+ {
645
+ style: {
646
+ padding: "16px 16px 12px",
647
+ borderBottom: "1px solid #e4e4e7"
648
+ },
649
+ children: /* @__PURE__ */ jsxs(
650
+ "div",
651
+ {
652
+ style: {
653
+ display: "flex",
654
+ justifyContent: "space-between",
655
+ alignItems: "center"
656
+ },
657
+ children: [
658
+ /* @__PURE__ */ jsx2(
659
+ "span",
660
+ {
661
+ style: {
662
+ fontSize: "15px",
663
+ fontWeight: 600,
664
+ color: "#18181b"
665
+ },
666
+ children: "Report a Bug"
667
+ }
668
+ ),
669
+ /* @__PURE__ */ jsx2(
670
+ "button",
671
+ {
672
+ type: "button",
673
+ onClick: () => setIsOpen(false),
674
+ style: {
675
+ background: "none",
676
+ border: "none",
677
+ cursor: "pointer",
678
+ padding: "4px",
679
+ color: "#71717a",
680
+ fontSize: "18px",
681
+ lineHeight: 1
682
+ },
683
+ "aria-label": "Close",
684
+ children: "\xD7"
685
+ }
686
+ )
687
+ ]
688
+ }
689
+ )
690
+ }
691
+ ),
692
+ /* @__PURE__ */ jsx2("div", { style: { padding: "16px" }, children: submitted ? /* @__PURE__ */ jsx2(
693
+ "div",
694
+ {
695
+ style: {
696
+ textAlign: "center",
697
+ padding: "20px 0",
698
+ color: "#16a34a",
699
+ fontSize: "14px",
700
+ fontWeight: 500
701
+ },
702
+ children: "Bug report sent. Thank you!"
703
+ }
704
+ ) : /* @__PURE__ */ jsxs(Fragment2, { children: [
705
+ /* @__PURE__ */ jsx2(
706
+ "textarea",
707
+ {
708
+ value: description,
709
+ onChange: (e) => setDescription(e.target.value),
710
+ placeholder: "Describe what went wrong...",
711
+ style: {
712
+ width: "100%",
713
+ minHeight: "100px",
714
+ padding: "10px 12px",
715
+ borderRadius: "8px",
716
+ border: "1px solid #d4d4d8",
717
+ fontSize: "14px",
718
+ fontFamily: "inherit",
719
+ resize: "vertical",
720
+ outline: "none",
721
+ boxSizing: "border-box",
722
+ color: "#18181b",
723
+ backgroundColor: "#fafafa"
724
+ },
725
+ onFocus: (e) => {
726
+ e.target.style.borderColor = "#18181b";
727
+ },
728
+ onBlur: (e) => {
729
+ e.target.style.borderColor = "#d4d4d8";
730
+ }
731
+ }
732
+ ),
733
+ /* @__PURE__ */ jsx2(
734
+ "button",
735
+ {
736
+ type: "button",
737
+ onClick: handleSubmit,
738
+ disabled: !description.trim() || isSubmitting,
739
+ style: {
740
+ marginTop: "12px",
741
+ width: "100%",
742
+ padding: "10px",
743
+ borderRadius: "8px",
744
+ border: "none",
745
+ backgroundColor: !description.trim() || isSubmitting ? "#a1a1aa" : "#18181b",
746
+ color: "#fafafa",
747
+ fontSize: "14px",
748
+ fontWeight: 500,
749
+ cursor: !description.trim() || isSubmitting ? "not-allowed" : "pointer",
750
+ fontFamily: "inherit",
751
+ transition: "background-color 0.15s ease"
752
+ },
753
+ children: isSubmitting ? "Sending..." : "Send Report"
754
+ }
755
+ )
756
+ ] }) }),
757
+ /* @__PURE__ */ jsx2(
758
+ "div",
759
+ {
760
+ style: {
761
+ padding: "8px 16px 10px",
762
+ borderTop: "1px solid #e4e4e7",
763
+ textAlign: "center"
764
+ },
765
+ children: /* @__PURE__ */ jsx2("span", { style: { fontSize: "11px", color: "#a1a1aa" }, children: "Powered by Glitchgrab" })
766
+ }
767
+ )
768
+ ]
769
+ }
770
+ )
771
+ ] });
772
+ }
773
+ export {
774
+ GlitchgrabErrorBoundary,
775
+ GlitchgrabProvider,
776
+ ReportButton,
777
+ addBreadcrumb,
778
+ captureContext,
779
+ captureDeviceInfo,
780
+ clearBreadcrumbs,
781
+ getBreadcrumbs,
782
+ initBreadcrumbs,
783
+ sanitizeUrl,
784
+ sendReport,
785
+ useGlitchgrab
786
+ };
787
+ //# sourceMappingURL=index.mjs.map