@diegotsi/flint-react 0.6.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/index.ts
@@ -22,21 +32,20 @@ var index_exports = {};
22
32
  __export(index_exports, {
23
33
  FlintModal: () => FlintModal,
24
34
  FlintWidget: () => FlintWidget,
25
- flint: () => flint
35
+ flint: () => import_core8.flint
26
36
  });
27
37
  module.exports = __toCommonJS(index_exports);
28
38
 
29
- // src/FlintWidget.tsx
30
- var import_react4 = require("react");
31
- var import_react_i18next3 = require("react-i18next");
32
-
33
39
  // src/FlintModal.tsx
34
40
  var import_react2 = require("react");
35
41
  var import_react_i18next = require("react-i18next");
36
42
 
43
+ // src/api.ts
44
+ var import_core = require("@flint/core");
45
+
37
46
  // src/ScreenAnnotator.tsx
38
- var import_react = require("react");
39
47
  var import_modern_screenshot = require("modern-screenshot");
48
+ var import_react = require("react");
40
49
  var import_jsx_runtime = require("react/jsx-runtime");
41
50
  function normalizeRect(startX, startY, endX, endY) {
42
51
  return {
@@ -81,9 +90,7 @@ function ScreenAnnotator({ zIndex, onCapture, onCancel }) {
81
90
  const dpr = window.devicePixelRatio ?? 1;
82
91
  const vw = window.innerWidth;
83
92
  const vh = window.innerHeight;
84
- await new Promise(
85
- (resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve()))
86
- );
93
+ await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve())));
87
94
  try {
88
95
  const fullCanvas = await (0, import_modern_screenshot.domToCanvas)(document.documentElement, {
89
96
  scale: dpr,
@@ -98,17 +105,7 @@ function ScreenAnnotator({ zIndex, onCapture, onCancel }) {
98
105
  canvas.width = vw * dpr;
99
106
  canvas.height = vh * dpr;
100
107
  const ctx = canvas.getContext("2d");
101
- ctx.drawImage(
102
- fullCanvas,
103
- 0,
104
- 0,
105
- vw * dpr,
106
- vh * dpr,
107
- 0,
108
- 0,
109
- vw * dpr,
110
- vh * dpr
111
- );
108
+ ctx.drawImage(fullCanvas, 0, 0, vw * dpr, vh * dpr, 0, 0, vw * dpr, vh * dpr);
112
109
  ctx.fillStyle = "rgba(255,200,0,0.25)";
113
110
  ctx.fillRect(finalRect.x * dpr, finalRect.y * dpr, finalRect.w * dpr, finalRect.h * dpr);
114
111
  ctx.strokeStyle = "#f97316";
@@ -146,123 +143,50 @@ function ScreenAnnotator({ zIndex, onCapture, onCancel }) {
146
143
  pointerEvents: isCapturing ? "none" : "auto"
147
144
  },
148
145
  children: [
149
- !isCapturing && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: {
150
- position: "absolute",
151
- top: 16,
152
- left: "50%",
153
- transform: "translateX(-50%)",
154
- background: "rgba(0,0,0,0.75)",
155
- color: "#fff",
156
- padding: "8px 18px",
157
- borderRadius: 8,
158
- fontSize: 14,
159
- fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
160
- pointerEvents: "none",
161
- whiteSpace: "nowrap",
162
- backdropFilter: "blur(4px)"
163
- }, children: "Drag to highlight the problem area \xA0\xB7\xA0 Esc to cancel" }),
164
- rect && phase === "selecting" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: {
165
- position: "absolute",
166
- left: rect.x,
167
- top: rect.y,
168
- width: rect.w,
169
- height: rect.h,
170
- background: "rgba(255,200,0,0.2)",
171
- border: "2px dashed #f97316",
172
- boxSizing: "border-box",
173
- pointerEvents: "none"
174
- } })
146
+ !isCapturing && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
147
+ "div",
148
+ {
149
+ style: {
150
+ position: "absolute",
151
+ top: 16,
152
+ left: "50%",
153
+ transform: "translateX(-50%)",
154
+ background: "rgba(0,0,0,0.75)",
155
+ color: "#fff",
156
+ padding: "8px 18px",
157
+ borderRadius: 8,
158
+ fontSize: 14,
159
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
160
+ pointerEvents: "none",
161
+ whiteSpace: "nowrap",
162
+ backdropFilter: "blur(4px)"
163
+ },
164
+ children: "Drag to highlight the problem area \xA0\xB7\xA0 Esc to cancel"
165
+ }
166
+ ),
167
+ rect && phase === "selecting" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
168
+ "div",
169
+ {
170
+ style: {
171
+ position: "absolute",
172
+ left: rect.x,
173
+ top: rect.y,
174
+ width: rect.w,
175
+ height: rect.h,
176
+ background: "rgba(255,200,0,0.2)",
177
+ border: "2px dashed #f97316",
178
+ boxSizing: "border-box",
179
+ pointerEvents: "none"
180
+ }
181
+ }
182
+ )
175
183
  ]
176
184
  }
177
185
  );
178
186
  }
179
187
 
180
- // src/api.ts
181
- var import_fflate = require("fflate");
182
- async function submitReport(serverUrl, projectKey, payload, screenshot) {
183
- const url = `${serverUrl.replace(/\/$/, "")}/api/v1/bug-reports`;
184
- let body;
185
- let headers = {
186
- "x-project-key": projectKey
187
- };
188
- if (screenshot) {
189
- const form = new FormData();
190
- form.append("reporterId", payload.reporterId);
191
- form.append("reporterName", payload.reporterName);
192
- form.append("description", payload.description);
193
- if (payload.expectedBehavior) form.append("expectedBehavior", payload.expectedBehavior);
194
- if (payload.stepsToReproduce) form.append("stepsToReproduce", JSON.stringify(payload.stepsToReproduce));
195
- if (payload.externalReplayUrl) form.append("externalReplayUrl", payload.externalReplayUrl);
196
- if (payload.additionalContext) form.append("additionalContext", payload.additionalContext);
197
- form.append("severity", payload.severity);
198
- if (payload.url) form.append("url", payload.url);
199
- if (payload.meta) form.append("meta", JSON.stringify(payload.meta));
200
- form.append("screenshot", screenshot);
201
- body = form;
202
- } else {
203
- body = JSON.stringify(payload);
204
- headers["Content-Type"] = "application/json";
205
- }
206
- const res = await fetch(url, { method: "POST", headers, body });
207
- if (!res.ok) {
208
- const err = await res.json().catch(() => ({ error: "Unknown error" }));
209
- throw new Error(err.error ?? `HTTP ${res.status}`);
210
- }
211
- return res.json();
212
- }
213
- async function submitReplay(serverUrl, projectKey, reportId, events) {
214
- const json = JSON.stringify(events);
215
- const encoded = new TextEncoder().encode(json);
216
- const compressed = (0, import_fflate.gzipSync)(encoded);
217
- const url = `${serverUrl.replace(/\/$/, "")}/api/v1/bug-reports/${reportId}/replay`;
218
- await fetch(url, {
219
- method: "POST",
220
- headers: {
221
- "x-project-key": projectKey,
222
- "Content-Type": "application/octet-stream"
223
- },
224
- body: compressed.buffer
225
- });
226
- }
227
-
228
188
  // src/theme.ts
229
- var light = {
230
- background: "rgba(255,255,255,0.90)",
231
- backgroundSecondary: "rgba(249,250,251,0.75)",
232
- accent: "#2563eb",
233
- accentHover: "#1d4ed8",
234
- text: "#111827",
235
- textMuted: "#6b7280",
236
- border: "rgba(255,255,255,0.9)",
237
- shadow: "0 32px 80px rgba(0,0,0,0.18), 0 8px 32px rgba(0,0,0,0.1), 0 0 0 1px rgba(0,0,0,0.04)",
238
- buttonText: "#ffffff",
239
- backdropFilter: "blur(32px) saturate(1.8)"
240
- };
241
- var dark = {
242
- background: "rgba(15,20,35,0.88)",
243
- backgroundSecondary: "rgba(5,8,18,0.65)",
244
- accent: "#f97316",
245
- accentHover: "#ea6c0a",
246
- text: "#dde3ef",
247
- textMuted: "#6b7a93",
248
- border: "rgba(255,255,255,0.08)",
249
- shadow: "0 24px 60px rgba(0,0,0,0.6), 0 0 0 1px rgba(255,255,255,0.04)",
250
- buttonText: "#ffffff",
251
- backdropFilter: "blur(32px) saturate(1.6)"
252
- };
253
- function resolveTheme(theme) {
254
- if (theme === "dark") return dark;
255
- if (theme === "light") return light;
256
- const override = theme;
257
- return {
258
- ...light,
259
- background: override.background ?? light.background,
260
- accent: override.accent ?? light.accent,
261
- accentHover: override.accent ?? light.accentHover,
262
- text: override.text ?? light.text,
263
- border: override.border ?? light.border
264
- };
265
- }
189
+ var import_core2 = require("@flint/core");
266
190
 
267
191
  // src/FlintModal.tsx
268
192
  var import_jsx_runtime2 = require("react/jsx-runtime");
@@ -322,10 +246,15 @@ function FlintModal({
322
246
  getNetworkErrors,
323
247
  getReplayEvents,
324
248
  getExternalReplayUrl,
325
- initialSelection = ""
249
+ initialSelection = "",
250
+ enableScreenshot = true,
251
+ statusPageUrl,
252
+ onBeforeSubmit,
253
+ onSuccess,
254
+ onError
326
255
  }) {
327
256
  const { t } = (0, import_react_i18next.useTranslation)();
328
- const colors = resolveTheme(theme);
257
+ const colors = (0, import_core2.resolveTheme)(theme);
329
258
  const isDark = theme === "dark";
330
259
  const [severity, setSeverity] = (0, import_react2.useState)("P2");
331
260
  const [description, setDescription] = (0, import_react2.useState)("");
@@ -382,33 +311,40 @@ function FlintModal({
382
311
  lang: textLang
383
312
  };
384
313
  }
314
+ const payload = {
315
+ reporterId: user?.id ?? "anonymous",
316
+ reporterName: user?.name ?? "Anonymous",
317
+ reporterEmail: user?.email,
318
+ description: isText ? `[Text issue] "${textOriginal.trim()}" \u2192 "${textSuggested.trim()}"` : description.trim(),
319
+ expectedBehavior: !isText ? expectedBehavior.trim() || void 0 : void 0,
320
+ externalReplayUrl: getExternalReplayUrl() || void 0,
321
+ severity: isText ? "P3" : severity,
322
+ url: window.location.href,
323
+ meta: collectedMeta,
324
+ label: isText ? "TEXT" : void 0
325
+ };
326
+ if (onBeforeSubmit) {
327
+ const proceed = await onBeforeSubmit(payload);
328
+ if (!proceed) {
329
+ setStatus("idle");
330
+ return;
331
+ }
332
+ }
385
333
  try {
386
- const res = await submitReport(
387
- serverUrl,
388
- projectKey,
389
- {
390
- reporterId: user?.id ?? "anonymous",
391
- reporterName: user?.name ?? "Anonymous",
392
- description: isText ? `[Text issue] "${textOriginal.trim()}" \u2192 "${textSuggested.trim()}"` : description.trim(),
393
- expectedBehavior: !isText ? expectedBehavior.trim() || void 0 : void 0,
394
- externalReplayUrl: getExternalReplayUrl() || void 0,
395
- severity: isText ? "P3" : severity,
396
- url: window.location.href,
397
- meta: collectedMeta,
398
- label: isText ? "TEXT" : void 0
399
- },
400
- !isText ? screenshot ?? void 0 : void 0
401
- );
334
+ const res = await (0, import_core.submitReport)(serverUrl, projectKey, payload, !isText ? screenshot ?? void 0 : void 0);
402
335
  setResult(res);
403
336
  setStatus("success");
337
+ onSuccess?.(res);
404
338
  const events = getReplayEvents();
405
339
  if (events.length > 0) {
406
- submitReplay(serverUrl, projectKey, res.id, events).catch(() => {
340
+ (0, import_core.submitReplay)(serverUrl, projectKey, res.id, events).catch(() => {
407
341
  });
408
342
  }
409
343
  } catch (err) {
410
- setErrorMsg(err instanceof Error ? err.message : t("errorLabel"));
344
+ const error = err instanceof Error ? err : new Error(t("errorLabel"));
345
+ setErrorMsg(error.message);
411
346
  setStatus("error");
347
+ onError?.(error);
412
348
  }
413
349
  };
414
350
  const inputBorder = isDark ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.1)";
@@ -458,157 +394,249 @@ function FlintModal({
458
394
  const isSuccess = status === "success";
459
395
  const ringBorder = isSuccess ? "3px solid #22c55e" : `3px solid ${isDark ? "rgba(255,255,255,0.08)" : "rgba(0,0,0,0.08)"}`;
460
396
  const ringTopColor = isSuccess ? "#22c55e" : colors.accent;
461
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: overlayStyle, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: modalStyle, role: "dialog", "aria-modal": "true", "aria-label": isSuccess ? t("successTitle") : t("sending"), children: [
462
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ModalHeader, { colors, inputBorder, showClose: false, onClose }),
463
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { padding: "40px 32px 48px", textAlign: "center", display: "flex", flexDirection: "column", alignItems: "center" }, children: [
464
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { position: "relative", width: 80, height: 80, marginBottom: 28 }, children: [
465
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: {
466
- position: "absolute",
467
- inset: -10,
468
- borderRadius: "50%",
469
- border: `1.5px solid ${colors.accent}`,
470
- animation: "_flint_ripple 1.8s ease-out infinite",
471
- opacity: isSuccess ? 0 : 1,
472
- transition: "opacity 0.3s ease"
473
- } }),
474
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: {
475
- position: "absolute",
476
- inset: -10,
477
- borderRadius: "50%",
478
- border: `1.5px solid ${colors.accent}`,
479
- animation: "_flint_ripple 1.8s ease-out infinite 0.6s",
480
- opacity: isSuccess ? 0 : 1,
481
- transition: "opacity 0.3s ease"
482
- } }),
483
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: {
484
- position: "absolute",
485
- inset: 0,
486
- borderRadius: "50%",
487
- border: ringBorder,
488
- borderTopColor: ringTopColor,
489
- animation: isSuccess ? "none" : "_flint_spin 0.85s linear infinite",
490
- transition: "border-color 0.45s ease, border-top-color 0.45s ease"
491
- } }),
492
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { position: "absolute", inset: 14, borderRadius: "50%" }, children: [
493
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: {
494
- position: "absolute",
495
- inset: 0,
496
- borderRadius: "50%",
497
- background: `linear-gradient(135deg, ${colors.accent}30, ${colors.accentHover}50)`,
498
- display: "flex",
499
- alignItems: "center",
500
- justifyContent: "center",
501
- animation: isSuccess ? "none" : "_flint_pulse 2s ease-in-out infinite",
502
- opacity: isSuccess ? 0 : 1,
503
- transition: "opacity 0.3s ease"
504
- }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SparkIcon, { color: colors.accent, size: 20 }) }),
505
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: {
506
- position: "absolute",
507
- inset: 0,
508
- borderRadius: "50%",
509
- background: "rgba(34,197,94,0.15)",
510
- display: "flex",
511
- alignItems: "center",
512
- justifyContent: "center",
513
- opacity: isSuccess ? 1 : 0,
514
- transform: isSuccess ? "scale(1)" : "scale(0.65)",
515
- transition: "opacity 0.35s ease 0.2s, transform 0.4s cubic-bezier(0.16,1,0.3,1) 0.2s"
516
- }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CheckIcon, { size: 20 }) })
517
- ] })
518
- ] }),
519
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { position: "relative", height: 26, width: "100%", marginBottom: 10 }, children: [
520
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: {
521
- position: "absolute",
522
- inset: 0,
523
- display: "flex",
524
- alignItems: "center",
525
- justifyContent: "center",
526
- fontSize: 19,
527
- fontWeight: 700,
528
- color: colors.text,
529
- letterSpacing: "-0.02em",
530
- opacity: isSuccess ? 0 : 1,
531
- transition: "opacity 0.25s ease",
532
- pointerEvents: "none"
533
- }, children: t("sending") }),
534
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: {
535
- position: "absolute",
536
- inset: 0,
537
- display: "flex",
538
- alignItems: "center",
539
- justifyContent: "center",
540
- fontSize: 19,
541
- fontWeight: 700,
542
- color: colors.text,
543
- letterSpacing: "-0.02em",
544
- opacity: isSuccess ? 1 : 0,
545
- transform: isSuccess ? "translateY(0)" : "translateY(6px)",
546
- transition: "opacity 0.35s ease 0.25s, transform 0.35s ease 0.25s",
547
- pointerEvents: isSuccess ? "auto" : "none"
548
- }, children: t("successTitle") })
549
- ] }),
550
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { position: "relative", minHeight: 76, width: "100%" }, children: [
551
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: {
552
- position: "absolute",
553
- inset: 0,
554
- display: "flex",
555
- alignItems: "center",
556
- justifyContent: "center",
557
- gap: 6,
558
- color: colors.textMuted,
559
- fontSize: 15,
560
- opacity: isSuccess ? 0 : 1,
561
- transition: "opacity 0.2s ease",
562
- pointerEvents: "none"
563
- }, children: [
564
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: t("capturingContext") }),
565
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SendingDots, { color: colors.accent })
566
- ] }),
567
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: {
568
- position: "absolute",
569
- inset: 0,
570
- display: "flex",
571
- flexDirection: "column",
572
- alignItems: "center",
573
- gap: 10,
574
- opacity: isSuccess ? 1 : 0,
575
- transform: isSuccess ? "translateY(0)" : "translateY(8px)",
576
- transition: "opacity 0.35s ease 0.35s, transform 0.35s ease 0.35s",
577
- pointerEvents: isSuccess ? "auto" : "none"
578
- }, children: [
579
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: { fontSize: 15, color: colors.textMuted, margin: 0 }, children: result ? `ID: ${result.id}` : "" }),
580
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
581
- "button",
582
- {
583
- onClick: onClose,
584
- style: {
585
- width: "100%",
586
- padding: "13px 20px",
587
- borderRadius: 12,
588
- border: "none",
589
- background: `linear-gradient(135deg, ${colors.accent}, ${colors.accentHover})`,
590
- color: colors.buttonText,
591
- fontSize: 17,
592
- fontWeight: 700,
593
- cursor: "pointer",
594
- letterSpacing: "-0.01em",
595
- boxShadow: accentGlow,
596
- fontFamily: "inherit",
597
- display: "flex",
598
- alignItems: "center",
599
- justifyContent: "center",
600
- gap: 8
601
- },
602
- children: t("close")
603
- }
604
- )
605
- ] })
606
- ] })
607
- ] })
608
- ] }) });
397
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: overlayStyle, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
398
+ "div",
399
+ {
400
+ style: modalStyle,
401
+ role: "dialog",
402
+ "aria-modal": "true",
403
+ "aria-label": isSuccess ? t("successTitle") : t("sending"),
404
+ children: [
405
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ModalHeader, { colors, inputBorder, showClose: false, onClose }),
406
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
407
+ "div",
408
+ {
409
+ style: {
410
+ padding: "40px 32px 48px",
411
+ textAlign: "center",
412
+ display: "flex",
413
+ flexDirection: "column",
414
+ alignItems: "center"
415
+ },
416
+ children: [
417
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { position: "relative", width: 80, height: 80, marginBottom: 28 }, children: [
418
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
419
+ "div",
420
+ {
421
+ style: {
422
+ position: "absolute",
423
+ inset: -10,
424
+ borderRadius: "50%",
425
+ border: `1.5px solid ${colors.accent}`,
426
+ animation: "_flint_ripple 1.8s ease-out infinite",
427
+ opacity: isSuccess ? 0 : 1,
428
+ transition: "opacity 0.3s ease"
429
+ }
430
+ }
431
+ ),
432
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
433
+ "div",
434
+ {
435
+ style: {
436
+ position: "absolute",
437
+ inset: -10,
438
+ borderRadius: "50%",
439
+ border: `1.5px solid ${colors.accent}`,
440
+ animation: "_flint_ripple 1.8s ease-out infinite 0.6s",
441
+ opacity: isSuccess ? 0 : 1,
442
+ transition: "opacity 0.3s ease"
443
+ }
444
+ }
445
+ ),
446
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
447
+ "div",
448
+ {
449
+ style: {
450
+ position: "absolute",
451
+ inset: 0,
452
+ borderRadius: "50%",
453
+ border: ringBorder,
454
+ borderTopColor: ringTopColor,
455
+ animation: isSuccess ? "none" : "_flint_spin 0.85s linear infinite",
456
+ transition: "border-color 0.45s ease, border-top-color 0.45s ease"
457
+ }
458
+ }
459
+ ),
460
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { position: "absolute", inset: 14, borderRadius: "50%" }, children: [
461
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
462
+ "div",
463
+ {
464
+ style: {
465
+ position: "absolute",
466
+ inset: 0,
467
+ borderRadius: "50%",
468
+ background: `linear-gradient(135deg, ${colors.accent}30, ${colors.accentHover}50)`,
469
+ display: "flex",
470
+ alignItems: "center",
471
+ justifyContent: "center",
472
+ animation: isSuccess ? "none" : "_flint_pulse 2s ease-in-out infinite",
473
+ opacity: isSuccess ? 0 : 1,
474
+ transition: "opacity 0.3s ease"
475
+ },
476
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SparkIcon, { color: colors.accent, size: 20 })
477
+ }
478
+ ),
479
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
480
+ "div",
481
+ {
482
+ style: {
483
+ position: "absolute",
484
+ inset: 0,
485
+ borderRadius: "50%",
486
+ background: "rgba(34,197,94,0.15)",
487
+ display: "flex",
488
+ alignItems: "center",
489
+ justifyContent: "center",
490
+ opacity: isSuccess ? 1 : 0,
491
+ transform: isSuccess ? "scale(1)" : "scale(0.65)",
492
+ transition: "opacity 0.35s ease 0.2s, transform 0.4s cubic-bezier(0.16,1,0.3,1) 0.2s"
493
+ },
494
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CheckIcon, { size: 20 })
495
+ }
496
+ )
497
+ ] })
498
+ ] }),
499
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { position: "relative", height: 26, width: "100%", marginBottom: 10 }, children: [
500
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
501
+ "div",
502
+ {
503
+ style: {
504
+ position: "absolute",
505
+ inset: 0,
506
+ display: "flex",
507
+ alignItems: "center",
508
+ justifyContent: "center",
509
+ fontSize: 19,
510
+ fontWeight: 700,
511
+ color: colors.text,
512
+ letterSpacing: "-0.02em",
513
+ opacity: isSuccess ? 0 : 1,
514
+ transition: "opacity 0.25s ease",
515
+ pointerEvents: "none"
516
+ },
517
+ children: t("sending")
518
+ }
519
+ ),
520
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
521
+ "div",
522
+ {
523
+ style: {
524
+ position: "absolute",
525
+ inset: 0,
526
+ display: "flex",
527
+ alignItems: "center",
528
+ justifyContent: "center",
529
+ fontSize: 19,
530
+ fontWeight: 700,
531
+ color: colors.text,
532
+ letterSpacing: "-0.02em",
533
+ opacity: isSuccess ? 1 : 0,
534
+ transform: isSuccess ? "translateY(0)" : "translateY(6px)",
535
+ transition: "opacity 0.35s ease 0.25s, transform 0.35s ease 0.25s",
536
+ pointerEvents: isSuccess ? "auto" : "none"
537
+ },
538
+ children: t("successTitle")
539
+ }
540
+ )
541
+ ] }),
542
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { position: "relative", minHeight: 76, width: "100%" }, children: [
543
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
544
+ "div",
545
+ {
546
+ style: {
547
+ position: "absolute",
548
+ inset: 0,
549
+ display: "flex",
550
+ alignItems: "center",
551
+ justifyContent: "center",
552
+ gap: 6,
553
+ color: colors.textMuted,
554
+ fontSize: 15,
555
+ opacity: isSuccess ? 0 : 1,
556
+ transition: "opacity 0.2s ease",
557
+ pointerEvents: "none"
558
+ },
559
+ children: [
560
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: t("capturingContext") }),
561
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SendingDots, { color: colors.accent })
562
+ ]
563
+ }
564
+ ),
565
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
566
+ "div",
567
+ {
568
+ style: {
569
+ position: "absolute",
570
+ inset: 0,
571
+ display: "flex",
572
+ flexDirection: "column",
573
+ alignItems: "center",
574
+ gap: 10,
575
+ opacity: isSuccess ? 1 : 0,
576
+ transform: isSuccess ? "translateY(0)" : "translateY(8px)",
577
+ transition: "opacity 0.35s ease 0.35s, transform 0.35s ease 0.35s",
578
+ pointerEvents: isSuccess ? "auto" : "none"
579
+ },
580
+ children: [
581
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: { fontSize: 15, color: colors.textMuted, margin: 0 }, children: result ? `ID: ${result.id}` : "" }),
582
+ statusPageUrl && user?.id && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
583
+ "a",
584
+ {
585
+ href: `${statusPageUrl}/status?project_key=${encodeURIComponent(projectKey)}&reporter_id=${encodeURIComponent(user.id)}&server_url=${encodeURIComponent(serverUrl)}`,
586
+ target: "_blank",
587
+ rel: "noreferrer",
588
+ style: {
589
+ fontSize: 14,
590
+ color: colors.accent,
591
+ textDecoration: "none",
592
+ fontWeight: 600,
593
+ animation: "_flint_success_up 0.35s ease 0.4s both"
594
+ },
595
+ children: [
596
+ "\u{1F4CB}",
597
+ " Track your bugs ",
598
+ "\u2192"
599
+ ]
600
+ }
601
+ ),
602
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
603
+ "button",
604
+ {
605
+ onClick: onClose,
606
+ style: {
607
+ width: "100%",
608
+ padding: "13px 20px",
609
+ borderRadius: 12,
610
+ border: "none",
611
+ background: `linear-gradient(135deg, ${colors.accent}, ${colors.accentHover})`,
612
+ color: colors.buttonText,
613
+ fontSize: 17,
614
+ fontWeight: 700,
615
+ cursor: "pointer",
616
+ letterSpacing: "-0.01em",
617
+ boxShadow: accentGlow,
618
+ fontFamily: "inherit",
619
+ display: "flex",
620
+ alignItems: "center",
621
+ justifyContent: "center",
622
+ gap: 8
623
+ },
624
+ children: t("close")
625
+ }
626
+ )
627
+ ]
628
+ }
629
+ )
630
+ ] })
631
+ ]
632
+ }
633
+ )
634
+ ]
635
+ }
636
+ ) });
609
637
  }
610
638
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
611
- annotating && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
639
+ enableScreenshot && annotating && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
612
640
  ScreenAnnotator,
613
641
  {
614
642
  zIndex: zIndex + 1,
@@ -632,37 +660,43 @@ function FlintModal({
632
660
  }
633
661
  ),
634
662
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("form", { onSubmit: handleSubmit, style: { padding: "20px 24px 24px" }, children: [
635
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: {
636
- display: "flex",
637
- gap: 4,
638
- marginBottom: 20,
639
- background: colors.backgroundSecondary,
640
- borderRadius: 12,
641
- padding: 4,
642
- border: `1px solid ${inputBorder}`
643
- }, children: ["bug", "text"].map((m) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
644
- "button",
663
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
664
+ "div",
645
665
  {
646
- type: "button",
647
- onClick: () => setMode(m),
648
666
  style: {
649
- flex: 1,
650
- padding: "8px 10px",
651
- borderRadius: 9,
652
- border: "none",
653
- cursor: "pointer",
654
- fontSize: 13,
655
- fontWeight: mode === m ? 700 : 500,
656
- fontFamily: "inherit",
657
- transition: "background 0.15s, color 0.15s",
658
- background: mode === m ? `linear-gradient(135deg, ${colors.accent}, ${colors.accentHover})` : "transparent",
659
- color: mode === m ? colors.buttonText : colors.textMuted,
660
- boxShadow: mode === m ? `0 2px 8px ${colors.accent}30` : "none"
667
+ display: "flex",
668
+ gap: 4,
669
+ marginBottom: 20,
670
+ background: colors.backgroundSecondary,
671
+ borderRadius: 12,
672
+ padding: 4,
673
+ border: `1px solid ${inputBorder}`
661
674
  },
662
- children: m === "bug" ? "\u{1F41B} Bug" : "\u{1F524} Text / Translation"
663
- },
664
- m
665
- )) }),
675
+ children: ["bug", "text"].map((m) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
676
+ "button",
677
+ {
678
+ type: "button",
679
+ onClick: () => setMode(m),
680
+ style: {
681
+ flex: 1,
682
+ padding: "8px 10px",
683
+ borderRadius: 9,
684
+ border: "none",
685
+ cursor: "pointer",
686
+ fontSize: 13,
687
+ fontWeight: mode === m ? 700 : 500,
688
+ fontFamily: "inherit",
689
+ transition: "background 0.15s, color 0.15s",
690
+ background: mode === m ? `linear-gradient(135deg, ${colors.accent}, ${colors.accentHover})` : "transparent",
691
+ color: mode === m ? colors.buttonText : colors.textMuted,
692
+ boxShadow: mode === m ? `0 2px 8px ${colors.accent}30` : "none"
693
+ },
694
+ children: m === "bug" ? "\u{1F41B} Bug" : "\u{1F524} Text / Translation"
695
+ },
696
+ m
697
+ ))
698
+ }
699
+ ),
666
700
  mode === "text" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
667
701
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { marginBottom: 14 }, children: [
668
702
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(FieldLabel, { colors, htmlFor: "flint-text-original", children: "Original text" }),
@@ -759,7 +793,7 @@ function FlintModal({
759
793
  }
760
794
  )
761
795
  ] }),
762
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { marginBottom: 20, display: mode === "text" ? "none" : void 0 }, children: [
796
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { marginBottom: 20, display: mode === "text" || !enableScreenshot ? "none" : void 0 }, children: [
763
797
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(FieldLabel, { colors, children: t("screenshotLabel") }),
764
798
  screenshot ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 10 }, children: [
765
799
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
@@ -770,7 +804,20 @@ function FlintModal({
770
804
  style: { height: 60, borderRadius: 8, objectFit: "cover", border: `1px solid ${inputBorder}` }
771
805
  }
772
806
  ),
773
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: 13, color: colors.textMuted, flex: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: screenshot.name }),
807
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
808
+ "span",
809
+ {
810
+ style: {
811
+ fontSize: 13,
812
+ color: colors.textMuted,
813
+ flex: 1,
814
+ overflow: "hidden",
815
+ textOverflow: "ellipsis",
816
+ whiteSpace: "nowrap"
817
+ },
818
+ children: screenshot.name
819
+ }
820
+ ),
774
821
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
775
822
  "button",
776
823
  {
@@ -857,31 +904,43 @@ function FlintModal({
857
904
  }
858
905
  )
859
906
  ] }),
860
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: {
861
- display: "flex",
862
- alignItems: "center",
863
- gap: 8,
864
- padding: "9px 12px",
865
- borderRadius: 10,
866
- background: isDark ? "rgba(255,255,255,0.04)" : "rgba(0,77,240,0.04)",
867
- border: `1px solid ${isDark ? "rgba(255,255,255,0.08)" : "rgba(0,77,240,0.1)"}`,
868
- marginBottom: 16
869
- }, children: [
870
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: 16 }, children: "\u{1F3A5}" }),
871
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: 14, color: colors.textMuted, lineHeight: 1.4 }, children: t("replayInfo") })
872
- ] }),
873
- status === "error" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: {
874
- padding: "10px 13px",
875
- borderRadius: 10,
876
- background: "rgba(239,68,68,0.08)",
877
- border: "1px solid rgba(239,68,68,0.2)",
878
- color: "#f87171",
879
- fontSize: 14,
880
- marginBottom: 16
881
- }, children: [
882
- "\u26A0\uFE0F ",
883
- errorMsg || t("errorLabel")
884
- ] }),
907
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
908
+ "div",
909
+ {
910
+ style: {
911
+ display: "flex",
912
+ alignItems: "center",
913
+ gap: 8,
914
+ padding: "9px 12px",
915
+ borderRadius: 10,
916
+ background: isDark ? "rgba(255,255,255,0.04)" : "rgba(0,77,240,0.04)",
917
+ border: `1px solid ${isDark ? "rgba(255,255,255,0.08)" : "rgba(0,77,240,0.1)"}`,
918
+ marginBottom: 16
919
+ },
920
+ children: [
921
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: 16 }, children: "\u{1F3A5}" }),
922
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: 14, color: colors.textMuted, lineHeight: 1.4 }, children: t("replayInfo") })
923
+ ]
924
+ }
925
+ ),
926
+ status === "error" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
927
+ "div",
928
+ {
929
+ style: {
930
+ padding: "10px 13px",
931
+ borderRadius: 10,
932
+ background: "rgba(239,68,68,0.08)",
933
+ border: "1px solid rgba(239,68,68,0.2)",
934
+ color: "#f87171",
935
+ fontSize: 14,
936
+ marginBottom: 16
937
+ },
938
+ children: [
939
+ "\u26A0\uFE0F ",
940
+ errorMsg || t("errorLabel")
941
+ ]
942
+ }
943
+ ),
885
944
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
886
945
  "button",
887
946
  {
@@ -942,49 +1001,68 @@ function ModalHeader({
942
1001
  titleId,
943
1002
  title
944
1003
  }) {
945
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: {
946
- display: "flex",
947
- alignItems: "center",
948
- gap: 10,
949
- padding: "16px 20px 14px",
950
- borderBottom: `1px solid ${inputBorder}`
951
- }, children: [
952
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: {
953
- width: 28,
954
- height: 28,
955
- borderRadius: 8,
956
- background: `linear-gradient(135deg, ${colors.accent}20, ${colors.accentHover}35)`,
957
- border: `1px solid ${colors.accent}30`,
958
- display: "flex",
959
- alignItems: "center",
960
- justifyContent: "center",
961
- flexShrink: 0
962
- }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SparkIcon, { color: colors.accent, size: 13 }) }),
963
- titleId && title ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h2", { id: titleId, style: { margin: 0, fontSize: 16, fontWeight: 600, color: colors.text, letterSpacing: "-0.01em", flex: 1 }, children: title }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { flex: 1, fontSize: 15, fontWeight: 600, color: colors.textMuted }, children: "Flint" }),
964
- showClose && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
965
- "button",
966
- {
967
- onClick: onClose,
968
- "aria-label": "Close",
969
- style: {
970
- background: "none",
971
- border: "none",
972
- cursor: "pointer",
973
- padding: 4,
974
- color: colors.textMuted,
975
- fontSize: 22,
976
- lineHeight: 1,
977
- borderRadius: 6,
978
- display: "flex",
979
- alignItems: "center",
980
- justifyContent: "center",
981
- opacity: 0.6,
982
- fontFamily: "inherit"
983
- },
984
- children: "\xD7"
985
- }
986
- )
987
- ] });
1004
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1005
+ "div",
1006
+ {
1007
+ style: {
1008
+ display: "flex",
1009
+ alignItems: "center",
1010
+ gap: 10,
1011
+ padding: "16px 20px 14px",
1012
+ borderBottom: `1px solid ${inputBorder}`
1013
+ },
1014
+ children: [
1015
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1016
+ "div",
1017
+ {
1018
+ style: {
1019
+ width: 28,
1020
+ height: 28,
1021
+ borderRadius: 8,
1022
+ background: `linear-gradient(135deg, ${colors.accent}20, ${colors.accentHover}35)`,
1023
+ border: `1px solid ${colors.accent}30`,
1024
+ display: "flex",
1025
+ alignItems: "center",
1026
+ justifyContent: "center",
1027
+ flexShrink: 0
1028
+ },
1029
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SparkIcon, { color: colors.accent, size: 13 })
1030
+ }
1031
+ ),
1032
+ titleId && title ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1033
+ "h2",
1034
+ {
1035
+ id: titleId,
1036
+ style: { margin: 0, fontSize: 16, fontWeight: 600, color: colors.text, letterSpacing: "-0.01em", flex: 1 },
1037
+ children: title
1038
+ }
1039
+ ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { flex: 1, fontSize: 15, fontWeight: 600, color: colors.textMuted }, children: "Flint" }),
1040
+ showClose && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1041
+ "button",
1042
+ {
1043
+ onClick: onClose,
1044
+ "aria-label": "Close",
1045
+ style: {
1046
+ background: "none",
1047
+ border: "none",
1048
+ cursor: "pointer",
1049
+ padding: 4,
1050
+ color: colors.textMuted,
1051
+ fontSize: 22,
1052
+ lineHeight: 1,
1053
+ borderRadius: 6,
1054
+ display: "flex",
1055
+ alignItems: "center",
1056
+ justifyContent: "center",
1057
+ opacity: 0.6,
1058
+ fontFamily: "inherit"
1059
+ },
1060
+ children: "\xD7"
1061
+ }
1062
+ )
1063
+ ]
1064
+ }
1065
+ );
988
1066
  }
989
1067
  function FieldLabel({
990
1068
  children,
@@ -1046,19 +1124,74 @@ function SeverityButton({ sev, label, selected, hint, color, accent, border, bg,
1046
1124
  },
1047
1125
  children: [
1048
1126
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { width: 8, height: 8, borderRadius: "50%", background: color, display: "block" } }),
1049
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: 13, fontWeight: selected ? 700 : 500, color: selected ? accent : text, letterSpacing: "0.02em" }, children: sev }),
1127
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1128
+ "span",
1129
+ {
1130
+ style: {
1131
+ fontSize: 13,
1132
+ fontWeight: selected ? 700 : 500,
1133
+ color: selected ? accent : text,
1134
+ letterSpacing: "0.02em"
1135
+ },
1136
+ children: sev
1137
+ }
1138
+ ),
1050
1139
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: 11, color: selected ? accent : text, opacity: 0.6, letterSpacing: "0.02em" }, children: label })
1051
1140
  ]
1052
1141
  }
1053
1142
  );
1054
1143
  }
1055
1144
  function SparkIcon({ color = "currentColor", size = 14 }) {
1056
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M13 2L3 14h9l-1 8 10-12h-9l1-8z" }) });
1145
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1146
+ "svg",
1147
+ {
1148
+ width: size,
1149
+ height: size,
1150
+ viewBox: "0 0 24 24",
1151
+ fill: "none",
1152
+ stroke: color,
1153
+ strokeWidth: "2.2",
1154
+ strokeLinecap: "round",
1155
+ strokeLinejoin: "round",
1156
+ "aria-hidden": "true",
1157
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M13 2L3 14h9l-1 8 10-12h-9l1-8z" })
1158
+ }
1159
+ );
1057
1160
  }
1058
1161
  function CheckIcon({ size = 20 }) {
1059
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "#22c55e", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("polyline", { points: "20 6 9 17 4 12" }) });
1162
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1163
+ "svg",
1164
+ {
1165
+ width: size,
1166
+ height: size,
1167
+ viewBox: "0 0 24 24",
1168
+ fill: "none",
1169
+ stroke: "#22c55e",
1170
+ strokeWidth: "2.5",
1171
+ strokeLinecap: "round",
1172
+ strokeLinejoin: "round",
1173
+ "aria-hidden": "true",
1174
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("polyline", { points: "20 6 9 17 4 12" })
1175
+ }
1176
+ );
1060
1177
  }
1061
1178
 
1179
+ // src/FlintWidget.tsx
1180
+ var import_react4 = require("react");
1181
+ var import_react_i18next3 = require("react-i18next");
1182
+
1183
+ // src/collectors/console.ts
1184
+ var import_core3 = require("@flint/core");
1185
+
1186
+ // src/collectors/environment.ts
1187
+ var import_core4 = require("@flint/core");
1188
+
1189
+ // src/collectors/frustration.ts
1190
+ var import_core5 = require("@flint/core");
1191
+
1192
+ // src/collectors/network.ts
1193
+ var import_core6 = require("@flint/core");
1194
+
1062
1195
  // src/i18n/index.ts
1063
1196
  var import_i18next = require("i18next");
1064
1197
  var import_react_i18next2 = require("react-i18next");
@@ -1108,251 +1241,15 @@ widgetI18n.use(import_react_i18next2.initReactI18next).init({
1108
1241
  });
1109
1242
  var i18n_default = widgetI18n;
1110
1243
 
1111
- // src/collectors/environment.ts
1112
- function collectEnvironment() {
1113
- const ua = navigator.userAgent;
1114
- let browser = "Unknown";
1115
- const chromeM = ua.match(/Chrome\/(\d+)/);
1116
- const firefoxM = ua.match(/Firefox\/(\d+)/);
1117
- const edgeM = ua.match(/Edg\/(\d+)/);
1118
- const safariM = ua.match(/Version\/(\d+)/);
1119
- const operaM = ua.match(/OPR\/(\d+)/);
1120
- if (operaM) {
1121
- browser = `Opera ${operaM[1]}`;
1122
- } else if (edgeM) {
1123
- browser = `Edge ${edgeM[1]}`;
1124
- } else if (chromeM && !/Edg|OPR/.test(ua)) {
1125
- browser = `Chrome ${chromeM[1]}`;
1126
- } else if (firefoxM) {
1127
- browser = `Firefox ${firefoxM[1]}`;
1128
- } else if (safariM && /Safari\//.test(ua)) {
1129
- browser = `Safari ${safariM[1]}`;
1130
- }
1131
- let os = "Unknown";
1132
- const macM = ua.match(/Mac OS X (\d+[._]\d+)/);
1133
- const winM = ua.match(/Windows NT (\d+\.\d+)/);
1134
- const androidM = ua.match(/Android (\d+)/);
1135
- const iosM = ua.match(/iPhone OS (\d+[._]\d+)/);
1136
- if (macM) {
1137
- os = `macOS ${macM[1].replace("_", ".")}`;
1138
- } else if (winM) {
1139
- const winMap = {
1140
- "10.0": "10/11",
1141
- "6.3": "8.1",
1142
- "6.2": "8",
1143
- "6.1": "7"
1144
- };
1145
- os = `Windows ${winMap[winM[1]] ?? winM[1]}`;
1146
- } else if (androidM) {
1147
- os = `Android ${androidM[1]}`;
1148
- } else if (iosM) {
1149
- os = `iOS ${iosM[1].replace(/_/g, ".")}`;
1150
- } else if (/Linux/.test(ua)) {
1151
- os = "Linux";
1152
- }
1153
- return {
1154
- browser,
1155
- os,
1156
- viewport: `${window.innerWidth}x${window.innerHeight}`,
1157
- screen: `${screen.width}x${screen.height}`,
1158
- language: navigator.language,
1159
- timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
1160
- online: navigator.onLine
1161
- };
1162
- }
1163
-
1164
- // src/collectors/console.ts
1165
- var MAX_ENTRIES = 50;
1166
- function createConsoleCollector() {
1167
- const entries = [];
1168
- let active = false;
1169
- const originals = {
1170
- log: console.log.bind(console),
1171
- warn: console.warn.bind(console),
1172
- error: console.error.bind(console)
1173
- };
1174
- let origOnerror = null;
1175
- let origUnhandled = null;
1176
- function push(level, args) {
1177
- let str;
1178
- try {
1179
- str = args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
1180
- } catch {
1181
- str = String(args[0]);
1182
- }
1183
- if (str.length > 500) str = str.slice(0, 500) + "\u2026";
1184
- entries.push({ level, args: str, timestamp: Date.now() });
1185
- if (entries.length > MAX_ENTRIES) entries.shift();
1186
- }
1187
- return {
1188
- start() {
1189
- if (active) return;
1190
- active = true;
1191
- console.log = (...args) => {
1192
- push("log", args);
1193
- originals.log(...args);
1194
- };
1195
- console.warn = (...args) => {
1196
- push("warn", args);
1197
- originals.warn(...args);
1198
- };
1199
- console.error = (...args) => {
1200
- push("error", args);
1201
- originals.error(...args);
1202
- };
1203
- origOnerror = window.onerror;
1204
- window.onerror = (msg, src, line, col, err) => {
1205
- push("error", [err?.message ?? String(msg), `${src}:${line}:${col}`]);
1206
- if (typeof origOnerror === "function")
1207
- return origOnerror(msg, src, line, col, err);
1208
- return false;
1209
- };
1210
- origUnhandled = window.onunhandledrejection;
1211
- window.onunhandledrejection = (event) => {
1212
- const reason = event.reason instanceof Error ? event.reason.message : JSON.stringify(event.reason);
1213
- push("error", ["UnhandledRejection:", reason]);
1214
- if (typeof origUnhandled === "function")
1215
- origUnhandled.call(window, event);
1216
- };
1217
- },
1218
- stop() {
1219
- if (!active) return;
1220
- active = false;
1221
- console.log = originals.log;
1222
- console.warn = originals.warn;
1223
- console.error = originals.error;
1224
- window.onerror = origOnerror;
1225
- window.onunhandledrejection = origUnhandled;
1226
- },
1227
- getEntries() {
1228
- return [...entries];
1229
- }
1230
- };
1231
- }
1232
-
1233
- // src/collectors/network.ts
1234
- var MAX_ENTRIES2 = 50;
1235
- var BLOCKED_HOSTS = /* @__PURE__ */ new Set([
1236
- "browser-intake-datadoghq.com",
1237
- "rum.browser-intake-datadoghq.com",
1238
- "logs.browser-intake-datadoghq.com",
1239
- "session-replay.browser-intake-datadoghq.com"
1240
- ]);
1241
- function isBlockedUrl(url, extra) {
1242
- try {
1243
- const host = new URL(url, location.href).hostname;
1244
- const all = [...BLOCKED_HOSTS, ...extra];
1245
- return all.some((b) => host === b || host.endsWith("." + b));
1246
- } catch {
1247
- return false;
1248
- }
1249
- }
1250
- function truncateUrl(url) {
1251
- try {
1252
- const u = new URL(url, location.href);
1253
- const base = `${u.origin}${u.pathname}`;
1254
- return base.length > 200 ? base.slice(0, 200) + "\u2026" : base;
1255
- } catch {
1256
- return url.length > 200 ? url.slice(0, 200) + "\u2026" : url;
1257
- }
1258
- }
1259
- function createNetworkCollector(extraBlockedHosts = []) {
1260
- const entries = [];
1261
- const blocked = new Set(extraBlockedHosts);
1262
- let origFetch = null;
1263
- let origXHROpen = null;
1264
- let active = false;
1265
- function push(entry) {
1266
- entries.push(entry);
1267
- if (entries.length > MAX_ENTRIES2) entries.shift();
1268
- }
1269
- return {
1270
- start() {
1271
- if (active) return;
1272
- active = true;
1273
- origFetch = window.fetch;
1274
- window.fetch = async (input, init) => {
1275
- const method = (init?.method ?? "GET").toUpperCase();
1276
- const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
1277
- const startTime = Date.now();
1278
- const res = await origFetch.call(window, input, init);
1279
- if (res.status >= 400 && !isBlockedUrl(url, blocked)) {
1280
- push({
1281
- method,
1282
- url: truncateUrl(url),
1283
- status: res.status,
1284
- duration: Date.now() - startTime,
1285
- timestamp: startTime
1286
- });
1287
- }
1288
- return res;
1289
- };
1290
- origXHROpen = XMLHttpRequest.prototype.open;
1291
- XMLHttpRequest.prototype.open = function(method, url, async, username, password) {
1292
- const startTime = Date.now();
1293
- const urlStr = typeof url === "string" ? url : url.href;
1294
- this.addEventListener("load", () => {
1295
- if (this.status >= 400 && !isBlockedUrl(urlStr, blocked)) {
1296
- push({
1297
- method: method.toUpperCase(),
1298
- url: truncateUrl(urlStr),
1299
- status: this.status,
1300
- duration: Date.now() - startTime,
1301
- timestamp: startTime
1302
- });
1303
- }
1304
- });
1305
- return origXHROpen.apply(this, [
1306
- method,
1307
- url,
1308
- async ?? true,
1309
- username,
1310
- password
1311
- ]);
1312
- };
1313
- },
1314
- stop() {
1315
- if (!active) return;
1316
- active = false;
1317
- if (origFetch) window.fetch = origFetch;
1318
- if (origXHROpen) XMLHttpRequest.prototype.open = origXHROpen;
1319
- },
1320
- getEntries() {
1321
- return [...entries];
1322
- }
1323
- };
1324
- }
1325
-
1326
1244
  // src/store.ts
1245
+ var import_core7 = require("@flint/core");
1327
1246
  var import_react3 = require("react");
1328
- var state = { user: void 0, sessionReplay: void 0 };
1329
- var listeners = /* @__PURE__ */ new Set();
1330
- function emit() {
1331
- listeners.forEach((fn) => fn());
1332
- }
1333
- function subscribeStore(fn) {
1334
- listeners.add(fn);
1335
- return () => listeners.delete(fn);
1336
- }
1337
- function getSnapshot() {
1338
- return state;
1339
- }
1247
+ var import_core8 = require("@flint/core");
1340
1248
  function useFlintStore() {
1341
- return (0, import_react3.useSyncExternalStore)(subscribeStore, getSnapshot);
1249
+ return (0, import_react3.useSyncExternalStore)(import_core7.subscribe, import_core7.getSnapshot);
1342
1250
  }
1343
- var flint = {
1344
- setUser(user) {
1345
- state = { ...state, user: user ?? void 0 };
1346
- emit();
1347
- },
1348
- setSessionReplay(url) {
1349
- state = { ...state, sessionReplay: url };
1350
- emit();
1351
- }
1352
- };
1353
1251
 
1354
1252
  // src/FlintWidget.tsx
1355
- var import_rrweb = require("rrweb");
1356
1253
  var import_jsx_runtime3 = require("react/jsx-runtime");
1357
1254
  var REPLAY_WINDOW_MS = 6e4;
1358
1255
  function FlintWidget(props) {
@@ -1371,7 +1268,19 @@ function WidgetContent({
1371
1268
  buttonLabel,
1372
1269
  theme = "dark",
1373
1270
  zIndex = 9999,
1374
- datadogSite
1271
+ statusPageUrl,
1272
+ datadogSite,
1273
+ enableReplay = true,
1274
+ enableScreenshot = true,
1275
+ enableConsole = true,
1276
+ enableNetwork = true,
1277
+ enableFrustration = false,
1278
+ autoReportFrustration = false,
1279
+ onBeforeSubmit,
1280
+ onSuccess,
1281
+ onError,
1282
+ onOpen,
1283
+ onClose
1375
1284
  }) {
1376
1285
  const globalState = useFlintStore();
1377
1286
  const resolvedUser = user ?? globalState.user;
@@ -1385,9 +1294,9 @@ function WidgetContent({
1385
1294
  const ddRum = window.DD_RUM;
1386
1295
  const ctx = ddRum?.getInternalContext?.();
1387
1296
  if (ctx?.session_id) {
1388
- const now = Date.now();
1389
- const fromTs = now - 3e4;
1390
- const toTs = now + 5e3;
1297
+ const now = Math.floor(Date.now() / 1e3);
1298
+ const fromTs = now - 30;
1299
+ const toTs = now + 5;
1391
1300
  return `https://${datadogSite}/rum/replay/sessions/${ctx.session_id}?from_ts=${fromTs}&to_ts=${toTs}&tab=replay&live=false`;
1392
1301
  }
1393
1302
  } catch {
@@ -1399,7 +1308,7 @@ function WidgetContent({
1399
1308
  const [open, setOpen] = (0, import_react4.useState)(false);
1400
1309
  const [hovered, setHovered] = (0, import_react4.useState)(false);
1401
1310
  const pendingSelection = (0, import_react4.useRef)("");
1402
- const colors = resolveTheme(theme);
1311
+ const colors = (0, import_core2.resolveTheme)(theme);
1403
1312
  const [selectionTooltip, setSelectionTooltip] = (0, import_react4.useState)(null);
1404
1313
  const tooltipRef = (0, import_react4.useRef)(null);
1405
1314
  const triggerRef = (0, import_react4.useRef)(null);
@@ -1440,16 +1349,17 @@ function WidgetContent({
1440
1349
  pendingSelection.current = text;
1441
1350
  setSelectionTooltip(null);
1442
1351
  setOpen(true);
1352
+ onOpen?.();
1443
1353
  };
1444
1354
  const consoleCollector = (0, import_react4.useRef)(null);
1445
1355
  const networkCollector = (0, import_react4.useRef)(null);
1446
1356
  const replayEvents = (0, import_react4.useRef)([]);
1447
1357
  const stopReplay = (0, import_react4.useRef)(null);
1448
- if (!consoleCollector.current) {
1449
- consoleCollector.current = createConsoleCollector();
1358
+ if (enableConsole && !consoleCollector.current) {
1359
+ consoleCollector.current = (0, import_core3.createConsoleCollector)();
1450
1360
  consoleCollector.current.start();
1451
1361
  }
1452
- if (!networkCollector.current) {
1362
+ if (enableNetwork && !networkCollector.current) {
1453
1363
  const flintHost = (() => {
1454
1364
  try {
1455
1365
  return new URL(serverUrl).hostname;
@@ -1457,26 +1367,61 @@ function WidgetContent({
1457
1367
  return "";
1458
1368
  }
1459
1369
  })();
1460
- networkCollector.current = createNetworkCollector(flintHost ? [flintHost] : []);
1370
+ networkCollector.current = (0, import_core6.createNetworkCollector)(flintHost ? [flintHost] : []);
1461
1371
  networkCollector.current.start();
1462
1372
  }
1373
+ const frustrationCollector = (0, import_react4.useRef)(null);
1374
+ if (enableFrustration && !frustrationCollector.current) {
1375
+ frustrationCollector.current = (0, import_core5.createFrustrationCollector)();
1376
+ frustrationCollector.current.start();
1377
+ }
1463
1378
  (0, import_react4.useEffect)(() => {
1464
- const stopFn = (0, import_rrweb.record)({
1465
- emit(event) {
1466
- replayEvents.current.push(event);
1467
- const cutoff = Date.now() - REPLAY_WINDOW_MS;
1468
- while (replayEvents.current.length > 0 && replayEvents.current[0].timestamp < cutoff) {
1469
- replayEvents.current.shift();
1470
- }
1471
- }
1472
- });
1473
- stopReplay.current = stopFn ?? null;
1379
+ let cancelled = false;
1380
+ if (enableReplay) {
1381
+ import("rrweb").then(({ record }) => {
1382
+ if (cancelled) return;
1383
+ const stopFn = record({
1384
+ emit(event) {
1385
+ replayEvents.current.push(event);
1386
+ const cutoff = Date.now() - REPLAY_WINDOW_MS;
1387
+ while (replayEvents.current.length > 0 && replayEvents.current[0].timestamp < cutoff) {
1388
+ replayEvents.current.shift();
1389
+ }
1390
+ }
1391
+ });
1392
+ stopReplay.current = stopFn ?? null;
1393
+ });
1394
+ }
1474
1395
  return () => {
1396
+ cancelled = true;
1475
1397
  consoleCollector.current?.stop();
1476
1398
  networkCollector.current?.stop();
1399
+ frustrationCollector.current?.stop();
1477
1400
  stopReplay.current?.();
1478
1401
  };
1479
- }, []);
1402
+ }, [enableReplay]);
1403
+ (0, import_react4.useEffect)(() => {
1404
+ if (!enableFrustration || !autoReportFrustration || !frustrationCollector.current) return;
1405
+ const unsubscribe = frustrationCollector.current.onFrustration(async (event) => {
1406
+ const user2 = resolvedUser;
1407
+ await (0, import_core.submitReport)(serverUrl, projectKey, {
1408
+ reporterId: user2?.id ?? "anonymous",
1409
+ reporterName: user2?.name ?? "Anonymous",
1410
+ reporterEmail: user2?.email,
1411
+ description: `[Auto-detected] ${event.type.replace(/_/g, " ")}: ${event.details}`,
1412
+ severity: event.type === "error_loop" ? "P1" : event.type === "rage_click" ? "P2" : "P3",
1413
+ url: event.url,
1414
+ meta: {
1415
+ environment: (0, import_core4.collectEnvironment)(),
1416
+ consoleLogs: consoleCollector.current?.getEntries() ?? [],
1417
+ networkErrors: networkCollector.current?.getEntries() ?? [],
1418
+ frustrationEvent: event
1419
+ }
1420
+ }).catch(() => {
1421
+ });
1422
+ });
1423
+ return unsubscribe;
1424
+ }, [enableFrustration, autoReportFrustration]);
1480
1425
  const label = buttonLabel ?? t("buttonLabel");
1481
1426
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
1482
1427
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
@@ -1486,7 +1431,10 @@ function WidgetContent({
1486
1431
  onMouseDown: () => {
1487
1432
  pendingSelection.current = window.getSelection()?.toString().trim() ?? "";
1488
1433
  },
1489
- onClick: () => setOpen(true),
1434
+ onClick: () => {
1435
+ setOpen(true);
1436
+ onOpen?.();
1437
+ },
1490
1438
  onMouseEnter: () => setHovered(true),
1491
1439
  onMouseLeave: () => setHovered(false),
1492
1440
  "aria-label": label,
@@ -1567,25 +1515,45 @@ function WidgetContent({
1567
1515
  zIndex,
1568
1516
  onClose: () => {
1569
1517
  setOpen(false);
1518
+ onClose?.();
1570
1519
  pendingSelection.current = "";
1571
1520
  },
1572
- getEnvironment: collectEnvironment,
1521
+ getEnvironment: import_core4.collectEnvironment,
1573
1522
  getConsoleLogs: () => consoleCollector.current?.getEntries() ?? [],
1574
1523
  getNetworkErrors: () => networkCollector.current?.getEntries() ?? [],
1575
1524
  getReplayEvents: () => [...replayEvents.current],
1576
1525
  getExternalReplayUrl,
1577
- initialSelection: pendingSelection.current
1526
+ initialSelection: pendingSelection.current,
1527
+ enableScreenshot,
1528
+ statusPageUrl,
1529
+ onBeforeSubmit,
1530
+ onSuccess,
1531
+ onError
1578
1532
  }
1579
1533
  )
1580
1534
  ] });
1581
1535
  }
1582
1536
  function TextIcon() {
1583
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
1584
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M17 10H3" }),
1585
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M21 6H3" }),
1586
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M21 14H3" }),
1587
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M17 18H3" })
1588
- ] });
1537
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1538
+ "svg",
1539
+ {
1540
+ width: "12",
1541
+ height: "12",
1542
+ viewBox: "0 0 24 24",
1543
+ fill: "none",
1544
+ stroke: "currentColor",
1545
+ strokeWidth: "2.2",
1546
+ strokeLinecap: "round",
1547
+ strokeLinejoin: "round",
1548
+ "aria-hidden": "true",
1549
+ children: [
1550
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M17 10H3" }),
1551
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M21 6H3" }),
1552
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M21 14H3" }),
1553
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M17 18H3" })
1554
+ ]
1555
+ }
1556
+ );
1589
1557
  }
1590
1558
  function SparkIcon2() {
1591
1559
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(