@flamingo-stack/openframe-frontend-core 0.0.216 → 0.0.217-snapshot.20260601003634
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-SMCG2CCC.cjs → chunk-6DCKL73F.cjs} +24 -24
- package/dist/{chunk-SMCG2CCC.cjs.map → chunk-6DCKL73F.cjs.map} +1 -1
- package/dist/{chunk-QTKU6ULP.js → chunk-BVFRD34B.js} +2 -2
- package/dist/{chunk-CDLYRFDE.js → chunk-ENBGG2K2.js} +3767 -3610
- package/dist/chunk-ENBGG2K2.js.map +1 -0
- package/dist/{chunk-K4DFAVSO.cjs → chunk-G2HHSZ3S.cjs} +9 -9
- package/dist/{chunk-K4DFAVSO.cjs.map → chunk-G2HHSZ3S.cjs.map} +1 -1
- package/dist/{chunk-2V4SACHE.js → chunk-L6IBKPVM.js} +2 -2
- package/dist/{chunk-572WQWIX.cjs → chunk-MVQ3OODK.cjs} +9 -9
- package/dist/{chunk-572WQWIX.cjs.map → chunk-MVQ3OODK.cjs.map} +1 -1
- package/dist/{chunk-GVNQAGXB.js → chunk-N5IKPYRL.js} +3 -81
- package/dist/chunk-N5IKPYRL.js.map +1 -0
- package/dist/{chunk-VC3ND5RB.js → chunk-SWZUZYWR.js} +2 -2
- package/dist/{chunk-IH76P5R6.cjs → chunk-TYIBMDUZ.cjs} +8 -86
- package/dist/chunk-TYIBMDUZ.cjs.map +1 -0
- package/dist/{chunk-ZGTDUPTW.cjs → chunk-YWDC5BXM.cjs} +382 -225
- package/dist/chunk-YWDC5BXM.cjs.map +1 -0
- package/dist/components/chat/chat-attachment-bar.d.ts +13 -2
- package/dist/components/chat/chat-attachment-bar.d.ts.map +1 -1
- package/dist/components/chat/chat-input.d.ts.map +1 -1
- package/dist/components/chat/chat-message-row.d.ts +25 -0
- package/dist/components/chat/chat-message-row.d.ts.map +1 -0
- package/dist/components/chat/index.cjs +6 -2
- package/dist/components/chat/index.cjs.map +1 -1
- package/dist/components/chat/index.d.ts +1 -0
- package/dist/components/chat/index.d.ts.map +1 -1
- package/dist/components/chat/index.js +5 -1
- package/dist/components/chat/types/component.types.d.ts +8 -1
- package/dist/components/chat/types/component.types.d.ts.map +1 -1
- package/dist/components/contact/index.cjs +3 -3
- package/dist/components/contact/index.js +2 -2
- package/dist/components/features/index.cjs +2 -2
- package/dist/components/features/index.js +1 -1
- package/dist/components/form.d.ts +1 -1
- package/dist/components/form.d.ts.map +1 -1
- package/dist/components/index.cjs +56 -52
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +9 -5
- package/dist/components/index.js.map +1 -1
- package/dist/components/navigation/index.cjs +2 -2
- package/dist/components/navigation/index.js +1 -1
- package/dist/components/onboarding-guides/index.cjs +18 -18
- package/dist/components/onboarding-guides/index.js +3 -3
- package/dist/components/shared/dev-section/dev-card-row.d.ts +5 -45
- package/dist/components/shared/dev-section/dev-card-row.d.ts.map +1 -1
- package/dist/components/shared/legal-document/use-legal-docs.d.ts.map +1 -1
- package/dist/components/tickets/help-center-card.d.ts.map +1 -1
- package/dist/components/tickets/help-center-list.d.ts.map +1 -1
- package/dist/components/tickets/hooks/use-ticket-engagements.d.ts +9 -1
- package/dist/components/tickets/hooks/use-ticket-engagements.d.ts.map +1 -1
- package/dist/components/tickets/hooks/use-tickets-list.d.ts +7 -0
- package/dist/components/tickets/hooks/use-tickets-list.d.ts.map +1 -1
- package/dist/components/tickets/index.cjs +309 -256
- package/dist/components/tickets/index.cjs.map +1 -1
- package/dist/components/tickets/index.d.ts +1 -0
- package/dist/components/tickets/index.d.ts.map +1 -1
- package/dist/components/tickets/index.js +376 -323
- package/dist/components/tickets/index.js.map +1 -1
- package/dist/components/tickets/ticket-detail-drawer.d.ts.map +1 -1
- package/dist/components/tickets/ticket-reply-composer.d.ts +33 -0
- package/dist/components/tickets/ticket-reply-composer.d.ts.map +1 -0
- package/dist/components/tickets/types.d.ts +13 -0
- package/dist/components/tickets/types.d.ts.map +1 -1
- package/dist/components/ui/index.cjs +6 -2
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.js +5 -1
- package/dist/components/ui/ticket-attachments-list.d.ts +5 -1
- package/dist/components/ui/ticket-attachments-list.d.ts.map +1 -1
- package/dist/index.cjs +6 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +5 -1
- package/dist/utils/index.cjs +59 -4
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.js +59 -4
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/scroll-into-view.d.ts +43 -48
- package/dist/utils/scroll-into-view.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/chat/chat-attachment-bar.tsx +58 -22
- package/src/components/chat/chat-input.tsx +68 -29
- package/src/components/chat/chat-message-row.tsx +124 -0
- package/src/components/chat/index.ts +1 -0
- package/src/components/chat/types/component.types.ts +8 -1
- package/src/components/shared/dev-section/dev-card-row.tsx +5 -183
- package/src/components/shared/legal-document/use-legal-docs.ts +5 -1
- package/src/components/tickets/help-center-card.tsx +26 -29
- package/src/components/tickets/help-center-list.tsx +57 -10
- package/src/components/tickets/hooks/use-ticket-engagements.ts +41 -5
- package/src/components/tickets/hooks/use-tickets-list.ts +13 -0
- package/src/components/tickets/index.ts +4 -0
- package/src/components/tickets/ticket-detail-drawer.tsx +144 -200
- package/src/components/tickets/ticket-reply-composer.tsx +195 -0
- package/src/components/tickets/types.ts +14 -0
- package/src/components/ui/ticket-attachments-list.tsx +26 -8
- package/src/styles/app-globals.css +13 -0
- package/src/utils/scroll-into-view.ts +127 -53
- package/dist/chunk-CDLYRFDE.js.map +0 -1
- package/dist/chunk-GVNQAGXB.js.map +0 -1
- package/dist/chunk-IH76P5R6.cjs.map +0 -1
- package/dist/chunk-ZGTDUPTW.cjs.map +0 -1
- /package/dist/{chunk-QTKU6ULP.js.map → chunk-BVFRD34B.js.map} +0 -0
- /package/dist/{chunk-2V4SACHE.js.map → chunk-L6IBKPVM.js.map} +0 -0
- /package/dist/{chunk-VC3ND5RB.js.map → chunk-SWZUZYWR.js.map} +0 -0
|
@@ -4,18 +4,16 @@ import {
|
|
|
4
4
|
EmptyState,
|
|
5
5
|
UnifiedPagination,
|
|
6
6
|
init_unified_pagination
|
|
7
|
-
} from "../../chunk-
|
|
7
|
+
} from "../../chunk-L6IBKPVM.js";
|
|
8
8
|
import {
|
|
9
|
-
ConversationCardRow,
|
|
10
|
-
ConversationCardRowSkeletonList,
|
|
11
9
|
DevCardRowContent,
|
|
12
10
|
DevCardRowSkeletonList,
|
|
13
11
|
DevSectionPage
|
|
14
|
-
} from "../../chunk-
|
|
12
|
+
} from "../../chunk-N5IKPYRL.js";
|
|
15
13
|
import "../../chunk-ORJREQ2W.js";
|
|
16
14
|
import {
|
|
17
15
|
ContactForm
|
|
18
|
-
} from "../../chunk-
|
|
16
|
+
} from "../../chunk-BVFRD34B.js";
|
|
19
17
|
import {
|
|
20
18
|
AlertDialog,
|
|
21
19
|
AlertDialogAction,
|
|
@@ -28,18 +26,22 @@ import {
|
|
|
28
26
|
Card,
|
|
29
27
|
ChatAttachmentAddButton,
|
|
30
28
|
ChatAttachmentChipStrip,
|
|
29
|
+
ChatInput,
|
|
30
|
+
ChatMessageRow,
|
|
31
|
+
ChatMessageRowSkeleton,
|
|
31
32
|
ChatTicketItem,
|
|
32
33
|
Label,
|
|
33
34
|
SquareAvatar,
|
|
34
35
|
StatusBadge,
|
|
35
36
|
Textarea,
|
|
37
|
+
TicketAttachmentsList,
|
|
36
38
|
embedAuthedFetch,
|
|
37
39
|
formatRelativeTime,
|
|
38
40
|
getStatusColorScheme,
|
|
39
41
|
scrollElementIntoView,
|
|
40
42
|
useChatAttachments,
|
|
41
43
|
useChatIdentity
|
|
42
|
-
} from "../../chunk-
|
|
44
|
+
} from "../../chunk-ENBGG2K2.js";
|
|
43
45
|
import "../../chunk-LXC6P2EO.js";
|
|
44
46
|
import "../../chunk-EL5YVPD5.js";
|
|
45
47
|
import {
|
|
@@ -68,7 +70,7 @@ import "../../chunk-CZR7ARBA.js";
|
|
|
68
70
|
import "../../chunk-GGWZFCYS.js";
|
|
69
71
|
|
|
70
72
|
// src/components/tickets/ticket-center.tsx
|
|
71
|
-
import { useCallback as
|
|
73
|
+
import { useCallback as useCallback4, useState as useState4 } from "react";
|
|
72
74
|
import { useQueryClient as useQueryClient2 } from "@tanstack/react-query";
|
|
73
75
|
init_button();
|
|
74
76
|
import { RefreshCw } from "lucide-react";
|
|
@@ -82,6 +84,7 @@ function isOptimistic(t) {
|
|
|
82
84
|
return t._optimistic === true;
|
|
83
85
|
}
|
|
84
86
|
var TICKET_TEXT_MAX_CHARS = 5e3;
|
|
87
|
+
var TICKET_LIVE_POLL_MS = 8e3;
|
|
85
88
|
var TOAST_COPY = {
|
|
86
89
|
open_success: { title: "Ticket opened", description: "We received your message and will follow up shortly." },
|
|
87
90
|
open_mirror_pending: { title: "Ticket opened", description: "Syncing \u2014 your ticket will appear momentarily." },
|
|
@@ -217,7 +220,7 @@ function TicketOpenForm({
|
|
|
217
220
|
}
|
|
218
221
|
|
|
219
222
|
// src/components/tickets/ticket-row.tsx
|
|
220
|
-
import { useCallback, useRef } from "react";
|
|
223
|
+
import { useCallback as useCallback2, useRef } from "react";
|
|
221
224
|
|
|
222
225
|
// src/components/collapsible.tsx
|
|
223
226
|
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible";
|
|
@@ -226,15 +229,16 @@ var CollapsibleContent2 = CollapsiblePrimitive.CollapsibleContent;
|
|
|
226
229
|
|
|
227
230
|
// src/components/tickets/ticket-detail-drawer.tsx
|
|
228
231
|
init_button();
|
|
229
|
-
import {
|
|
232
|
+
import { useStickToBottom } from "use-stick-to-bottom";
|
|
230
233
|
|
|
231
234
|
// src/components/tickets/hooks/use-ticket-engagements.ts
|
|
232
235
|
import { useQuery } from "@tanstack/react-query";
|
|
233
236
|
var LIST_ENGAGEMENTS_ENDPOINT = "/api/chat/agent/list-engagements";
|
|
234
|
-
function useTicketEngagements(externalTicketId, enabled = true) {
|
|
237
|
+
function useTicketEngagements(externalTicketId, enabled = true, refetchInterval = false) {
|
|
235
238
|
const identity = useChatIdentity();
|
|
236
239
|
const identityKey = identity.user?.email ?? "anon";
|
|
237
|
-
const
|
|
240
|
+
const fetchable = enabled && !!externalTicketId && !externalTicketId.startsWith("temp-");
|
|
241
|
+
const queryEnabled = fetchable && identity.authTier !== "anon" && !!identity.user?.email;
|
|
238
242
|
const query = useQuery({
|
|
239
243
|
queryKey: ["ticket-engagements", externalTicketId, identityKey],
|
|
240
244
|
enabled: queryEnabled,
|
|
@@ -245,6 +249,11 @@ function useTicketEngagements(externalTicketId, enabled = true) {
|
|
|
245
249
|
gcTime: 0,
|
|
246
250
|
refetchOnMount: "always",
|
|
247
251
|
refetchOnWindowFocus: true,
|
|
252
|
+
// Live conversation: poll while the caller opts in (drawer open). New
|
|
253
|
+
// agent replies + attachments appear within one interval without a
|
|
254
|
+
// manual refresh. `refetchIntervalInBackground` stays false (default)
|
|
255
|
+
// so polling pauses on a hidden tab.
|
|
256
|
+
refetchInterval,
|
|
248
257
|
queryFn: async () => {
|
|
249
258
|
const response = await embedAuthedFetch(LIST_ENGAGEMENTS_ENDPOINT, {
|
|
250
259
|
method: "POST",
|
|
@@ -260,7 +269,20 @@ function useTicketEngagements(externalTicketId, enabled = true) {
|
|
|
260
269
|
});
|
|
261
270
|
return {
|
|
262
271
|
engagements: query.data ?? [],
|
|
263
|
-
|
|
272
|
+
// Loading-state truth that prevents the "body → blink → skeleton → data"
|
|
273
|
+
// double-flash. The bug: `useChatIdentity` starts at anon defaults and
|
|
274
|
+
// resolves async, so on the first render `queryEnabled` is false and the
|
|
275
|
+
// OLD `queryEnabled && query.isLoading` returned FALSE — the panel rendered
|
|
276
|
+
// the ticket body, THEN identity resolved, the query enabled, isLoading
|
|
277
|
+
// flipped true → skeleton appeared (the blink), then data landed.
|
|
278
|
+
//
|
|
279
|
+
// Fix: for a fetchable ticket we are "loading" whenever we don't yet have
|
|
280
|
+
// the timeline to show — that includes the window while identity is still
|
|
281
|
+
// resolving (so we skeleton from the FIRST render, never the body) AND the
|
|
282
|
+
// cold query fetch (`data === undefined`). A background poll keeps
|
|
283
|
+
// `query.data` defined, so it never re-flashes the skeleton. Non-fetchable
|
|
284
|
+
// (optimistic/disabled) or a resolved-anon viewer → not loading.
|
|
285
|
+
isLoading: fetchable && (identity.isLoading || queryEnabled && query.data === void 0),
|
|
264
286
|
isFetching: query.isFetching,
|
|
265
287
|
error: query.error ?? null,
|
|
266
288
|
refetch: () => {
|
|
@@ -305,8 +327,134 @@ function TicketLinkedDeliveryCard({
|
|
|
305
327
|
);
|
|
306
328
|
}
|
|
307
329
|
|
|
308
|
-
// src/components/tickets/ticket-
|
|
330
|
+
// src/components/tickets/ticket-reply-composer.tsx
|
|
331
|
+
init_button();
|
|
332
|
+
import { useCallback, useState as useState2 } from "react";
|
|
309
333
|
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
334
|
+
function TicketReplyComposer({
|
|
335
|
+
ticket,
|
|
336
|
+
busy,
|
|
337
|
+
supportSystemDown,
|
|
338
|
+
onSendMessage,
|
|
339
|
+
onClose
|
|
340
|
+
}) {
|
|
341
|
+
const [resolution, setResolution] = useState2("");
|
|
342
|
+
const [closeDialogOpen, setCloseDialogOpen] = useState2(false);
|
|
343
|
+
const attachments = useChatAttachments();
|
|
344
|
+
const ticketRef = { id: ticket.id, external_id: ticket.external_id };
|
|
345
|
+
const hasReadyFiles = attachments.readyAttachments.length > 0;
|
|
346
|
+
const handleSend = useCallback(
|
|
347
|
+
async (text) => {
|
|
348
|
+
const ref = { id: ticket.id, external_id: ticket.external_id };
|
|
349
|
+
const ok = await onSendMessage(ref, text.trim(), attachments.readyAttachments);
|
|
350
|
+
if (ok) attachments.clear();
|
|
351
|
+
return ok;
|
|
352
|
+
},
|
|
353
|
+
// Depend on the reactive projections, not the whole bag (a fresh object each
|
|
354
|
+
// render). `readyAttachments` is memo-stable; `clear` is callback-stable.
|
|
355
|
+
[
|
|
356
|
+
onSendMessage,
|
|
357
|
+
ticket.id,
|
|
358
|
+
ticket.external_id,
|
|
359
|
+
attachments.readyAttachments,
|
|
360
|
+
attachments.clear
|
|
361
|
+
]
|
|
362
|
+
);
|
|
363
|
+
const confirmClose = async () => {
|
|
364
|
+
setCloseDialogOpen(false);
|
|
365
|
+
await onClose(ticketRef, resolution.trim() || void 0);
|
|
366
|
+
setResolution("");
|
|
367
|
+
};
|
|
368
|
+
const disabled = busy || supportSystemDown;
|
|
369
|
+
return /* @__PURE__ */ jsxs2("div", { className: "flex flex-col gap-2", children: [
|
|
370
|
+
/* @__PURE__ */ jsx3(
|
|
371
|
+
ChatAttachmentChipStrip,
|
|
372
|
+
{
|
|
373
|
+
attachments: attachments.attachments,
|
|
374
|
+
onRemove: attachments.removeAttachment,
|
|
375
|
+
disabled,
|
|
376
|
+
size: "compact"
|
|
377
|
+
}
|
|
378
|
+
),
|
|
379
|
+
/* @__PURE__ */ jsx3(
|
|
380
|
+
ChatInput,
|
|
381
|
+
{
|
|
382
|
+
fullWidth: true,
|
|
383
|
+
autoFocus: true,
|
|
384
|
+
placeholder: "Type a reply\u2026",
|
|
385
|
+
sending: busy || attachments.hasInflightUploads,
|
|
386
|
+
disabled: supportSystemDown,
|
|
387
|
+
allowEmptySend: hasReadyFiles,
|
|
388
|
+
maxLength: TICKET_TEXT_MAX_CHARS,
|
|
389
|
+
onSend: handleSend
|
|
390
|
+
}
|
|
391
|
+
),
|
|
392
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2 w-full", children: [
|
|
393
|
+
!supportSystemDown && /* @__PURE__ */ jsx3(
|
|
394
|
+
ChatAttachmentAddButton,
|
|
395
|
+
{
|
|
396
|
+
attachmentsEnabled: true,
|
|
397
|
+
attachmentsCount: attachments.attachments.length,
|
|
398
|
+
onAddFiles: attachments.addFiles,
|
|
399
|
+
disabled,
|
|
400
|
+
size: "compact"
|
|
401
|
+
}
|
|
402
|
+
),
|
|
403
|
+
/* @__PURE__ */ jsx3("div", { className: "flex-1 min-w-0" }),
|
|
404
|
+
/* @__PURE__ */ jsx3(
|
|
405
|
+
Button,
|
|
406
|
+
{
|
|
407
|
+
type: "button",
|
|
408
|
+
variant: "transparent",
|
|
409
|
+
size: "small",
|
|
410
|
+
onClick: () => setCloseDialogOpen(true),
|
|
411
|
+
disabled,
|
|
412
|
+
className: "text-ods-text-secondary hover:text-ods-text-primary",
|
|
413
|
+
children: "Close ticket"
|
|
414
|
+
}
|
|
415
|
+
)
|
|
416
|
+
] }),
|
|
417
|
+
/* @__PURE__ */ jsx3(AlertDialog, { open: closeDialogOpen, onOpenChange: setCloseDialogOpen, children: /* @__PURE__ */ jsxs2(AlertDialogContent, { className: "bg-ods-card border-ods-border", children: [
|
|
418
|
+
/* @__PURE__ */ jsxs2(AlertDialogHeader, { children: [
|
|
419
|
+
/* @__PURE__ */ jsx3(AlertDialogTitle, { className: "text-ods-text-primary", children: "Close this ticket?" }),
|
|
420
|
+
/* @__PURE__ */ jsx3(AlertDialogDescription, { className: "text-ods-text-secondary", children: "Add an optional resolution note below. You can reopen the ticket later if needed." })
|
|
421
|
+
] }),
|
|
422
|
+
/* @__PURE__ */ jsx3(
|
|
423
|
+
Textarea,
|
|
424
|
+
{
|
|
425
|
+
value: resolution,
|
|
426
|
+
onChange: (e) => setResolution(e.target.value),
|
|
427
|
+
placeholder: "Resolution (optional)",
|
|
428
|
+
rows: 3,
|
|
429
|
+
maxLength: TICKET_TEXT_MAX_CHARS,
|
|
430
|
+
className: "mt-2"
|
|
431
|
+
}
|
|
432
|
+
),
|
|
433
|
+
/* @__PURE__ */ jsxs2(AlertDialogFooter, { children: [
|
|
434
|
+
/* @__PURE__ */ jsx3(
|
|
435
|
+
AlertDialogCancel,
|
|
436
|
+
{
|
|
437
|
+
disabled: busy,
|
|
438
|
+
className: "bg-transparent border-ods-border text-ods-text-primary hover:bg-ods-border",
|
|
439
|
+
children: "Cancel"
|
|
440
|
+
}
|
|
441
|
+
),
|
|
442
|
+
/* @__PURE__ */ jsx3(
|
|
443
|
+
AlertDialogAction,
|
|
444
|
+
{
|
|
445
|
+
onClick: () => void confirmClose(),
|
|
446
|
+
disabled: busy,
|
|
447
|
+
className: "bg-ods-accent text-ods-text-on-accent hover:bg-ods-accent-hover",
|
|
448
|
+
children: "Close ticket"
|
|
449
|
+
}
|
|
450
|
+
)
|
|
451
|
+
] })
|
|
452
|
+
] }) })
|
|
453
|
+
] });
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// src/components/tickets/ticket-detail-drawer.tsx
|
|
457
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
310
458
|
function TicketDetailDrawer({
|
|
311
459
|
ticket,
|
|
312
460
|
busy,
|
|
@@ -319,22 +467,22 @@ function TicketDetailDrawer({
|
|
|
319
467
|
onClearReplyError
|
|
320
468
|
}) {
|
|
321
469
|
const isClosed = (ticket.status ?? "").toUpperCase() === "CLOSED";
|
|
322
|
-
return /* @__PURE__ */
|
|
323
|
-
/* @__PURE__ */
|
|
324
|
-
ticket.clickup && /* @__PURE__ */
|
|
325
|
-
/* @__PURE__ */
|
|
326
|
-
/* @__PURE__ */
|
|
327
|
-
/* @__PURE__ */
|
|
470
|
+
return /* @__PURE__ */ jsxs3("div", { className: "bg-ods-card border-t border-ods-border px-4 py-4 flex flex-col gap-4", children: [
|
|
471
|
+
/* @__PURE__ */ jsx4(AssignedAgentRow, { assignedOwner: ticket.assignedOwner }),
|
|
472
|
+
ticket.clickup && /* @__PURE__ */ jsx4(TicketLinkedDeliveryCard, { clickup: ticket.clickup }),
|
|
473
|
+
/* @__PURE__ */ jsxs3("div", { children: [
|
|
474
|
+
/* @__PURE__ */ jsx4("p", { className: "text-xs font-medium text-ods-text-secondary mb-2 uppercase tracking-wider", children: "Conversation" }),
|
|
475
|
+
/* @__PURE__ */ jsx4(TicketTimelinePanel, { ticket })
|
|
328
476
|
] }),
|
|
329
|
-
/* @__PURE__ */
|
|
330
|
-
replyError && /* @__PURE__ */
|
|
477
|
+
/* @__PURE__ */ jsxs3("div", { className: "border-t border-ods-border pt-4", children: [
|
|
478
|
+
replyError && /* @__PURE__ */ jsx4(
|
|
331
479
|
ReplyFailureBanner,
|
|
332
480
|
{
|
|
333
481
|
error: replyError,
|
|
334
482
|
onDismiss: onClearReplyError ?? (() => void 0)
|
|
335
483
|
}
|
|
336
484
|
),
|
|
337
|
-
isClosed ? /* @__PURE__ */
|
|
485
|
+
isClosed ? /* @__PURE__ */ jsx4(
|
|
338
486
|
ReopenAction,
|
|
339
487
|
{
|
|
340
488
|
ticketRef: { id: ticket.id, external_id: ticket.external_id },
|
|
@@ -343,37 +491,49 @@ function TicketDetailDrawer({
|
|
|
343
491
|
onReopen,
|
|
344
492
|
onActionCollapsed
|
|
345
493
|
}
|
|
346
|
-
) : /* @__PURE__ */
|
|
347
|
-
|
|
494
|
+
) : /* @__PURE__ */ jsx4(
|
|
495
|
+
TicketReplyComposer,
|
|
348
496
|
{
|
|
349
497
|
ticket,
|
|
350
498
|
busy,
|
|
351
499
|
supportSystemDown,
|
|
352
500
|
onSendMessage,
|
|
353
|
-
onClose
|
|
354
|
-
onActionCollapsed
|
|
501
|
+
onClose
|
|
355
502
|
}
|
|
356
503
|
)
|
|
357
504
|
] })
|
|
358
505
|
] });
|
|
359
506
|
}
|
|
360
507
|
var TURN_SEPARATOR_RE = /[\s]{1,16}---[\s]{1,16}/g;
|
|
508
|
+
var TICKET_FEED_FRAME = "bg-ods-card border border-ods-border rounded-[6px] overflow-y-auto w-full";
|
|
509
|
+
var TICKET_FEED_HEIGHT = "h-[60vh] md:h-[420px]";
|
|
510
|
+
var TICKET_FEED_INNER = "flex flex-col gap-4 md:gap-6 px-4 md:px-6 py-4 md:py-6";
|
|
511
|
+
var TICKET_FEED_SKELETON_ROWS = 6;
|
|
361
512
|
function TicketTimelinePanel({ ticket }) {
|
|
362
513
|
const identity = useChatIdentity();
|
|
363
514
|
const externalId = isOptimistic(ticket) ? null : ticket.external_id;
|
|
364
|
-
const { engagements, isLoading } = useTicketEngagements(
|
|
515
|
+
const { engagements, isLoading } = useTicketEngagements(
|
|
516
|
+
externalId,
|
|
517
|
+
!!externalId,
|
|
518
|
+
TICKET_LIVE_POLL_MS
|
|
519
|
+
);
|
|
520
|
+
const { scrollRef, contentRef } = useStickToBottom({ initial: "instant", resize: "smooth" });
|
|
365
521
|
const bodyTurns = ticket.body ? ticket.body.split(TURN_SEPARATOR_RE).map((t) => t.trim()).filter(Boolean) : [];
|
|
522
|
+
const customerEngagementBodies = new Set(
|
|
523
|
+
engagements.filter((e) => e.authorRole === "customer").map((e) => (e.body ?? "").trim()).filter(Boolean)
|
|
524
|
+
);
|
|
525
|
+
const suppressBodyTurnZero = bodyTurns.length > 0 && customerEngagementBodies.has(bodyTurns[0]);
|
|
366
526
|
const sessionEmailLower = identity.user?.email?.trim().toLowerCase() ?? null;
|
|
367
527
|
const isViewerTheCustomer = !!sessionEmailLower && ticket.customer_emails.some((e) => e.trim().toLowerCase() === sessionEmailLower);
|
|
368
528
|
const viewerName = identity.user?.name?.trim() || null;
|
|
369
529
|
const ticketCustomerName = ticket.customer_name?.trim() || null;
|
|
370
530
|
const customerName = (isViewerTheCustomer ? viewerName : null) || ticketCustomerName || viewerName || identity.user?.email || "You";
|
|
371
531
|
const customerAvatar = isViewerTheCustomer ? identity.user?.avatarUrl ?? void 0 : void 0;
|
|
532
|
+
if (isLoading) {
|
|
533
|
+
return /* @__PURE__ */ jsx4("div", { className: `${TICKET_FEED_FRAME} ${TICKET_FEED_HEIGHT}`, children: /* @__PURE__ */ jsx4("div", { className: TICKET_FEED_INNER, children: Array.from({ length: TICKET_FEED_SKELETON_ROWS }, (_, i) => /* @__PURE__ */ jsx4(ChatMessageRowSkeleton, {}, i)) }) });
|
|
534
|
+
}
|
|
372
535
|
if (bodyTurns.length === 0 && engagements.length === 0) {
|
|
373
|
-
|
|
374
|
-
return /* @__PURE__ */ jsx3(ConversationCardRowSkeletonList, { rows: 2 });
|
|
375
|
-
}
|
|
376
|
-
return /* @__PURE__ */ jsx3(
|
|
536
|
+
return /* @__PURE__ */ jsx4(
|
|
377
537
|
EmptyState,
|
|
378
538
|
{
|
|
379
539
|
type: "generic",
|
|
@@ -383,19 +543,17 @@ function TicketTimelinePanel({ ticket }) {
|
|
|
383
543
|
}
|
|
384
544
|
);
|
|
385
545
|
}
|
|
386
|
-
return /* @__PURE__ */
|
|
546
|
+
return /* @__PURE__ */ jsx4("div", { ref: scrollRef, className: `${TICKET_FEED_FRAME} ${TICKET_FEED_HEIGHT}`, children: /* @__PURE__ */ jsxs3("div", { ref: contentRef, className: TICKET_FEED_INNER, children: [
|
|
387
547
|
bodyTurns.map((turn, i) => {
|
|
548
|
+
if (i === 0 && suppressBodyTurnZero) return null;
|
|
388
549
|
const isResolution = turn.startsWith("[Resolution]");
|
|
389
|
-
const role = i === 0 ? "Original message" : isResolution ? "Resolution" : `Update ${i}`;
|
|
390
550
|
const text = isResolution ? turn.replace(/^\[Resolution\]\s*/, "") : turn;
|
|
391
|
-
return /* @__PURE__ */
|
|
392
|
-
|
|
551
|
+
return /* @__PURE__ */ jsx4(
|
|
552
|
+
ChatMessageRow,
|
|
393
553
|
{
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
body: text,
|
|
398
|
-
variant: "current-user"
|
|
554
|
+
displayName: customerName,
|
|
555
|
+
avatarUrl: customerAvatar,
|
|
556
|
+
body: text
|
|
399
557
|
},
|
|
400
558
|
`body-${i}-${turn.slice(0, 24)}`
|
|
401
559
|
);
|
|
@@ -418,32 +576,30 @@ function TicketTimelinePanel({ ticket }) {
|
|
|
418
576
|
author = "Support team";
|
|
419
577
|
avatarSrc = void 0;
|
|
420
578
|
}
|
|
421
|
-
|
|
422
|
-
|
|
579
|
+
const engAttachments = mapEngagementAttachments(eng.attachments);
|
|
580
|
+
return /* @__PURE__ */ jsx4(
|
|
581
|
+
ChatMessageRow,
|
|
423
582
|
{
|
|
424
|
-
author,
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
timestamp: eng.createdAt,
|
|
583
|
+
displayName: author,
|
|
584
|
+
avatarUrl: avatarSrc,
|
|
585
|
+
timeLabel: eng.createdAt ? formatRelativeTime(eng.createdAt) : null,
|
|
428
586
|
body: stripAttachmentsPreamble(eng.body ?? ""),
|
|
429
|
-
|
|
430
|
-
variant: isCustomer ? "current-user" : "support"
|
|
587
|
+
footer: engAttachments.length > 0 ? /* @__PURE__ */ jsx4("div", { className: "mt-2", children: /* @__PURE__ */ jsx4(TicketAttachmentsList, { attachments: engAttachments, size: "compact" }) }) : null
|
|
431
588
|
},
|
|
432
589
|
eng.id
|
|
433
590
|
);
|
|
434
|
-
})
|
|
435
|
-
|
|
436
|
-
// content rendered — drawer is showing the customer's body
|
|
437
|
-
// turns + cached engagements while a background refetch is
|
|
438
|
-
// in flight. Single row keeps the placeholder modest.
|
|
439
|
-
/* @__PURE__ */ jsx3(ConversationCardRowSkeletonList, { rows: 1 })
|
|
440
|
-
] });
|
|
591
|
+
})
|
|
592
|
+
] }) });
|
|
441
593
|
}
|
|
442
594
|
function mapEngagementAttachments(files) {
|
|
443
595
|
return files.map((f) => ({
|
|
444
596
|
id: f.id,
|
|
445
597
|
fileName: f.name ?? `file-${f.id}`,
|
|
446
598
|
fileSize: f.size ? formatBytes(f.size) : "",
|
|
599
|
+
// Show an inline thumbnail for image attachments (the signed `url` is a
|
|
600
|
+
// viewable URL). Non-images fall back to the file-type icon. SquareAvatar
|
|
601
|
+
// degrades to initials on a broken/expired image URL.
|
|
602
|
+
thumbnailSrc: f.url && (f.mime?.startsWith("image/") ?? false) ? f.url : void 0,
|
|
447
603
|
onDownload: f.url ? () => window.open(f.url, "_blank", "noopener,noreferrer") : void 0
|
|
448
604
|
}));
|
|
449
605
|
}
|
|
@@ -466,10 +622,12 @@ function ReopenAction({
|
|
|
466
622
|
const handleReopen = async () => {
|
|
467
623
|
void await onReopen(ticketRef);
|
|
468
624
|
};
|
|
469
|
-
return /* @__PURE__ */
|
|
625
|
+
return /* @__PURE__ */ jsx4("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx4(
|
|
470
626
|
Button,
|
|
471
627
|
{
|
|
472
628
|
type: "button",
|
|
629
|
+
variant: "outline",
|
|
630
|
+
size: "small",
|
|
473
631
|
onClick: () => void handleReopen(),
|
|
474
632
|
disabled: busy || supportSystemDown,
|
|
475
633
|
loading: busy,
|
|
@@ -477,146 +635,19 @@ function ReopenAction({
|
|
|
477
635
|
}
|
|
478
636
|
) });
|
|
479
637
|
}
|
|
480
|
-
function OpenActions({
|
|
481
|
-
ticket,
|
|
482
|
-
busy,
|
|
483
|
-
supportSystemDown,
|
|
484
|
-
onSendMessage,
|
|
485
|
-
onClose,
|
|
486
|
-
onActionCollapsed
|
|
487
|
-
}) {
|
|
488
|
-
const [messageText, setMessageText] = useState2("");
|
|
489
|
-
const [resolution, setResolution] = useState2("");
|
|
490
|
-
const [closeDialogOpen, setCloseDialogOpen] = useState2(false);
|
|
491
|
-
const attachments = useChatAttachments();
|
|
492
|
-
const disabled = busy || supportSystemDown;
|
|
493
|
-
const ticketRef = { id: ticket.id, external_id: ticket.external_id };
|
|
494
|
-
const hasText = messageText.trim().length > 0;
|
|
495
|
-
const hasReadyFiles = attachments.readyAttachments.length > 0;
|
|
496
|
-
const canSend = !disabled && (hasText || hasReadyFiles) && !attachments.hasInflightUploads;
|
|
497
|
-
const sendMessage = async () => {
|
|
498
|
-
if (!canSend) return;
|
|
499
|
-
const ok = await onSendMessage(ticketRef, messageText.trim(), attachments.readyAttachments);
|
|
500
|
-
if (ok) {
|
|
501
|
-
setMessageText("");
|
|
502
|
-
attachments.clear();
|
|
503
|
-
}
|
|
504
|
-
};
|
|
505
|
-
const confirmClose = async () => {
|
|
506
|
-
setCloseDialogOpen(false);
|
|
507
|
-
await onClose(ticketRef, resolution.trim() || void 0);
|
|
508
|
-
setResolution("");
|
|
509
|
-
};
|
|
510
|
-
return /* @__PURE__ */ jsxs2("div", { className: "flex flex-col gap-3", children: [
|
|
511
|
-
/* @__PURE__ */ jsxs2("div", { className: "flex flex-col gap-2", children: [
|
|
512
|
-
/* @__PURE__ */ jsx3(
|
|
513
|
-
Textarea,
|
|
514
|
-
{
|
|
515
|
-
value: messageText,
|
|
516
|
-
onChange: (e) => setMessageText(e.target.value),
|
|
517
|
-
placeholder: "Type a reply\u2026 (attach files if needed)",
|
|
518
|
-
disabled,
|
|
519
|
-
rows: 3,
|
|
520
|
-
maxLength: TICKET_TEXT_MAX_CHARS
|
|
521
|
-
}
|
|
522
|
-
),
|
|
523
|
-
/* @__PURE__ */ jsx3(
|
|
524
|
-
ChatAttachmentChipStrip,
|
|
525
|
-
{
|
|
526
|
-
attachments: attachments.attachments,
|
|
527
|
-
onRemove: attachments.removeAttachment,
|
|
528
|
-
disabled
|
|
529
|
-
}
|
|
530
|
-
),
|
|
531
|
-
/* @__PURE__ */ jsxs2("div", { className: "flex justify-between items-center gap-2 flex-wrap", children: [
|
|
532
|
-
/* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2", children: [
|
|
533
|
-
/* @__PURE__ */ jsx3(
|
|
534
|
-
ChatAttachmentAddButton,
|
|
535
|
-
{
|
|
536
|
-
attachmentsEnabled: !supportSystemDown,
|
|
537
|
-
attachmentsCount: attachments.attachments.length,
|
|
538
|
-
onAddFiles: attachments.addFiles,
|
|
539
|
-
disabled
|
|
540
|
-
}
|
|
541
|
-
),
|
|
542
|
-
/* @__PURE__ */ jsx3("span", { className: "text-xs text-ods-text-secondary", children: "Attach files" })
|
|
543
|
-
] }),
|
|
544
|
-
/* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2", children: [
|
|
545
|
-
/* @__PURE__ */ jsx3(
|
|
546
|
-
Button,
|
|
547
|
-
{
|
|
548
|
-
type: "button",
|
|
549
|
-
variant: "transparent",
|
|
550
|
-
onClick: () => setCloseDialogOpen(true),
|
|
551
|
-
disabled,
|
|
552
|
-
className: "bg-ods-error hover:bg-ods-error-hover text-white border-transparent",
|
|
553
|
-
children: "Close ticket"
|
|
554
|
-
}
|
|
555
|
-
),
|
|
556
|
-
/* @__PURE__ */ jsx3(
|
|
557
|
-
Button,
|
|
558
|
-
{
|
|
559
|
-
type: "button",
|
|
560
|
-
onClick: () => void sendMessage(),
|
|
561
|
-
disabled: !canSend,
|
|
562
|
-
loading: busy,
|
|
563
|
-
children: "Send reply"
|
|
564
|
-
}
|
|
565
|
-
)
|
|
566
|
-
] })
|
|
567
|
-
] })
|
|
568
|
-
] }),
|
|
569
|
-
/* @__PURE__ */ jsx3(AlertDialog, { open: closeDialogOpen, onOpenChange: setCloseDialogOpen, children: /* @__PURE__ */ jsxs2(AlertDialogContent, { className: "bg-ods-card border-ods-border", children: [
|
|
570
|
-
/* @__PURE__ */ jsxs2(AlertDialogHeader, { children: [
|
|
571
|
-
/* @__PURE__ */ jsx3(AlertDialogTitle, { className: "text-ods-text-primary font-['DM_Sans'] text-[20px] font-semibold", children: "Close this ticket?" }),
|
|
572
|
-
/* @__PURE__ */ jsx3(AlertDialogDescription, { className: "text-ods-text-secondary font-['DM_Sans'] text-[14px]", children: "Add an optional resolution note below. You can reopen the ticket later if needed." })
|
|
573
|
-
] }),
|
|
574
|
-
/* @__PURE__ */ jsx3(
|
|
575
|
-
Textarea,
|
|
576
|
-
{
|
|
577
|
-
value: resolution,
|
|
578
|
-
onChange: (e) => setResolution(e.target.value),
|
|
579
|
-
placeholder: "Resolution (optional)",
|
|
580
|
-
rows: 3,
|
|
581
|
-
maxLength: TICKET_TEXT_MAX_CHARS,
|
|
582
|
-
className: "mt-2"
|
|
583
|
-
}
|
|
584
|
-
),
|
|
585
|
-
/* @__PURE__ */ jsxs2(AlertDialogFooter, { children: [
|
|
586
|
-
/* @__PURE__ */ jsx3(
|
|
587
|
-
AlertDialogCancel,
|
|
588
|
-
{
|
|
589
|
-
disabled: busy,
|
|
590
|
-
className: "bg-transparent border-ods-border text-ods-text-primary hover:bg-ods-border",
|
|
591
|
-
children: "Cancel"
|
|
592
|
-
}
|
|
593
|
-
),
|
|
594
|
-
/* @__PURE__ */ jsx3(
|
|
595
|
-
AlertDialogAction,
|
|
596
|
-
{
|
|
597
|
-
onClick: () => void confirmClose(),
|
|
598
|
-
disabled: busy,
|
|
599
|
-
className: "bg-ods-error hover:bg-ods-error-hover text-white",
|
|
600
|
-
children: "Close ticket"
|
|
601
|
-
}
|
|
602
|
-
)
|
|
603
|
-
] })
|
|
604
|
-
] }) })
|
|
605
|
-
] });
|
|
606
|
-
}
|
|
607
638
|
function ReplyFailureBanner({
|
|
608
639
|
error,
|
|
609
640
|
onDismiss
|
|
610
641
|
}) {
|
|
611
|
-
return /* @__PURE__ */
|
|
642
|
+
return /* @__PURE__ */ jsxs3(
|
|
612
643
|
"div",
|
|
613
644
|
{
|
|
614
645
|
role: "status",
|
|
615
646
|
"aria-live": "polite",
|
|
616
647
|
className: "mb-3 flex items-start gap-3 rounded-md border border-ods-attention-red-error bg-ods-attention-red-error-secondary px-3 py-2 text-sm text-ods-attention-red-error",
|
|
617
648
|
children: [
|
|
618
|
-
/* @__PURE__ */
|
|
619
|
-
/* @__PURE__ */
|
|
649
|
+
/* @__PURE__ */ jsx4("span", { className: "font-medium leading-snug", children: error.message }),
|
|
650
|
+
/* @__PURE__ */ jsx4(
|
|
620
651
|
Button,
|
|
621
652
|
{
|
|
622
653
|
type: "button",
|
|
@@ -637,10 +668,10 @@ function AssignedAgentRow({
|
|
|
637
668
|
const trimmedName = assignedOwner?.name?.trim() || null;
|
|
638
669
|
const emailFallback = assignedOwner?.email?.trim() || null;
|
|
639
670
|
const displayLabel = trimmedName ?? (emailFallback ? emailFallback.split("@")[0] : null);
|
|
640
|
-
return /* @__PURE__ */
|
|
641
|
-
/* @__PURE__ */
|
|
642
|
-
displayLabel ? /* @__PURE__ */
|
|
643
|
-
/* @__PURE__ */
|
|
671
|
+
return /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2 text-xs", children: [
|
|
672
|
+
/* @__PURE__ */ jsx4("span", { className: "text-ods-text-secondary uppercase tracking-wider font-medium", children: "Assigned to" }),
|
|
673
|
+
displayLabel ? /* @__PURE__ */ jsxs3("span", { className: "flex items-center gap-1.5 text-ods-text-primary font-medium", children: [
|
|
674
|
+
/* @__PURE__ */ jsx4(
|
|
644
675
|
SquareAvatar,
|
|
645
676
|
{
|
|
646
677
|
size: "sm",
|
|
@@ -651,12 +682,12 @@ function AssignedAgentRow({
|
|
|
651
682
|
}
|
|
652
683
|
),
|
|
653
684
|
displayLabel
|
|
654
|
-
] }) : /* @__PURE__ */
|
|
685
|
+
] }) : /* @__PURE__ */ jsx4("span", { className: "text-ods-text-secondary italic", children: "Unassigned" })
|
|
655
686
|
] });
|
|
656
687
|
}
|
|
657
688
|
|
|
658
689
|
// src/components/tickets/ticket-row.tsx
|
|
659
|
-
import { jsx as
|
|
690
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
660
691
|
function TicketRow({
|
|
661
692
|
ticket,
|
|
662
693
|
expanded,
|
|
@@ -670,7 +701,7 @@ function TicketRow({
|
|
|
670
701
|
}) {
|
|
671
702
|
const optimistic = isOptimistic(ticket);
|
|
672
703
|
const rowRef = useRef(null);
|
|
673
|
-
const handleClick =
|
|
704
|
+
const handleClick = useCallback2(() => {
|
|
674
705
|
onToggle(ticket.id);
|
|
675
706
|
scrollElementIntoView(rowRef.current, {
|
|
676
707
|
adjustTargetY: (raw) => {
|
|
@@ -706,13 +737,13 @@ function TicketRow({
|
|
|
706
737
|
// when the task exists but its status hasn't synced yet.
|
|
707
738
|
linkedTaskLabel: ticket.clickup ? ticket.clickup.status ? ticket.clickup.status.replace(/\b\w/g, (c) => c.toUpperCase()) : "Linked work" : void 0
|
|
708
739
|
};
|
|
709
|
-
return /* @__PURE__ */
|
|
740
|
+
return /* @__PURE__ */ jsx5("div", { ref: rowRef, className: "scroll-mt-24", children: /* @__PURE__ */ jsxs4(
|
|
710
741
|
Collapsible,
|
|
711
742
|
{
|
|
712
743
|
open: expanded && !optimistic,
|
|
713
744
|
className: "border-b border-ods-border last:border-b-0",
|
|
714
745
|
children: [
|
|
715
|
-
/* @__PURE__ */
|
|
746
|
+
/* @__PURE__ */ jsx5(
|
|
716
747
|
ChatTicketItem,
|
|
717
748
|
{
|
|
718
749
|
ticket: tileData,
|
|
@@ -721,12 +752,12 @@ function TicketRow({
|
|
|
721
752
|
"aria-controls": `ticket-drawer-${ticket.id}`
|
|
722
753
|
}
|
|
723
754
|
),
|
|
724
|
-
/* @__PURE__ */
|
|
755
|
+
/* @__PURE__ */ jsx5(
|
|
725
756
|
CollapsibleContent2,
|
|
726
757
|
{
|
|
727
758
|
id: `ticket-drawer-${ticket.id}`,
|
|
728
759
|
className: "overflow-hidden data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down",
|
|
729
|
-
children: /* @__PURE__ */
|
|
760
|
+
children: /* @__PURE__ */ jsx5(
|
|
730
761
|
TicketDetailDrawer,
|
|
731
762
|
{
|
|
732
763
|
ticket,
|
|
@@ -758,6 +789,7 @@ function useTicketsList(filters) {
|
|
|
758
789
|
const pageSize = Math.max(1, Math.min(100, Math.floor(filters.pageSize ?? DEFAULT_PAGE_SIZE) || DEFAULT_PAGE_SIZE));
|
|
759
790
|
const enabled = !!customerEmail;
|
|
760
791
|
const identityKey = customerEmail || "anon";
|
|
792
|
+
const refetchInterval = filters.refetchInterval ?? false;
|
|
761
793
|
const query = useQuery2({
|
|
762
794
|
queryKey: ["tickets", "self", identityKey, search, statusFilter, page, pageSize],
|
|
763
795
|
enabled,
|
|
@@ -770,6 +802,10 @@ function useTicketsList(filters) {
|
|
|
770
802
|
gcTime: 0,
|
|
771
803
|
refetchOnMount: "always",
|
|
772
804
|
refetchOnWindowFocus: true,
|
|
805
|
+
// Live status: poll while the caller opts in (drawer open). Defaults to
|
|
806
|
+
// false. `refetchIntervalInBackground` stays false (the default) so polling
|
|
807
|
+
// pauses on a hidden tab — no wasted requests when the user tabs away.
|
|
808
|
+
refetchInterval,
|
|
773
809
|
queryFn: async () => {
|
|
774
810
|
const body = {
|
|
775
811
|
query: search,
|
|
@@ -831,7 +867,7 @@ function useTicketsList(filters) {
|
|
|
831
867
|
}
|
|
832
868
|
|
|
833
869
|
// src/components/tickets/hooks/use-ticket-actions.ts
|
|
834
|
-
import { useCallback as
|
|
870
|
+
import { useCallback as useCallback3, useEffect, useMemo, useRef as useRef2, useState as useState3 } from "react";
|
|
835
871
|
import { useQueryClient } from "@tanstack/react-query";
|
|
836
872
|
var TICKET_ACTION_ENDPOINT = "/api/chat/agent/ticket-action";
|
|
837
873
|
var REPLY_BANNER_CODES = /* @__PURE__ */ new Set([
|
|
@@ -848,14 +884,14 @@ function useTicketActions(options) {
|
|
|
848
884
|
const [isSubmittingForm, setIsSubmittingForm] = useState3(false);
|
|
849
885
|
const busyRowsRef = useRef2(/* @__PURE__ */ new Set());
|
|
850
886
|
const [busyRows, setBusyRows] = useState3(() => /* @__PURE__ */ new Set());
|
|
851
|
-
const setRowBusy =
|
|
887
|
+
const setRowBusy = useCallback3((id, busy) => {
|
|
852
888
|
if (busy) busyRowsRef.current.add(id);
|
|
853
889
|
else busyRowsRef.current.delete(id);
|
|
854
890
|
setBusyRows(new Set(busyRowsRef.current));
|
|
855
891
|
}, []);
|
|
856
|
-
const isRowBusy =
|
|
892
|
+
const isRowBusy = useCallback3((id) => busyRows.has(id), [busyRows]);
|
|
857
893
|
const [replyErrorByTicket, setReplyErrorByTicket] = useState3(() => /* @__PURE__ */ new Map());
|
|
858
|
-
const setReplyError =
|
|
894
|
+
const setReplyError = useCallback3(
|
|
859
895
|
(externalId, mapped) => {
|
|
860
896
|
setReplyErrorByTicket((prev) => {
|
|
861
897
|
const next = new Map(prev);
|
|
@@ -866,11 +902,11 @@ function useTicketActions(options) {
|
|
|
866
902
|
},
|
|
867
903
|
[]
|
|
868
904
|
);
|
|
869
|
-
const replyErrorFor =
|
|
905
|
+
const replyErrorFor = useCallback3(
|
|
870
906
|
(externalId) => replyErrorByTicket.get(externalId) ?? null,
|
|
871
907
|
[replyErrorByTicket]
|
|
872
908
|
);
|
|
873
|
-
const clearReplyError =
|
|
909
|
+
const clearReplyError = useCallback3(
|
|
874
910
|
(externalId) => setReplyError(externalId, null),
|
|
875
911
|
[setReplyError]
|
|
876
912
|
);
|
|
@@ -884,12 +920,12 @@ function useTicketActions(options) {
|
|
|
884
920
|
};
|
|
885
921
|
}, []);
|
|
886
922
|
const queueRef = useRef2(Promise.resolve());
|
|
887
|
-
const enqueue =
|
|
923
|
+
const enqueue = useCallback3((work) => {
|
|
888
924
|
const next = queueRef.current.then(work, work);
|
|
889
925
|
queueRef.current = next.catch(() => void 0);
|
|
890
926
|
return next;
|
|
891
927
|
}, []);
|
|
892
|
-
const executeTicketAction =
|
|
928
|
+
const executeTicketAction = useCallback3(
|
|
893
929
|
async (toolName, args) => {
|
|
894
930
|
const res = await embedAuthedFetch(TICKET_ACTION_ENDPOINT, {
|
|
895
931
|
method: "POST",
|
|
@@ -905,7 +941,7 @@ function useTicketActions(options) {
|
|
|
905
941
|
},
|
|
906
942
|
[]
|
|
907
943
|
);
|
|
908
|
-
const watchMirrorSync =
|
|
944
|
+
const watchMirrorSync = useCallback3(
|
|
909
945
|
(placeholderId, expectedTicketId) => {
|
|
910
946
|
const prior = watcherControllersRef.current.get(placeholderId);
|
|
911
947
|
if (prior) prior.abort();
|
|
@@ -952,7 +988,7 @@ function useTicketActions(options) {
|
|
|
952
988
|
[queryClient, removeOptimistic, toast2]
|
|
953
989
|
);
|
|
954
990
|
const lastUpdateErrorRef = useRef2(null);
|
|
955
|
-
const surfaceError =
|
|
991
|
+
const surfaceError = useCallback3(
|
|
956
992
|
(err, action) => {
|
|
957
993
|
const mapped = mapTicketActionError(err);
|
|
958
994
|
lastUpdateErrorRef.current = mapped;
|
|
@@ -966,7 +1002,7 @@ function useTicketActions(options) {
|
|
|
966
1002
|
},
|
|
967
1003
|
[toast2, onSupportSystemDown]
|
|
968
1004
|
);
|
|
969
|
-
const submitTicket =
|
|
1005
|
+
const submitTicket = useCallback3(
|
|
970
1006
|
async (input) => {
|
|
971
1007
|
if (formInFlightRef.current) return false;
|
|
972
1008
|
formInFlightRef.current = true;
|
|
@@ -1035,7 +1071,7 @@ function useTicketActions(options) {
|
|
|
1035
1071
|
surfaceError
|
|
1036
1072
|
]
|
|
1037
1073
|
);
|
|
1038
|
-
const updateTicket =
|
|
1074
|
+
const updateTicket = useCallback3(
|
|
1039
1075
|
async (ticket, serverArgs, successCopy, action) => {
|
|
1040
1076
|
if (busyRowsRef.current.has(ticket.id)) return false;
|
|
1041
1077
|
setRowBusy(ticket.id, true);
|
|
@@ -1081,7 +1117,7 @@ function useTicketActions(options) {
|
|
|
1081
1117
|
// and cascade-recreate addNote/closeTicket/etc.
|
|
1082
1118
|
[setRowBusy, enqueue, executeTicketAction, queryClient, toast2, surfaceError, removeTicketFromCache]
|
|
1083
1119
|
);
|
|
1084
|
-
const sendMessage =
|
|
1120
|
+
const sendMessage = useCallback3(
|
|
1085
1121
|
async (ticket, text, attachments) => {
|
|
1086
1122
|
const trimmed = text.trim();
|
|
1087
1123
|
const hasText = trimmed.length > 0;
|
|
@@ -1110,7 +1146,7 @@ function useTicketActions(options) {
|
|
|
1110
1146
|
},
|
|
1111
1147
|
[updateTicket, clearReplyError, setReplyError]
|
|
1112
1148
|
);
|
|
1113
|
-
const closeTicket =
|
|
1149
|
+
const closeTicket = useCallback3(
|
|
1114
1150
|
(ticket, resolution) => updateTicket(
|
|
1115
1151
|
ticket,
|
|
1116
1152
|
{
|
|
@@ -1122,7 +1158,7 @@ function useTicketActions(options) {
|
|
|
1122
1158
|
),
|
|
1123
1159
|
[updateTicket]
|
|
1124
1160
|
);
|
|
1125
|
-
const reopenTicket =
|
|
1161
|
+
const reopenTicket = useCallback3(
|
|
1126
1162
|
(ticket) => updateTicket(ticket, { status: "OPEN" }, TOAST_COPY.reopen_success, "reopen ticket"),
|
|
1127
1163
|
[updateTicket]
|
|
1128
1164
|
);
|
|
@@ -1274,14 +1310,14 @@ function resolveErrorCode(bodyCode, status) {
|
|
|
1274
1310
|
}
|
|
1275
1311
|
|
|
1276
1312
|
// src/components/tickets/ticket-center.tsx
|
|
1277
|
-
import { jsx as
|
|
1313
|
+
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1278
1314
|
function TicketCenter({ toast: toast2 = toast } = {}) {
|
|
1279
1315
|
const identity = useChatIdentity();
|
|
1280
1316
|
if (identity.isLoading) {
|
|
1281
|
-
return /* @__PURE__ */
|
|
1317
|
+
return /* @__PURE__ */ jsx6(TicketCenterSkeleton, {});
|
|
1282
1318
|
}
|
|
1283
1319
|
if (identity.authTier === "anon" || !identity.user?.email) {
|
|
1284
|
-
return /* @__PURE__ */
|
|
1320
|
+
return /* @__PURE__ */ jsx6(
|
|
1285
1321
|
EmptyState,
|
|
1286
1322
|
{
|
|
1287
1323
|
type: "generic",
|
|
@@ -1291,7 +1327,7 @@ function TicketCenter({ toast: toast2 = toast } = {}) {
|
|
|
1291
1327
|
}
|
|
1292
1328
|
);
|
|
1293
1329
|
}
|
|
1294
|
-
return /* @__PURE__ */
|
|
1330
|
+
return /* @__PURE__ */ jsx6(
|
|
1295
1331
|
TicketCenterAuthed,
|
|
1296
1332
|
{
|
|
1297
1333
|
toast: toast2,
|
|
@@ -1310,14 +1346,14 @@ function TicketCenterAuthed({
|
|
|
1310
1346
|
const [optimisticTickets, setOptimisticTickets] = useState4([]);
|
|
1311
1347
|
const [expandedTicketId, setExpandedTicketId] = useState4(null);
|
|
1312
1348
|
const [supportSystemDown, setSupportSystemDown] = useState4(false);
|
|
1313
|
-
const prependOptimistic =
|
|
1349
|
+
const prependOptimistic = useCallback4((placeholder) => {
|
|
1314
1350
|
setOptimisticTickets((prev) => [placeholder, ...prev]);
|
|
1315
1351
|
}, []);
|
|
1316
|
-
const removeOptimistic =
|
|
1352
|
+
const removeOptimistic = useCallback4((placeholderId) => {
|
|
1317
1353
|
setOptimisticTickets((prev) => prev.filter((t) => t.id !== placeholderId));
|
|
1318
1354
|
setExpandedTicketId((prev) => prev === placeholderId ? null : prev);
|
|
1319
1355
|
}, []);
|
|
1320
|
-
const removeTicketFromCache =
|
|
1356
|
+
const removeTicketFromCache = useCallback4(
|
|
1321
1357
|
(ticketId) => {
|
|
1322
1358
|
queryClient.setQueriesData(
|
|
1323
1359
|
{ queryKey: ["tickets"] },
|
|
@@ -1334,12 +1370,12 @@ function TicketCenterAuthed({
|
|
|
1334
1370
|
toast: toast2,
|
|
1335
1371
|
onSupportSystemDown: () => setSupportSystemDown(true)
|
|
1336
1372
|
});
|
|
1337
|
-
const toggleRow =
|
|
1373
|
+
const toggleRow = useCallback4((id) => {
|
|
1338
1374
|
setExpandedTicketId((prev) => prev === id ? null : id);
|
|
1339
1375
|
}, []);
|
|
1340
1376
|
const merged = [...optimisticTickets, ...tickets];
|
|
1341
|
-
return /* @__PURE__ */
|
|
1342
|
-
/* @__PURE__ */
|
|
1377
|
+
return /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-6", children: [
|
|
1378
|
+
/* @__PURE__ */ jsx6(
|
|
1343
1379
|
TicketOpenForm,
|
|
1344
1380
|
{
|
|
1345
1381
|
onSubmit: (input) => actions.submitTicket(input),
|
|
@@ -1347,15 +1383,15 @@ function TicketCenterAuthed({
|
|
|
1347
1383
|
supportSystemDown
|
|
1348
1384
|
}
|
|
1349
1385
|
),
|
|
1350
|
-
/* @__PURE__ */
|
|
1351
|
-
/* @__PURE__ */
|
|
1352
|
-
/* @__PURE__ */
|
|
1353
|
-
/* @__PURE__ */
|
|
1354
|
-
lastUpdatedAt && /* @__PURE__ */
|
|
1386
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-2", children: [
|
|
1387
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between gap-3", children: [
|
|
1388
|
+
/* @__PURE__ */ jsx6("p", { className: "text-xs font-medium text-ods-text-secondary uppercase tracking-wider", children: "Your Current Tickets" }),
|
|
1389
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-3 text-xs text-ods-text-secondary", children: [
|
|
1390
|
+
lastUpdatedAt && /* @__PURE__ */ jsxs5("span", { children: [
|
|
1355
1391
|
"Updated ",
|
|
1356
1392
|
formatRelativeTime(new Date(lastUpdatedAt))
|
|
1357
1393
|
] }),
|
|
1358
|
-
/* @__PURE__ */
|
|
1394
|
+
/* @__PURE__ */ jsx6(
|
|
1359
1395
|
Button,
|
|
1360
1396
|
{
|
|
1361
1397
|
type: "button",
|
|
@@ -1364,12 +1400,12 @@ function TicketCenterAuthed({
|
|
|
1364
1400
|
onClick: refetch,
|
|
1365
1401
|
disabled: isFetching,
|
|
1366
1402
|
"aria-label": "Refresh ticket list",
|
|
1367
|
-
leftIcon: /* @__PURE__ */
|
|
1403
|
+
leftIcon: /* @__PURE__ */ jsx6(RefreshCw, { className: "h-4 w-4" })
|
|
1368
1404
|
}
|
|
1369
1405
|
)
|
|
1370
1406
|
] })
|
|
1371
1407
|
] }),
|
|
1372
|
-
isLoading ? /* @__PURE__ */
|
|
1408
|
+
isLoading ? /* @__PURE__ */ jsx6(TicketListSkeleton, {}) : merged.length === 0 ? /* @__PURE__ */ jsx6(Card, { className: "p-6", children: /* @__PURE__ */ jsx6(
|
|
1373
1409
|
EmptyState,
|
|
1374
1410
|
{
|
|
1375
1411
|
type: "generic",
|
|
@@ -1377,7 +1413,7 @@ function TicketCenterAuthed({
|
|
|
1377
1413
|
description: "Open one above to start the conversation.",
|
|
1378
1414
|
showCTA: false
|
|
1379
1415
|
}
|
|
1380
|
-
) }) : /* @__PURE__ */
|
|
1416
|
+
) }) : /* @__PURE__ */ jsx6(Card, { className: "overflow-hidden", children: merged.map((ticket) => /* @__PURE__ */ jsx6(
|
|
1381
1417
|
TicketRow,
|
|
1382
1418
|
{
|
|
1383
1419
|
ticket,
|
|
@@ -1396,34 +1432,34 @@ function TicketCenterAuthed({
|
|
|
1396
1432
|
] });
|
|
1397
1433
|
}
|
|
1398
1434
|
function TicketCenterSkeleton() {
|
|
1399
|
-
return /* @__PURE__ */
|
|
1400
|
-
/* @__PURE__ */
|
|
1401
|
-
/* @__PURE__ */
|
|
1402
|
-
/* @__PURE__ */
|
|
1403
|
-
/* @__PURE__ */
|
|
1435
|
+
return /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-6", children: [
|
|
1436
|
+
/* @__PURE__ */ jsxs5(Card, { className: "p-6", children: [
|
|
1437
|
+
/* @__PURE__ */ jsx6(Skeleton, { className: "h-7 w-48 mb-4" }),
|
|
1438
|
+
/* @__PURE__ */ jsx6(Skeleton, { className: "h-10 w-full mb-3" }),
|
|
1439
|
+
/* @__PURE__ */ jsx6(Skeleton, { className: "h-24 w-full" })
|
|
1404
1440
|
] }),
|
|
1405
|
-
/* @__PURE__ */
|
|
1441
|
+
/* @__PURE__ */ jsx6(TicketListSkeleton, {})
|
|
1406
1442
|
] });
|
|
1407
1443
|
}
|
|
1408
1444
|
function TicketListSkeleton() {
|
|
1409
|
-
return /* @__PURE__ */
|
|
1410
|
-
/* @__PURE__ */
|
|
1411
|
-
/* @__PURE__ */
|
|
1412
|
-
/* @__PURE__ */
|
|
1445
|
+
return /* @__PURE__ */ jsx6(Card, { className: "overflow-hidden", children: [0, 1, 2].map((i) => /* @__PURE__ */ jsxs5("div", { className: "h-20 px-4 flex items-center gap-4 border-b border-ods-border last:border-b-0", children: [
|
|
1446
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex-1 flex flex-col gap-2", children: [
|
|
1447
|
+
/* @__PURE__ */ jsx6(Skeleton, { className: "h-4 w-2/3" }),
|
|
1448
|
+
/* @__PURE__ */ jsx6(Skeleton, { className: "h-3 w-full" })
|
|
1413
1449
|
] }),
|
|
1414
|
-
/* @__PURE__ */
|
|
1415
|
-
/* @__PURE__ */
|
|
1450
|
+
/* @__PURE__ */ jsx6(Skeleton, { className: "h-8 w-20" }),
|
|
1451
|
+
/* @__PURE__ */ jsx6(Skeleton, { className: "h-8 w-16" })
|
|
1416
1452
|
] }, i)) });
|
|
1417
1453
|
}
|
|
1418
1454
|
|
|
1419
1455
|
// src/components/tickets/help-center-list.tsx
|
|
1420
|
-
import { useCallback as
|
|
1456
|
+
import { useCallback as useCallback6, useState as useState6 } from "react";
|
|
1421
1457
|
import { useQueryClient as useQueryClient3 } from "@tanstack/react-query";
|
|
1422
1458
|
init_unified_pagination();
|
|
1423
1459
|
|
|
1424
1460
|
// src/components/tickets/help-center-card.tsx
|
|
1425
|
-
import { useCallback as
|
|
1426
|
-
import { Fragment, jsx as
|
|
1461
|
+
import { useCallback as useCallback5, useEffect as useEffect2, useRef as useRef3 } from "react";
|
|
1462
|
+
import { Fragment, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1427
1463
|
var STICKY_HEADER_OFFSET_PX = 96;
|
|
1428
1464
|
function HelpCenterCard({
|
|
1429
1465
|
ticket,
|
|
@@ -1448,25 +1484,20 @@ function HelpCenterCard({
|
|
|
1448
1484
|
const isExpandable = !optimistic;
|
|
1449
1485
|
const isExpanded = expanded && isExpandable;
|
|
1450
1486
|
const rowRef = useRef3(null);
|
|
1451
|
-
const handleClick =
|
|
1487
|
+
const handleClick = useCallback5(() => {
|
|
1452
1488
|
onToggle(ticket.id);
|
|
1453
|
-
scrollElementIntoView(rowRef.current, {
|
|
1454
|
-
headerOffset: STICKY_HEADER_OFFSET_PX,
|
|
1455
|
-
adjustTargetY: (raw) => {
|
|
1456
|
-
if (!rowRef.current) return raw;
|
|
1457
|
-
const expandedDrawer = document.querySelector(
|
|
1458
|
-
'div[id^="help-center-drawer-"]'
|
|
1459
|
-
);
|
|
1460
|
-
if (!(expandedDrawer instanceof HTMLElement)) return raw;
|
|
1461
|
-
const drawerRect = expandedDrawer.getBoundingClientRect();
|
|
1462
|
-
const myRect = rowRef.current.getBoundingClientRect();
|
|
1463
|
-
if (drawerRect.bottom > myRect.top) return raw;
|
|
1464
|
-
return raw - drawerRect.height;
|
|
1465
|
-
}
|
|
1466
|
-
});
|
|
1467
1489
|
}, [onToggle, ticket.id]);
|
|
1468
|
-
|
|
1469
|
-
|
|
1490
|
+
useEffect2(() => {
|
|
1491
|
+
if (!isExpanded) return;
|
|
1492
|
+
const raf = requestAnimationFrame(() => {
|
|
1493
|
+
scrollElementIntoView(rowRef.current, {
|
|
1494
|
+
headerOffset: STICKY_HEADER_OFFSET_PX
|
|
1495
|
+
});
|
|
1496
|
+
});
|
|
1497
|
+
return () => cancelAnimationFrame(raf);
|
|
1498
|
+
}, [isExpanded]);
|
|
1499
|
+
const rightBadges = /* @__PURE__ */ jsxs6(Fragment, { children: [
|
|
1500
|
+
/* @__PURE__ */ jsx7(
|
|
1470
1501
|
StatusBadge,
|
|
1471
1502
|
{
|
|
1472
1503
|
text: rawStatus,
|
|
@@ -1475,7 +1506,7 @@ function HelpCenterCard({
|
|
|
1475
1506
|
className: "border border-ods-border"
|
|
1476
1507
|
}
|
|
1477
1508
|
),
|
|
1478
|
-
priority && /* @__PURE__ */
|
|
1509
|
+
priority && /* @__PURE__ */ jsx7(
|
|
1479
1510
|
StatusBadge,
|
|
1480
1511
|
{
|
|
1481
1512
|
text: priority,
|
|
@@ -1485,7 +1516,7 @@ function HelpCenterCard({
|
|
|
1485
1516
|
}
|
|
1486
1517
|
)
|
|
1487
1518
|
] });
|
|
1488
|
-
return /* @__PURE__ */
|
|
1519
|
+
return /* @__PURE__ */ jsxs6(
|
|
1489
1520
|
"div",
|
|
1490
1521
|
{
|
|
1491
1522
|
ref: rowRef,
|
|
@@ -1493,7 +1524,7 @@ function HelpCenterCard({
|
|
|
1493
1524
|
className: `border-b border-ods-border last:border-b-0 ${optimistic ? "opacity-60" : ""}`,
|
|
1494
1525
|
"aria-busy": optimistic || void 0,
|
|
1495
1526
|
children: [
|
|
1496
|
-
/* @__PURE__ */
|
|
1527
|
+
/* @__PURE__ */ jsx7(
|
|
1497
1528
|
"button",
|
|
1498
1529
|
{
|
|
1499
1530
|
type: "button",
|
|
@@ -1502,7 +1533,7 @@ function HelpCenterCard({
|
|
|
1502
1533
|
"aria-expanded": isExpandable ? isExpanded : void 0,
|
|
1503
1534
|
"aria-controls": isExpanded ? `help-center-drawer-${ticket.id}` : void 0,
|
|
1504
1535
|
className: "w-full text-left p-[12px] md:p-[16px] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ods-accent focus-visible:ring-inset disabled:cursor-default",
|
|
1505
|
-
children: /* @__PURE__ */
|
|
1536
|
+
children: /* @__PURE__ */ jsx7(
|
|
1506
1537
|
DevCardRowContent,
|
|
1507
1538
|
{
|
|
1508
1539
|
title,
|
|
@@ -1514,7 +1545,7 @@ function HelpCenterCard({
|
|
|
1514
1545
|
)
|
|
1515
1546
|
}
|
|
1516
1547
|
),
|
|
1517
|
-
isExpanded && /* @__PURE__ */
|
|
1548
|
+
isExpanded && /* @__PURE__ */ jsx7("div", { id: `help-center-drawer-${ticket.id}`, children: /* @__PURE__ */ jsx7(
|
|
1518
1549
|
TicketDetailDrawer,
|
|
1519
1550
|
{
|
|
1520
1551
|
ticket,
|
|
@@ -1540,31 +1571,31 @@ function mapPriorityScheme(priority) {
|
|
|
1540
1571
|
|
|
1541
1572
|
// src/components/tickets/help-center-create-form.tsx
|
|
1542
1573
|
import { useState as useState5 } from "react";
|
|
1543
|
-
import { jsx as
|
|
1574
|
+
import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1544
1575
|
var SUBJECT_MAX_CHARS = 200;
|
|
1545
1576
|
function HelpCenterCreateFormSkeleton() {
|
|
1546
|
-
return /* @__PURE__ */
|
|
1547
|
-
/* @__PURE__ */
|
|
1548
|
-
/* @__PURE__ */
|
|
1549
|
-
/* @__PURE__ */
|
|
1550
|
-
/* @__PURE__ */
|
|
1551
|
-
/* @__PURE__ */
|
|
1552
|
-
/* @__PURE__ */
|
|
1553
|
-
/* @__PURE__ */
|
|
1554
|
-
/* @__PURE__ */
|
|
1555
|
-
/* @__PURE__ */
|
|
1577
|
+
return /* @__PURE__ */ jsxs7("div", { className: "h-full flex flex-col border border-ods-border rounded-2xl md:rounded-3xl p-6 md:p-8 lg:p-10", children: [
|
|
1578
|
+
/* @__PURE__ */ jsx8("div", { className: "mb-6 md:mb-8", children: /* @__PURE__ */ jsx8("div", { className: "h-10 w-72 bg-ods-border rounded animate-pulse mb-3 md:mb-4" }) }),
|
|
1579
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex flex-col flex-grow space-y-4 md:space-y-6", children: [
|
|
1580
|
+
/* @__PURE__ */ jsx8("input", { type: "hidden", "aria-hidden": true }),
|
|
1581
|
+
/* @__PURE__ */ jsx8("input", { type: "hidden", "aria-hidden": true }),
|
|
1582
|
+
/* @__PURE__ */ jsx8("input", { type: "hidden", "aria-hidden": true }),
|
|
1583
|
+
/* @__PURE__ */ jsx8("input", { type: "hidden", "aria-hidden": true }),
|
|
1584
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex flex-col", children: [
|
|
1585
|
+
/* @__PURE__ */ jsx8("div", { className: "h-[27px] w-20 bg-ods-border rounded animate-pulse mb-1" }),
|
|
1586
|
+
/* @__PURE__ */ jsx8("div", { className: "h-12 w-full bg-ods-border rounded animate-pulse" })
|
|
1556
1587
|
] }),
|
|
1557
|
-
/* @__PURE__ */
|
|
1558
|
-
/* @__PURE__ */
|
|
1559
|
-
/* @__PURE__ */
|
|
1588
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex flex-col flex-grow", children: [
|
|
1589
|
+
/* @__PURE__ */ jsx8("div", { className: "h-[27px] w-32 bg-ods-border rounded animate-pulse mb-1" }),
|
|
1590
|
+
/* @__PURE__ */ jsx8("div", { className: "h-24 w-full bg-ods-border rounded animate-pulse flex-grow" })
|
|
1560
1591
|
] }),
|
|
1561
|
-
/* @__PURE__ */
|
|
1562
|
-
/* @__PURE__ */
|
|
1563
|
-
/* @__PURE__ */
|
|
1592
|
+
/* @__PURE__ */ jsx8("div", { className: "flex flex-col gap-2", children: /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-2", children: [
|
|
1593
|
+
/* @__PURE__ */ jsx8("div", { className: "h-7 w-7 bg-ods-border rounded animate-pulse shrink-0" }),
|
|
1594
|
+
/* @__PURE__ */ jsx8("div", { className: "h-4 w-40 bg-ods-border rounded animate-pulse" })
|
|
1564
1595
|
] }) }),
|
|
1565
|
-
/* @__PURE__ */
|
|
1566
|
-
/* @__PURE__ */
|
|
1567
|
-
/* @__PURE__ */
|
|
1596
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex flex-col md:flex-row gap-4 md:gap-6 items-center justify-end w-full pt-2 mt-auto", children: [
|
|
1597
|
+
/* @__PURE__ */ jsx8("div", { className: "h-4 w-72 bg-ods-border rounded animate-pulse" }),
|
|
1598
|
+
/* @__PURE__ */ jsx8("div", { className: "h-12 w-32 bg-ods-border rounded animate-pulse" })
|
|
1568
1599
|
] })
|
|
1569
1600
|
] })
|
|
1570
1601
|
] });
|
|
@@ -1577,12 +1608,12 @@ function HelpCenterCreateForm({
|
|
|
1577
1608
|
}) {
|
|
1578
1609
|
const [subject, setSubject] = useState5("");
|
|
1579
1610
|
const [subjectError, setSubjectError] = useState5(null);
|
|
1580
|
-
const subjectField = /* @__PURE__ */
|
|
1581
|
-
/* @__PURE__ */
|
|
1611
|
+
const subjectField = /* @__PURE__ */ jsxs7("div", { className: "flex flex-col", children: [
|
|
1612
|
+
/* @__PURE__ */ jsxs7(Label, { htmlFor: "help-center-subject", children: [
|
|
1582
1613
|
"Subject",
|
|
1583
|
-
/* @__PURE__ */
|
|
1614
|
+
/* @__PURE__ */ jsx8("span", { className: "text-ods-accent", children: "*" })
|
|
1584
1615
|
] }),
|
|
1585
|
-
/* @__PURE__ */
|
|
1616
|
+
/* @__PURE__ */ jsx8(
|
|
1586
1617
|
Input,
|
|
1587
1618
|
{
|
|
1588
1619
|
id: "help-center-subject",
|
|
@@ -1600,7 +1631,7 @@ function HelpCenterCreateForm({
|
|
|
1600
1631
|
className: "bg-ods-card border-ods-border text-ods-text-primary placeholder-ods-text-secondary px-3 h-12"
|
|
1601
1632
|
}
|
|
1602
1633
|
),
|
|
1603
|
-
subjectError && /* @__PURE__ */
|
|
1634
|
+
subjectError && /* @__PURE__ */ jsx8(
|
|
1604
1635
|
"span",
|
|
1605
1636
|
{
|
|
1606
1637
|
id: "help-center-subject-error",
|
|
@@ -1609,7 +1640,7 @@ function HelpCenterCreateForm({
|
|
|
1609
1640
|
}
|
|
1610
1641
|
)
|
|
1611
1642
|
] });
|
|
1612
|
-
return /* @__PURE__ */
|
|
1643
|
+
return /* @__PURE__ */ jsx8(
|
|
1613
1644
|
ContactForm,
|
|
1614
1645
|
{
|
|
1615
1646
|
title: "Open a new ticket",
|
|
@@ -1646,7 +1677,7 @@ function HelpCenterCreateForm({
|
|
|
1646
1677
|
}
|
|
1647
1678
|
|
|
1648
1679
|
// src/components/tickets/help-center-list.tsx
|
|
1649
|
-
import { jsx as
|
|
1680
|
+
import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1650
1681
|
function HelpCenterList({ toast: toast2 = toast } = {}) {
|
|
1651
1682
|
const identity = useChatIdentity();
|
|
1652
1683
|
const searchParams = useSearchParams();
|
|
@@ -1654,13 +1685,14 @@ function HelpCenterList({ toast: toast2 = toast } = {}) {
|
|
|
1654
1685
|
const pathname = usePathname();
|
|
1655
1686
|
const search = searchParams.get("search") || "";
|
|
1656
1687
|
const status = searchParams.get("status") || "all";
|
|
1688
|
+
const ticketParam = searchParams.get("ticket") || "";
|
|
1657
1689
|
const rawPage = Number(searchParams.get("page"));
|
|
1658
1690
|
const page = Number.isFinite(rawPage) && rawPage > 0 ? Math.floor(rawPage) : 1;
|
|
1659
1691
|
if (identity.isLoading) {
|
|
1660
|
-
return /* @__PURE__ */
|
|
1692
|
+
return /* @__PURE__ */ jsx9(DevSectionPage, { sectionKey: "tickets", preControls: /* @__PURE__ */ jsx9(HelpCenterCreateFormSkeleton, {}), children: /* @__PURE__ */ jsx9(DevCardRowSkeletonList, {}) });
|
|
1661
1693
|
}
|
|
1662
1694
|
if (identity.authTier === "anon" || !identity.user?.email) {
|
|
1663
|
-
return /* @__PURE__ */
|
|
1695
|
+
return /* @__PURE__ */ jsx9(DevSectionPage, { sectionKey: "tickets", children: /* @__PURE__ */ jsx9(
|
|
1664
1696
|
EmptyState,
|
|
1665
1697
|
{
|
|
1666
1698
|
type: "generic",
|
|
@@ -1672,12 +1704,13 @@ function HelpCenterList({ toast: toast2 = toast } = {}) {
|
|
|
1672
1704
|
}
|
|
1673
1705
|
const sessionName = [identity.user?.firstName, identity.user?.lastName].filter(Boolean).join(" ").trim() || identity.user?.email?.split("@")[0] || "Customer";
|
|
1674
1706
|
const sessionEmail = identity.user.email;
|
|
1675
|
-
return /* @__PURE__ */
|
|
1707
|
+
return /* @__PURE__ */ jsx9(
|
|
1676
1708
|
HelpCenterListAuthed,
|
|
1677
1709
|
{
|
|
1678
1710
|
search,
|
|
1679
1711
|
status,
|
|
1680
1712
|
page,
|
|
1713
|
+
ticketParam,
|
|
1681
1714
|
searchParams,
|
|
1682
1715
|
router,
|
|
1683
1716
|
pathname,
|
|
@@ -1691,6 +1724,7 @@ function HelpCenterListAuthed({
|
|
|
1691
1724
|
search,
|
|
1692
1725
|
status,
|
|
1693
1726
|
page,
|
|
1727
|
+
ticketParam,
|
|
1694
1728
|
searchParams,
|
|
1695
1729
|
router,
|
|
1696
1730
|
pathname,
|
|
@@ -1699,6 +1733,18 @@ function HelpCenterListAuthed({
|
|
|
1699
1733
|
sessionEmail
|
|
1700
1734
|
}) {
|
|
1701
1735
|
const queryClient = useQueryClient3();
|
|
1736
|
+
const [optimisticTickets, setOptimisticTickets] = useState6([]);
|
|
1737
|
+
const [supportSystemDown, setSupportSystemDown] = useState6(false);
|
|
1738
|
+
const setOpenTicket = useCallback6(
|
|
1739
|
+
(externalId) => {
|
|
1740
|
+
const params = new URLSearchParams(searchParams.toString());
|
|
1741
|
+
if (externalId) params.set("ticket", externalId);
|
|
1742
|
+
else params.delete("ticket");
|
|
1743
|
+
const qs = params.toString();
|
|
1744
|
+
router.replace(qs ? `${pathname}?${qs}` : pathname, { scroll: false });
|
|
1745
|
+
},
|
|
1746
|
+
[searchParams, router, pathname]
|
|
1747
|
+
);
|
|
1702
1748
|
const { tickets, isLoading, isFetching, error, refetch, totalPages } = useTicketsList({
|
|
1703
1749
|
// `sessionEmail` is drilled in from the parent — see the same
|
|
1704
1750
|
// pattern + race-cause rationale documented in
|
|
@@ -1709,19 +1755,21 @@ function HelpCenterListAuthed({
|
|
|
1709
1755
|
customerEmail: sessionEmail,
|
|
1710
1756
|
search,
|
|
1711
1757
|
status,
|
|
1712
|
-
page
|
|
1758
|
+
page,
|
|
1759
|
+
// Live status: while a drawer is open, poll so an out-of-band HubSpot
|
|
1760
|
+
// status change (e.g. agent closes the ticket) flips the badge +
|
|
1761
|
+
// open/reopen affordance within one interval. Idle (no drawer) → no poll.
|
|
1762
|
+
// `ticketParam` (the open ticket's external_id) is the open signal.
|
|
1763
|
+
refetchInterval: ticketParam ? TICKET_LIVE_POLL_MS : false
|
|
1713
1764
|
});
|
|
1714
|
-
const
|
|
1715
|
-
const
|
|
1716
|
-
const [supportSystemDown, setSupportSystemDown] = useState6(false);
|
|
1717
|
-
const prependOptimistic = useCallback5((placeholder) => {
|
|
1765
|
+
const expandedTicketId = ticketParam && tickets.find((t) => t.external_id === ticketParam)?.id || null;
|
|
1766
|
+
const prependOptimistic = useCallback6((placeholder) => {
|
|
1718
1767
|
setOptimisticTickets((prev) => [placeholder, ...prev]);
|
|
1719
1768
|
}, []);
|
|
1720
|
-
const removeOptimistic =
|
|
1769
|
+
const removeOptimistic = useCallback6((placeholderId) => {
|
|
1721
1770
|
setOptimisticTickets((prev) => prev.filter((t) => t.id !== placeholderId));
|
|
1722
|
-
setExpandedTicketId((prev) => prev === placeholderId ? null : prev);
|
|
1723
1771
|
}, []);
|
|
1724
|
-
const removeTicketFromCache =
|
|
1772
|
+
const removeTicketFromCache = useCallback6(
|
|
1725
1773
|
(ticketId) => {
|
|
1726
1774
|
queryClient.setQueriesData(
|
|
1727
1775
|
{ queryKey: ["tickets"] },
|
|
@@ -1732,7 +1780,6 @@ function HelpCenterListAuthed({
|
|
|
1732
1780
|
return { ...prev, tickets: nextTickets };
|
|
1733
1781
|
}
|
|
1734
1782
|
);
|
|
1735
|
-
setExpandedTicketId((prev) => prev === ticketId ? null : prev);
|
|
1736
1783
|
},
|
|
1737
1784
|
[queryClient]
|
|
1738
1785
|
);
|
|
@@ -1743,13 +1790,18 @@ function HelpCenterListAuthed({
|
|
|
1743
1790
|
toast: toast2,
|
|
1744
1791
|
onSupportSystemDown: () => setSupportSystemDown(true)
|
|
1745
1792
|
});
|
|
1746
|
-
const toggleRow =
|
|
1747
|
-
|
|
1748
|
-
|
|
1793
|
+
const toggleRow = useCallback6(
|
|
1794
|
+
(id) => {
|
|
1795
|
+
const t = tickets.find((x) => x.id === id);
|
|
1796
|
+
if (!t?.external_id) return;
|
|
1797
|
+
setOpenTicket(t.external_id === ticketParam ? null : t.external_id);
|
|
1798
|
+
},
|
|
1799
|
+
[tickets, ticketParam, setOpenTicket]
|
|
1800
|
+
);
|
|
1749
1801
|
const merged = [...optimisticTickets, ...tickets];
|
|
1750
1802
|
const hasActiveFilters = search !== "" || status !== "" && status !== "all";
|
|
1751
1803
|
const hasResults = merged.length > 0;
|
|
1752
|
-
const form = /* @__PURE__ */
|
|
1804
|
+
const form = /* @__PURE__ */ jsx9(
|
|
1753
1805
|
HelpCenterCreateForm,
|
|
1754
1806
|
{
|
|
1755
1807
|
actions,
|
|
@@ -1758,22 +1810,22 @@ function HelpCenterListAuthed({
|
|
|
1758
1810
|
supportSystemDown
|
|
1759
1811
|
}
|
|
1760
1812
|
);
|
|
1761
|
-
const body = /* @__PURE__ */
|
|
1762
|
-
error && /* @__PURE__ */
|
|
1763
|
-
/* @__PURE__ */
|
|
1813
|
+
const body = /* @__PURE__ */ jsxs8("div", { className: "w-full flex flex-col gap-[40px]", children: [
|
|
1814
|
+
error && /* @__PURE__ */ jsxs8("div", { className: "bg-ods-card border border-ods-border rounded-[6px] p-[40px] text-center w-full flex flex-col items-center gap-3", children: [
|
|
1815
|
+
/* @__PURE__ */ jsxs8("p", { className: "text-ods-error text-base", children: [
|
|
1764
1816
|
"Couldn\u2019t load your tickets. ",
|
|
1765
1817
|
error.message
|
|
1766
1818
|
] }),
|
|
1767
|
-
/* @__PURE__ */
|
|
1819
|
+
/* @__PURE__ */ jsx9(Button, { type: "button", variant: "accent", onClick: () => refetch(), children: "Retry" })
|
|
1768
1820
|
] }),
|
|
1769
|
-
!error && /* @__PURE__ */
|
|
1821
|
+
!error && /* @__PURE__ */ jsx9("div", { className: "w-full", children: isLoading ? /* @__PURE__ */ jsx9(DevCardRowSkeletonList, {}) : !hasResults && isFetching ? (
|
|
1770
1822
|
// Bridge state — background refetch in flight and the
|
|
1771
1823
|
// optimistic placeholder was just removed by the mutation
|
|
1772
1824
|
// callback. Without this branch "No tickets yet" would flash
|
|
1773
1825
|
// for ~50ms between `removeOptimistic` and the server
|
|
1774
1826
|
// response landing.
|
|
1775
|
-
/* @__PURE__ */
|
|
1776
|
-
) : !hasResults ? hasActiveFilters ? /* @__PURE__ */
|
|
1827
|
+
/* @__PURE__ */ jsx9(DevCardRowSkeletonList, { rows: 1 })
|
|
1828
|
+
) : !hasResults ? hasActiveFilters ? /* @__PURE__ */ jsx9(
|
|
1777
1829
|
EmptyState,
|
|
1778
1830
|
{
|
|
1779
1831
|
type: "search",
|
|
@@ -1788,7 +1840,7 @@ function HelpCenterListAuthed({
|
|
|
1788
1840
|
router.replace(`${pathname}?${params.toString()}`, { scroll: false });
|
|
1789
1841
|
}
|
|
1790
1842
|
}
|
|
1791
|
-
) : /* @__PURE__ */
|
|
1843
|
+
) : /* @__PURE__ */ jsx9(
|
|
1792
1844
|
EmptyState,
|
|
1793
1845
|
{
|
|
1794
1846
|
type: "generic",
|
|
@@ -1805,7 +1857,7 @@ function HelpCenterListAuthed({
|
|
|
1805
1857
|
// hidden) instead of bubbling up to the window. `clip`
|
|
1806
1858
|
// keeps the visual clip but NOT the scroll-container
|
|
1807
1859
|
// status, so click-to-scroll actually moves the page.
|
|
1808
|
-
/* @__PURE__ */
|
|
1860
|
+
/* @__PURE__ */ jsx9("div", { className: "bg-ods-card border border-ods-border rounded-[6px] overflow-clip w-full", children: merged.map((ticket) => /* @__PURE__ */ jsx9(
|
|
1809
1861
|
HelpCenterCard,
|
|
1810
1862
|
{
|
|
1811
1863
|
ticket,
|
|
@@ -1816,16 +1868,16 @@ function HelpCenterListAuthed({
|
|
|
1816
1868
|
onSendMessage: actions.sendMessage,
|
|
1817
1869
|
onClose: actions.closeTicket,
|
|
1818
1870
|
onReopen: actions.reopenTicket,
|
|
1819
|
-
onActionCollapsed: () =>
|
|
1871
|
+
onActionCollapsed: () => setOpenTicket(null),
|
|
1820
1872
|
replyError: actions.replyErrorFor(ticket.external_id),
|
|
1821
1873
|
onClearReplyError: () => actions.clearReplyError(ticket.external_id)
|
|
1822
1874
|
},
|
|
1823
1875
|
ticket.id
|
|
1824
1876
|
)) })
|
|
1825
1877
|
) }),
|
|
1826
|
-
!error && totalPages > 1 && /* @__PURE__ */
|
|
1878
|
+
!error && totalPages > 1 && /* @__PURE__ */ jsx9(UnifiedPagination, { currentPage: page, totalPages })
|
|
1827
1879
|
] });
|
|
1828
|
-
return /* @__PURE__ */
|
|
1880
|
+
return /* @__PURE__ */ jsx9(DevSectionPage, { sectionKey: "tickets", preControls: form, children: body });
|
|
1829
1881
|
}
|
|
1830
1882
|
export {
|
|
1831
1883
|
HelpCenterCard,
|
|
@@ -1837,6 +1889,7 @@ export {
|
|
|
1837
1889
|
TicketDetailDrawer,
|
|
1838
1890
|
TicketLinkedDeliveryCard,
|
|
1839
1891
|
TicketOpenForm,
|
|
1892
|
+
TicketReplyComposer,
|
|
1840
1893
|
TicketRow,
|
|
1841
1894
|
isOptimistic,
|
|
1842
1895
|
mapTicketActionError,
|