@mhosaic/feedback 0.6.3 → 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-3RIR3JHF.mjs → chunk-W6JAJT2U.mjs} +749 -33
- 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-3RIR3JHF.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
|
|
@@ -396,7 +467,35 @@ var DEFAULT_STRINGS = {
|
|
|
396
467
|
"annotator.count_suffix": "annotations",
|
|
397
468
|
"annotator.loading": "Loading\u2026",
|
|
398
469
|
"annotator.apply": "Apply",
|
|
399
|
-
"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"
|
|
400
499
|
};
|
|
401
500
|
var FRENCH_STRINGS = {
|
|
402
501
|
"fab.label": "Envoyer un commentaire",
|
|
@@ -441,7 +540,35 @@ var FRENCH_STRINGS = {
|
|
|
441
540
|
"annotator.count_suffix": "annotations",
|
|
442
541
|
"annotator.loading": "Chargement\u2026",
|
|
443
542
|
"annotator.apply": "Appliquer",
|
|
444
|
-
"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"
|
|
445
572
|
};
|
|
446
573
|
var LOCALE_PACKS = {
|
|
447
574
|
fr: FRENCH_STRINGS
|
|
@@ -1058,13 +1185,123 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
|
|
|
1058
1185
|
] });
|
|
1059
1186
|
}
|
|
1060
1187
|
|
|
1061
|
-
// src/widget/
|
|
1062
|
-
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
|
|
1063
1192
|
import { jsx as jsx4, jsxs as jsxs3 } from "preact/jsx-runtime";
|
|
1064
|
-
function
|
|
1065
|
-
|
|
1066
|
-
|
|
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
|
+
};
|
|
1067
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(() => {
|
|
1068
1305
|
previouslyFocused.current = document.activeElement;
|
|
1069
1306
|
const onKey = (e) => {
|
|
1070
1307
|
if (e.key !== "Escape") return;
|
|
@@ -1086,7 +1323,7 @@ function Modal({ onDismiss, children, closeLabel = "Close" }) {
|
|
|
1086
1323
|
if (prev && typeof prev.focus === "function") prev.focus();
|
|
1087
1324
|
};
|
|
1088
1325
|
}, [onDismiss]);
|
|
1089
|
-
return /* @__PURE__ */
|
|
1326
|
+
return /* @__PURE__ */ jsx6(
|
|
1090
1327
|
"div",
|
|
1091
1328
|
{
|
|
1092
1329
|
class: "backdrop",
|
|
@@ -1094,8 +1331,8 @@ function Modal({ onDismiss, children, closeLabel = "Close" }) {
|
|
|
1094
1331
|
onClick: (e) => {
|
|
1095
1332
|
if (e.target === e.currentTarget) onDismiss();
|
|
1096
1333
|
},
|
|
1097
|
-
children: /* @__PURE__ */
|
|
1098
|
-
/* @__PURE__ */
|
|
1334
|
+
children: /* @__PURE__ */ jsxs5("div", { ref: modalRef, class: "modal", role: "dialog", "aria-modal": "true", children: [
|
|
1335
|
+
/* @__PURE__ */ jsx6(
|
|
1099
1336
|
"button",
|
|
1100
1337
|
{
|
|
1101
1338
|
type: "button",
|
|
@@ -1111,6 +1348,177 @@ function Modal({ onDismiss, children, closeLabel = "Close" }) {
|
|
|
1111
1348
|
);
|
|
1112
1349
|
}
|
|
1113
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
|
+
|
|
1114
1522
|
// src/widget/styles.ts
|
|
1115
1523
|
var WIDGET_STYLES = `
|
|
1116
1524
|
:host {
|
|
@@ -1496,10 +1904,232 @@ var WIDGET_STYLES = `
|
|
|
1496
1904
|
padding: 10px 14px;
|
|
1497
1905
|
border-top: 1px solid var(--mfb-border);
|
|
1498
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
|
+
}
|
|
1499
2129
|
`;
|
|
1500
2130
|
|
|
1501
2131
|
// src/widget/mount.tsx
|
|
1502
|
-
import { Fragment, jsx as
|
|
2132
|
+
import { Fragment, jsx as jsx9, jsxs as jsxs8 } from "preact/jsx-runtime";
|
|
1503
2133
|
function mountWidget(options) {
|
|
1504
2134
|
const shadow = options.host.attachShadow({ mode: "open" });
|
|
1505
2135
|
const style = document.createElement("style");
|
|
@@ -1507,45 +2137,113 @@ function mountWidget(options) {
|
|
|
1507
2137
|
shadow.appendChild(style);
|
|
1508
2138
|
const mountPoint = document.createElement("div");
|
|
1509
2139
|
shadow.appendChild(mountPoint);
|
|
1510
|
-
let currentState = { open: false, status: "idle" };
|
|
2140
|
+
let currentState = { open: false, status: "idle", tab: "send" };
|
|
1511
2141
|
function rerender(state) {
|
|
1512
2142
|
currentState = state;
|
|
1513
2143
|
render(h(Root, { state }), mountPoint);
|
|
1514
2144
|
}
|
|
2145
|
+
function clearSelected(s) {
|
|
2146
|
+
const { selectedReportId: _drop, ...rest } = s;
|
|
2147
|
+
void _drop;
|
|
2148
|
+
return rest;
|
|
2149
|
+
}
|
|
1515
2150
|
function Root({ state }) {
|
|
1516
2151
|
const handleSubmit = useCallback(async (values) => {
|
|
1517
|
-
rerender({
|
|
2152
|
+
rerender({ ...currentState, status: "submitting" });
|
|
1518
2153
|
try {
|
|
1519
2154
|
await options.onSubmit(values);
|
|
1520
|
-
rerender({
|
|
1521
|
-
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
|
+
);
|
|
1522
2168
|
} catch (err) {
|
|
1523
|
-
rerender({
|
|
2169
|
+
rerender({
|
|
2170
|
+
...currentState,
|
|
2171
|
+
status: "error",
|
|
2172
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2173
|
+
});
|
|
1524
2174
|
}
|
|
1525
2175
|
}, []);
|
|
1526
|
-
|
|
1527
|
-
|
|
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(
|
|
1528
2181
|
Fab,
|
|
1529
2182
|
{
|
|
1530
2183
|
label: options.strings["fab.label"],
|
|
1531
2184
|
onClick: () => rerender({ ...currentState, open: true })
|
|
1532
2185
|
}
|
|
1533
2186
|
),
|
|
1534
|
-
state.open && /* @__PURE__ */
|
|
2187
|
+
state.open && /* @__PURE__ */ jsxs8(
|
|
1535
2188
|
Modal,
|
|
1536
2189
|
{
|
|
1537
|
-
onDismiss: () => rerender({ open: false, status: "idle" }),
|
|
2190
|
+
onDismiss: () => rerender(clearSelected({ ...currentState, open: false, status: "idle" })),
|
|
1538
2191
|
closeLabel: options.strings["form.close"],
|
|
1539
|
-
children:
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
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
|
+
]
|
|
1549
2247
|
}
|
|
1550
2248
|
)
|
|
1551
2249
|
] });
|
|
@@ -1561,6 +2259,9 @@ function mountWidget(options) {
|
|
|
1561
2259
|
dispose() {
|
|
1562
2260
|
render(null, mountPoint);
|
|
1563
2261
|
options.host.innerHTML = "";
|
|
2262
|
+
},
|
|
2263
|
+
notifyIdentityChanged() {
|
|
2264
|
+
rerender({ ...currentState });
|
|
1564
2265
|
}
|
|
1565
2266
|
};
|
|
1566
2267
|
}
|
|
@@ -1615,6 +2316,15 @@ function createFeedback(config) {
|
|
|
1615
2316
|
};
|
|
1616
2317
|
if (screenshot) payload.screenshot = screenshot;
|
|
1617
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
|
+
}
|
|
1618
2328
|
let finalPayload = payload;
|
|
1619
2329
|
for (const t of transformers) finalPayload = await t(finalPayload);
|
|
1620
2330
|
try {
|
|
@@ -1634,7 +2344,12 @@ function createFeedback(config) {
|
|
|
1634
2344
|
showFAB: config.showFAB ?? true,
|
|
1635
2345
|
onSubmit: async (values) => {
|
|
1636
2346
|
await buildAndSubmit(values);
|
|
1637
|
-
}
|
|
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
|
|
1638
2353
|
});
|
|
1639
2354
|
const instance = {
|
|
1640
2355
|
show() {
|
|
@@ -1658,6 +2373,7 @@ function createFeedback(config) {
|
|
|
1658
2373
|
},
|
|
1659
2374
|
identify(u) {
|
|
1660
2375
|
user = u;
|
|
2376
|
+
handle.notifyIdentityChanged();
|
|
1661
2377
|
},
|
|
1662
2378
|
setMetadata(kv) {
|
|
1663
2379
|
metadata = { ...metadata, ...kv };
|
|
@@ -1682,4 +2398,4 @@ function createFeedback(config) {
|
|
|
1682
2398
|
export {
|
|
1683
2399
|
createFeedback
|
|
1684
2400
|
};
|
|
1685
|
-
//# sourceMappingURL=chunk-
|
|
2401
|
+
//# sourceMappingURL=chunk-W6JAJT2U.mjs.map
|