@mhosaic/feedback 0.6.2 → 0.7.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/{chunk-KXLX4IGN.mjs → chunk-W6JAJT2U.mjs} +752 -34
- package/dist/chunk-W6JAJT2U.mjs.map +1 -0
- package/dist/embed.min.js +227 -5
- package/dist/embed.min.js.map +1 -1
- package/dist/error-tracking.d.ts +1 -1
- package/dist/{index-DRmZlKSW.d.ts → index-CasX-2Bm.d.ts} +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.mjs +1 -1
- package/dist/react.d.ts +2 -2
- package/dist/react.mjs +1 -1
- package/dist/replay.d.ts +1 -1
- package/dist/{types-BCay5lo8.d.ts → types-CNwNoYUE.d.ts} +10 -0
- package/dist/webvitals.d.ts +1 -1
- package/package.json +1 -1
- package/dist/chunk-KXLX4IGN.mjs.map +0 -1
|
@@ -27,6 +27,9 @@ function createApiClient(options) {
|
|
|
27
27
|
form.append("technical_context", JSON.stringify(payload.technical_context));
|
|
28
28
|
if (payload.screenshot) form.append("screenshot", payload.screenshot, "screenshot.png");
|
|
29
29
|
if (payload.synthetic) form.append("synthetic", "true");
|
|
30
|
+
if (payload.user?.id) {
|
|
31
|
+
form.append("user", JSON.stringify(payload.user));
|
|
32
|
+
}
|
|
30
33
|
const response = await fetcher(`${endpoint}/api/feedback/v1/reports/`, {
|
|
31
34
|
method: "POST",
|
|
32
35
|
headers: { Authorization: `Bearer ${options.apiKey}` },
|
|
@@ -38,7 +41,75 @@ function createApiClient(options) {
|
|
|
38
41
|
}
|
|
39
42
|
return response.json();
|
|
40
43
|
}
|
|
41
|
-
|
|
44
|
+
function widgetHeaders(externalId) {
|
|
45
|
+
return {
|
|
46
|
+
Authorization: `Bearer ${options.apiKey}`,
|
|
47
|
+
"X-Mhosaic-User": externalId
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
async function listMine(externalId) {
|
|
51
|
+
const response = await fetcher(`${endpoint}/api/feedback/v1/reports/widget/mine/`, {
|
|
52
|
+
method: "GET",
|
|
53
|
+
headers: widgetHeaders(externalId)
|
|
54
|
+
});
|
|
55
|
+
if (response.status === 404) return [];
|
|
56
|
+
if (!response.ok) {
|
|
57
|
+
const text = await response.text().catch(() => "");
|
|
58
|
+
throw new Error(`listMine failed: ${response.status} ${text}`);
|
|
59
|
+
}
|
|
60
|
+
return response.json();
|
|
61
|
+
}
|
|
62
|
+
async function getReport(reportId, externalId) {
|
|
63
|
+
const response = await fetcher(
|
|
64
|
+
`${endpoint}/api/feedback/v1/reports/widget/${reportId}/`,
|
|
65
|
+
{ method: "GET", headers: widgetHeaders(externalId) }
|
|
66
|
+
);
|
|
67
|
+
if (!response.ok) {
|
|
68
|
+
const text = await response.text().catch(() => "");
|
|
69
|
+
throw new Error(`getReport failed: ${response.status} ${text}`);
|
|
70
|
+
}
|
|
71
|
+
return response.json();
|
|
72
|
+
}
|
|
73
|
+
async function addComment(reportId, externalId, body, clientNonce) {
|
|
74
|
+
const response = await fetcher(
|
|
75
|
+
`${endpoint}/api/feedback/v1/reports/widget/${reportId}/comments/`,
|
|
76
|
+
{
|
|
77
|
+
method: "POST",
|
|
78
|
+
headers: {
|
|
79
|
+
...widgetHeaders(externalId),
|
|
80
|
+
"Content-Type": "application/json"
|
|
81
|
+
},
|
|
82
|
+
body: JSON.stringify({
|
|
83
|
+
body,
|
|
84
|
+
...clientNonce !== void 0 && { client_nonce: clientNonce }
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
);
|
|
88
|
+
if (!response.ok) {
|
|
89
|
+
const text = await response.text().catch(() => "");
|
|
90
|
+
throw new Error(`addComment failed: ${response.status} ${text}`);
|
|
91
|
+
}
|
|
92
|
+
return response.json();
|
|
93
|
+
}
|
|
94
|
+
async function closeAsResolved(reportId, externalId) {
|
|
95
|
+
const response = await fetcher(
|
|
96
|
+
`${endpoint}/api/feedback/v1/reports/widget/${reportId}/`,
|
|
97
|
+
{
|
|
98
|
+
method: "PATCH",
|
|
99
|
+
headers: {
|
|
100
|
+
...widgetHeaders(externalId),
|
|
101
|
+
"Content-Type": "application/json"
|
|
102
|
+
},
|
|
103
|
+
body: JSON.stringify({ status: "closed" })
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
if (!response.ok) {
|
|
107
|
+
const text = await response.text().catch(() => "");
|
|
108
|
+
throw new Error(`closeAsResolved failed: ${response.status} ${text}`);
|
|
109
|
+
}
|
|
110
|
+
return response.json();
|
|
111
|
+
}
|
|
112
|
+
return { submitReport, listMine, getReport, addComment, closeAsResolved };
|
|
42
113
|
}
|
|
43
114
|
|
|
44
115
|
// src/capture/urlSanitizer.ts
|
|
@@ -366,6 +437,7 @@ var DEFAULT_STRINGS = {
|
|
|
366
437
|
"form.submitting": "Sending\u2026",
|
|
367
438
|
"form.success": "Thanks \u2014 your feedback was sent.",
|
|
368
439
|
"form.error": "Could not send. Please try again.",
|
|
440
|
+
"form.description.required": "Please describe the issue before sending.",
|
|
369
441
|
"form.screenshot.label": "Screenshot",
|
|
370
442
|
"form.screenshot.cta_click": "Click",
|
|
371
443
|
"form.screenshot.cta_rest": "drop, or paste an image",
|
|
@@ -395,7 +467,35 @@ var DEFAULT_STRINGS = {
|
|
|
395
467
|
"annotator.count_suffix": "annotations",
|
|
396
468
|
"annotator.loading": "Loading\u2026",
|
|
397
469
|
"annotator.apply": "Apply",
|
|
398
|
-
"annotator.applying": "Applying\u2026"
|
|
470
|
+
"annotator.applying": "Applying\u2026",
|
|
471
|
+
"tab.send": "Send",
|
|
472
|
+
"tab.mine": "My reports",
|
|
473
|
+
"mine.empty.title": "No reports yet",
|
|
474
|
+
"mine.empty.body": "Once you send feedback you can follow the thread here.",
|
|
475
|
+
"mine.refresh": "Refresh",
|
|
476
|
+
"mine.loading": "Loading\u2026",
|
|
477
|
+
"mine.error": "Could not load your reports.",
|
|
478
|
+
"mine.replies_one": "1 reply",
|
|
479
|
+
"mine.replies_many": "{count} replies",
|
|
480
|
+
"detail.back": "Back",
|
|
481
|
+
"detail.thread": "Conversation",
|
|
482
|
+
"detail.no_replies": "No replies yet \u2014 we\u2019ll let you know when an operator responds.",
|
|
483
|
+
"detail.compose_placeholder": "Add a follow-up reply\u2026",
|
|
484
|
+
"detail.compose_send": "Reply",
|
|
485
|
+
"detail.compose_sending": "Sending\u2026",
|
|
486
|
+
"detail.close_cta": "Mark as resolved",
|
|
487
|
+
"detail.close_busy": "Marking\u2026",
|
|
488
|
+
"detail.history": "Status history",
|
|
489
|
+
"detail.author.staff": "Operator",
|
|
490
|
+
"detail.author.mcp": "Mhosaic Team",
|
|
491
|
+
"detail.author.system": "System",
|
|
492
|
+
"status.new": "New",
|
|
493
|
+
"status.in_progress": "In progress",
|
|
494
|
+
"status.awaiting_validation": "Awaiting your validation",
|
|
495
|
+
"status.closed": "Closed",
|
|
496
|
+
"status.rejected": "Rejected",
|
|
497
|
+
"status.duplicate": "Duplicate",
|
|
498
|
+
"status.wontfix": "Won\u2019t fix"
|
|
399
499
|
};
|
|
400
500
|
var FRENCH_STRINGS = {
|
|
401
501
|
"fab.label": "Envoyer un commentaire",
|
|
@@ -410,6 +510,7 @@ var FRENCH_STRINGS = {
|
|
|
410
510
|
"form.submitting": "Envoi\u2026",
|
|
411
511
|
"form.success": "Merci \u2014 votre commentaire a \xE9t\xE9 envoy\xE9.",
|
|
412
512
|
"form.error": "\xC9chec d\u2019envoi. Veuillez r\xE9essayer.",
|
|
513
|
+
"form.description.required": "D\xE9crivez le probl\xE8me avant d\u2019envoyer.",
|
|
413
514
|
"form.screenshot.label": "Capture d\u2019\xE9cran",
|
|
414
515
|
"form.screenshot.cta_click": "Cliquez",
|
|
415
516
|
"form.screenshot.cta_rest": "d\xE9posez ou collez une image",
|
|
@@ -439,7 +540,35 @@ var FRENCH_STRINGS = {
|
|
|
439
540
|
"annotator.count_suffix": "annotations",
|
|
440
541
|
"annotator.loading": "Chargement\u2026",
|
|
441
542
|
"annotator.apply": "Appliquer",
|
|
442
|
-
"annotator.applying": "Application\u2026"
|
|
543
|
+
"annotator.applying": "Application\u2026",
|
|
544
|
+
"tab.send": "Envoyer",
|
|
545
|
+
"tab.mine": "Mes rapports",
|
|
546
|
+
"mine.empty.title": "Aucun rapport",
|
|
547
|
+
"mine.empty.body": "Apr\xE8s votre premier envoi vous pourrez suivre la conversation ici.",
|
|
548
|
+
"mine.refresh": "Actualiser",
|
|
549
|
+
"mine.loading": "Chargement\u2026",
|
|
550
|
+
"mine.error": "Impossible de charger vos rapports.",
|
|
551
|
+
"mine.replies_one": "1 r\xE9ponse",
|
|
552
|
+
"mine.replies_many": "{count} r\xE9ponses",
|
|
553
|
+
"detail.back": "Retour",
|
|
554
|
+
"detail.thread": "Conversation",
|
|
555
|
+
"detail.no_replies": "Pas encore de r\xE9ponse \u2014 vous serez notifi\xE9 d\xE8s qu\u2019un op\xE9rateur r\xE9pondra.",
|
|
556
|
+
"detail.compose_placeholder": "Ajouter une r\xE9ponse\u2026",
|
|
557
|
+
"detail.compose_send": "R\xE9pondre",
|
|
558
|
+
"detail.compose_sending": "Envoi\u2026",
|
|
559
|
+
"detail.close_cta": "Marquer comme r\xE9solu",
|
|
560
|
+
"detail.close_busy": "Validation\u2026",
|
|
561
|
+
"detail.history": "Historique du statut",
|
|
562
|
+
"detail.author.staff": "Op\xE9rateur",
|
|
563
|
+
"detail.author.mcp": "\xC9quipe Mhosaic",
|
|
564
|
+
"detail.author.system": "Syst\xE8me",
|
|
565
|
+
"status.new": "Nouveau",
|
|
566
|
+
"status.in_progress": "En cours",
|
|
567
|
+
"status.awaiting_validation": "En attente de validation",
|
|
568
|
+
"status.closed": "Ferm\xE9",
|
|
569
|
+
"status.rejected": "Rejet\xE9",
|
|
570
|
+
"status.duplicate": "Doublon",
|
|
571
|
+
"status.wontfix": "Non corrig\xE9"
|
|
443
572
|
};
|
|
444
573
|
var LOCALE_PACKS = {
|
|
445
574
|
fr: FRENCH_STRINGS
|
|
@@ -917,7 +1046,7 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
|
|
|
917
1046
|
const handleSubmit = (e) => {
|
|
918
1047
|
e.preventDefault();
|
|
919
1048
|
if (!description.trim()) {
|
|
920
|
-
setLocalError(strings["form.description.
|
|
1049
|
+
setLocalError(strings["form.description.required"]);
|
|
921
1050
|
return;
|
|
922
1051
|
}
|
|
923
1052
|
setLocalError("");
|
|
@@ -1056,13 +1185,123 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
|
|
|
1056
1185
|
] });
|
|
1057
1186
|
}
|
|
1058
1187
|
|
|
1059
|
-
// src/widget/
|
|
1060
|
-
import { useEffect as useEffect3, useRef as useRef3 } from "preact/hooks";
|
|
1188
|
+
// src/widget/MineList.tsx
|
|
1189
|
+
import { useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "preact/hooks";
|
|
1190
|
+
|
|
1191
|
+
// src/widget/ReportRow.tsx
|
|
1061
1192
|
import { jsx as jsx4, jsxs as jsxs3 } from "preact/jsx-runtime";
|
|
1062
|
-
function
|
|
1063
|
-
|
|
1064
|
-
|
|
1193
|
+
function statusClassName(status) {
|
|
1194
|
+
return `pill pill-status pill-status--${status}`;
|
|
1195
|
+
}
|
|
1196
|
+
function severityClassName(severity) {
|
|
1197
|
+
return `pill pill-severity pill-severity--${severity}`;
|
|
1198
|
+
}
|
|
1199
|
+
function typeClassName() {
|
|
1200
|
+
return "pill pill-type";
|
|
1201
|
+
}
|
|
1202
|
+
function formatRelative(iso) {
|
|
1203
|
+
const then = Date.parse(iso);
|
|
1204
|
+
if (!Number.isFinite(then)) return "";
|
|
1205
|
+
const seconds = Math.max(1, Math.round((Date.now() - then) / 1e3));
|
|
1206
|
+
if (seconds < 60) return `${seconds}s`;
|
|
1207
|
+
const minutes = Math.round(seconds / 60);
|
|
1208
|
+
if (minutes < 60) return `${minutes}m`;
|
|
1209
|
+
const hours = Math.round(minutes / 60);
|
|
1210
|
+
if (hours < 48) return `${hours}h`;
|
|
1211
|
+
const days = Math.round(hours / 24);
|
|
1212
|
+
return `${days}d`;
|
|
1213
|
+
}
|
|
1214
|
+
function repliesLabel(count, strings) {
|
|
1215
|
+
if (count === 1) return strings["mine.replies_one"];
|
|
1216
|
+
return strings["mine.replies_many"].replace("{count}", String(count));
|
|
1217
|
+
}
|
|
1218
|
+
function ReportRow({ row, strings, onClick }) {
|
|
1219
|
+
const preview = row.description.length > 120 ? row.description.slice(0, 117) + "\u2026" : row.description;
|
|
1220
|
+
return /* @__PURE__ */ jsxs3("button", { type: "button", class: "mine-row", onClick, children: [
|
|
1221
|
+
/* @__PURE__ */ jsxs3("div", { class: "mine-row-pills", children: [
|
|
1222
|
+
/* @__PURE__ */ jsx4("span", { class: statusClassName(row.status), children: strings[`status.${row.status}`] ?? row.status }),
|
|
1223
|
+
/* @__PURE__ */ jsx4("span", { class: typeClassName(), children: strings[`type.${row.feedback_type}`] }),
|
|
1224
|
+
/* @__PURE__ */ jsx4("span", { class: severityClassName(row.severity), children: strings[`severity.${row.severity}`] })
|
|
1225
|
+
] }),
|
|
1226
|
+
/* @__PURE__ */ jsx4("div", { class: "mine-row-preview", children: preview }),
|
|
1227
|
+
/* @__PURE__ */ jsxs3("div", { class: "mine-row-meta", children: [
|
|
1228
|
+
/* @__PURE__ */ jsx4("span", { children: formatRelative(row.updated_at || row.created_at) }),
|
|
1229
|
+
row.comment_count > 0 && /* @__PURE__ */ jsxs3("span", { children: [
|
|
1230
|
+
"\xB7 ",
|
|
1231
|
+
repliesLabel(row.comment_count, strings)
|
|
1232
|
+
] })
|
|
1233
|
+
] })
|
|
1234
|
+
] });
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
// src/widget/MineList.tsx
|
|
1238
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "preact/jsx-runtime";
|
|
1239
|
+
var POLL_MS = 3e4;
|
|
1240
|
+
function MineList({ api, externalId, strings, onSelect }) {
|
|
1241
|
+
const [rows, setRows] = useState3(null);
|
|
1242
|
+
const [error, setError] = useState3(null);
|
|
1243
|
+
const [refreshing, setRefreshing] = useState3(false);
|
|
1244
|
+
const mountedRef = useRef3(true);
|
|
1245
|
+
const fetchRows = async () => {
|
|
1246
|
+
setRefreshing(true);
|
|
1247
|
+
setError(null);
|
|
1248
|
+
try {
|
|
1249
|
+
const next = await api.listMine(externalId);
|
|
1250
|
+
if (!mountedRef.current) return;
|
|
1251
|
+
setRows(next);
|
|
1252
|
+
} catch (err) {
|
|
1253
|
+
if (!mountedRef.current) return;
|
|
1254
|
+
setError(err instanceof Error ? err.message : strings["mine.error"]);
|
|
1255
|
+
} finally {
|
|
1256
|
+
if (mountedRef.current) setRefreshing(false);
|
|
1257
|
+
}
|
|
1258
|
+
};
|
|
1065
1259
|
useEffect3(() => {
|
|
1260
|
+
mountedRef.current = true;
|
|
1261
|
+
void fetchRows();
|
|
1262
|
+
const timer = setInterval(() => {
|
|
1263
|
+
void fetchRows();
|
|
1264
|
+
}, POLL_MS);
|
|
1265
|
+
return () => {
|
|
1266
|
+
mountedRef.current = false;
|
|
1267
|
+
clearInterval(timer);
|
|
1268
|
+
};
|
|
1269
|
+
}, [externalId]);
|
|
1270
|
+
const isEmpty = rows !== null && rows.length === 0;
|
|
1271
|
+
const isLoading = rows === null && !error;
|
|
1272
|
+
return /* @__PURE__ */ jsxs4("div", { class: "mine-list", children: [
|
|
1273
|
+
/* @__PURE__ */ jsxs4("div", { class: "mine-list-header", children: [
|
|
1274
|
+
/* @__PURE__ */ jsx5("h2", { children: strings["tab.mine"] }),
|
|
1275
|
+
/* @__PURE__ */ jsx5(
|
|
1276
|
+
"button",
|
|
1277
|
+
{
|
|
1278
|
+
type: "button",
|
|
1279
|
+
class: "btn",
|
|
1280
|
+
onClick: () => {
|
|
1281
|
+
void fetchRows();
|
|
1282
|
+
},
|
|
1283
|
+
disabled: refreshing,
|
|
1284
|
+
children: refreshing ? strings["mine.loading"] : strings["mine.refresh"]
|
|
1285
|
+
}
|
|
1286
|
+
)
|
|
1287
|
+
] }),
|
|
1288
|
+
isLoading && /* @__PURE__ */ jsx5("div", { class: "mine-loading", children: strings["mine.loading"] }),
|
|
1289
|
+
error && /* @__PURE__ */ jsx5("div", { class: "error", children: error }),
|
|
1290
|
+
isEmpty && /* @__PURE__ */ jsxs4("div", { class: "mine-empty", children: [
|
|
1291
|
+
/* @__PURE__ */ jsx5("strong", { children: strings["mine.empty.title"] }),
|
|
1292
|
+
/* @__PURE__ */ jsx5("p", { children: strings["mine.empty.body"] })
|
|
1293
|
+
] }),
|
|
1294
|
+
rows && rows.length > 0 && /* @__PURE__ */ jsx5("ul", { class: "mine-rows", children: rows.map((row) => /* @__PURE__ */ jsx5("li", { children: /* @__PURE__ */ jsx5(ReportRow, { row, strings, onClick: () => onSelect(row) }) })) })
|
|
1295
|
+
] });
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
// src/widget/Modal.tsx
|
|
1299
|
+
import { useEffect as useEffect4, useRef as useRef4 } from "preact/hooks";
|
|
1300
|
+
import { jsx as jsx6, jsxs as jsxs5 } from "preact/jsx-runtime";
|
|
1301
|
+
function Modal({ onDismiss, children, closeLabel = "Close" }) {
|
|
1302
|
+
const modalRef = useRef4(null);
|
|
1303
|
+
const previouslyFocused = useRef4(null);
|
|
1304
|
+
useEffect4(() => {
|
|
1066
1305
|
previouslyFocused.current = document.activeElement;
|
|
1067
1306
|
const onKey = (e) => {
|
|
1068
1307
|
if (e.key !== "Escape") return;
|
|
@@ -1084,7 +1323,7 @@ function Modal({ onDismiss, children, closeLabel = "Close" }) {
|
|
|
1084
1323
|
if (prev && typeof prev.focus === "function") prev.focus();
|
|
1085
1324
|
};
|
|
1086
1325
|
}, [onDismiss]);
|
|
1087
|
-
return /* @__PURE__ */
|
|
1326
|
+
return /* @__PURE__ */ jsx6(
|
|
1088
1327
|
"div",
|
|
1089
1328
|
{
|
|
1090
1329
|
class: "backdrop",
|
|
@@ -1092,8 +1331,8 @@ function Modal({ onDismiss, children, closeLabel = "Close" }) {
|
|
|
1092
1331
|
onClick: (e) => {
|
|
1093
1332
|
if (e.target === e.currentTarget) onDismiss();
|
|
1094
1333
|
},
|
|
1095
|
-
children: /* @__PURE__ */
|
|
1096
|
-
/* @__PURE__ */
|
|
1334
|
+
children: /* @__PURE__ */ jsxs5("div", { ref: modalRef, class: "modal", role: "dialog", "aria-modal": "true", children: [
|
|
1335
|
+
/* @__PURE__ */ jsx6(
|
|
1097
1336
|
"button",
|
|
1098
1337
|
{
|
|
1099
1338
|
type: "button",
|
|
@@ -1109,6 +1348,177 @@ function Modal({ onDismiss, children, closeLabel = "Close" }) {
|
|
|
1109
1348
|
);
|
|
1110
1349
|
}
|
|
1111
1350
|
|
|
1351
|
+
// src/widget/ReportDetailView.tsx
|
|
1352
|
+
import { useEffect as useEffect5, useRef as useRef5, useState as useState4 } from "preact/hooks";
|
|
1353
|
+
|
|
1354
|
+
// src/widget/CommentBubble.tsx
|
|
1355
|
+
import { jsx as jsx7, jsxs as jsxs6 } from "preact/jsx-runtime";
|
|
1356
|
+
function CommentBubble({ comment, strings }) {
|
|
1357
|
+
const isMine = comment.is_mine;
|
|
1358
|
+
const isAgent = !isMine && comment.author_source === "mcp";
|
|
1359
|
+
const isSystem = !isMine && comment.author_source === "system";
|
|
1360
|
+
const variant = isAgent ? "mcp" : isSystem ? "system" : "staff";
|
|
1361
|
+
const labelKey = variant === "mcp" ? "detail.author.mcp" : variant === "system" ? "detail.author.system" : "detail.author.staff";
|
|
1362
|
+
const label = comment.author_label || strings[labelKey];
|
|
1363
|
+
return /* @__PURE__ */ jsxs6("div", { class: `comment-bubble ${isMine ? "is-mine" : "is-other"}`, children: [
|
|
1364
|
+
!isMine && label && /* @__PURE__ */ jsx7("div", { class: `comment-author comment-author--${variant}`, children: label }),
|
|
1365
|
+
/* @__PURE__ */ jsx7("div", { class: "comment-body", children: comment.body }),
|
|
1366
|
+
/* @__PURE__ */ jsx7("div", { class: "comment-time", children: formatTime(comment.created_at) })
|
|
1367
|
+
] });
|
|
1368
|
+
}
|
|
1369
|
+
function formatTime(iso) {
|
|
1370
|
+
try {
|
|
1371
|
+
return new Date(iso).toLocaleString(void 0, {
|
|
1372
|
+
dateStyle: "short",
|
|
1373
|
+
timeStyle: "short"
|
|
1374
|
+
});
|
|
1375
|
+
} catch {
|
|
1376
|
+
return iso;
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
// src/widget/ReportDetailView.tsx
|
|
1381
|
+
import { jsx as jsx8, jsxs as jsxs7 } from "preact/jsx-runtime";
|
|
1382
|
+
var POLL_MS2 = 3e4;
|
|
1383
|
+
function ReportDetailView({
|
|
1384
|
+
api,
|
|
1385
|
+
externalId,
|
|
1386
|
+
reportId,
|
|
1387
|
+
strings,
|
|
1388
|
+
onBack
|
|
1389
|
+
}) {
|
|
1390
|
+
const [detail, setDetail] = useState4(null);
|
|
1391
|
+
const [error, setError] = useState4(null);
|
|
1392
|
+
const [composeBody, setComposeBody] = useState4("");
|
|
1393
|
+
const [sending, setSending] = useState4(false);
|
|
1394
|
+
const [closing, setClosing] = useState4(false);
|
|
1395
|
+
const mountedRef = useRef5(true);
|
|
1396
|
+
const fetchDetail = async () => {
|
|
1397
|
+
try {
|
|
1398
|
+
const next = await api.getReport(reportId, externalId);
|
|
1399
|
+
if (!mountedRef.current) return;
|
|
1400
|
+
setDetail(next);
|
|
1401
|
+
setError(null);
|
|
1402
|
+
} catch (err) {
|
|
1403
|
+
if (!mountedRef.current) return;
|
|
1404
|
+
setError(err instanceof Error ? err.message : "load_failed");
|
|
1405
|
+
}
|
|
1406
|
+
};
|
|
1407
|
+
useEffect5(() => {
|
|
1408
|
+
mountedRef.current = true;
|
|
1409
|
+
void fetchDetail();
|
|
1410
|
+
const timer = setInterval(() => {
|
|
1411
|
+
void fetchDetail();
|
|
1412
|
+
}, POLL_MS2);
|
|
1413
|
+
return () => {
|
|
1414
|
+
mountedRef.current = false;
|
|
1415
|
+
clearInterval(timer);
|
|
1416
|
+
};
|
|
1417
|
+
}, [reportId, externalId]);
|
|
1418
|
+
const handleSend = async () => {
|
|
1419
|
+
if (!composeBody.trim() || sending) return;
|
|
1420
|
+
setSending(true);
|
|
1421
|
+
try {
|
|
1422
|
+
const nonce = `${reportId}:${Date.now()}`;
|
|
1423
|
+
const created = await api.addComment(reportId, externalId, composeBody.trim(), nonce);
|
|
1424
|
+
if (!mountedRef.current) return;
|
|
1425
|
+
setComposeBody("");
|
|
1426
|
+
setDetail(
|
|
1427
|
+
(prev) => prev ? { ...prev, comments: appendComment(prev.comments, created) } : prev
|
|
1428
|
+
);
|
|
1429
|
+
void fetchDetail();
|
|
1430
|
+
} catch (err) {
|
|
1431
|
+
if (!mountedRef.current) return;
|
|
1432
|
+
setError(err instanceof Error ? err.message : "comment_failed");
|
|
1433
|
+
} finally {
|
|
1434
|
+
if (mountedRef.current) setSending(false);
|
|
1435
|
+
}
|
|
1436
|
+
};
|
|
1437
|
+
const handleClose = async () => {
|
|
1438
|
+
if (closing) return;
|
|
1439
|
+
setClosing(true);
|
|
1440
|
+
try {
|
|
1441
|
+
const next = await api.closeAsResolved(reportId, externalId);
|
|
1442
|
+
if (!mountedRef.current) return;
|
|
1443
|
+
setDetail(next);
|
|
1444
|
+
} catch (err) {
|
|
1445
|
+
if (!mountedRef.current) return;
|
|
1446
|
+
setError(err instanceof Error ? err.message : "close_failed");
|
|
1447
|
+
} finally {
|
|
1448
|
+
if (mountedRef.current) setClosing(false);
|
|
1449
|
+
}
|
|
1450
|
+
};
|
|
1451
|
+
if (!detail && !error) {
|
|
1452
|
+
return /* @__PURE__ */ jsx8("div", { class: "mine-loading", children: strings["mine.loading"] });
|
|
1453
|
+
}
|
|
1454
|
+
if (!detail) {
|
|
1455
|
+
return /* @__PURE__ */ jsx8("div", { class: "error", role: "alert", children: error });
|
|
1456
|
+
}
|
|
1457
|
+
const showCloseCta = detail.status === "awaiting_validation";
|
|
1458
|
+
return /* @__PURE__ */ jsxs7("div", { class: "report-detail", children: [
|
|
1459
|
+
/* @__PURE__ */ jsxs7("div", { class: "report-detail-header", children: [
|
|
1460
|
+
/* @__PURE__ */ jsxs7("button", { type: "button", class: "btn", onClick: onBack, children: [
|
|
1461
|
+
"\u2190 ",
|
|
1462
|
+
strings["detail.back"]
|
|
1463
|
+
] }),
|
|
1464
|
+
/* @__PURE__ */ jsx8("span", { class: `pill pill-status pill-status--${detail.status}`, children: strings[`status.${detail.status}`] ?? detail.status })
|
|
1465
|
+
] }),
|
|
1466
|
+
/* @__PURE__ */ jsxs7("div", { class: "report-detail-body", children: [
|
|
1467
|
+
/* @__PURE__ */ jsx8("p", { class: "report-detail-description", children: detail.description }),
|
|
1468
|
+
detail.screenshot_url && /* @__PURE__ */ jsx8(
|
|
1469
|
+
"a",
|
|
1470
|
+
{
|
|
1471
|
+
class: "report-detail-screenshot",
|
|
1472
|
+
href: detail.screenshot_url,
|
|
1473
|
+
target: "_blank",
|
|
1474
|
+
rel: "noopener noreferrer",
|
|
1475
|
+
children: /* @__PURE__ */ jsx8("img", { src: detail.screenshot_url, alt: "", loading: "lazy" })
|
|
1476
|
+
}
|
|
1477
|
+
),
|
|
1478
|
+
/* @__PURE__ */ jsx8("h3", { class: "report-detail-section", children: strings["detail.thread"] }),
|
|
1479
|
+
detail.comments.length === 0 ? /* @__PURE__ */ jsx8("p", { class: "report-detail-empty", children: strings["detail.no_replies"] }) : /* @__PURE__ */ jsx8("ul", { class: "report-comments", children: detail.comments.map((c) => /* @__PURE__ */ jsx8("li", { children: /* @__PURE__ */ jsx8(CommentBubble, { comment: c, strings }) })) }),
|
|
1480
|
+
/* @__PURE__ */ jsxs7("div", { class: "report-compose", children: [
|
|
1481
|
+
/* @__PURE__ */ jsx8(
|
|
1482
|
+
"textarea",
|
|
1483
|
+
{
|
|
1484
|
+
value: composeBody,
|
|
1485
|
+
placeholder: strings["detail.compose_placeholder"],
|
|
1486
|
+
onInput: (e) => setComposeBody(e.target.value),
|
|
1487
|
+
disabled: sending
|
|
1488
|
+
}
|
|
1489
|
+
),
|
|
1490
|
+
/* @__PURE__ */ jsxs7("div", { class: "report-compose-actions", children: [
|
|
1491
|
+
showCloseCta && /* @__PURE__ */ jsx8(
|
|
1492
|
+
"button",
|
|
1493
|
+
{
|
|
1494
|
+
type: "button",
|
|
1495
|
+
class: "btn",
|
|
1496
|
+
onClick: handleClose,
|
|
1497
|
+
disabled: closing,
|
|
1498
|
+
children: closing ? strings["detail.close_busy"] : strings["detail.close_cta"]
|
|
1499
|
+
}
|
|
1500
|
+
),
|
|
1501
|
+
/* @__PURE__ */ jsx8(
|
|
1502
|
+
"button",
|
|
1503
|
+
{
|
|
1504
|
+
type: "button",
|
|
1505
|
+
class: "btn btn--primary",
|
|
1506
|
+
onClick: handleSend,
|
|
1507
|
+
disabled: !composeBody.trim() || sending,
|
|
1508
|
+
children: sending ? strings["detail.compose_sending"] : strings["detail.compose_send"]
|
|
1509
|
+
}
|
|
1510
|
+
)
|
|
1511
|
+
] })
|
|
1512
|
+
] }),
|
|
1513
|
+
error && /* @__PURE__ */ jsx8("div", { class: "error", children: error })
|
|
1514
|
+
] })
|
|
1515
|
+
] });
|
|
1516
|
+
}
|
|
1517
|
+
function appendComment(current, next) {
|
|
1518
|
+
if (current.some((c) => c.id === next.id)) return current;
|
|
1519
|
+
return [...current, next];
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1112
1522
|
// src/widget/styles.ts
|
|
1113
1523
|
var WIDGET_STYLES = `
|
|
1114
1524
|
:host {
|
|
@@ -1494,10 +1904,232 @@ var WIDGET_STYLES = `
|
|
|
1494
1904
|
padding: 10px 14px;
|
|
1495
1905
|
border-top: 1px solid var(--mfb-border);
|
|
1496
1906
|
}
|
|
1907
|
+
|
|
1908
|
+
/* ---- v0.7: tabs + reader UI -------------------------------------- */
|
|
1909
|
+
|
|
1910
|
+
.tab-strip {
|
|
1911
|
+
display: flex;
|
|
1912
|
+
gap: 4px;
|
|
1913
|
+
border-bottom: 1px solid var(--mfb-border);
|
|
1914
|
+
margin: 0 -4px 4px;
|
|
1915
|
+
padding: 0 4px;
|
|
1916
|
+
}
|
|
1917
|
+
.tab-button {
|
|
1918
|
+
appearance: none;
|
|
1919
|
+
background: transparent;
|
|
1920
|
+
border: 0;
|
|
1921
|
+
border-bottom: 2px solid transparent;
|
|
1922
|
+
padding: 8px 12px;
|
|
1923
|
+
font: inherit;
|
|
1924
|
+
font-size: 13px;
|
|
1925
|
+
font-weight: 500;
|
|
1926
|
+
color: var(--mfb-text-muted);
|
|
1927
|
+
cursor: pointer;
|
|
1928
|
+
}
|
|
1929
|
+
.tab-button:hover { color: var(--mfb-text); }
|
|
1930
|
+
.tab-button.is-active {
|
|
1931
|
+
color: var(--mfb-text);
|
|
1932
|
+
border-bottom-color: var(--mfb-accent);
|
|
1933
|
+
}
|
|
1934
|
+
.tab-button[aria-selected="true"] { font-weight: 600; }
|
|
1935
|
+
|
|
1936
|
+
.mine-list { display: flex; flex-direction: column; gap: 10px; }
|
|
1937
|
+
.mine-list-header {
|
|
1938
|
+
display: flex;
|
|
1939
|
+
align-items: center;
|
|
1940
|
+
justify-content: space-between;
|
|
1941
|
+
}
|
|
1942
|
+
.mine-list-header h2 { margin: 0; font-size: 15px; font-weight: 600; }
|
|
1943
|
+
.mine-loading { color: var(--mfb-text-muted); font-size: 13px; }
|
|
1944
|
+
.mine-empty {
|
|
1945
|
+
text-align: center;
|
|
1946
|
+
padding: 24px 12px;
|
|
1947
|
+
color: var(--mfb-text-muted);
|
|
1948
|
+
font-size: 13px;
|
|
1949
|
+
}
|
|
1950
|
+
.mine-empty strong { display: block; color: var(--mfb-text); margin-bottom: 4px; }
|
|
1951
|
+
|
|
1952
|
+
.mine-rows {
|
|
1953
|
+
list-style: none;
|
|
1954
|
+
margin: 0;
|
|
1955
|
+
padding: 0;
|
|
1956
|
+
display: flex;
|
|
1957
|
+
flex-direction: column;
|
|
1958
|
+
gap: 6px;
|
|
1959
|
+
max-height: 380px;
|
|
1960
|
+
overflow-y: auto;
|
|
1961
|
+
}
|
|
1962
|
+
.mine-row {
|
|
1963
|
+
appearance: none;
|
|
1964
|
+
text-align: left;
|
|
1965
|
+
background: var(--mfb-surface);
|
|
1966
|
+
border: 1px solid var(--mfb-border);
|
|
1967
|
+
border-radius: var(--mfb-radius);
|
|
1968
|
+
padding: 10px 12px;
|
|
1969
|
+
font: inherit;
|
|
1970
|
+
color: inherit;
|
|
1971
|
+
cursor: pointer;
|
|
1972
|
+
display: flex;
|
|
1973
|
+
flex-direction: column;
|
|
1974
|
+
gap: 4px;
|
|
1975
|
+
width: 100%;
|
|
1976
|
+
transition: border-color 120ms ease, background 120ms ease;
|
|
1977
|
+
}
|
|
1978
|
+
.mine-row:hover { border-color: var(--mfb-text-muted); }
|
|
1979
|
+
.mine-row:focus-visible {
|
|
1980
|
+
outline: 2px solid var(--mfb-accent);
|
|
1981
|
+
outline-offset: 2px;
|
|
1982
|
+
}
|
|
1983
|
+
.mine-row-pills { display: flex; gap: 4px; flex-wrap: wrap; }
|
|
1984
|
+
.mine-row-preview {
|
|
1985
|
+
font-size: 13px;
|
|
1986
|
+
color: var(--mfb-text);
|
|
1987
|
+
display: -webkit-box;
|
|
1988
|
+
-webkit-line-clamp: 2;
|
|
1989
|
+
-webkit-box-orient: vertical;
|
|
1990
|
+
overflow: hidden;
|
|
1991
|
+
}
|
|
1992
|
+
.mine-row-meta {
|
|
1993
|
+
display: flex;
|
|
1994
|
+
gap: 6px;
|
|
1995
|
+
font-size: 11px;
|
|
1996
|
+
color: var(--mfb-text-muted);
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
.pill {
|
|
2000
|
+
display: inline-block;
|
|
2001
|
+
font-size: 10px;
|
|
2002
|
+
font-weight: 600;
|
|
2003
|
+
text-transform: uppercase;
|
|
2004
|
+
letter-spacing: 0.04em;
|
|
2005
|
+
padding: 2px 8px;
|
|
2006
|
+
border-radius: 999px;
|
|
2007
|
+
border: 1px solid transparent;
|
|
2008
|
+
}
|
|
2009
|
+
.pill-status { background: #eff6ff; color: #1e40af; border-color: #dbeafe; }
|
|
2010
|
+
.pill-status--in_progress { background: #fffbeb; color: #92400e; border-color: #fde68a; }
|
|
2011
|
+
.pill-status--awaiting_validation { background: #faf5ff; color: #6b21a8; border-color: #e9d5ff; }
|
|
2012
|
+
.pill-status--closed { background: #ecfdf5; color: #065f46; border-color: #a7f3d0; }
|
|
2013
|
+
.pill-status--rejected, .pill-status--wontfix, .pill-status--duplicate { background: #f3f4f6; color: #374151; border-color: #e5e7eb; }
|
|
2014
|
+
.pill-type { background: var(--mfb-surface); color: var(--mfb-text-muted); border-color: var(--mfb-border); }
|
|
2015
|
+
.pill-severity { background: var(--mfb-surface); color: var(--mfb-text-muted); border-color: var(--mfb-border); }
|
|
2016
|
+
.pill-severity--blocker { background: #fef2f2; color: #991b1b; border-color: #fecaca; }
|
|
2017
|
+
.pill-severity--high { background: #fff7ed; color: #9a3412; border-color: #fed7aa; }
|
|
2018
|
+
.pill-severity--medium { background: #fefce8; color: #854d0e; border-color: #fef08a; }
|
|
2019
|
+
.pill-severity--low { background: var(--mfb-surface); color: var(--mfb-text-muted); }
|
|
2020
|
+
|
|
2021
|
+
.report-detail { display: flex; flex-direction: column; gap: 12px; }
|
|
2022
|
+
.report-detail-header {
|
|
2023
|
+
display: flex;
|
|
2024
|
+
align-items: center;
|
|
2025
|
+
justify-content: space-between;
|
|
2026
|
+
gap: 8px;
|
|
2027
|
+
}
|
|
2028
|
+
.report-detail-body { display: flex; flex-direction: column; gap: 10px; }
|
|
2029
|
+
.report-detail-description {
|
|
2030
|
+
font-size: 14px;
|
|
2031
|
+
white-space: pre-wrap;
|
|
2032
|
+
margin: 0;
|
|
2033
|
+
}
|
|
2034
|
+
.report-detail-screenshot {
|
|
2035
|
+
display: block;
|
|
2036
|
+
border: 1px solid var(--mfb-border);
|
|
2037
|
+
border-radius: var(--mfb-radius);
|
|
2038
|
+
overflow: hidden;
|
|
2039
|
+
background: #1a1a1a;
|
|
2040
|
+
}
|
|
2041
|
+
.report-detail-screenshot img {
|
|
2042
|
+
display: block;
|
|
2043
|
+
width: 100%;
|
|
2044
|
+
max-height: 200px;
|
|
2045
|
+
object-fit: contain;
|
|
2046
|
+
}
|
|
2047
|
+
.report-detail-section {
|
|
2048
|
+
font-size: 12px;
|
|
2049
|
+
text-transform: uppercase;
|
|
2050
|
+
letter-spacing: 0.04em;
|
|
2051
|
+
color: var(--mfb-text-muted);
|
|
2052
|
+
margin: 8px 0 4px;
|
|
2053
|
+
font-weight: 600;
|
|
2054
|
+
}
|
|
2055
|
+
.report-detail-empty {
|
|
2056
|
+
font-size: 12px;
|
|
2057
|
+
color: var(--mfb-text-muted);
|
|
2058
|
+
margin: 0;
|
|
2059
|
+
}
|
|
2060
|
+
|
|
2061
|
+
.report-comments {
|
|
2062
|
+
list-style: none;
|
|
2063
|
+
margin: 0;
|
|
2064
|
+
padding: 0;
|
|
2065
|
+
display: flex;
|
|
2066
|
+
flex-direction: column;
|
|
2067
|
+
gap: 6px;
|
|
2068
|
+
max-height: 300px;
|
|
2069
|
+
overflow-y: auto;
|
|
2070
|
+
}
|
|
2071
|
+
|
|
2072
|
+
.comment-bubble {
|
|
2073
|
+
padding: 8px 10px;
|
|
2074
|
+
border-radius: 12px;
|
|
2075
|
+
max-width: 88%;
|
|
2076
|
+
font-size: 13px;
|
|
2077
|
+
line-height: 1.4;
|
|
2078
|
+
}
|
|
2079
|
+
.comment-bubble.is-mine {
|
|
2080
|
+
align-self: flex-end;
|
|
2081
|
+
background: var(--mfb-accent);
|
|
2082
|
+
color: var(--mfb-accent-contrast);
|
|
2083
|
+
}
|
|
2084
|
+
.comment-bubble.is-other {
|
|
2085
|
+
align-self: flex-start;
|
|
2086
|
+
background: var(--mfb-surface);
|
|
2087
|
+
border: 1px solid var(--mfb-border);
|
|
2088
|
+
color: var(--mfb-text);
|
|
2089
|
+
}
|
|
2090
|
+
.comment-author {
|
|
2091
|
+
font-size: 10px;
|
|
2092
|
+
text-transform: uppercase;
|
|
2093
|
+
letter-spacing: 0.04em;
|
|
2094
|
+
font-weight: 600;
|
|
2095
|
+
margin-bottom: 2px;
|
|
2096
|
+
}
|
|
2097
|
+
.comment-author--mcp { color: #6b21a8; }
|
|
2098
|
+
.comment-author--staff { color: #1e40af; }
|
|
2099
|
+
.comment-author--system { color: var(--mfb-text-muted); }
|
|
2100
|
+
.comment-body { white-space: pre-wrap; }
|
|
2101
|
+
.comment-time {
|
|
2102
|
+
font-size: 10px;
|
|
2103
|
+
margin-top: 4px;
|
|
2104
|
+
opacity: 0.7;
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
.report-compose {
|
|
2108
|
+
display: flex;
|
|
2109
|
+
flex-direction: column;
|
|
2110
|
+
gap: 6px;
|
|
2111
|
+
margin-top: 4px;
|
|
2112
|
+
}
|
|
2113
|
+
.report-compose textarea {
|
|
2114
|
+
font: inherit;
|
|
2115
|
+
font-size: 13px;
|
|
2116
|
+
padding: 8px 10px;
|
|
2117
|
+
border: 1px solid var(--mfb-border);
|
|
2118
|
+
border-radius: var(--mfb-radius);
|
|
2119
|
+
background: var(--mfb-surface);
|
|
2120
|
+
color: inherit;
|
|
2121
|
+
min-height: 64px;
|
|
2122
|
+
resize: vertical;
|
|
2123
|
+
}
|
|
2124
|
+
.report-compose-actions {
|
|
2125
|
+
display: flex;
|
|
2126
|
+
justify-content: flex-end;
|
|
2127
|
+
gap: 6px;
|
|
2128
|
+
}
|
|
1497
2129
|
`;
|
|
1498
2130
|
|
|
1499
2131
|
// src/widget/mount.tsx
|
|
1500
|
-
import { Fragment, jsx as
|
|
2132
|
+
import { Fragment, jsx as jsx9, jsxs as jsxs8 } from "preact/jsx-runtime";
|
|
1501
2133
|
function mountWidget(options) {
|
|
1502
2134
|
const shadow = options.host.attachShadow({ mode: "open" });
|
|
1503
2135
|
const style = document.createElement("style");
|
|
@@ -1505,45 +2137,113 @@ function mountWidget(options) {
|
|
|
1505
2137
|
shadow.appendChild(style);
|
|
1506
2138
|
const mountPoint = document.createElement("div");
|
|
1507
2139
|
shadow.appendChild(mountPoint);
|
|
1508
|
-
let currentState = { open: false, status: "idle" };
|
|
2140
|
+
let currentState = { open: false, status: "idle", tab: "send" };
|
|
1509
2141
|
function rerender(state) {
|
|
1510
2142
|
currentState = state;
|
|
1511
2143
|
render(h(Root, { state }), mountPoint);
|
|
1512
2144
|
}
|
|
2145
|
+
function clearSelected(s) {
|
|
2146
|
+
const { selectedReportId: _drop, ...rest } = s;
|
|
2147
|
+
void _drop;
|
|
2148
|
+
return rest;
|
|
2149
|
+
}
|
|
1513
2150
|
function Root({ state }) {
|
|
1514
2151
|
const handleSubmit = useCallback(async (values) => {
|
|
1515
|
-
rerender({
|
|
2152
|
+
rerender({ ...currentState, status: "submitting" });
|
|
1516
2153
|
try {
|
|
1517
2154
|
await options.onSubmit(values);
|
|
1518
|
-
rerender({
|
|
1519
|
-
setTimeout(
|
|
2155
|
+
rerender({ ...currentState, status: "success" });
|
|
2156
|
+
setTimeout(
|
|
2157
|
+
() => rerender({
|
|
2158
|
+
...currentState,
|
|
2159
|
+
open: false,
|
|
2160
|
+
status: "idle",
|
|
2161
|
+
// After a successful submit, jump the user to "My reports" so
|
|
2162
|
+
// they immediately see their just-filed report in the list
|
|
2163
|
+
// (and the conversation that's about to start).
|
|
2164
|
+
tab: options.api ? "mine" : "send"
|
|
2165
|
+
}),
|
|
2166
|
+
1200
|
|
2167
|
+
);
|
|
1520
2168
|
} catch (err) {
|
|
1521
|
-
rerender({
|
|
2169
|
+
rerender({
|
|
2170
|
+
...currentState,
|
|
2171
|
+
status: "error",
|
|
2172
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2173
|
+
});
|
|
1522
2174
|
}
|
|
1523
2175
|
}, []);
|
|
1524
|
-
|
|
1525
|
-
|
|
2176
|
+
const externalId = options.getExternalId?.();
|
|
2177
|
+
const fabVisible = options.showFAB && (options.getExternalId === void 0 ? true : Boolean(externalId));
|
|
2178
|
+
const showMineTab = Boolean(options.api && externalId);
|
|
2179
|
+
return /* @__PURE__ */ jsxs8(Fragment, { children: [
|
|
2180
|
+
fabVisible && /* @__PURE__ */ jsx9(
|
|
1526
2181
|
Fab,
|
|
1527
2182
|
{
|
|
1528
2183
|
label: options.strings["fab.label"],
|
|
1529
2184
|
onClick: () => rerender({ ...currentState, open: true })
|
|
1530
2185
|
}
|
|
1531
2186
|
),
|
|
1532
|
-
state.open && /* @__PURE__ */
|
|
2187
|
+
state.open && /* @__PURE__ */ jsxs8(
|
|
1533
2188
|
Modal,
|
|
1534
2189
|
{
|
|
1535
|
-
onDismiss: () => rerender({ open: false, status: "idle" }),
|
|
2190
|
+
onDismiss: () => rerender(clearSelected({ ...currentState, open: false, status: "idle" })),
|
|
1536
2191
|
closeLabel: options.strings["form.close"],
|
|
1537
|
-
children:
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
2192
|
+
children: [
|
|
2193
|
+
showMineTab && /* @__PURE__ */ jsxs8("div", { class: "tab-strip", role: "tablist", children: [
|
|
2194
|
+
/* @__PURE__ */ jsx9(
|
|
2195
|
+
"button",
|
|
2196
|
+
{
|
|
2197
|
+
type: "button",
|
|
2198
|
+
role: "tab",
|
|
2199
|
+
"aria-selected": state.tab === "send",
|
|
2200
|
+
class: `tab-button ${state.tab === "send" ? "is-active" : ""}`,
|
|
2201
|
+
onClick: () => rerender(clearSelected({ ...currentState, tab: "send" })),
|
|
2202
|
+
children: options.strings["tab.send"]
|
|
2203
|
+
}
|
|
2204
|
+
),
|
|
2205
|
+
/* @__PURE__ */ jsx9(
|
|
2206
|
+
"button",
|
|
2207
|
+
{
|
|
2208
|
+
type: "button",
|
|
2209
|
+
role: "tab",
|
|
2210
|
+
"aria-selected": state.tab === "mine",
|
|
2211
|
+
class: `tab-button ${state.tab === "mine" ? "is-active" : ""}`,
|
|
2212
|
+
onClick: () => rerender(clearSelected({ ...currentState, tab: "mine" })),
|
|
2213
|
+
children: options.strings["tab.mine"]
|
|
2214
|
+
}
|
|
2215
|
+
)
|
|
2216
|
+
] }),
|
|
2217
|
+
state.tab === "send" && /* @__PURE__ */ jsx9(
|
|
2218
|
+
Form,
|
|
2219
|
+
{
|
|
2220
|
+
strings: options.strings,
|
|
2221
|
+
onSubmit: handleSubmit,
|
|
2222
|
+
onCancel: () => rerender({ ...currentState, open: false, status: "idle" }),
|
|
2223
|
+
status: state.status,
|
|
2224
|
+
...state.error !== void 0 && { errorMessage: state.error }
|
|
2225
|
+
}
|
|
2226
|
+
),
|
|
2227
|
+
state.tab === "mine" && options.api && externalId && !state.selectedReportId && /* @__PURE__ */ jsx9(
|
|
2228
|
+
MineList,
|
|
2229
|
+
{
|
|
2230
|
+
api: options.api,
|
|
2231
|
+
externalId,
|
|
2232
|
+
strings: options.strings,
|
|
2233
|
+
onSelect: (row) => rerender({ ...currentState, selectedReportId: row.id })
|
|
2234
|
+
}
|
|
2235
|
+
),
|
|
2236
|
+
state.tab === "mine" && options.api && externalId && state.selectedReportId && /* @__PURE__ */ jsx9(
|
|
2237
|
+
ReportDetailView,
|
|
2238
|
+
{
|
|
2239
|
+
api: options.api,
|
|
2240
|
+
externalId,
|
|
2241
|
+
reportId: state.selectedReportId,
|
|
2242
|
+
strings: options.strings,
|
|
2243
|
+
onBack: () => rerender(clearSelected({ ...currentState }))
|
|
2244
|
+
}
|
|
2245
|
+
)
|
|
2246
|
+
]
|
|
1547
2247
|
}
|
|
1548
2248
|
)
|
|
1549
2249
|
] });
|
|
@@ -1559,6 +2259,9 @@ function mountWidget(options) {
|
|
|
1559
2259
|
dispose() {
|
|
1560
2260
|
render(null, mountPoint);
|
|
1561
2261
|
options.host.innerHTML = "";
|
|
2262
|
+
},
|
|
2263
|
+
notifyIdentityChanged() {
|
|
2264
|
+
rerender({ ...currentState });
|
|
1562
2265
|
}
|
|
1563
2266
|
};
|
|
1564
2267
|
}
|
|
@@ -1613,6 +2316,15 @@ function createFeedback(config) {
|
|
|
1613
2316
|
};
|
|
1614
2317
|
if (screenshot) payload.screenshot = screenshot;
|
|
1615
2318
|
if (values.synthetic) payload.synthetic = true;
|
|
2319
|
+
if (user?.id !== void 0 && user.id !== null && user.id !== "") {
|
|
2320
|
+
payload.user = {
|
|
2321
|
+
// The host can pass `id` as a string or number; the backend
|
|
2322
|
+
// stores it as an opaque string. Coerce here to a stable shape.
|
|
2323
|
+
id: String(user.id),
|
|
2324
|
+
...user.email !== void 0 && { email: user.email },
|
|
2325
|
+
...user.name !== void 0 && { name: user.name }
|
|
2326
|
+
};
|
|
2327
|
+
}
|
|
1616
2328
|
let finalPayload = payload;
|
|
1617
2329
|
for (const t of transformers) finalPayload = await t(finalPayload);
|
|
1618
2330
|
try {
|
|
@@ -1632,7 +2344,12 @@ function createFeedback(config) {
|
|
|
1632
2344
|
showFAB: config.showFAB ?? true,
|
|
1633
2345
|
onSubmit: async (values) => {
|
|
1634
2346
|
await buildAndSubmit(values);
|
|
1635
|
-
}
|
|
2347
|
+
},
|
|
2348
|
+
api,
|
|
2349
|
+
// Keep this a callback (not a snapshot) so the mount picks up identity
|
|
2350
|
+
// changes that happen after createFeedback() — `notifyIdentityChanged()`
|
|
2351
|
+
// is the trigger for a re-render.
|
|
2352
|
+
getExternalId: () => user?.id !== void 0 && user.id !== null && user.id !== "" ? String(user.id) : void 0
|
|
1636
2353
|
});
|
|
1637
2354
|
const instance = {
|
|
1638
2355
|
show() {
|
|
@@ -1656,6 +2373,7 @@ function createFeedback(config) {
|
|
|
1656
2373
|
},
|
|
1657
2374
|
identify(u) {
|
|
1658
2375
|
user = u;
|
|
2376
|
+
handle.notifyIdentityChanged();
|
|
1659
2377
|
},
|
|
1660
2378
|
setMetadata(kv) {
|
|
1661
2379
|
metadata = { ...metadata, ...kv };
|
|
@@ -1680,4 +2398,4 @@ function createFeedback(config) {
|
|
|
1680
2398
|
export {
|
|
1681
2399
|
createFeedback
|
|
1682
2400
|
};
|
|
1683
|
-
//# sourceMappingURL=chunk-
|
|
2401
|
+
//# sourceMappingURL=chunk-W6JAJT2U.mjs.map
|