@flamingo-stack/openframe-frontend-core 0.0.214 → 0.0.215-snapshot.20260529010429

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.
Files changed (46) hide show
  1. package/dist/{chunk-KDNGUYZM.cjs → chunk-2G3NXF6J.cjs} +17 -17
  2. package/dist/{chunk-KDNGUYZM.cjs.map → chunk-2G3NXF6J.cjs.map} +1 -1
  3. package/dist/{chunk-HWY35EAK.js → chunk-7PCP7YQR.js} +2 -2
  4. package/dist/{chunk-YZUUFTIK.cjs → chunk-D4MNFY67.cjs} +3 -2
  5. package/dist/chunk-D4MNFY67.cjs.map +1 -0
  6. package/dist/{chunk-IZL2KEH6.cjs → chunk-NGFP4RVL.cjs} +24 -24
  7. package/dist/{chunk-IZL2KEH6.cjs.map → chunk-NGFP4RVL.cjs.map} +1 -1
  8. package/dist/{chunk-5OWDOFKK.js → chunk-R6MLPU4A.js} +2 -2
  9. package/dist/{chunk-UKND27XC.js → chunk-WQZP3JIZ.js} +3 -2
  10. package/dist/{chunk-UKND27XC.js.map → chunk-WQZP3JIZ.js.map} +1 -1
  11. package/dist/components/chat/index.cjs +2 -2
  12. package/dist/components/chat/index.js +1 -1
  13. package/dist/components/contact/index.cjs +3 -3
  14. package/dist/components/contact/index.js +2 -2
  15. package/dist/components/features/index.cjs +2 -2
  16. package/dist/components/features/index.js +1 -1
  17. package/dist/components/index.cjs +49 -49
  18. package/dist/components/index.js +2 -2
  19. package/dist/components/navigation/index.cjs +2 -2
  20. package/dist/components/navigation/index.js +1 -1
  21. package/dist/components/tickets/help-center-card.d.ts +5 -1
  22. package/dist/components/tickets/help-center-card.d.ts.map +1 -1
  23. package/dist/components/tickets/hooks/use-ticket-actions.d.ts +8 -0
  24. package/dist/components/tickets/hooks/use-ticket-actions.d.ts.map +1 -1
  25. package/dist/components/tickets/index.cjs +254 -103
  26. package/dist/components/tickets/index.cjs.map +1 -1
  27. package/dist/components/tickets/index.js +184 -33
  28. package/dist/components/tickets/index.js.map +1 -1
  29. package/dist/components/tickets/ticket-detail-drawer.d.ts +11 -2
  30. package/dist/components/tickets/ticket-detail-drawer.d.ts.map +1 -1
  31. package/dist/components/tickets/types.d.ts +28 -1
  32. package/dist/components/tickets/types.d.ts.map +1 -1
  33. package/dist/components/ui/index.cjs +2 -2
  34. package/dist/components/ui/index.js +1 -1
  35. package/dist/index.cjs +2 -2
  36. package/dist/index.js +1 -1
  37. package/package.json +1 -1
  38. package/src/components/platform/ShellTypeBadge.tsx +2 -2
  39. package/src/components/tickets/help-center-card.tsx +8 -0
  40. package/src/components/tickets/help-center-list.tsx +2 -0
  41. package/src/components/tickets/hooks/use-ticket-actions.ts +139 -5
  42. package/src/components/tickets/ticket-detail-drawer.tsx +129 -1
  43. package/src/components/tickets/types.ts +32 -0
  44. package/dist/chunk-YZUUFTIK.cjs.map +0 -1
  45. /package/dist/{chunk-HWY35EAK.js.map → chunk-7PCP7YQR.js.map} +0 -0
  46. /package/dist/{chunk-5OWDOFKK.js.map → chunk-R6MLPU4A.js.map} +0 -0
@@ -9,11 +9,11 @@ import {
9
9
  EmptyState,
10
10
  UnifiedPagination,
11
11
  init_unified_pagination
12
- } from "../../chunk-5OWDOFKK.js";
12
+ } from "../../chunk-R6MLPU4A.js";
13
13
  import "../../chunk-4XLJWX2N.js";
14
14
  import {
15
15
  ContactForm
16
- } from "../../chunk-HWY35EAK.js";
16
+ } from "../../chunk-7PCP7YQR.js";
17
17
  import {
18
18
  AlertDialog,
19
19
  AlertDialogAction,
@@ -28,6 +28,7 @@ import {
28
28
  ChatAttachmentChipStrip,
29
29
  ChatTicketItem,
30
30
  Label,
31
+ SquareAvatar,
31
32
  StatusBadge,
32
33
  Textarea,
33
34
  embedAuthedFetch,
@@ -36,7 +37,7 @@ import {
36
37
  scrollElementIntoView,
37
38
  useChatAttachments,
38
39
  useChatIdentity
39
- } from "../../chunk-UKND27XC.js";
40
+ } from "../../chunk-WQZP3JIZ.js";
40
41
  import {
41
42
  toast
42
43
  } from "../../chunk-HICZPTRR.js";
@@ -311,35 +312,47 @@ function TicketDetailDrawer({
311
312
  onSendMessage,
312
313
  onClose,
313
314
  onReopen,
314
- onActionCollapsed
315
+ onActionCollapsed,
316
+ replyError,
317
+ onClearReplyError
315
318
  }) {
316
319
  const isClosed = (ticket.status ?? "").toUpperCase() === "CLOSED";
317
320
  return /* @__PURE__ */ jsxs2("div", { className: "bg-ods-card border-t border-ods-border px-4 py-4 flex flex-col gap-4", children: [
321
+ /* @__PURE__ */ jsx3(AssignedAgentRow, { assignedOwner: ticket.assignedOwner }),
318
322
  ticket.clickup && /* @__PURE__ */ jsx3(TicketLinkedDeliveryCard, { clickup: ticket.clickup }),
319
323
  /* @__PURE__ */ jsxs2("div", { children: [
320
324
  /* @__PURE__ */ jsx3("p", { className: "text-xs font-medium text-ods-text-secondary mb-2 uppercase tracking-wider", children: "Conversation" }),
321
325
  /* @__PURE__ */ jsx3(TicketTimelinePanel, { ticket })
322
326
  ] }),
323
- /* @__PURE__ */ jsx3("div", { className: "border-t border-ods-border pt-4", children: isClosed ? /* @__PURE__ */ jsx3(
324
- ReopenAction,
325
- {
326
- ticketRef: { id: ticket.id, external_id: ticket.external_id },
327
- busy,
328
- supportSystemDown,
329
- onReopen,
330
- onActionCollapsed
331
- }
332
- ) : /* @__PURE__ */ jsx3(
333
- OpenActions,
334
- {
335
- ticket,
336
- busy,
337
- supportSystemDown,
338
- onSendMessage,
339
- onClose,
340
- onActionCollapsed
341
- }
342
- ) })
327
+ /* @__PURE__ */ jsxs2("div", { className: "border-t border-ods-border pt-4", children: [
328
+ replyError && /* @__PURE__ */ jsx3(
329
+ ReplyFailureBanner,
330
+ {
331
+ error: replyError,
332
+ onDismiss: onClearReplyError ?? (() => void 0)
333
+ }
334
+ ),
335
+ isClosed ? /* @__PURE__ */ jsx3(
336
+ ReopenAction,
337
+ {
338
+ ticketRef: { id: ticket.id, external_id: ticket.external_id },
339
+ busy,
340
+ supportSystemDown,
341
+ onReopen,
342
+ onActionCollapsed
343
+ }
344
+ ) : /* @__PURE__ */ jsx3(
345
+ OpenActions,
346
+ {
347
+ ticket,
348
+ busy,
349
+ supportSystemDown,
350
+ onSendMessage,
351
+ onClose,
352
+ onActionCollapsed
353
+ }
354
+ )
355
+ ] })
343
356
  ] });
344
357
  }
345
358
  var TURN_SEPARATOR_RE = /[\s]{1,16}---[\s]{1,16}/g;
@@ -591,6 +604,56 @@ function OpenActions({
591
604
  ] }) })
592
605
  ] });
593
606
  }
607
+ function ReplyFailureBanner({
608
+ error,
609
+ onDismiss
610
+ }) {
611
+ return /* @__PURE__ */ jsxs2(
612
+ "div",
613
+ {
614
+ role: "status",
615
+ "aria-live": "polite",
616
+ 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
+ children: [
618
+ /* @__PURE__ */ jsx3("span", { className: "font-medium leading-snug", children: error.message }),
619
+ /* @__PURE__ */ jsx3(
620
+ Button,
621
+ {
622
+ type: "button",
623
+ variant: "transparent",
624
+ onClick: onDismiss,
625
+ "aria-label": "Dismiss reply failure",
626
+ className: "ml-auto px-2 py-0.5 text-xs font-medium uppercase tracking-wider text-ods-attention-red-error hover:bg-ods-attention-red-error/10 border-transparent",
627
+ children: "Dismiss"
628
+ }
629
+ )
630
+ ]
631
+ }
632
+ );
633
+ }
634
+ function AssignedAgentRow({
635
+ assignedOwner
636
+ }) {
637
+ const trimmedName = assignedOwner?.name?.trim() || null;
638
+ const emailFallback = assignedOwner?.email?.trim() || null;
639
+ const displayLabel = trimmedName ?? (emailFallback ? emailFallback.split("@")[0] : null);
640
+ return /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2 text-xs", children: [
641
+ /* @__PURE__ */ jsx3("span", { className: "text-ods-text-secondary uppercase tracking-wider font-medium", children: "Assigned to" }),
642
+ displayLabel ? /* @__PURE__ */ jsxs2("span", { className: "flex items-center gap-1.5 text-ods-text-primary font-medium", children: [
643
+ /* @__PURE__ */ jsx3(
644
+ SquareAvatar,
645
+ {
646
+ size: "sm",
647
+ variant: "round",
648
+ src: assignedOwner?.avatarUrl ?? void 0,
649
+ alt: displayLabel,
650
+ fallback: displayLabel
651
+ }
652
+ ),
653
+ displayLabel
654
+ ] }) : /* @__PURE__ */ jsx3("span", { className: "text-ods-text-secondary italic", children: "Unassigned" })
655
+ ] });
656
+ }
594
657
 
595
658
  // src/components/tickets/ticket-row.tsx
596
659
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
@@ -771,6 +834,12 @@ function useTicketsList(filters) {
771
834
  import { useCallback as useCallback2, useEffect, useMemo, useRef as useRef2, useState as useState3 } from "react";
772
835
  import { useQueryClient } from "@tanstack/react-query";
773
836
  var TICKET_ACTION_ENDPOINT = "/api/chat/agent/ticket-action";
837
+ var REPLY_BANNER_CODES = /* @__PURE__ */ new Set([
838
+ "HUBSPOT_5XX",
839
+ "HUBSPOT_400_VALIDATION",
840
+ "HUBSPOT_404_THREAD",
841
+ "HUBSPOT_REPLY_UNKNOWN"
842
+ ]);
774
843
  var MIRROR_SYNC_BACKOFF_MS = [3e3, 6e3, 12e3];
775
844
  function useTicketActions(options) {
776
845
  const queryClient = useQueryClient();
@@ -785,6 +854,26 @@ function useTicketActions(options) {
785
854
  setBusyRows(new Set(busyRowsRef.current));
786
855
  }, []);
787
856
  const isRowBusy = useCallback2((id) => busyRows.has(id), [busyRows]);
857
+ const [replyErrorByTicket, setReplyErrorByTicket] = useState3(() => /* @__PURE__ */ new Map());
858
+ const setReplyError = useCallback2(
859
+ (externalId, mapped) => {
860
+ setReplyErrorByTicket((prev) => {
861
+ const next = new Map(prev);
862
+ if (mapped) next.set(externalId, mapped);
863
+ else next.delete(externalId);
864
+ return next;
865
+ });
866
+ },
867
+ []
868
+ );
869
+ const replyErrorFor = useCallback2(
870
+ (externalId) => replyErrorByTicket.get(externalId) ?? null,
871
+ [replyErrorByTicket]
872
+ );
873
+ const clearReplyError = useCallback2(
874
+ (externalId) => setReplyError(externalId, null),
875
+ [setReplyError]
876
+ );
788
877
  const watcherControllersRef = useRef2(/* @__PURE__ */ new Map());
789
878
  useEffect(() => {
790
879
  return () => {
@@ -862,9 +951,11 @@ function useTicketActions(options) {
862
951
  },
863
952
  [queryClient, removeOptimistic, toast2]
864
953
  );
954
+ const lastUpdateErrorRef = useRef2(null);
865
955
  const surfaceError = useCallback2(
866
956
  (err, action) => {
867
957
  const mapped = mapTicketActionError(err);
958
+ lastUpdateErrorRef.current = mapped;
868
959
  if (mapped.supportSystemDown) onSupportSystemDown();
869
960
  toast2({
870
961
  title: `Could not ${action}`,
@@ -899,6 +990,10 @@ function useTicketActions(options) {
899
990
  // seconds via the mirror refetch. Drawer uses live chat
900
991
  // identity for own-replies during this window anyway.
901
992
  customer_name: null,
993
+ // No assignee until the real ticket lands. Drawer renders
994
+ // "Unassigned" for this brief window.
995
+ assigned_to: null,
996
+ assignedOwner: null,
902
997
  hubspot_updated_at: (/* @__PURE__ */ new Date()).toISOString(),
903
998
  _optimistic: true
904
999
  };
@@ -974,12 +1069,13 @@ function useTicketActions(options) {
974
1069
  [setRowBusy, enqueue, executeTicketAction, queryClient, toast2, surfaceError, removeTicketFromCache]
975
1070
  );
976
1071
  const sendMessage = useCallback2(
977
- (ticket, text, attachments) => {
1072
+ async (ticket, text, attachments) => {
978
1073
  const trimmed = text.trim();
979
1074
  const hasText = trimmed.length > 0;
980
1075
  const hasFiles = attachments.length > 0;
981
- if (!hasText && !hasFiles) return Promise.resolve(false);
982
- return updateTicket(
1076
+ if (!hasText && !hasFiles) return false;
1077
+ lastUpdateErrorRef.current = null;
1078
+ const ok = await updateTicket(
983
1079
  ticket,
984
1080
  {
985
1081
  ...hasText ? { content_addendum: trimmed } : {},
@@ -988,8 +1084,18 @@ function useTicketActions(options) {
988
1084
  TOAST_COPY.comment_success,
989
1085
  "send message"
990
1086
  );
1087
+ if (ok) {
1088
+ clearReplyError(ticket.external_id);
1089
+ } else {
1090
+ const mapped = lastUpdateErrorRef.current;
1091
+ if (mapped && REPLY_BANNER_CODES.has(mapped.code)) {
1092
+ setReplyError(ticket.external_id, mapped);
1093
+ }
1094
+ lastUpdateErrorRef.current = null;
1095
+ }
1096
+ return ok;
991
1097
  },
992
- [updateTicket]
1098
+ [updateTicket, clearReplyError, setReplyError]
993
1099
  );
994
1100
  const closeTicket = useCallback2(
995
1101
  (ticket, resolution) => updateTicket(
@@ -1014,9 +1120,20 @@ function useTicketActions(options) {
1014
1120
  closeTicket,
1015
1121
  reopenTicket,
1016
1122
  isSubmittingForm,
1017
- isRowBusy
1123
+ isRowBusy,
1124
+ replyErrorFor,
1125
+ clearReplyError
1018
1126
  }),
1019
- [submitTicket, sendMessage, closeTicket, reopenTicket, isSubmittingForm, isRowBusy]
1127
+ [
1128
+ submitTicket,
1129
+ sendMessage,
1130
+ closeTicket,
1131
+ reopenTicket,
1132
+ isSubmittingForm,
1133
+ isRowBusy,
1134
+ replyErrorFor,
1135
+ clearReplyError
1136
+ ]
1020
1137
  );
1021
1138
  }
1022
1139
  var TicketActionFailure = class extends Error {
@@ -1075,6 +1192,34 @@ function mapTicketActionError(err) {
1075
1192
  supportSystemDown: false,
1076
1193
  removeRowFromCache: false
1077
1194
  };
1195
+ case "HUBSPOT_5XX":
1196
+ return {
1197
+ code: err.code,
1198
+ message: "We couldn't reach the support system. Your reply wasn't sent \u2014 please retry in a moment.",
1199
+ supportSystemDown: false,
1200
+ removeRowFromCache: false
1201
+ };
1202
+ case "HUBSPOT_400_VALIDATION":
1203
+ return {
1204
+ code: err.code,
1205
+ message: "Your reply was rejected. Please rephrase or remove unsupported content and try again.",
1206
+ supportSystemDown: false,
1207
+ removeRowFromCache: false
1208
+ };
1209
+ case "HUBSPOT_404_THREAD":
1210
+ return {
1211
+ code: err.code,
1212
+ message: "This conversation is no longer accepting replies. Open a new ticket to continue.",
1213
+ supportSystemDown: false,
1214
+ removeRowFromCache: false
1215
+ };
1216
+ case "HUBSPOT_REPLY_UNKNOWN":
1217
+ return {
1218
+ code: err.code,
1219
+ message: "Your reply didn't go through. Please retry.",
1220
+ supportSystemDown: false,
1221
+ removeRowFromCache: false
1222
+ };
1078
1223
  default:
1079
1224
  return {
1080
1225
  code: "UNKNOWN",
@@ -1274,7 +1419,9 @@ function HelpCenterCard({
1274
1419
  onSendMessage,
1275
1420
  onClose,
1276
1421
  onReopen,
1277
- onActionCollapsed
1422
+ onActionCollapsed,
1423
+ replyError,
1424
+ onClearReplyError
1278
1425
  }) {
1279
1426
  const optimistic = isOptimistic(ticket);
1280
1427
  const rawStatus = (ticket.status ?? "OPEN").toUpperCase();
@@ -1361,7 +1508,9 @@ function HelpCenterCard({
1361
1508
  onSendMessage,
1362
1509
  onClose,
1363
1510
  onReopen,
1364
- onActionCollapsed
1511
+ onActionCollapsed,
1512
+ replyError,
1513
+ onClearReplyError
1365
1514
  }
1366
1515
  ) })
1367
1516
  ]
@@ -1647,7 +1796,9 @@ function HelpCenterListAuthed({
1647
1796
  onSendMessage: actions.sendMessage,
1648
1797
  onClose: actions.closeTicket,
1649
1798
  onReopen: actions.reopenTicket,
1650
- onActionCollapsed: () => setExpandedTicketId(null)
1799
+ onActionCollapsed: () => setExpandedTicketId(null),
1800
+ replyError: actions.replyErrorFor(ticket.external_id),
1801
+ onClearReplyError: () => actions.clearReplyError(ticket.external_id)
1651
1802
  },
1652
1803
  ticket.id
1653
1804
  )) })