spaps-issue-reporting-react 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/README.md +69 -0
- package/dist/index.d.mts +343 -0
- package/dist/index.d.ts +343 -0
- package/dist/index.js +933 -0
- package/dist/index.mjs +894 -0
- package/package.json +75 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,894 @@
|
|
|
1
|
+
// src/components.tsx
|
|
2
|
+
import * as Dialog from "@radix-ui/react-dialog";
|
|
3
|
+
import * as Popover from "@radix-ui/react-popover";
|
|
4
|
+
import {
|
|
5
|
+
BugBeetle,
|
|
6
|
+
CheckCircle,
|
|
7
|
+
Circle,
|
|
8
|
+
Spinner,
|
|
9
|
+
X
|
|
10
|
+
} from "@phosphor-icons/react";
|
|
11
|
+
import { formatDistanceToNow } from "date-fns";
|
|
12
|
+
import { useEffect, useMemo as useMemo2, useState as useState2 } from "react";
|
|
13
|
+
|
|
14
|
+
// src/provider.tsx
|
|
15
|
+
import {
|
|
16
|
+
createContext as createContext2,
|
|
17
|
+
useCallback,
|
|
18
|
+
useContext as useContext2,
|
|
19
|
+
useMemo,
|
|
20
|
+
useState
|
|
21
|
+
} from "react";
|
|
22
|
+
import {
|
|
23
|
+
useMutation,
|
|
24
|
+
useQuery,
|
|
25
|
+
useQueryClient
|
|
26
|
+
} from "@tanstack/react-query";
|
|
27
|
+
|
|
28
|
+
// src/report-mode.ts
|
|
29
|
+
import { createContext, useContext } from "react";
|
|
30
|
+
var ReportModeContext = createContext(
|
|
31
|
+
null
|
|
32
|
+
);
|
|
33
|
+
function useReportMode() {
|
|
34
|
+
return useContext(ReportModeContext);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// src/provider.tsx
|
|
38
|
+
import { jsx } from "react/jsx-runtime";
|
|
39
|
+
var LIST_LIMIT = 200;
|
|
40
|
+
var NOTE_MIN_LENGTH = 10;
|
|
41
|
+
var NOTE_MAX_LENGTH = 2e3;
|
|
42
|
+
var initialModalState = {
|
|
43
|
+
isOpen: false,
|
|
44
|
+
mode: "create",
|
|
45
|
+
issueReportId: null,
|
|
46
|
+
issue: null,
|
|
47
|
+
target: null,
|
|
48
|
+
error: null,
|
|
49
|
+
isHydrating: false
|
|
50
|
+
};
|
|
51
|
+
var IssueReportingContext = createContext2(
|
|
52
|
+
void 0
|
|
53
|
+
);
|
|
54
|
+
var defaultIssueReportingCopy = {
|
|
55
|
+
entryAriaLabel: "Report issue",
|
|
56
|
+
popoverTitle: "Issue Reports",
|
|
57
|
+
reportNewAction: "Report New Issue",
|
|
58
|
+
filtersAll: "All",
|
|
59
|
+
filtersOpen: "Open",
|
|
60
|
+
filtersClosed: "Closed",
|
|
61
|
+
historyHelpText: "We read every report. If something is broken or could work better, report it here and it goes straight to the people building the app.",
|
|
62
|
+
historyLoading: "Loading issues...",
|
|
63
|
+
historyLoadFailed: "Failed to load issues",
|
|
64
|
+
emptyAll: "No issues reported yet",
|
|
65
|
+
emptyOpen: "No open issues",
|
|
66
|
+
emptyClosed: "No resolved or ignored issues",
|
|
67
|
+
reportModeTitle: "Report mode is active",
|
|
68
|
+
reportModeDescription: "Click a highlighted section to capture the broken surface.",
|
|
69
|
+
reportModeCancelAction: "Cancel",
|
|
70
|
+
createTitlePrefix: "Report Issue",
|
|
71
|
+
editTitlePrefix: "Edit Issue",
|
|
72
|
+
replyTitlePrefix: "Reply to",
|
|
73
|
+
createDescriptionPrefix: "Reporting issue for page:",
|
|
74
|
+
editDescription: "Update the issue description below.",
|
|
75
|
+
replyDescription: "The original issue was marked resolved or ignored. Describe why it still needs attention.",
|
|
76
|
+
notePlaceholder: "Describe the issue in detail (min 10 characters)...",
|
|
77
|
+
noteMinimumSuffix: "more characters needed",
|
|
78
|
+
keyboardShortcutHint: "Cmd/Ctrl + Enter to submit",
|
|
79
|
+
originalIssueLabel: "Original issue",
|
|
80
|
+
cancelAction: "Cancel",
|
|
81
|
+
submitAction: "Submit",
|
|
82
|
+
submittingAction: "Submitting...",
|
|
83
|
+
editAction: "Edit",
|
|
84
|
+
replyAction: "Reply",
|
|
85
|
+
hydrateLoading: "Loading the latest issue details...",
|
|
86
|
+
hydrateFailed: "Failed to load the latest issue details.",
|
|
87
|
+
retryAction: "Retry"
|
|
88
|
+
};
|
|
89
|
+
var issueReportingKeys = {
|
|
90
|
+
all: ["spaps-issue-reporting"],
|
|
91
|
+
status: () => [...issueReportingKeys.all, "status"],
|
|
92
|
+
history: () => [...issueReportingKeys.all, "history"],
|
|
93
|
+
detail: (issueReportId) => [...issueReportingKeys.all, "detail", issueReportId]
|
|
94
|
+
};
|
|
95
|
+
function resolvePageUrl(getPageUrl) {
|
|
96
|
+
if (getPageUrl) {
|
|
97
|
+
return getPageUrl();
|
|
98
|
+
}
|
|
99
|
+
if (typeof window === "undefined") {
|
|
100
|
+
return "/";
|
|
101
|
+
}
|
|
102
|
+
return `${window.location.pathname}${window.location.search}${window.location.hash}`;
|
|
103
|
+
}
|
|
104
|
+
function normalizeTarget(target, getPageUrl) {
|
|
105
|
+
if (typeof target === "string") {
|
|
106
|
+
const normalized = target.trim();
|
|
107
|
+
return {
|
|
108
|
+
component_key: normalized,
|
|
109
|
+
component_label: normalized,
|
|
110
|
+
page_url: resolvePageUrl(getPageUrl),
|
|
111
|
+
surface_ref: null,
|
|
112
|
+
metadata: {}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
const key = target.componentKey.trim();
|
|
116
|
+
const label = (target.componentLabel ?? key).trim();
|
|
117
|
+
return {
|
|
118
|
+
component_key: key,
|
|
119
|
+
component_label: label,
|
|
120
|
+
page_url: resolvePageUrl(getPageUrl),
|
|
121
|
+
surface_ref: target.surfaceRef ?? null,
|
|
122
|
+
metadata: target.metadata ?? {}
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function normalizeIssueTarget(issue) {
|
|
126
|
+
return {
|
|
127
|
+
component_key: issue.target.component_key,
|
|
128
|
+
component_label: issue.target.component_label,
|
|
129
|
+
page_url: issue.target.page_url,
|
|
130
|
+
surface_ref: issue.target.surface_ref ?? null,
|
|
131
|
+
metadata: issue.target.metadata ?? {}
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function toErrorMessage(error, fallback) {
|
|
135
|
+
if (error instanceof Error && error.message.trim()) {
|
|
136
|
+
return error.message;
|
|
137
|
+
}
|
|
138
|
+
return fallback;
|
|
139
|
+
}
|
|
140
|
+
function isOpenIssueStatus(status) {
|
|
141
|
+
return status === "open" || status === "in_progress";
|
|
142
|
+
}
|
|
143
|
+
function isClosedIssueStatus(status) {
|
|
144
|
+
return status === "resolved" || status === "ignored";
|
|
145
|
+
}
|
|
146
|
+
function filterIssueReports(issues, filter) {
|
|
147
|
+
if (filter === "open") {
|
|
148
|
+
return issues.filter((issue) => isOpenIssueStatus(issue.status));
|
|
149
|
+
}
|
|
150
|
+
if (filter === "closed") {
|
|
151
|
+
return issues.filter((issue) => isClosedIssueStatus(issue.status));
|
|
152
|
+
}
|
|
153
|
+
return issues;
|
|
154
|
+
}
|
|
155
|
+
function getIssueStatusBadgeLabel(status) {
|
|
156
|
+
switch (status) {
|
|
157
|
+
case "in_progress":
|
|
158
|
+
return "In Progress";
|
|
159
|
+
case "resolved":
|
|
160
|
+
return "Resolved";
|
|
161
|
+
case "ignored":
|
|
162
|
+
return "Ignored";
|
|
163
|
+
default:
|
|
164
|
+
return "Open";
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
function getIssueStatusClassName(status) {
|
|
168
|
+
switch (status) {
|
|
169
|
+
case "in_progress":
|
|
170
|
+
return "bg-amber-100 text-amber-700";
|
|
171
|
+
case "resolved":
|
|
172
|
+
return "bg-emerald-100 text-emerald-700";
|
|
173
|
+
case "ignored":
|
|
174
|
+
return "bg-slate-200 text-slate-700";
|
|
175
|
+
default:
|
|
176
|
+
return "bg-rose-100 text-rose-700";
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function getEntryPointState(status) {
|
|
180
|
+
if (!status) {
|
|
181
|
+
return "neutral";
|
|
182
|
+
}
|
|
183
|
+
if (status.has_open) {
|
|
184
|
+
return "open";
|
|
185
|
+
}
|
|
186
|
+
if (status.has_recent_resolved) {
|
|
187
|
+
return "recent_resolved";
|
|
188
|
+
}
|
|
189
|
+
return "neutral";
|
|
190
|
+
}
|
|
191
|
+
function getEntryPointClassName(state) {
|
|
192
|
+
if (state === "open") {
|
|
193
|
+
return "text-rose-600";
|
|
194
|
+
}
|
|
195
|
+
if (state === "recent_resolved") {
|
|
196
|
+
return "text-emerald-600";
|
|
197
|
+
}
|
|
198
|
+
return "text-slate-500";
|
|
199
|
+
}
|
|
200
|
+
function getIssueNoteLengthMessage(note, copy) {
|
|
201
|
+
if (note.length < NOTE_MIN_LENGTH) {
|
|
202
|
+
return `${NOTE_MIN_LENGTH - note.length} ${copy.noteMinimumSuffix}`;
|
|
203
|
+
}
|
|
204
|
+
return `${note.length}/${NOTE_MAX_LENGTH}`;
|
|
205
|
+
}
|
|
206
|
+
function useIssueReporting() {
|
|
207
|
+
const context = useContext2(IssueReportingContext);
|
|
208
|
+
if (!context) {
|
|
209
|
+
throw new Error(
|
|
210
|
+
"useIssueReporting must be used within an IssueReportingProvider"
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
return context;
|
|
214
|
+
}
|
|
215
|
+
function useIssueReportingStatus() {
|
|
216
|
+
const { client, isEligible } = useIssueReporting();
|
|
217
|
+
return useQuery({
|
|
218
|
+
queryKey: issueReportingKeys.status(),
|
|
219
|
+
queryFn: () => client.issueReporting.getStatus(),
|
|
220
|
+
enabled: isEligible,
|
|
221
|
+
retry: false,
|
|
222
|
+
staleTime: 3e4
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
function useIssueReportingHistory(filter = "all") {
|
|
226
|
+
const { client, isEligible } = useIssueReporting();
|
|
227
|
+
const query = useQuery({
|
|
228
|
+
queryKey: issueReportingKeys.history(),
|
|
229
|
+
queryFn: () => client.issueReporting.list({ limit: LIST_LIMIT, offset: 0 }),
|
|
230
|
+
enabled: isEligible,
|
|
231
|
+
retry: false
|
|
232
|
+
});
|
|
233
|
+
const items = useMemo(
|
|
234
|
+
() => filterIssueReports(query.data?.items ?? [], filter),
|
|
235
|
+
[filter, query.data?.items]
|
|
236
|
+
);
|
|
237
|
+
return {
|
|
238
|
+
...query,
|
|
239
|
+
items,
|
|
240
|
+
total: items.length
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
function useIssueReportingMutations() {
|
|
244
|
+
const queryClient = useQueryClient();
|
|
245
|
+
const { client } = useIssueReporting();
|
|
246
|
+
const invalidateAll = useCallback(async () => {
|
|
247
|
+
await queryClient.invalidateQueries({
|
|
248
|
+
queryKey: issueReportingKeys.all
|
|
249
|
+
});
|
|
250
|
+
}, [queryClient]);
|
|
251
|
+
const createMutation = useMutation({
|
|
252
|
+
mutationFn: (payload) => client.issueReporting.create(payload),
|
|
253
|
+
onSuccess: invalidateAll
|
|
254
|
+
});
|
|
255
|
+
const updateMutation = useMutation({
|
|
256
|
+
mutationFn: ({
|
|
257
|
+
issueReportId,
|
|
258
|
+
note
|
|
259
|
+
}) => client.issueReporting.update(issueReportId, { note }),
|
|
260
|
+
onSuccess: invalidateAll
|
|
261
|
+
});
|
|
262
|
+
const replyMutation = useMutation({
|
|
263
|
+
mutationFn: ({
|
|
264
|
+
issueReportId,
|
|
265
|
+
note,
|
|
266
|
+
reporterRoleHint
|
|
267
|
+
}) => client.issueReporting.reply(issueReportId, {
|
|
268
|
+
note,
|
|
269
|
+
reporter_role_hint: reporterRoleHint
|
|
270
|
+
}),
|
|
271
|
+
onSuccess: invalidateAll
|
|
272
|
+
});
|
|
273
|
+
return {
|
|
274
|
+
createMutation,
|
|
275
|
+
updateMutation,
|
|
276
|
+
replyMutation
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
function IssueReportingProvider({
|
|
280
|
+
client,
|
|
281
|
+
isEligible,
|
|
282
|
+
reporterRoleHint,
|
|
283
|
+
getPageUrl,
|
|
284
|
+
copy,
|
|
285
|
+
children
|
|
286
|
+
}) {
|
|
287
|
+
const mergedCopy = useMemo(
|
|
288
|
+
() => ({
|
|
289
|
+
...defaultIssueReportingCopy,
|
|
290
|
+
...copy
|
|
291
|
+
}),
|
|
292
|
+
[copy]
|
|
293
|
+
);
|
|
294
|
+
const queryClient = useQueryClient();
|
|
295
|
+
const [isReportMode, setIsReportMode] = useState(false);
|
|
296
|
+
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
|
297
|
+
const [modalState, setModalState] = useState(initialModalState);
|
|
298
|
+
const closePopover = useCallback(() => {
|
|
299
|
+
setIsPopoverOpen(false);
|
|
300
|
+
}, []);
|
|
301
|
+
const openPopover = useCallback(() => {
|
|
302
|
+
setIsPopoverOpen(true);
|
|
303
|
+
}, []);
|
|
304
|
+
const enterReportMode = useCallback(() => {
|
|
305
|
+
setIsReportMode(true);
|
|
306
|
+
setIsPopoverOpen(false);
|
|
307
|
+
}, []);
|
|
308
|
+
const cancelReportMode = useCallback(() => {
|
|
309
|
+
setIsReportMode(false);
|
|
310
|
+
}, []);
|
|
311
|
+
const closeModal = useCallback(() => {
|
|
312
|
+
setModalState(initialModalState);
|
|
313
|
+
setIsPopoverOpen(true);
|
|
314
|
+
}, []);
|
|
315
|
+
const hydrateIssueIntoModal = useCallback(
|
|
316
|
+
async (issueReportId, mode) => {
|
|
317
|
+
setModalState({
|
|
318
|
+
isOpen: true,
|
|
319
|
+
mode,
|
|
320
|
+
issueReportId,
|
|
321
|
+
issue: null,
|
|
322
|
+
target: null,
|
|
323
|
+
error: null,
|
|
324
|
+
isHydrating: true
|
|
325
|
+
});
|
|
326
|
+
try {
|
|
327
|
+
const issue = await queryClient.fetchQuery({
|
|
328
|
+
queryKey: issueReportingKeys.detail(issueReportId),
|
|
329
|
+
queryFn: () => client.issueReporting.get(issueReportId)
|
|
330
|
+
});
|
|
331
|
+
setModalState({
|
|
332
|
+
isOpen: true,
|
|
333
|
+
mode,
|
|
334
|
+
issueReportId,
|
|
335
|
+
issue,
|
|
336
|
+
target: normalizeIssueTarget(issue),
|
|
337
|
+
error: null,
|
|
338
|
+
isHydrating: false
|
|
339
|
+
});
|
|
340
|
+
} catch (error) {
|
|
341
|
+
setModalState({
|
|
342
|
+
isOpen: true,
|
|
343
|
+
mode,
|
|
344
|
+
issueReportId,
|
|
345
|
+
issue: null,
|
|
346
|
+
target: null,
|
|
347
|
+
error: toErrorMessage(error, mergedCopy.hydrateFailed),
|
|
348
|
+
isHydrating: false
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
},
|
|
352
|
+
[client.issueReporting, mergedCopy.hydrateFailed, queryClient]
|
|
353
|
+
);
|
|
354
|
+
const retryModalHydration = useCallback(async () => {
|
|
355
|
+
if (!modalState.issueReportId || modalState.mode === "create") {
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
await hydrateIssueIntoModal(modalState.issueReportId, modalState.mode);
|
|
359
|
+
}, [hydrateIssueIntoModal, modalState.issueReportId, modalState.mode]);
|
|
360
|
+
const openExistingIssueModal = useCallback(
|
|
361
|
+
async (issueReportId, mode) => {
|
|
362
|
+
setIsPopoverOpen(false);
|
|
363
|
+
await hydrateIssueIntoModal(issueReportId, mode);
|
|
364
|
+
},
|
|
365
|
+
[hydrateIssueIntoModal]
|
|
366
|
+
);
|
|
367
|
+
const selectPanel = useCallback(
|
|
368
|
+
(target) => {
|
|
369
|
+
if (!isEligible) {
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
const normalizedTarget = normalizeTarget(target, getPageUrl);
|
|
373
|
+
setIsReportMode(false);
|
|
374
|
+
setModalState({
|
|
375
|
+
isOpen: true,
|
|
376
|
+
mode: "create",
|
|
377
|
+
issueReportId: null,
|
|
378
|
+
issue: null,
|
|
379
|
+
target: normalizedTarget,
|
|
380
|
+
error: null,
|
|
381
|
+
isHydrating: false
|
|
382
|
+
});
|
|
383
|
+
},
|
|
384
|
+
[getPageUrl, isEligible]
|
|
385
|
+
);
|
|
386
|
+
const contextValue = useMemo(
|
|
387
|
+
() => ({
|
|
388
|
+
client,
|
|
389
|
+
isEligible,
|
|
390
|
+
reporterRoleHint,
|
|
391
|
+
copy: mergedCopy,
|
|
392
|
+
isReportMode,
|
|
393
|
+
enterReportMode,
|
|
394
|
+
cancelReportMode,
|
|
395
|
+
selectPanel,
|
|
396
|
+
isPopoverOpen,
|
|
397
|
+
openPopover,
|
|
398
|
+
closePopover,
|
|
399
|
+
modalState,
|
|
400
|
+
closeModal,
|
|
401
|
+
openExistingIssueModal,
|
|
402
|
+
retryModalHydration
|
|
403
|
+
}),
|
|
404
|
+
[
|
|
405
|
+
cancelReportMode,
|
|
406
|
+
client,
|
|
407
|
+
closeModal,
|
|
408
|
+
closePopover,
|
|
409
|
+
enterReportMode,
|
|
410
|
+
isEligible,
|
|
411
|
+
isPopoverOpen,
|
|
412
|
+
isReportMode,
|
|
413
|
+
mergedCopy,
|
|
414
|
+
modalState,
|
|
415
|
+
openExistingIssueModal,
|
|
416
|
+
openPopover,
|
|
417
|
+
reporterRoleHint,
|
|
418
|
+
retryModalHydration,
|
|
419
|
+
selectPanel
|
|
420
|
+
]
|
|
421
|
+
);
|
|
422
|
+
const reportModeValue = useMemo(
|
|
423
|
+
() => ({
|
|
424
|
+
isReportMode,
|
|
425
|
+
selectPanel,
|
|
426
|
+
cancelReportMode
|
|
427
|
+
}),
|
|
428
|
+
[cancelReportMode, isReportMode, selectPanel]
|
|
429
|
+
);
|
|
430
|
+
return /* @__PURE__ */ jsx(ReportModeContext.Provider, { value: reportModeValue, children: /* @__PURE__ */ jsx(IssueReportingContext.Provider, { value: contextValue, children }) });
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// src/components.tsx
|
|
434
|
+
import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
435
|
+
function cn(...values) {
|
|
436
|
+
return values.filter(Boolean).join(" ");
|
|
437
|
+
}
|
|
438
|
+
function truncate(value, max = 80) {
|
|
439
|
+
if (value.length <= max) {
|
|
440
|
+
return value;
|
|
441
|
+
}
|
|
442
|
+
return `${value.slice(0, max - 3)}...`;
|
|
443
|
+
}
|
|
444
|
+
function formatRelativeTime(timestamp) {
|
|
445
|
+
return formatDistanceToNow(new Date(timestamp), {
|
|
446
|
+
addSuffix: true
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
function IssueReportModeBanner() {
|
|
450
|
+
const { copy } = useIssueReporting();
|
|
451
|
+
const reportMode = useReportMode();
|
|
452
|
+
if (!reportMode?.isReportMode) {
|
|
453
|
+
return null;
|
|
454
|
+
}
|
|
455
|
+
return /* @__PURE__ */ jsx2("div", { className: "fixed inset-x-4 top-4 z-[70] flex justify-center", children: /* @__PURE__ */ jsx2("div", { className: "max-w-xl rounded-full border border-amber-300 bg-amber-50/95 px-4 py-3 text-sm text-amber-950 shadow-lg backdrop-blur", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-center gap-3", children: [
|
|
456
|
+
/* @__PURE__ */ jsx2("div", { className: "font-medium", children: copy.reportModeTitle }),
|
|
457
|
+
/* @__PURE__ */ jsx2("div", { className: "text-amber-900/80", children: copy.reportModeDescription }),
|
|
458
|
+
/* @__PURE__ */ jsx2(
|
|
459
|
+
"button",
|
|
460
|
+
{
|
|
461
|
+
type: "button",
|
|
462
|
+
className: "rounded-full border border-amber-400 px-3 py-1 font-medium text-amber-900 transition hover:bg-amber-100",
|
|
463
|
+
onClick: reportMode.cancelReportMode,
|
|
464
|
+
children: copy.reportModeCancelAction
|
|
465
|
+
}
|
|
466
|
+
)
|
|
467
|
+
] }) }) });
|
|
468
|
+
}
|
|
469
|
+
function IssueList({
|
|
470
|
+
filter,
|
|
471
|
+
onEdit,
|
|
472
|
+
onReply
|
|
473
|
+
}) {
|
|
474
|
+
const { copy } = useIssueReporting();
|
|
475
|
+
const history = useIssueReportingHistory(filter);
|
|
476
|
+
if (history.isPending) {
|
|
477
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-5 text-sm text-slate-600", children: [
|
|
478
|
+
/* @__PURE__ */ jsx2(Spinner, { className: "h-4 w-4 animate-spin" }),
|
|
479
|
+
/* @__PURE__ */ jsx2("span", { children: copy.historyLoading })
|
|
480
|
+
] });
|
|
481
|
+
}
|
|
482
|
+
if (history.error) {
|
|
483
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-3 rounded-2xl border border-rose-200 bg-rose-50 px-4 py-4 text-sm text-rose-700", children: [
|
|
484
|
+
/* @__PURE__ */ jsx2("div", { children: history.error.message || copy.historyLoadFailed }),
|
|
485
|
+
/* @__PURE__ */ jsx2(
|
|
486
|
+
"button",
|
|
487
|
+
{
|
|
488
|
+
type: "button",
|
|
489
|
+
className: "rounded-full border border-rose-300 px-3 py-1 font-medium transition hover:bg-rose-100",
|
|
490
|
+
onClick: () => history.refetch(),
|
|
491
|
+
children: copy.retryAction
|
|
492
|
+
}
|
|
493
|
+
)
|
|
494
|
+
] });
|
|
495
|
+
}
|
|
496
|
+
if (history.items.length === 0) {
|
|
497
|
+
const emptyMessage = filter === "open" ? copy.emptyOpen : filter === "closed" ? copy.emptyClosed : copy.emptyAll;
|
|
498
|
+
return /* @__PURE__ */ jsx2("div", { className: "rounded-2xl border border-dashed border-slate-200 bg-slate-50 px-4 py-8 text-center text-sm text-slate-500", children: emptyMessage });
|
|
499
|
+
}
|
|
500
|
+
return /* @__PURE__ */ jsx2("div", { className: "max-h-80 space-y-2 overflow-y-auto pr-1", children: history.items.map((issue) => {
|
|
501
|
+
const isClosed = isClosedIssueStatus(issue.status);
|
|
502
|
+
const actionLabel = isClosed ? copy.replyAction : copy.editAction;
|
|
503
|
+
const onAction = () => isClosed ? onReply(issue.id) : onEdit(issue.id);
|
|
504
|
+
return /* @__PURE__ */ jsx2(
|
|
505
|
+
"div",
|
|
506
|
+
{
|
|
507
|
+
className: "rounded-2xl border border-slate-200 bg-white px-3 py-3 shadow-sm",
|
|
508
|
+
children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
|
|
509
|
+
/* @__PURE__ */ jsx2("div", { className: "mt-0.5 flex-shrink-0", children: isClosed ? /* @__PURE__ */ jsx2(CheckCircle, { className: "h-4 w-4 text-emerald-600", weight: "fill" }) : /* @__PURE__ */ jsx2(Circle, { className: "h-4 w-4 text-rose-600", weight: "fill" }) }),
|
|
510
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
511
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-2", children: [
|
|
512
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
513
|
+
/* @__PURE__ */ jsx2("div", { className: "truncate text-sm font-semibold text-slate-900", children: issue.target.component_label }),
|
|
514
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-1 flex flex-wrap items-center gap-2", children: [
|
|
515
|
+
/* @__PURE__ */ jsx2(
|
|
516
|
+
"span",
|
|
517
|
+
{
|
|
518
|
+
className: cn(
|
|
519
|
+
"rounded-full px-2 py-0.5 text-[11px] font-medium",
|
|
520
|
+
getIssueStatusClassName(issue.status)
|
|
521
|
+
),
|
|
522
|
+
children: getIssueStatusBadgeLabel(issue.status)
|
|
523
|
+
}
|
|
524
|
+
),
|
|
525
|
+
/* @__PURE__ */ jsx2("span", { className: "text-xs text-slate-400", children: formatRelativeTime(issue.updated_at) })
|
|
526
|
+
] })
|
|
527
|
+
] }),
|
|
528
|
+
/* @__PURE__ */ jsx2(
|
|
529
|
+
"button",
|
|
530
|
+
{
|
|
531
|
+
type: "button",
|
|
532
|
+
className: "rounded-full border border-slate-200 px-3 py-1 text-xs font-medium text-slate-700 transition hover:bg-slate-50",
|
|
533
|
+
onClick: onAction,
|
|
534
|
+
children: actionLabel
|
|
535
|
+
}
|
|
536
|
+
)
|
|
537
|
+
] }),
|
|
538
|
+
/* @__PURE__ */ jsx2("p", { className: "mt-2 text-sm text-slate-600", children: truncate(issue.note) }),
|
|
539
|
+
/* @__PURE__ */ jsx2("div", { className: "mt-2 truncate text-xs text-slate-400", children: issue.target.page_url })
|
|
540
|
+
] })
|
|
541
|
+
] })
|
|
542
|
+
},
|
|
543
|
+
issue.id
|
|
544
|
+
);
|
|
545
|
+
}) });
|
|
546
|
+
}
|
|
547
|
+
function IssueReportPopover({ children }) {
|
|
548
|
+
const [filter, setFilter] = useState2("all");
|
|
549
|
+
const {
|
|
550
|
+
copy,
|
|
551
|
+
isPopoverOpen,
|
|
552
|
+
openPopover,
|
|
553
|
+
closePopover,
|
|
554
|
+
enterReportMode,
|
|
555
|
+
openExistingIssueModal
|
|
556
|
+
} = useIssueReporting();
|
|
557
|
+
const history = useIssueReportingHistory("all");
|
|
558
|
+
const status = useIssueReportingStatus();
|
|
559
|
+
const allItems = history.data?.items ?? [];
|
|
560
|
+
const counts = useMemo2(
|
|
561
|
+
() => ({
|
|
562
|
+
all: allItems.length,
|
|
563
|
+
open: filterIssueReports(allItems, "open").length,
|
|
564
|
+
closed: filterIssueReports(allItems, "closed").length
|
|
565
|
+
}),
|
|
566
|
+
[allItems]
|
|
567
|
+
);
|
|
568
|
+
return /* @__PURE__ */ jsxs(
|
|
569
|
+
Popover.Root,
|
|
570
|
+
{
|
|
571
|
+
open: isPopoverOpen,
|
|
572
|
+
onOpenChange: (open) => open ? openPopover() : closePopover(),
|
|
573
|
+
children: [
|
|
574
|
+
/* @__PURE__ */ jsx2(Popover.Trigger, { asChild: true, children }),
|
|
575
|
+
/* @__PURE__ */ jsx2(Popover.Portal, { children: /* @__PURE__ */ jsx2(
|
|
576
|
+
Popover.Content,
|
|
577
|
+
{
|
|
578
|
+
align: "end",
|
|
579
|
+
side: "top",
|
|
580
|
+
sideOffset: 12,
|
|
581
|
+
className: "z-[70] w-[360px] rounded-3xl border border-slate-200 bg-white p-4 shadow-[0_18px_48px_rgba(15,23,42,0.18)]",
|
|
582
|
+
children: /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
583
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3", children: [
|
|
584
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
585
|
+
/* @__PURE__ */ jsx2("h3", { className: "text-sm font-semibold text-slate-900", children: copy.popoverTitle }),
|
|
586
|
+
status.data?.has_open ? /* @__PURE__ */ jsxs("p", { className: "mt-1 text-xs text-slate-500", children: [
|
|
587
|
+
status.data.open_count,
|
|
588
|
+
" open issue",
|
|
589
|
+
status.data.open_count === 1 ? "" : "s"
|
|
590
|
+
] }) : null
|
|
591
|
+
] }),
|
|
592
|
+
/* @__PURE__ */ jsx2(
|
|
593
|
+
"button",
|
|
594
|
+
{
|
|
595
|
+
type: "button",
|
|
596
|
+
className: "rounded-full bg-slate-900 px-3 py-2 text-xs font-semibold text-white transition hover:bg-slate-800",
|
|
597
|
+
onClick: () => {
|
|
598
|
+
closePopover();
|
|
599
|
+
enterReportMode();
|
|
600
|
+
},
|
|
601
|
+
children: copy.reportNewAction
|
|
602
|
+
}
|
|
603
|
+
)
|
|
604
|
+
] }),
|
|
605
|
+
/* @__PURE__ */ jsx2("div", { className: "flex gap-2", children: [
|
|
606
|
+
["all", copy.filtersAll, counts.all],
|
|
607
|
+
["open", copy.filtersOpen, counts.open],
|
|
608
|
+
["closed", copy.filtersClosed, counts.closed]
|
|
609
|
+
].map(([value, label, count]) => /* @__PURE__ */ jsxs(
|
|
610
|
+
"button",
|
|
611
|
+
{
|
|
612
|
+
type: "button",
|
|
613
|
+
className: cn(
|
|
614
|
+
"rounded-full px-3 py-1.5 text-xs font-medium transition",
|
|
615
|
+
filter === value ? "bg-slate-900 text-white" : "bg-slate-100 text-slate-600 hover:bg-slate-200"
|
|
616
|
+
),
|
|
617
|
+
onClick: () => setFilter(value),
|
|
618
|
+
children: [
|
|
619
|
+
label,
|
|
620
|
+
count > 0 ? ` (${count})` : ""
|
|
621
|
+
]
|
|
622
|
+
},
|
|
623
|
+
value
|
|
624
|
+
)) }),
|
|
625
|
+
/* @__PURE__ */ jsx2(
|
|
626
|
+
IssueList,
|
|
627
|
+
{
|
|
628
|
+
filter,
|
|
629
|
+
onEdit: (issueReportId) => openExistingIssueModal(issueReportId, "edit"),
|
|
630
|
+
onReply: (issueReportId) => openExistingIssueModal(issueReportId, "reply")
|
|
631
|
+
}
|
|
632
|
+
),
|
|
633
|
+
/* @__PURE__ */ jsx2("p", { className: "border-t border-slate-100 pt-3 text-xs leading-relaxed text-slate-500", children: copy.historyHelpText })
|
|
634
|
+
] })
|
|
635
|
+
}
|
|
636
|
+
) })
|
|
637
|
+
]
|
|
638
|
+
}
|
|
639
|
+
);
|
|
640
|
+
}
|
|
641
|
+
function IssueReportModal() {
|
|
642
|
+
const {
|
|
643
|
+
copy,
|
|
644
|
+
reporterRoleHint,
|
|
645
|
+
modalState,
|
|
646
|
+
closeModal,
|
|
647
|
+
retryModalHydration
|
|
648
|
+
} = useIssueReporting();
|
|
649
|
+
const { createMutation, updateMutation, replyMutation } = useIssueReportingMutations();
|
|
650
|
+
const [note, setNote] = useState2("");
|
|
651
|
+
const [submitError, setSubmitError] = useState2(null);
|
|
652
|
+
const { isOpen, mode, issue, target, isHydrating, error } = modalState;
|
|
653
|
+
useEffect(() => {
|
|
654
|
+
if (!isOpen) {
|
|
655
|
+
setNote("");
|
|
656
|
+
setSubmitError(null);
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
if (mode === "edit" && issue) {
|
|
660
|
+
setNote(issue.note);
|
|
661
|
+
} else {
|
|
662
|
+
setNote("");
|
|
663
|
+
}
|
|
664
|
+
setSubmitError(null);
|
|
665
|
+
}, [isOpen, mode, issue]);
|
|
666
|
+
const isValid = note.trim().length >= 10 && note.trim().length <= 2e3;
|
|
667
|
+
const isSubmitting = createMutation.isPending || updateMutation.isPending || replyMutation.isPending;
|
|
668
|
+
const title = mode === "create" ? `${copy.createTitlePrefix}: ${target?.component_label ?? ""}` : mode === "edit" ? `${copy.editTitlePrefix}: ${target?.component_label ?? ""}` : `${copy.replyTitlePrefix}: ${target?.component_label ?? ""}`;
|
|
669
|
+
const handleSubmit = async () => {
|
|
670
|
+
if (!target || !isValid || isSubmitting) {
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
setSubmitError(null);
|
|
674
|
+
try {
|
|
675
|
+
const normalizedNote = note.trim();
|
|
676
|
+
if (mode === "create") {
|
|
677
|
+
await createMutation.mutateAsync({
|
|
678
|
+
target,
|
|
679
|
+
note: normalizedNote,
|
|
680
|
+
reporter_role_hint: reporterRoleHint
|
|
681
|
+
});
|
|
682
|
+
} else if (mode === "edit" && issue) {
|
|
683
|
+
await updateMutation.mutateAsync({
|
|
684
|
+
issueReportId: issue.id,
|
|
685
|
+
note: normalizedNote
|
|
686
|
+
});
|
|
687
|
+
} else if (mode === "reply" && issue) {
|
|
688
|
+
await replyMutation.mutateAsync({
|
|
689
|
+
issueReportId: issue.id,
|
|
690
|
+
note: normalizedNote,
|
|
691
|
+
reporterRoleHint
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
closeModal();
|
|
695
|
+
} catch (submissionError) {
|
|
696
|
+
setSubmitError(
|
|
697
|
+
submissionError instanceof Error ? submissionError.message : "Failed to submit issue report"
|
|
698
|
+
);
|
|
699
|
+
}
|
|
700
|
+
};
|
|
701
|
+
return /* @__PURE__ */ jsx2(Dialog.Root, { open: isOpen, onOpenChange: (open) => !open && closeModal(), children: /* @__PURE__ */ jsxs(Dialog.Portal, { children: [
|
|
702
|
+
/* @__PURE__ */ jsx2(Dialog.Overlay, { className: "fixed inset-0 z-[80] bg-slate-950/45 backdrop-blur-sm" }),
|
|
703
|
+
/* @__PURE__ */ jsxs(Dialog.Content, { className: "fixed left-1/2 top-1/2 z-[81] w-[calc(100vw-2rem)] max-w-xl -translate-x-1/2 -translate-y-1/2 rounded-[28px] border border-slate-200 bg-white p-6 shadow-[0_28px_80px_rgba(15,23,42,0.24)] focus:outline-none", children: [
|
|
704
|
+
/* @__PURE__ */ jsx2(Dialog.Title, { className: "text-lg font-semibold text-slate-950", children: title }),
|
|
705
|
+
/* @__PURE__ */ jsx2(Dialog.Description, { className: "mt-2 text-sm text-slate-600", children: mode === "create" && target ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
706
|
+
copy.createDescriptionPrefix,
|
|
707
|
+
" ",
|
|
708
|
+
/* @__PURE__ */ jsx2("code", { className: "rounded bg-slate-100 px-1.5 py-0.5 text-xs text-slate-700", children: target.page_url })
|
|
709
|
+
] }) : mode === "edit" ? copy.editDescription : copy.replyDescription }),
|
|
710
|
+
isHydrating ? /* @__PURE__ */ jsxs("div", { className: "mt-5 flex items-center gap-2 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-4 text-sm text-slate-600", children: [
|
|
711
|
+
/* @__PURE__ */ jsx2(Spinner, { className: "h-4 w-4 animate-spin" }),
|
|
712
|
+
/* @__PURE__ */ jsx2("span", { children: copy.hydrateLoading })
|
|
713
|
+
] }) : error ? /* @__PURE__ */ jsxs("div", { className: "mt-5 space-y-3 rounded-2xl border border-rose-200 bg-rose-50 px-4 py-4 text-sm text-rose-700", children: [
|
|
714
|
+
/* @__PURE__ */ jsx2("div", { children: error }),
|
|
715
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
716
|
+
/* @__PURE__ */ jsx2(
|
|
717
|
+
"button",
|
|
718
|
+
{
|
|
719
|
+
type: "button",
|
|
720
|
+
className: "rounded-full border border-rose-300 px-3 py-1 font-medium transition hover:bg-rose-100",
|
|
721
|
+
onClick: () => retryModalHydration(),
|
|
722
|
+
children: copy.retryAction
|
|
723
|
+
}
|
|
724
|
+
),
|
|
725
|
+
/* @__PURE__ */ jsx2(
|
|
726
|
+
"button",
|
|
727
|
+
{
|
|
728
|
+
type: "button",
|
|
729
|
+
className: "rounded-full border border-slate-300 px-3 py-1 font-medium text-slate-700 transition hover:bg-slate-50",
|
|
730
|
+
onClick: closeModal,
|
|
731
|
+
children: copy.cancelAction
|
|
732
|
+
}
|
|
733
|
+
)
|
|
734
|
+
] })
|
|
735
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
736
|
+
mode === "reply" && issue ? /* @__PURE__ */ jsxs("div", { className: "mt-5 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3", children: [
|
|
737
|
+
/* @__PURE__ */ jsx2("div", { className: "text-xs font-medium uppercase tracking-wide text-slate-500", children: copy.originalIssueLabel }),
|
|
738
|
+
/* @__PURE__ */ jsx2("p", { className: "mt-1 text-sm text-slate-700", children: issue.note })
|
|
739
|
+
] }) : null,
|
|
740
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-5 space-y-2", children: [
|
|
741
|
+
/* @__PURE__ */ jsx2(
|
|
742
|
+
"textarea",
|
|
743
|
+
{
|
|
744
|
+
value: note,
|
|
745
|
+
onChange: (event) => setNote(event.target.value),
|
|
746
|
+
onKeyDown: (event) => {
|
|
747
|
+
if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
|
|
748
|
+
event.preventDefault();
|
|
749
|
+
void handleSubmit();
|
|
750
|
+
}
|
|
751
|
+
},
|
|
752
|
+
placeholder: copy.notePlaceholder,
|
|
753
|
+
className: "h-36 w-full resize-none rounded-2xl border border-slate-300 px-4 py-3 text-sm text-slate-800 outline-none transition focus:border-slate-500 focus:ring-2 focus:ring-slate-200",
|
|
754
|
+
disabled: isSubmitting,
|
|
755
|
+
autoFocus: true
|
|
756
|
+
}
|
|
757
|
+
),
|
|
758
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-xs text-slate-500", children: [
|
|
759
|
+
/* @__PURE__ */ jsx2("span", { children: getIssueNoteLengthMessage(note, copy) }),
|
|
760
|
+
/* @__PURE__ */ jsx2("span", { children: copy.keyboardShortcutHint })
|
|
761
|
+
] })
|
|
762
|
+
] }),
|
|
763
|
+
submitError ? /* @__PURE__ */ jsx2("div", { className: "mt-4 rounded-2xl border border-rose-200 bg-rose-50 px-4 py-3 text-sm text-rose-700", children: submitError }) : null,
|
|
764
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-6 flex justify-end gap-3", children: [
|
|
765
|
+
/* @__PURE__ */ jsx2(
|
|
766
|
+
"button",
|
|
767
|
+
{
|
|
768
|
+
type: "button",
|
|
769
|
+
className: "rounded-full border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 transition hover:bg-slate-50",
|
|
770
|
+
onClick: closeModal,
|
|
771
|
+
disabled: isSubmitting,
|
|
772
|
+
children: copy.cancelAction
|
|
773
|
+
}
|
|
774
|
+
),
|
|
775
|
+
/* @__PURE__ */ jsx2(
|
|
776
|
+
"button",
|
|
777
|
+
{
|
|
778
|
+
type: "button",
|
|
779
|
+
className: cn(
|
|
780
|
+
"rounded-full px-4 py-2 text-sm font-semibold text-white transition",
|
|
781
|
+
isValid && !isSubmitting ? "bg-slate-900 hover:bg-slate-800" : "cursor-not-allowed bg-slate-300"
|
|
782
|
+
),
|
|
783
|
+
onClick: () => void handleSubmit(),
|
|
784
|
+
disabled: !isValid || isSubmitting,
|
|
785
|
+
children: isSubmitting ? copy.submittingAction : copy.submitAction
|
|
786
|
+
}
|
|
787
|
+
)
|
|
788
|
+
] })
|
|
789
|
+
] }),
|
|
790
|
+
/* @__PURE__ */ jsx2(Dialog.Close, { asChild: true, children: /* @__PURE__ */ jsx2(
|
|
791
|
+
"button",
|
|
792
|
+
{
|
|
793
|
+
type: "button",
|
|
794
|
+
className: "absolute right-4 top-4 inline-flex h-9 w-9 items-center justify-center rounded-full border border-slate-200 text-slate-500 transition hover:bg-slate-50 hover:text-slate-900",
|
|
795
|
+
"aria-label": "Close issue report modal",
|
|
796
|
+
children: /* @__PURE__ */ jsx2(X, { className: "h-4 w-4" })
|
|
797
|
+
}
|
|
798
|
+
) })
|
|
799
|
+
] })
|
|
800
|
+
] }) });
|
|
801
|
+
}
|
|
802
|
+
function FloatingIssueReportButton({
|
|
803
|
+
className,
|
|
804
|
+
positionClassName
|
|
805
|
+
}) {
|
|
806
|
+
const {
|
|
807
|
+
copy,
|
|
808
|
+
isEligible,
|
|
809
|
+
isReportMode,
|
|
810
|
+
isPopoverOpen,
|
|
811
|
+
openPopover,
|
|
812
|
+
closePopover
|
|
813
|
+
} = useIssueReporting();
|
|
814
|
+
const status = useIssueReportingStatus();
|
|
815
|
+
const entryPointState = getEntryPointState(status.data);
|
|
816
|
+
if (!isEligible) {
|
|
817
|
+
return null;
|
|
818
|
+
}
|
|
819
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
820
|
+
/* @__PURE__ */ jsx2(IssueReportModeBanner, {}),
|
|
821
|
+
!isReportMode ? /* @__PURE__ */ jsx2("div", { className: cn("fixed bottom-12 right-4 z-[65]", positionClassName), children: /* @__PURE__ */ jsx2(IssueReportPopover, { children: /* @__PURE__ */ jsx2(
|
|
822
|
+
"button",
|
|
823
|
+
{
|
|
824
|
+
type: "button",
|
|
825
|
+
"aria-label": copy.entryAriaLabel,
|
|
826
|
+
onClick: () => isPopoverOpen ? closePopover() : openPopover(),
|
|
827
|
+
className: cn(
|
|
828
|
+
"flex h-12 w-12 items-center justify-center rounded-full border border-slate-200 bg-white shadow-lg transition hover:-translate-y-0.5 hover:shadow-xl focus:outline-none focus:ring-2 focus:ring-slate-300",
|
|
829
|
+
status.isPending && "animate-pulse",
|
|
830
|
+
className
|
|
831
|
+
),
|
|
832
|
+
children: /* @__PURE__ */ jsx2(
|
|
833
|
+
BugBeetle,
|
|
834
|
+
{
|
|
835
|
+
className: cn("h-6 w-6", getEntryPointClassName(entryPointState)),
|
|
836
|
+
weight: "fill"
|
|
837
|
+
}
|
|
838
|
+
)
|
|
839
|
+
}
|
|
840
|
+
) }) }) : null,
|
|
841
|
+
/* @__PURE__ */ jsx2(IssueReportModal, {})
|
|
842
|
+
] });
|
|
843
|
+
}
|
|
844
|
+
function ReportableSection({
|
|
845
|
+
reportableName,
|
|
846
|
+
children,
|
|
847
|
+
className,
|
|
848
|
+
as: Component = "div"
|
|
849
|
+
}) {
|
|
850
|
+
const reportMode = useReportMode();
|
|
851
|
+
const isSelectable = Boolean(reportMode?.isReportMode);
|
|
852
|
+
return /* @__PURE__ */ jsx2(
|
|
853
|
+
Component,
|
|
854
|
+
{
|
|
855
|
+
className: cn(
|
|
856
|
+
className,
|
|
857
|
+
isSelectable && "cursor-pointer ring-2 ring-amber-400 transition hover:ring-amber-500"
|
|
858
|
+
),
|
|
859
|
+
onClick: isSelectable ? () => {
|
|
860
|
+
reportMode?.selectPanel(reportableName);
|
|
861
|
+
} : void 0,
|
|
862
|
+
onKeyDown: isSelectable ? (event) => {
|
|
863
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
864
|
+
event.preventDefault();
|
|
865
|
+
reportMode?.selectPanel(reportableName);
|
|
866
|
+
}
|
|
867
|
+
} : void 0,
|
|
868
|
+
role: isSelectable ? "button" : void 0,
|
|
869
|
+
tabIndex: isSelectable ? 0 : void 0,
|
|
870
|
+
children
|
|
871
|
+
}
|
|
872
|
+
);
|
|
873
|
+
}
|
|
874
|
+
export {
|
|
875
|
+
FloatingIssueReportButton,
|
|
876
|
+
IssueReportingProvider,
|
|
877
|
+
ReportModeContext,
|
|
878
|
+
ReportableSection,
|
|
879
|
+
defaultIssueReportingCopy,
|
|
880
|
+
filterIssueReports,
|
|
881
|
+
getEntryPointClassName,
|
|
882
|
+
getEntryPointState,
|
|
883
|
+
getIssueNoteLengthMessage,
|
|
884
|
+
getIssueStatusBadgeLabel,
|
|
885
|
+
getIssueStatusClassName,
|
|
886
|
+
isClosedIssueStatus,
|
|
887
|
+
isOpenIssueStatus,
|
|
888
|
+
issueReportingKeys,
|
|
889
|
+
useIssueReporting,
|
|
890
|
+
useIssueReportingHistory,
|
|
891
|
+
useIssueReportingMutations,
|
|
892
|
+
useIssueReportingStatus,
|
|
893
|
+
useReportMode
|
|
894
|
+
};
|