@quanticjs/notification-ui 9.0.0 → 9.0.2

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/index.cjs CHANGED
@@ -456,54 +456,80 @@ function NotificationInbox({
456
456
  appId
457
457
  });
458
458
  if (isLoading) {
459
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
460
- "div",
461
- {
462
- role: "status",
463
- "aria-label": "Loading notifications",
464
- className: (0, import_react_ui4.cn)("flex flex-col gap-2 p-4", className),
465
- children: [
466
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "sr-only", children: "Loading notifications" }),
467
- [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { "aria-hidden": "true", className: "h-12 animate-pulse rounded bg-muted" }, i))
468
- ]
469
- }
470
- );
459
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { role: "status", "aria-label": "Loading notifications", className: (0, import_react_ui4.cn)(className), children: [
460
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "sr-only", children: "Loading notifications" }),
461
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_ui4.Card, { "aria-hidden": "true", children: [
462
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(InboxHeader, {}),
463
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("ul", { className: "divide-y divide-border", children: [0, 1, 2, 3].map((i) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("li", { className: "flex items-start gap-3 px-4 py-3.5", children: [
464
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_ui4.Skeleton, { className: "mt-1 h-2 w-2 rounded-full" }),
465
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col gap-2", children: [
466
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_ui4.Skeleton, { className: "h-3.5 w-2/3" }),
467
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_ui4.Skeleton, { className: "h-3 w-1/2" })
468
+ ] })
469
+ ] }, i)) })
470
+ ] })
471
+ ] });
471
472
  }
472
473
  if (isError) {
473
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: (0, import_react_ui4.cn)("flex flex-col items-start gap-3 p-4", className), children: [
474
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "text-sm text-foreground", children: "Failed to load notifications" }),
475
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
476
- "button",
477
- {
478
- type: "button",
479
- onClick: () => void refetch(),
480
- className: "rounded bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground hover:opacity-90 focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring",
481
- children: "Try again"
482
- }
483
- )
474
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_ui4.Card, { className, children: [
475
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(InboxHeader, {}),
476
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex flex-col items-center gap-3 px-6 py-12 text-center", children: [
477
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
478
+ "span",
479
+ {
480
+ "aria-hidden": "true",
481
+ className: "grid size-12 place-items-center rounded-full bg-destructive/10 text-destructive [&_svg]:size-6",
482
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(AlertIcon, {})
483
+ }
484
+ ),
485
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex flex-col gap-1", children: [
486
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "text-sm font-medium text-foreground", children: "Failed to load notifications" }),
487
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "text-sm text-muted-foreground", children: "Something went wrong while loading your inbox." })
488
+ ] }),
489
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_ui4.Button, { type: "button", onClick: () => void refetch(), children: "Try again" })
490
+ ] })
484
491
  ] });
485
492
  }
486
493
  const items = data?.items ?? [];
487
494
  if (items.length === 0) {
488
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: (0, import_react_ui4.cn)("p-6 text-center text-sm text-muted-foreground", className), children: "No notifications yet" });
489
- }
490
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("section", { "aria-label": "Notifications", className: (0, import_react_ui4.cn)("flex flex-col", className), children: [
491
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("header", { className: "flex items-center justify-between border-b border-border px-4 py-2", children: [
492
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h2", { className: "text-sm font-semibold text-foreground", children: "Notifications" }),
495
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_ui4.Card, { className, children: [
496
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(InboxHeader, {}),
493
497
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
494
- "button",
498
+ import_react_ui4.EmptyState,
495
499
  {
496
- type: "button",
497
- onClick: () => markAllRead.mutate(),
498
- disabled: markAllRead.isPending,
499
- className: "text-sm text-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring disabled:opacity-50",
500
- children: "Mark all read"
500
+ icon: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(BellOffIcon, {}),
501
+ title: "No notifications yet",
502
+ description: "New alerts and updates will appear here."
501
503
  }
502
504
  )
503
- ] }),
505
+ ] });
506
+ }
507
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_ui4.Card, { role: "region", "aria-label": "Notifications", className, children: [
508
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
509
+ InboxHeader,
510
+ {
511
+ action: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
512
+ import_react_ui4.Button,
513
+ {
514
+ type: "button",
515
+ variant: "ghost",
516
+ size: "sm",
517
+ onClick: () => markAllRead.mutate(),
518
+ disabled: markAllRead.isPending,
519
+ children: "Mark all read"
520
+ }
521
+ )
522
+ }
523
+ ),
504
524
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("ul", { className: "divide-y divide-border", children: items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(InboxRow, { item, onMarkRead: () => markRead.mutate(item.id) }, item.id)) })
505
525
  ] });
506
526
  }
527
+ function InboxHeader({ action }) {
528
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center justify-between gap-2 border-b border-border px-4 py-3", children: [
529
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h2", { className: "text-sm font-semibold text-foreground", children: "Notifications" }),
530
+ action
531
+ ] });
532
+ }
507
533
  function InboxRow({
508
534
  item,
509
535
  onMarkRead
@@ -514,20 +540,63 @@ function InboxRow({
514
540
  type: "button",
515
541
  onClick: onMarkRead,
516
542
  "aria-label": `${item.title}${item.isRead ? "" : " (unread)"}`,
517
- className: (0, import_react_ui4.cn)(
518
- "flex w-full flex-col items-start gap-0.5 px-4 py-3 text-start hover:bg-muted focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring",
519
- item.isRead ? "text-muted-foreground" : "text-foreground"
520
- ),
543
+ className: "flex w-full items-start gap-3 px-4 py-3.5 text-start transition-colors hover:bg-muted focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring",
521
544
  children: [
522
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "flex items-center gap-2 text-sm font-medium", children: [
523
- !item.isRead && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { "aria-hidden": "true", className: "h-2 w-2 rounded-full bg-coral" }),
524
- item.title
525
- ] }),
526
- item.body && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-xs text-muted-foreground", children: item.body })
545
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { "aria-hidden": "true", className: "mt-1.5 flex size-2 shrink-0 items-center justify-center", children: !item.isRead && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "size-2 rounded-full bg-coral" }) }),
546
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [
547
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
548
+ "span",
549
+ {
550
+ className: (0, import_react_ui4.cn)(
551
+ "truncate text-sm",
552
+ item.isRead ? "font-normal text-muted-foreground" : "font-semibold text-foreground"
553
+ ),
554
+ children: item.title
555
+ }
556
+ ),
557
+ item.body && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "truncate text-xs text-muted-foreground", children: item.body })
558
+ ] })
527
559
  ]
528
560
  }
529
561
  ) });
530
562
  }
563
+ function AlertIcon() {
564
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
565
+ "svg",
566
+ {
567
+ viewBox: "0 0 24 24",
568
+ fill: "none",
569
+ stroke: "currentColor",
570
+ strokeWidth: "2",
571
+ strokeLinecap: "round",
572
+ strokeLinejoin: "round",
573
+ children: [
574
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0Z" }),
575
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("line", { x1: "12", x2: "12", y1: "9", y2: "13" }),
576
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("line", { x1: "12", x2: "12.01", y1: "17", y2: "17" })
577
+ ]
578
+ }
579
+ );
580
+ }
581
+ function BellOffIcon() {
582
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
583
+ "svg",
584
+ {
585
+ viewBox: "0 0 24 24",
586
+ fill: "none",
587
+ stroke: "currentColor",
588
+ strokeWidth: "2",
589
+ strokeLinecap: "round",
590
+ strokeLinejoin: "round",
591
+ children: [
592
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M8.7 3A6 6 0 0 1 18 8a21.3 21.3 0 0 0 .6 5" }),
593
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M17 17H3s3-2 3-9a4.67 4.67 0 0 1 .3-1.7" }),
594
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M10.3 21a1.94 1.94 0 0 0 3.4 0" }),
595
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("line", { x1: "2", x2: "22", y1: "2", y2: "22" })
596
+ ]
597
+ }
598
+ );
599
+ }
531
600
 
532
601
  // src/notification-center.tsx
533
602
  var import_react5 = require("react");
@@ -890,51 +959,134 @@ function TemplateList({ basePath = "/api/templates", className }) {
890
959
  (client) => client.get(basePath)
891
960
  );
892
961
  if (isLoading) {
893
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
894
- "div",
895
- {
896
- role: "status",
897
- "aria-label": "Loading templates",
898
- className: (0, import_react_ui11.cn)("flex flex-col gap-2 p-4", className),
899
- children: [
900
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "sr-only", children: "Loading templates" }),
901
- [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { "aria-hidden": "true", className: "h-12 animate-pulse rounded bg-muted" }, i))
902
- ]
903
- }
904
- );
962
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { role: "status", "aria-label": "Loading templates", className: (0, import_react_ui11.cn)(className), children: [
963
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "sr-only", children: "Loading templates" }),
964
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_react_ui11.Card, { "aria-hidden": "true", children: [
965
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ListHeader, {}),
966
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("ul", { className: "divide-y divide-border", children: [0, 1, 2, 3, 4].map((i) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("li", { className: "flex flex-col gap-2 px-4 py-3.5", children: [
967
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex items-center gap-2.5", children: [
968
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_ui11.Skeleton, { className: "h-3.5 w-40" }),
969
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_ui11.Skeleton, { className: "h-5 w-16 rounded-full" })
970
+ ] }),
971
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_ui11.Skeleton, { className: "h-3 w-56" })
972
+ ] }, i)) })
973
+ ] })
974
+ ] });
905
975
  }
906
976
  if (isError) {
907
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: (0, import_react_ui11.cn)("flex flex-col items-start gap-3 p-4", className), children: [
908
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "text-sm text-foreground", children: "Failed to load templates" }),
977
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_react_ui11.Card, { className, children: [
978
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ListHeader, {}),
979
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex flex-col items-center gap-3 px-6 py-12 text-center", children: [
980
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
981
+ "span",
982
+ {
983
+ "aria-hidden": "true",
984
+ className: "grid size-12 place-items-center rounded-full bg-destructive/10 text-destructive [&_svg]:size-6",
985
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(AlertIcon2, {})
986
+ }
987
+ ),
988
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex flex-col gap-1", children: [
989
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "text-sm font-medium text-foreground", children: "Failed to load templates" }),
990
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "text-sm text-muted-foreground", children: "Something went wrong while loading your templates." })
991
+ ] }),
992
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_ui11.Button, { type: "button", onClick: () => void refetch(), children: "Try again" })
993
+ ] })
994
+ ] });
995
+ }
996
+ const items = data ?? [];
997
+ if (items.length === 0) {
998
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_react_ui11.Card, { className, children: [
999
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ListHeader, {}),
909
1000
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
910
- "button",
1001
+ import_react_ui11.EmptyState,
911
1002
  {
912
- type: "button",
913
- onClick: () => void refetch(),
914
- className: "rounded bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground hover:opacity-90 focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring",
915
- children: "Try again"
1003
+ icon: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(FileIcon, {}),
1004
+ title: "No templates found",
1005
+ description: "This workspace doesn't have any notification templates yet."
916
1006
  }
917
1007
  )
918
1008
  ] });
919
1009
  }
920
- const items = data ?? [];
921
- if (items.length === 0) {
922
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: (0, import_react_ui11.cn)("p-6 text-center text-sm text-muted-foreground", className), children: "No templates found" });
923
- }
924
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("section", { "aria-label": "Templates", className: (0, import_react_ui11.cn)("flex flex-col", className), children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("table", { className: "w-full text-sm", children: [
925
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { className: "border-b border-border text-start text-muted-foreground", children: [
926
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("th", { scope: "col", className: "py-2 pe-4 font-medium", children: "Template" }),
927
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Status" }),
928
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Locales" }),
929
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Versions" })
930
- ] }) }),
931
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("tbody", { children: items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { className: "border-b border-border", children: [
932
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { className: "py-3 pe-4 font-medium text-foreground", children: item.templateId }),
933
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { className: "px-4 py-3", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(TemplateStatusBadge, { status: item.latestStatus }) }),
934
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { className: "px-4 py-3 text-muted-foreground", children: item.locales.join(", ") }),
935
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { className: "px-4 py-3 text-muted-foreground", children: item.versionCount })
936
- ] }, item.templateId)) })
937
- ] }) });
1010
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_react_ui11.Card, { role: "region", "aria-label": "Templates", className, children: [
1011
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ListHeader, { count: items.length }),
1012
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("ul", { className: "divide-y divide-border", children: items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(TemplateRow, { item }, item.templateId)) })
1013
+ ] });
1014
+ }
1015
+ function ListHeader({ count }) {
1016
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex items-center justify-between gap-2 border-b border-border px-4 py-3", children: [
1017
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex flex-col", children: [
1018
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("h2", { className: "text-sm font-semibold text-foreground", children: "Templates" }),
1019
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "text-xs text-muted-foreground", children: "Read-only view of all notification templates" })
1020
+ ] }),
1021
+ count != null && count > 0 && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "inline-flex items-center rounded-full bg-muted px-2 py-0.5 text-xs font-medium tabular-nums text-muted-foreground", children: [
1022
+ count,
1023
+ " templates"
1024
+ ] })
1025
+ ] });
1026
+ }
1027
+ function TemplateRow({ item }) {
1028
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("li", { className: "flex flex-col gap-1.5 px-4 py-3.5", children: [
1029
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex items-center gap-2.5", children: [
1030
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "truncate text-sm font-medium text-foreground", children: item.templateId }),
1031
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(TemplateStatusBadge, { status: item.latestStatus })
1032
+ ] }),
1033
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: [
1034
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "tabular-nums", children: [
1035
+ item.versionCount,
1036
+ " versions"
1037
+ ] }),
1038
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { "aria-hidden": "true", className: "text-border", children: "\xB7" }),
1039
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "flex flex-wrap items-center gap-1", children: [
1040
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { children: "Locales:" }),
1041
+ item.locales.map((locale) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1042
+ "span",
1043
+ {
1044
+ className: "inline-flex items-center rounded bg-muted px-1.5 py-0.5 text-[11px] font-medium uppercase tracking-wide text-muted-foreground",
1045
+ children: locale
1046
+ },
1047
+ locale
1048
+ ))
1049
+ ] })
1050
+ ] })
1051
+ ] });
1052
+ }
1053
+ function AlertIcon2() {
1054
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
1055
+ "svg",
1056
+ {
1057
+ viewBox: "0 0 24 24",
1058
+ fill: "none",
1059
+ stroke: "currentColor",
1060
+ strokeWidth: "2",
1061
+ strokeLinecap: "round",
1062
+ strokeLinejoin: "round",
1063
+ children: [
1064
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("path", { d: "M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0Z" }),
1065
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("line", { x1: "12", x2: "12", y1: "9", y2: "13" }),
1066
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("line", { x1: "12", x2: "12.01", y1: "17", y2: "17" })
1067
+ ]
1068
+ }
1069
+ );
1070
+ }
1071
+ function FileIcon() {
1072
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
1073
+ "svg",
1074
+ {
1075
+ viewBox: "0 0 24 24",
1076
+ fill: "none",
1077
+ stroke: "currentColor",
1078
+ strokeWidth: "2",
1079
+ strokeLinecap: "round",
1080
+ strokeLinejoin: "round",
1081
+ children: [
1082
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }),
1083
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }),
1084
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("path", { d: "M10 9H8" }),
1085
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("path", { d: "M16 13H8" }),
1086
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("path", { d: "M16 17H8" })
1087
+ ]
1088
+ }
1089
+ );
938
1090
  }
939
1091
 
940
1092
  // src/template-editor.tsx
@@ -2148,13 +2300,78 @@ function SegmentBuilder({
2148
2300
 
2149
2301
  // src/suppression-manager.tsx
2150
2302
  var import_react13 = require("react");
2151
- var import_react_ui21 = require("@quanticjs/react-ui");
2303
+ var import_react_ui22 = require("@quanticjs/react-ui");
2152
2304
  var import_react_query18 = require("@quanticjs/react-query");
2305
+
2306
+ // src/admin-panel.tsx
2307
+ var import_react_ui21 = require("@quanticjs/react-ui");
2153
2308
  var import_jsx_runtime22 = require("react/jsx-runtime");
2309
+ function PanelHeader({ title, subtitle }) {
2310
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "flex items-center justify-between gap-2 border-b border-border px-4 py-3", children: /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex flex-col", children: [
2311
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("h2", { className: "text-sm font-semibold text-foreground", children: title }),
2312
+ subtitle && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("p", { className: "text-xs text-muted-foreground tabular-nums", children: subtitle })
2313
+ ] }) });
2314
+ }
2315
+ function SkeletonRows({ label, rows = 4 }) {
2316
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { role: "status", "aria-label": label, className: "flex flex-col gap-2 p-4", children: [
2317
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "sr-only", children: label }),
2318
+ Array.from({ length: rows }).map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react_ui21.Skeleton, { className: "h-10 w-full" }, i))
2319
+ ] });
2320
+ }
2321
+ function ErrorPanel({ message, onRetry }) {
2322
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex flex-col items-center gap-3 px-6 py-12 text-center", children: [
2323
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2324
+ "span",
2325
+ {
2326
+ "aria-hidden": "true",
2327
+ className: "grid size-12 place-items-center rounded-full bg-destructive/10 text-destructive [&_svg]:size-6",
2328
+ children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(AlertIcon3, {})
2329
+ }
2330
+ ),
2331
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("p", { className: "text-sm font-medium text-foreground", children: message }),
2332
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react_ui21.Button, { type: "button", onClick: onRetry, children: "Try again" })
2333
+ ] });
2334
+ }
2335
+ function Pager({
2336
+ label,
2337
+ page,
2338
+ totalPages,
2339
+ onPrev,
2340
+ onNext
2341
+ }) {
2342
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
2343
+ "nav",
2344
+ {
2345
+ "aria-label": label,
2346
+ className: "flex items-center justify-between gap-2 border-t border-border px-4 py-3",
2347
+ children: [
2348
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react_ui21.Button, { type: "button", variant: "outline", size: "sm", onClick: onPrev, disabled: page <= 1, children: "Previous" }),
2349
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("span", { className: "text-xs text-muted-foreground tabular-nums", children: [
2350
+ "Page ",
2351
+ page,
2352
+ " of ",
2353
+ totalPages
2354
+ ] }),
2355
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react_ui21.Button, { type: "button", variant: "outline", size: "sm", onClick: onNext, disabled: page >= totalPages, children: "Next" })
2356
+ ]
2357
+ }
2358
+ );
2359
+ }
2360
+ var FIELD_CLASS = "rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring";
2361
+ function AlertIcon3() {
2362
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
2363
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("path", { d: "M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0Z" }),
2364
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("line", { x1: "12", x2: "12", y1: "9", y2: "13" }),
2365
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("line", { x1: "12", x2: "12.01", y1: "17", y2: "17" })
2366
+ ] });
2367
+ }
2368
+
2369
+ // src/suppression-manager.tsx
2370
+ var import_jsx_runtime23 = require("react/jsx-runtime");
2154
2371
  var LIMIT3 = 20;
2155
2372
  var CHANNELS3 = ["inapp", "email", "push", "sms"];
2156
2373
  function SuppressionManager({ basePath = "/api", className }) {
2157
- const toast = (0, import_react_ui21.useToast)();
2374
+ const toast = (0, import_react_ui22.useToast)();
2158
2375
  const [page, setPage] = (0, import_react13.useState)(1);
2159
2376
  const [channel, setChannel] = (0, import_react13.useState)("email");
2160
2377
  const [address, setAddress] = (0, import_react13.useState)("");
@@ -2194,149 +2411,88 @@ function SuppressionManager({ basePath = "/api", className }) {
2194
2411
  event.preventDefault();
2195
2412
  add.mutate({ channel, address: address.trim(), reason: reason.trim() });
2196
2413
  };
2197
- const addForm = /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("form", { onSubmit: onAdd, className: "flex flex-wrap items-end gap-3", noValidate: true, children: [
2198
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex flex-col gap-1", children: [
2199
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("label", { htmlFor: "suppression-channel", className: "text-sm font-medium text-foreground", children: "Channel" }),
2200
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2201
- "select",
2202
- {
2203
- id: "suppression-channel",
2204
- value: channel,
2205
- onChange: (e) => setChannel(e.target.value),
2206
- className: "rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring",
2207
- children: CHANNELS3.map((c) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("option", { value: c, children: c }, c))
2208
- }
2209
- )
2210
- ] }),
2211
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex flex-col gap-1", children: [
2212
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("label", { htmlFor: "suppression-address", className: "text-sm font-medium text-foreground", children: "Address" }),
2213
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2214
- "input",
2215
- {
2216
- id: "suppression-address",
2217
- type: "text",
2218
- value: address,
2219
- onChange: (e) => setAddress(e.target.value),
2220
- className: "rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring"
2221
- }
2222
- )
2223
- ] }),
2224
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex flex-col gap-1", children: [
2225
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("label", { htmlFor: "suppression-reason", className: "text-sm font-medium text-foreground", children: "Reason" }),
2226
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2227
- "input",
2228
- {
2229
- id: "suppression-reason",
2230
- type: "text",
2231
- value: reason,
2232
- onChange: (e) => setReason(e.target.value),
2233
- className: "rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring"
2234
- }
2235
- )
2236
- ] }),
2237
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2238
- "button",
2239
- {
2240
- type: "submit",
2241
- disabled: add.isPending,
2242
- className: "rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:opacity-90 focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring disabled:opacity-50",
2243
- children: add.isPending ? "Adding\u2026" : "Add suppression"
2244
- }
2245
- )
2246
- ] });
2247
- let body;
2248
- if (isLoading) {
2249
- body = /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { role: "status", "aria-label": "Loading suppressions", className: "flex flex-col gap-2 p-4", children: [
2250
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "sr-only", children: "Loading suppressions" }),
2251
- [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { "aria-hidden": "true", className: "h-10 animate-pulse rounded bg-muted" }, i))
2252
- ] });
2253
- } else if (isError) {
2254
- body = /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex flex-col items-start gap-3 p-4", children: [
2255
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("p", { className: "text-sm text-foreground", children: "Failed to load suppressions" }),
2256
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2257
- "button",
2258
- {
2259
- type: "button",
2260
- onClick: () => void refetch(),
2261
- className: "rounded bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground hover:opacity-90 focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring",
2262
- children: "Try again"
2263
- }
2264
- )
2265
- ] });
2266
- } else {
2267
- const rows = data?.items ?? [];
2268
- const totalPages = data?.totalPages ?? 1;
2269
- if (rows.length === 0) {
2270
- body = /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "p-6 text-center text-sm text-muted-foreground", children: "No suppressions" });
2271
- } else {
2272
- body = /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
2273
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("table", { className: "w-full text-sm", children: [
2274
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("tr", { className: "border-b border-border text-start text-muted-foreground", children: [
2275
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("th", { scope: "col", className: "py-2 pe-4 font-medium", children: "Channel" }),
2276
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Address" }),
2277
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Reason" }),
2278
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Created" }),
2279
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "sr-only", children: "Actions" }) })
2414
+ const rows = data?.items ?? [];
2415
+ const totalPages = data?.totalPages ?? 1;
2416
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("section", { "aria-label": "Suppression list", className: (0, import_react_ui22.cn)("flex flex-col gap-4", className), children: [
2417
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_react_ui22.Card, { children: /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("form", { onSubmit: onAdd, className: "flex flex-wrap items-end gap-3 p-4", noValidate: true, children: [
2418
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("label", { className: "flex flex-col gap-1 text-sm", children: [
2419
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "font-medium text-foreground", children: "Channel" }),
2420
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
2421
+ "select",
2422
+ {
2423
+ value: channel,
2424
+ onChange: (e) => setChannel(e.target.value),
2425
+ className: FIELD_CLASS,
2426
+ children: CHANNELS3.map((c) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("option", { value: c, children: c }, c))
2427
+ }
2428
+ )
2429
+ ] }),
2430
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("label", { className: "flex flex-1 flex-col gap-1 text-sm", children: [
2431
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "font-medium text-foreground", children: "Address" }),
2432
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("input", { type: "text", value: address, onChange: (e) => setAddress(e.target.value), className: FIELD_CLASS })
2433
+ ] }),
2434
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("label", { className: "flex flex-1 flex-col gap-1 text-sm", children: [
2435
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "font-medium text-foreground", children: "Reason" }),
2436
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("input", { type: "text", value: reason, onChange: (e) => setReason(e.target.value), className: FIELD_CLASS })
2437
+ ] }),
2438
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_react_ui22.Button, { type: "submit", disabled: add.isPending, children: add.isPending ? "Adding\u2026" : "Add suppression" })
2439
+ ] }) }),
2440
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_react_ui22.Card, { children: [
2441
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(PanelHeader, { title: "Suppressions", subtitle: `${data?.total ?? rows.length} entries` }),
2442
+ isLoading ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(SkeletonRows, { label: "Loading suppressions" }) : isError ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ErrorPanel, { message: "Failed to load suppressions", onRetry: () => void refetch() }) : rows.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_react_ui22.EmptyState, { icon: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(BanIcon, {}), title: "No suppressions", description: "No addresses are suppressed." }) : /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
2443
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("table", { className: "w-full text-sm", children: [
2444
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("tr", { className: "border-b border-border text-start text-muted-foreground", children: [
2445
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Channel" }),
2446
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Address" }),
2447
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Reason" }),
2448
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Created" }),
2449
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "sr-only", children: "Actions" }) })
2280
2450
  ] }) }),
2281
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("tbody", { children: rows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("tr", { className: "border-b border-border", children: [
2282
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("td", { className: "py-3 pe-4 text-foreground", children: row.channel }),
2283
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("td", { className: "px-4 py-3 text-foreground", children: row.address }),
2284
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("td", { className: "px-4 py-3 text-muted-foreground", children: row.reason }),
2285
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("td", { className: "px-4 py-3 text-muted-foreground", children: (0, import_react_ui21.formatDateTime)(row.createdAt) }),
2286
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("td", { className: "px-4 py-3", children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2287
- "button",
2451
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("tbody", { children: rows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("tr", { className: "border-b border-border last:border-0 hover:bg-muted/40", children: [
2452
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("td", { className: "px-4 py-3", children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "inline-flex items-center rounded bg-muted px-1.5 py-0.5 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: row.channel }) }),
2453
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("td", { className: "px-4 py-3 text-foreground", children: row.address }),
2454
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("td", { className: "px-4 py-3 text-muted-foreground", children: row.reason }),
2455
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("td", { className: "px-4 py-3 text-muted-foreground", children: (0, import_react_ui22.formatDateTime)(row.createdAt) }),
2456
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("td", { className: "px-4 py-3 text-end", children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
2457
+ import_react_ui22.Button,
2288
2458
  {
2289
2459
  type: "button",
2460
+ variant: "ghost",
2461
+ size: "sm",
2462
+ className: "text-destructive",
2290
2463
  disabled: remove.isPending,
2291
2464
  onClick: () => remove.mutate(row.id),
2292
- className: "rounded-md border border-border px-3 py-1 text-sm font-medium text-destructive hover:bg-muted focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring disabled:opacity-50",
2293
2465
  children: "Remove"
2294
2466
  }
2295
2467
  ) })
2296
2468
  ] }, row.id)) })
2297
2469
  ] }),
2298
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("nav", { "aria-label": "Suppression pagination", className: "flex items-center justify-between", children: [
2299
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2300
- "button",
2301
- {
2302
- type: "button",
2303
- onClick: () => setPage((p) => Math.max(1, p - 1)),
2304
- disabled: page <= 1,
2305
- className: "rounded-md border border-border px-3 py-1 text-sm text-foreground hover:bg-muted focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring disabled:opacity-50",
2306
- children: "Previous"
2307
- }
2308
- ),
2309
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("span", { className: "text-xs text-muted-foreground", children: [
2310
- "Page ",
2470
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
2471
+ Pager,
2472
+ {
2473
+ label: "Suppression pagination",
2311
2474
  page,
2312
- " of ",
2313
- totalPages
2314
- ] }),
2315
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2316
- "button",
2317
- {
2318
- type: "button",
2319
- onClick: () => setPage((p) => Math.min(totalPages, p + 1)),
2320
- disabled: page >= totalPages,
2321
- className: "rounded-md border border-border px-3 py-1 text-sm text-foreground hover:bg-muted focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring disabled:opacity-50",
2322
- children: "Next"
2323
- }
2324
- )
2325
- ] })
2326
- ] });
2327
- }
2328
- }
2329
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("section", { "aria-label": "Suppression list", className: (0, import_react_ui21.cn)("flex flex-col gap-4", className), children: [
2330
- addForm,
2331
- body
2475
+ totalPages,
2476
+ onPrev: () => setPage((p) => Math.max(1, p - 1)),
2477
+ onNext: () => setPage((p) => Math.min(totalPages, p + 1))
2478
+ }
2479
+ )
2480
+ ] })
2481
+ ] })
2482
+ ] });
2483
+ }
2484
+ function BanIcon() {
2485
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
2486
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
2487
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("path", { d: "m4.9 4.9 14.2 14.2" })
2332
2488
  ] });
2333
2489
  }
2334
2490
 
2335
2491
  // src/dlq-console.tsx
2336
2492
  var import_react14 = require("react");
2337
- var import_react_ui22 = require("@quanticjs/react-ui");
2493
+ var import_react_ui23 = require("@quanticjs/react-ui");
2338
2494
  var import_react_query19 = require("@quanticjs/react-query");
2339
- var import_jsx_runtime23 = require("react/jsx-runtime");
2495
+ var import_jsx_runtime24 = require("react/jsx-runtime");
2340
2496
  var LIMIT4 = 20;
2341
2497
  var STATUS_FILTERS = ["queued", "replayed", "discarded"];
2342
2498
  function buildErrorDescription(error) {
@@ -2360,35 +2516,30 @@ function DlqMessageDetailRow({ id, basePath }) {
2360
2516
  (client) => client.get(`${basePath}/admin/dlq/${id}`)
2361
2517
  );
2362
2518
  if (isLoading) {
2363
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { role: "status", "aria-label": "Loading message detail", className: "flex flex-col gap-2 p-3", children: [
2364
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "sr-only", children: "Loading message detail" }),
2365
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { "aria-hidden": "true", className: "h-16 animate-pulse rounded bg-muted" })
2519
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { role: "status", "aria-label": "Loading message detail", className: "p-4", children: [
2520
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { className: "sr-only", children: "Loading message detail" }),
2521
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { "aria-hidden": "true", className: "h-16 animate-pulse rounded bg-muted motion-reduce:animate-none" })
2366
2522
  ] });
2367
2523
  }
2368
2524
  if (isError) {
2369
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex flex-col items-start gap-3 p-3", children: [
2370
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { className: "text-sm text-foreground", children: "Failed to load message detail" }),
2371
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
2372
- "button",
2373
- {
2374
- type: "button",
2375
- onClick: () => void refetch(),
2376
- className: "rounded bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground hover:opacity-90 focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring",
2377
- children: "Try again"
2378
- }
2379
- )
2525
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "flex flex-col items-start gap-3 p-4", children: [
2526
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { className: "text-sm text-foreground", children: "Failed to load message detail" }),
2527
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_react_ui23.Button, { type: "button", size: "sm", onClick: () => void refetch(), children: "Try again" })
2380
2528
  ] });
2381
2529
  }
2382
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex flex-col gap-2 p-3 text-sm", children: [
2383
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("p", { className: "text-muted-foreground", children: [
2384
- "Error: ",
2385
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "text-foreground", children: data?.errorMessage ?? "\u2014" })
2530
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "flex flex-col gap-3 px-5 py-4 text-sm", children: [
2531
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { children: [
2532
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "mb-1 text-[11px] font-medium uppercase tracking-wider text-muted-foreground", children: "Error" }),
2533
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "font-mono text-xs text-destructive", children: data?.errorMessage ?? "\u2014" })
2386
2534
  ] }),
2387
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("pre", { className: "max-h-64 overflow-auto rounded bg-muted p-3 text-xs text-foreground", children: JSON.stringify(data?.payload ?? {}, null, 2) })
2535
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { children: [
2536
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "mb-1 text-[11px] font-medium uppercase tracking-wider text-muted-foreground", children: "Payload" }),
2537
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("pre", { className: "max-h-64 overflow-auto rounded-md border border-border bg-card p-3 font-mono text-xs leading-relaxed text-foreground", children: JSON.stringify(data?.payload ?? {}, null, 2) })
2538
+ ] })
2388
2539
  ] });
2389
2540
  }
2390
2541
  function DlqConsole({ basePath = "/api", className }) {
2391
- const toast = (0, import_react_ui22.useToast)();
2542
+ const toast = (0, import_react_ui23.useToast)();
2392
2543
  const [page, setPage] = (0, import_react14.useState)(1);
2393
2544
  const [statusFilter, setStatusFilter] = (0, import_react14.useState)("");
2394
2545
  const [expandedId, setExpandedId] = (0, import_react14.useState)(null);
@@ -2432,158 +2583,116 @@ function DlqConsole({ basePath = "/api", className }) {
2432
2583
  })
2433
2584
  }
2434
2585
  );
2435
- const filterControl = /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex items-center gap-2", children: [
2436
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("label", { htmlFor: "dlq-status", className: "text-sm font-medium text-foreground", children: "Status" }),
2437
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
2438
- "select",
2439
- {
2440
- id: "dlq-status",
2441
- value: statusFilter,
2442
- onChange: (e) => {
2443
- setStatusFilter(e.target.value);
2444
- setPage(1);
2445
- },
2446
- className: "rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring",
2447
- children: [
2448
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("option", { value: "", children: "All" }),
2449
- STATUS_FILTERS.map((s) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("option", { value: s, children: s }, s))
2450
- ]
2451
- }
2452
- )
2453
- ] });
2454
- let body;
2455
- if (isLoading) {
2456
- body = /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
2457
- "div",
2458
- {
2459
- role: "status",
2460
- "aria-label": "Loading dead-letter messages",
2461
- className: "flex flex-col gap-2 p-4",
2462
- children: [
2463
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "sr-only", children: "Loading dead-letter messages" }),
2464
- [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { "aria-hidden": "true", className: "h-10 animate-pulse rounded bg-muted" }, i))
2465
- ]
2466
- }
2467
- );
2468
- } else if (isError) {
2469
- body = /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex flex-col items-start gap-3 p-4", children: [
2470
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { className: "text-sm text-foreground", children: "Failed to load dead-letter messages" }),
2471
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
2472
- "button",
2586
+ const rows = data?.items ?? [];
2587
+ const totalPages = data?.totalPages ?? 1;
2588
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("section", { "aria-label": "Dead-letter queue", className: (0, import_react_ui23.cn)("flex flex-col gap-4", className), children: [
2589
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "flex items-center justify-end gap-2", children: [
2590
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("label", { htmlFor: "dlq-status", className: "text-sm font-medium text-foreground", children: "Status" }),
2591
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
2592
+ "select",
2473
2593
  {
2474
- type: "button",
2475
- onClick: () => void refetch(),
2476
- className: "rounded bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground hover:opacity-90 focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring",
2477
- children: "Try again"
2594
+ id: "dlq-status",
2595
+ value: statusFilter,
2596
+ onChange: (e) => {
2597
+ setStatusFilter(e.target.value);
2598
+ setPage(1);
2599
+ },
2600
+ className: FIELD_CLASS,
2601
+ children: [
2602
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("option", { value: "", children: "All" }),
2603
+ STATUS_FILTERS.map((s) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("option", { value: s, children: s }, s))
2604
+ ]
2478
2605
  }
2479
2606
  )
2480
- ] });
2481
- } else {
2482
- const rows = data?.items ?? [];
2483
- const totalPages = data?.totalPages ?? 1;
2484
- if (rows.length === 0) {
2485
- body = /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "p-6 text-center text-sm text-muted-foreground", children: "No dead-letter messages" });
2486
- } else {
2487
- body = /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
2488
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("table", { className: "w-full text-sm", children: [
2489
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("tr", { className: "border-b border-border text-start text-muted-foreground", children: [
2490
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("th", { scope: "col", className: "py-2 pe-4 font-medium", children: "Request" }),
2491
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Failure reason" }),
2492
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Status" }),
2493
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Attempts" }),
2494
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "First seen" }),
2495
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "sr-only", children: "Actions" }) })
2496
- ] }) }),
2497
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("tbody", { children: rows.map((row) => {
2498
- const isExpanded = expandedId === row.id;
2499
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_react14.Fragment, { children: [
2500
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("tr", { className: "border-b border-border", children: [
2501
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("td", { className: "py-3 pe-4", children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
2502
- "button",
2503
- {
2504
- type: "button",
2505
- "aria-expanded": isExpanded,
2506
- onClick: () => setExpandedId(isExpanded ? null : row.id),
2507
- className: "rounded text-start font-medium text-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring",
2508
- children: row.requestId ?? row.id
2509
- }
2510
- ) }),
2511
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("td", { className: "px-4 py-3 text-muted-foreground", children: row.failureReason }),
2512
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("td", { className: "px-4 py-3", children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_react_ui22.StatusBadge, { variant: statusVariant3(row.status), appearance: "dot", children: row.status }) }),
2513
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("td", { className: "px-4 py-3 text-muted-foreground", children: row.attemptCount }),
2514
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("td", { className: "px-4 py-3 text-muted-foreground", children: (0, import_react_ui22.formatDateTime)(row.firstSeenAt) }),
2515
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("td", { className: "px-4 py-3", children: /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex items-center gap-2", children: [
2516
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
2517
- "button",
2518
- {
2519
- type: "button",
2520
- disabled: replay.isPending,
2521
- onClick: () => replay.mutate(row.id),
2522
- className: "rounded-md border border-border px-3 py-1 text-sm font-medium text-foreground hover:bg-muted focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring disabled:opacity-50",
2523
- children: "Replay"
2524
- }
2525
- ),
2526
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
2527
- "button",
2528
- {
2529
- type: "button",
2530
- disabled: discard.isPending,
2531
- onClick: () => discard.mutate(row.id),
2532
- className: "rounded-md border border-border px-3 py-1 text-sm font-medium text-destructive hover:bg-muted focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring disabled:opacity-50",
2533
- children: "Discard"
2534
- }
2535
- )
2536
- ] }) })
2607
+ ] }),
2608
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_react_ui23.Card, { children: [
2609
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(PanelHeader, { title: "Messages", subtitle: !isLoading && !isError ? `${data?.total ?? rows.length} messages` : void 0 }),
2610
+ isLoading ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(SkeletonRows, { label: "Loading dead-letter messages" }) : isError ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ErrorPanel, { message: "Failed to load dead-letter messages", onRetry: () => void refetch() }) : rows.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_react_ui23.EmptyState, { icon: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(SkullIcon, {}), title: "No dead-letter messages", description: "The queue is empty for the current filter." }) : /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
2611
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("ul", { className: "divide-y divide-border", children: rows.map((row) => {
2612
+ const isExpanded = expandedId === row.id;
2613
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_react14.Fragment, { children: [
2614
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("li", { className: "flex items-start gap-3 px-4 py-3.5", children: [
2615
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2616
+ "button",
2617
+ {
2618
+ type: "button",
2619
+ "aria-expanded": isExpanded,
2620
+ "aria-label": isExpanded ? "Collapse" : "Expand",
2621
+ onClick: () => setExpandedId(isExpanded ? null : row.id),
2622
+ className: "mt-0.5 grid size-5 shrink-0 place-items-center rounded text-muted-foreground hover:bg-muted focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring [&_svg]:size-4",
2623
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ChevronIcon, { open: isExpanded })
2624
+ }
2625
+ ),
2626
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "min-w-0 flex-1", children: [
2627
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "flex flex-wrap items-center gap-2", children: [
2628
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { className: "font-mono text-xs text-muted-foreground", children: row.requestId ?? row.id }),
2629
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_react_ui23.StatusBadge, { variant: statusVariant3(row.status), appearance: "dot", children: row.status })
2630
+ ] }),
2631
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "mt-1 truncate text-sm font-medium text-foreground", children: row.failureReason }),
2632
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "mt-1 flex flex-wrap items-center gap-x-3 text-xs text-muted-foreground", children: [
2633
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("span", { className: "tabular-nums", children: [
2634
+ row.attemptCount,
2635
+ " attempts"
2636
+ ] }),
2637
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { "aria-hidden": "true", className: "text-border", children: "\xB7" }),
2638
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("span", { className: "tabular-nums", children: [
2639
+ "first seen ",
2640
+ (0, import_react_ui23.formatDateTime)(row.firstSeenAt)
2641
+ ] })
2642
+ ] })
2537
2643
  ] }),
2538
- isExpanded && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("tr", { className: "border-b border-border", children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("td", { colSpan: 6, className: "bg-muted/40", children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(DlqMessageDetailRow, { id: row.id, basePath }) }) })
2539
- ] }, row.id);
2540
- }) })
2541
- ] }),
2542
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("nav", { "aria-label": "Dead-letter pagination", className: "flex items-center justify-between", children: [
2543
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
2544
- "button",
2545
- {
2546
- type: "button",
2547
- onClick: () => setPage((p) => Math.max(1, p - 1)),
2548
- disabled: page <= 1,
2549
- className: "rounded-md border border-border px-3 py-1 text-sm text-foreground hover:bg-muted focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring disabled:opacity-50",
2550
- children: "Previous"
2551
- }
2552
- ),
2553
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("span", { className: "text-xs text-muted-foreground", children: [
2554
- "Page ",
2644
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "flex shrink-0 items-center gap-1.5", children: [
2645
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_react_ui23.Button, { type: "button", variant: "outline", size: "sm", disabled: replay.isPending, onClick: () => replay.mutate(row.id), children: "Replay" }),
2646
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_react_ui23.Button, { type: "button", variant: "ghost", size: "sm", className: "text-destructive", disabled: discard.isPending, onClick: () => discard.mutate(row.id), children: "Discard" })
2647
+ ] })
2648
+ ] }),
2649
+ isExpanded && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("li", { className: "border-t border-border bg-muted/40", children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(DlqMessageDetailRow, { id: row.id, basePath }) })
2650
+ ] }, row.id);
2651
+ }) }),
2652
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2653
+ Pager,
2654
+ {
2655
+ label: "Dead-letter pagination",
2555
2656
  page,
2556
- " of ",
2557
- totalPages
2558
- ] }),
2559
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
2560
- "button",
2561
- {
2562
- type: "button",
2563
- onClick: () => setPage((p) => Math.min(totalPages, p + 1)),
2564
- disabled: page >= totalPages,
2565
- className: "rounded-md border border-border px-3 py-1 text-sm text-foreground hover:bg-muted focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring disabled:opacity-50",
2566
- children: "Next"
2567
- }
2568
- )
2569
- ] })
2570
- ] });
2657
+ totalPages,
2658
+ onPrev: () => setPage((p) => Math.max(1, p - 1)),
2659
+ onNext: () => setPage((p) => Math.min(totalPages, p + 1))
2660
+ }
2661
+ )
2662
+ ] })
2663
+ ] })
2664
+ ] });
2665
+ }
2666
+ function ChevronIcon({ open }) {
2667
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2668
+ "svg",
2669
+ {
2670
+ viewBox: "0 0 24 24",
2671
+ fill: "none",
2672
+ stroke: "currentColor",
2673
+ strokeWidth: "2",
2674
+ strokeLinecap: "round",
2675
+ strokeLinejoin: "round",
2676
+ className: (0, import_react_ui23.cn)("transition-transform motion-reduce:transition-none", open && "rotate-90"),
2677
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("path", { d: "m9 18 6-6-6-6" })
2571
2678
  }
2572
- }
2573
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("section", { "aria-label": "Dead-letter queue", className: (0, import_react_ui22.cn)("flex flex-col gap-4", className), children: [
2574
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("header", { className: "flex items-center justify-between", children: [
2575
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("h2", { className: "text-sm font-semibold text-foreground", children: "Dead-letter queue" }),
2576
- filterControl
2577
- ] }),
2578
- body
2679
+ );
2680
+ }
2681
+ function SkullIcon() {
2682
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
2683
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("circle", { cx: "9", cy: "12", r: "1" }),
2684
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("circle", { cx: "15", cy: "12", r: "1" }),
2685
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("path", { d: "M8 20v2h8v-2" }),
2686
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("path", { d: "m12.5 17-.5-1-.5 1h1Z" }),
2687
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("path", { d: "M16 20a2 2 0 0 0 1.56-3.25 8 8 0 1 0-11.12 0A2 2 0 0 0 8 20" })
2579
2688
  ] });
2580
2689
  }
2581
2690
 
2582
2691
  // src/catalog-editor.tsx
2583
2692
  var import_react15 = require("react");
2584
- var import_react_ui23 = require("@quanticjs/react-ui");
2693
+ var import_react_ui24 = require("@quanticjs/react-ui");
2585
2694
  var import_react_query20 = require("@quanticjs/react-query");
2586
- var import_jsx_runtime24 = require("react/jsx-runtime");
2695
+ var import_jsx_runtime25 = require("react/jsx-runtime");
2587
2696
  function normalize2(data) {
2588
2697
  if (!data) return [];
2589
2698
  if (Array.isArray(data)) return data;
@@ -2595,7 +2704,7 @@ function normalize2(data) {
2595
2704
  );
2596
2705
  }
2597
2706
  function CatalogEditor({ basePath = "/api", className }) {
2598
- const toast = (0, import_react_ui23.useToast)();
2707
+ const toast = (0, import_react_ui24.useToast)();
2599
2708
  const [filter, setFilter] = (0, import_react15.useState)("");
2600
2709
  const [editingId, setEditingId] = (0, import_react15.useState)(null);
2601
2710
  const [editValue, setEditValue] = (0, import_react15.useState)("");
@@ -2665,10 +2774,10 @@ function CatalogEditor({ basePath = "/api", className }) {
2665
2774
  setEditingId(`${entry.key}:${entry.locale}`);
2666
2775
  setEditValue(entry.value);
2667
2776
  };
2668
- const addForm = /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("form", { onSubmit: onAdd, className: "flex flex-wrap items-end gap-3", noValidate: true, children: [
2669
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "flex flex-col gap-1", children: [
2670
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("label", { htmlFor: "catalog-new-key", className: "text-sm font-medium text-foreground", children: "Key" }),
2671
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2777
+ const addForm = /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("form", { onSubmit: onAdd, className: "flex flex-wrap items-end gap-3", noValidate: true, children: [
2778
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "flex flex-col gap-1", children: [
2779
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("label", { htmlFor: "catalog-new-key", className: "text-sm font-medium text-foreground", children: "Key" }),
2780
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
2672
2781
  "input",
2673
2782
  {
2674
2783
  id: "catalog-new-key",
@@ -2679,9 +2788,9 @@ function CatalogEditor({ basePath = "/api", className }) {
2679
2788
  }
2680
2789
  )
2681
2790
  ] }),
2682
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "flex flex-col gap-1", children: [
2683
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("label", { htmlFor: "catalog-new-locale", className: "text-sm font-medium text-foreground", children: "Locale" }),
2684
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2791
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "flex flex-col gap-1", children: [
2792
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("label", { htmlFor: "catalog-new-locale", className: "text-sm font-medium text-foreground", children: "Locale" }),
2793
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
2685
2794
  "input",
2686
2795
  {
2687
2796
  id: "catalog-new-locale",
@@ -2692,9 +2801,9 @@ function CatalogEditor({ basePath = "/api", className }) {
2692
2801
  }
2693
2802
  )
2694
2803
  ] }),
2695
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "flex flex-col gap-1", children: [
2696
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("label", { htmlFor: "catalog-new-value", className: "text-sm font-medium text-foreground", children: "Value" }),
2697
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2804
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "flex flex-col gap-1", children: [
2805
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("label", { htmlFor: "catalog-new-value", className: "text-sm font-medium text-foreground", children: "Value" }),
2806
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
2698
2807
  "input",
2699
2808
  {
2700
2809
  id: "catalog-new-value",
@@ -2705,7 +2814,7 @@ function CatalogEditor({ basePath = "/api", className }) {
2705
2814
  }
2706
2815
  )
2707
2816
  ] }),
2708
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2817
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
2709
2818
  "button",
2710
2819
  {
2711
2820
  type: "submit",
@@ -2715,9 +2824,9 @@ function CatalogEditor({ basePath = "/api", className }) {
2715
2824
  }
2716
2825
  )
2717
2826
  ] });
2718
- const filterInput = /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "flex flex-col gap-1", children: [
2719
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("label", { htmlFor: "catalog-filter", className: "text-sm font-medium text-foreground", children: "Filter by key" }),
2720
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2827
+ const filterInput = /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "flex flex-col gap-1", children: [
2828
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("label", { htmlFor: "catalog-filter", className: "text-sm font-medium text-foreground", children: "Filter by key" }),
2829
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
2721
2830
  "input",
2722
2831
  {
2723
2832
  id: "catalog-filter",
@@ -2730,14 +2839,14 @@ function CatalogEditor({ basePath = "/api", className }) {
2730
2839
  ] });
2731
2840
  let body;
2732
2841
  if (isLoading) {
2733
- body = /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { role: "status", "aria-label": "Loading catalog", className: "flex flex-col gap-2 p-4", children: [
2734
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { className: "sr-only", children: "Loading catalog" }),
2735
- [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { "aria-hidden": "true", className: "h-10 animate-pulse rounded bg-muted" }, i))
2842
+ body = /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { role: "status", "aria-label": "Loading catalog", className: "flex flex-col gap-2 p-4", children: [
2843
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "sr-only", children: "Loading catalog" }),
2844
+ [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { "aria-hidden": "true", className: "h-10 animate-pulse rounded bg-muted" }, i))
2736
2845
  ] });
2737
2846
  } else if (isError) {
2738
- body = /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "flex flex-col items-start gap-3 p-4", children: [
2739
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { className: "text-sm text-foreground", children: "Failed to load catalog" }),
2740
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2847
+ body = /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "flex flex-col items-start gap-3 p-4", children: [
2848
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("p", { className: "text-sm text-foreground", children: "Failed to load catalog" }),
2849
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
2741
2850
  "button",
2742
2851
  {
2743
2852
  type: "button",
@@ -2748,22 +2857,22 @@ function CatalogEditor({ basePath = "/api", className }) {
2748
2857
  )
2749
2858
  ] });
2750
2859
  } else if (filtered.length === 0) {
2751
- body = /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "p-6 text-center text-sm text-muted-foreground", children: entries.length === 0 ? "No catalog entries" : "No entries match your filter" });
2860
+ body = /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { className: "p-6 text-center text-sm text-muted-foreground", children: entries.length === 0 ? "No catalog entries" : "No entries match your filter" });
2752
2861
  } else {
2753
- body = /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("table", { className: "w-full text-sm", children: [
2754
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("tr", { className: "border-b border-border text-start text-muted-foreground", children: [
2755
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("th", { scope: "col", className: "py-2 pe-4 font-medium", children: "Key" }),
2756
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Locale" }),
2757
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Value" }),
2758
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { className: "sr-only", children: "Actions" }) })
2862
+ body = /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("table", { className: "w-full text-sm", children: [
2863
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("tr", { className: "border-b border-border text-start text-muted-foreground", children: [
2864
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("th", { scope: "col", className: "py-2 pe-4 font-medium", children: "Key" }),
2865
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Locale" }),
2866
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Value" }),
2867
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "sr-only", children: "Actions" }) })
2759
2868
  ] }) }),
2760
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("tbody", { children: filtered.map((entry) => {
2869
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("tbody", { children: filtered.map((entry) => {
2761
2870
  const rowId = `${entry.key}:${entry.locale}`;
2762
2871
  const isEditing = editingId === rowId;
2763
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("tr", { className: "border-b border-border", children: [
2764
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("td", { className: "py-3 pe-4 font-mono text-foreground", children: entry.key }),
2765
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("td", { className: "px-4 py-3 text-foreground", children: entry.locale }),
2766
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("td", { className: "px-4 py-3 text-foreground", children: isEditing ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2872
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("tr", { className: "border-b border-border", children: [
2873
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("td", { className: "py-3 pe-4 font-mono text-foreground", children: entry.key }),
2874
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("td", { className: "px-4 py-3 text-foreground", children: entry.locale }),
2875
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("td", { className: "px-4 py-3 text-foreground", children: isEditing ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
2767
2876
  "input",
2768
2877
  {
2769
2878
  type: "text",
@@ -2773,8 +2882,8 @@ function CatalogEditor({ basePath = "/api", className }) {
2773
2882
  className: "w-full rounded-md border border-border bg-background px-3 py-1.5 text-sm text-foreground focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring"
2774
2883
  }
2775
2884
  ) : entry.value }),
2776
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("td", { className: "px-4 py-3", children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "flex items-center gap-2", children: isEditing ? /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
2777
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2885
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("td", { className: "px-4 py-3", children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { className: "flex items-center gap-2", children: isEditing ? /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(import_jsx_runtime25.Fragment, { children: [
2886
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
2778
2887
  "button",
2779
2888
  {
2780
2889
  type: "button",
@@ -2788,7 +2897,7 @@ function CatalogEditor({ basePath = "/api", className }) {
2788
2897
  children: "Save"
2789
2898
  }
2790
2899
  ),
2791
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2900
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
2792
2901
  "button",
2793
2902
  {
2794
2903
  type: "button",
@@ -2797,8 +2906,8 @@ function CatalogEditor({ basePath = "/api", className }) {
2797
2906
  children: "Cancel"
2798
2907
  }
2799
2908
  )
2800
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
2801
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2909
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(import_jsx_runtime25.Fragment, { children: [
2910
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
2802
2911
  "button",
2803
2912
  {
2804
2913
  type: "button",
@@ -2807,7 +2916,7 @@ function CatalogEditor({ basePath = "/api", className }) {
2807
2916
  children: "Edit"
2808
2917
  }
2809
2918
  ),
2810
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2919
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
2811
2920
  "button",
2812
2921
  {
2813
2922
  type: "button",
@@ -2822,7 +2931,7 @@ function CatalogEditor({ basePath = "/api", className }) {
2822
2931
  }) })
2823
2932
  ] });
2824
2933
  }
2825
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("section", { "aria-label": "Catalog editor", className: (0, import_react_ui23.cn)("flex flex-col gap-4", className), children: [
2934
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("section", { "aria-label": "Catalog editor", className: (0, import_react_ui24.cn)("flex flex-col gap-4", className), children: [
2826
2935
  addForm,
2827
2936
  filterInput,
2828
2937
  body
@@ -2830,9 +2939,9 @@ function CatalogEditor({ basePath = "/api", className }) {
2830
2939
  }
2831
2940
 
2832
2941
  // src/missing-translations-panel.tsx
2833
- var import_react_ui24 = require("@quanticjs/react-ui");
2942
+ var import_react_ui25 = require("@quanticjs/react-ui");
2834
2943
  var import_react_query21 = require("@quanticjs/react-query");
2835
- var import_jsx_runtime25 = require("react/jsx-runtime");
2944
+ var import_jsx_runtime26 = require("react/jsx-runtime");
2836
2945
  function normalize3(data) {
2837
2946
  if (Array.isArray(data)) return data;
2838
2947
  return data?.missing ?? [];
@@ -2846,23 +2955,23 @@ function MissingTranslationsPanel({
2846
2955
  (client) => client.get(`${basePath}/i18n/catalog/missing`)
2847
2956
  );
2848
2957
  if (isLoading) {
2849
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
2958
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
2850
2959
  "div",
2851
2960
  {
2852
2961
  role: "status",
2853
2962
  "aria-label": "Loading missing translations",
2854
- className: (0, import_react_ui24.cn)("flex flex-col gap-2 p-4", className),
2963
+ className: (0, import_react_ui25.cn)("flex flex-col gap-2 p-4", className),
2855
2964
  children: [
2856
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "sr-only", children: "Loading missing translations" }),
2857
- [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { "aria-hidden": "true", className: "h-10 animate-pulse rounded bg-muted" }, i))
2965
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "sr-only", children: "Loading missing translations" }),
2966
+ [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { "aria-hidden": "true", className: "h-10 animate-pulse rounded bg-muted" }, i))
2858
2967
  ]
2859
2968
  }
2860
2969
  );
2861
2970
  }
2862
2971
  if (isError) {
2863
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: (0, import_react_ui24.cn)("flex flex-col items-start gap-3 p-4", className), children: [
2864
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("p", { className: "text-sm text-foreground", children: "Failed to load missing translations" }),
2865
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
2972
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: (0, import_react_ui25.cn)("flex flex-col items-start gap-3 p-4", className), children: [
2973
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("p", { className: "text-sm text-foreground", children: "Failed to load missing translations" }),
2974
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2866
2975
  "button",
2867
2976
  {
2868
2977
  type: "button",
@@ -2875,26 +2984,26 @@ function MissingTranslationsPanel({
2875
2984
  }
2876
2985
  const rows = normalize3(data);
2877
2986
  if (rows.length === 0) {
2878
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { className: (0, import_react_ui24.cn)("p-6 text-center text-sm text-muted-foreground", className), children: "No missing translations" });
2987
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: (0, import_react_ui25.cn)("p-6 text-center text-sm text-muted-foreground", className), children: "No missing translations" });
2879
2988
  }
2880
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("section", { "aria-label": "Missing translations", className: (0, import_react_ui24.cn)("flex flex-col gap-3", className), children: /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("table", { className: "w-full text-sm", children: [
2881
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("tr", { className: "border-b border-border text-start text-muted-foreground", children: [
2882
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("th", { scope: "col", className: "py-2 pe-4 font-medium", children: "Key" }),
2883
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Locale" }),
2884
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Type" })
2989
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("section", { "aria-label": "Missing translations", className: (0, import_react_ui25.cn)("flex flex-col gap-3", className), children: /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("table", { className: "w-full text-sm", children: [
2990
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("tr", { className: "border-b border-border text-start text-muted-foreground", children: [
2991
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("th", { scope: "col", className: "py-2 pe-4 font-medium", children: "Key" }),
2992
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Locale" }),
2993
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Type" })
2885
2994
  ] }) }),
2886
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("tbody", { children: rows.map((row, i) => /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("tr", { className: "border-b border-border", children: [
2887
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("td", { className: "py-3 pe-4 font-mono text-foreground", children: row.key }),
2888
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("td", { className: "px-4 py-3 text-foreground", children: row.locale }),
2889
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("td", { className: "px-4 py-3 text-muted-foreground", children: row.type ?? "\u2014" })
2995
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("tbody", { children: rows.map((row, i) => /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("tr", { className: "border-b border-border", children: [
2996
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("td", { className: "py-3 pe-4 font-mono text-foreground", children: row.key }),
2997
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("td", { className: "px-4 py-3 text-foreground", children: row.locale }),
2998
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("td", { className: "px-4 py-3 text-muted-foreground", children: row.type ?? "\u2014" })
2890
2999
  ] }, `${row.key}:${row.locale}:${i}`)) })
2891
3000
  ] }) });
2892
3001
  }
2893
3002
 
2894
3003
  // src/fallback-report-panel.tsx
2895
- var import_react_ui25 = require("@quanticjs/react-ui");
3004
+ var import_react_ui26 = require("@quanticjs/react-ui");
2896
3005
  var import_react_query22 = require("@quanticjs/react-query");
2897
- var import_jsx_runtime26 = require("react/jsx-runtime");
3006
+ var import_jsx_runtime27 = require("react/jsx-runtime");
2898
3007
  function normalize4(data) {
2899
3008
  if (Array.isArray(data)) return data;
2900
3009
  return data?.entries ?? [];
@@ -2905,23 +3014,23 @@ function FallbackReportPanel({ basePath = "/api", className }) {
2905
3014
  (client) => client.get(`${basePath}/i18n/catalog/fallback-report`)
2906
3015
  );
2907
3016
  if (isLoading) {
2908
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
3017
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
2909
3018
  "div",
2910
3019
  {
2911
3020
  role: "status",
2912
3021
  "aria-label": "Loading fallback report",
2913
- className: (0, import_react_ui25.cn)("flex flex-col gap-2 p-4", className),
3022
+ className: (0, import_react_ui26.cn)("flex flex-col gap-2 p-4", className),
2914
3023
  children: [
2915
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "sr-only", children: "Loading fallback report" }),
2916
- [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { "aria-hidden": "true", className: "h-10 animate-pulse rounded bg-muted" }, i))
3024
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "sr-only", children: "Loading fallback report" }),
3025
+ [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { "aria-hidden": "true", className: "h-10 animate-pulse rounded bg-muted" }, i))
2917
3026
  ]
2918
3027
  }
2919
3028
  );
2920
3029
  }
2921
3030
  if (isError) {
2922
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: (0, import_react_ui25.cn)("flex flex-col items-start gap-3 p-4", className), children: [
2923
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("p", { className: "text-sm text-foreground", children: "Failed to load fallback report" }),
2924
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
3031
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: (0, import_react_ui26.cn)("flex flex-col items-start gap-3 p-4", className), children: [
3032
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("p", { className: "text-sm text-foreground", children: "Failed to load fallback report" }),
3033
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
2925
3034
  "button",
2926
3035
  {
2927
3036
  type: "button",
@@ -2934,22 +3043,22 @@ function FallbackReportPanel({ basePath = "/api", className }) {
2934
3043
  }
2935
3044
  const rows = normalize4(data);
2936
3045
  if (rows.length === 0) {
2937
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: (0, import_react_ui25.cn)("p-6 text-center text-sm text-muted-foreground", className), children: "No fallbacks reported" });
3046
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: (0, import_react_ui26.cn)("p-6 text-center text-sm text-muted-foreground", className), children: "No fallbacks reported" });
2938
3047
  }
2939
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("section", { "aria-label": "Fallback report", className: (0, import_react_ui25.cn)("flex flex-col gap-3", className), children: /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("table", { className: "w-full text-sm", children: [
2940
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("tr", { className: "border-b border-border text-start text-muted-foreground", children: [
2941
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("th", { scope: "col", className: "py-2 pe-4 font-medium", children: "Key" }),
2942
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Requested" }),
2943
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Resolved" }),
2944
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Chain" })
3048
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("section", { "aria-label": "Fallback report", className: (0, import_react_ui26.cn)("flex flex-col gap-3", className), children: /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("table", { className: "w-full text-sm", children: [
3049
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("tr", { className: "border-b border-border text-start text-muted-foreground", children: [
3050
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("th", { scope: "col", className: "py-2 pe-4 font-medium", children: "Key" }),
3051
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Requested" }),
3052
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Resolved" }),
3053
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Chain" })
2945
3054
  ] }) }),
2946
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("tbody", { children: rows.map((row, i) => /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("tr", { className: "border-b border-border", children: [
2947
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("td", { className: "py-3 pe-4 font-mono text-foreground", children: row.key }),
2948
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("td", { className: "px-4 py-3 text-foreground", children: row.requestedLocale }),
2949
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("td", { className: "px-4 py-3 text-foreground", children: row.resolvedLocale }),
2950
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("td", { className: "px-4 py-3 text-muted-foreground", children: row.steps && row.steps.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("ol", { className: "flex flex-wrap items-center gap-1", children: row.steps.map((step, si) => /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("li", { className: "flex items-center gap-1", children: [
2951
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "rounded bg-muted px-1.5 py-0.5 text-xs text-foreground", children: step }),
2952
- si < (row.steps?.length ?? 0) - 1 && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { "aria-hidden": "true", className: "text-muted-foreground", children: "\u2192" })
3055
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("tbody", { children: rows.map((row, i) => /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("tr", { className: "border-b border-border", children: [
3056
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("td", { className: "py-3 pe-4 font-mono text-foreground", children: row.key }),
3057
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("td", { className: "px-4 py-3 text-foreground", children: row.requestedLocale }),
3058
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("td", { className: "px-4 py-3 text-foreground", children: row.resolvedLocale }),
3059
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("td", { className: "px-4 py-3 text-muted-foreground", children: row.steps && row.steps.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("ol", { className: "flex flex-wrap items-center gap-1", children: row.steps.map((step, si) => /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("li", { className: "flex items-center gap-1", children: [
3060
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "rounded bg-muted px-1.5 py-0.5 text-xs text-foreground", children: step }),
3061
+ si < (row.steps?.length ?? 0) - 1 && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { "aria-hidden": "true", className: "text-muted-foreground", children: "\u2192" })
2953
3062
  ] }, `${step}:${si}`)) }) : "\u2014" })
2954
3063
  ] }, `${row.key}:${row.requestedLocale}:${i}`)) })
2955
3064
  ] }) });
@@ -2957,12 +3066,12 @@ function FallbackReportPanel({ basePath = "/api", className }) {
2957
3066
 
2958
3067
  // src/recipient-admin-panel.tsx
2959
3068
  var import_react16 = require("react");
2960
- var import_react_ui26 = require("@quanticjs/react-ui");
3069
+ var import_react_ui27 = require("@quanticjs/react-ui");
2961
3070
  var import_react_query23 = require("@quanticjs/react-query");
2962
- var import_jsx_runtime27 = require("react/jsx-runtime");
3071
+ var import_jsx_runtime28 = require("react/jsx-runtime");
2963
3072
  var LIMIT5 = 20;
2964
3073
  function RecipientAdminPanel({ basePath = "/api", className }) {
2965
- const toast = (0, import_react_ui26.useToast)();
3074
+ const toast = (0, import_react_ui27.useToast)();
2966
3075
  const [page, setPage] = (0, import_react16.useState)(1);
2967
3076
  const [search, setSearch] = (0, import_react16.useState)("");
2968
3077
  const [query, setQuery] = (0, import_react16.useState)("");
@@ -3010,10 +3119,10 @@ function RecipientAdminPanel({ basePath = "/api", className }) {
3010
3119
  if (row.consentSms) active.push("sms");
3011
3120
  return active.length > 0 ? active.join(", ") : "none";
3012
3121
  };
3013
- const searchForm = /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("form", { onSubmit: onSearch, className: "flex flex-wrap items-end gap-3", noValidate: true, children: [
3014
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex flex-col gap-1", children: [
3015
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("label", { htmlFor: "recipient-search", className: "text-sm font-medium text-foreground", children: "Search recipients" }),
3016
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3122
+ const searchForm = /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("form", { onSubmit: onSearch, className: "flex flex-wrap items-end gap-3", noValidate: true, children: [
3123
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex flex-col gap-1", children: [
3124
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("label", { htmlFor: "recipient-search", className: "text-sm font-medium text-foreground", children: "Search recipients" }),
3125
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
3017
3126
  "input",
3018
3127
  {
3019
3128
  id: "recipient-search",
@@ -3024,7 +3133,7 @@ function RecipientAdminPanel({ basePath = "/api", className }) {
3024
3133
  }
3025
3134
  )
3026
3135
  ] }),
3027
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3136
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
3028
3137
  "button",
3029
3138
  {
3030
3139
  type: "submit",
@@ -3035,14 +3144,14 @@ function RecipientAdminPanel({ basePath = "/api", className }) {
3035
3144
  ] });
3036
3145
  let body;
3037
3146
  if (isLoading) {
3038
- body = /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { role: "status", "aria-label": "Loading recipients", className: "flex flex-col gap-2 p-4", children: [
3039
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "sr-only", children: "Loading recipients" }),
3040
- [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { "aria-hidden": "true", className: "h-10 animate-pulse rounded bg-muted" }, i))
3147
+ body = /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { role: "status", "aria-label": "Loading recipients", className: "flex flex-col gap-2 p-4", children: [
3148
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "sr-only", children: "Loading recipients" }),
3149
+ [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { "aria-hidden": "true", className: "h-10 animate-pulse rounded bg-muted" }, i))
3041
3150
  ] });
3042
3151
  } else if (isError) {
3043
- body = /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex flex-col items-start gap-3 p-4", children: [
3044
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("p", { className: "text-sm text-foreground", children: "Failed to load recipients" }),
3045
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3152
+ body = /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex flex-col items-start gap-3 p-4", children: [
3153
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("p", { className: "text-sm text-foreground", children: "Failed to load recipients" }),
3154
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
3046
3155
  "button",
3047
3156
  {
3048
3157
  type: "button",
@@ -3056,26 +3165,26 @@ function RecipientAdminPanel({ basePath = "/api", className }) {
3056
3165
  const rows = data?.items ?? [];
3057
3166
  const totalPages = data?.totalPages ?? 1;
3058
3167
  if (rows.length === 0) {
3059
- body = /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "p-6 text-center text-sm text-muted-foreground", children: "No recipients" });
3168
+ body = /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "p-6 text-center text-sm text-muted-foreground", children: "No recipients" });
3060
3169
  } else {
3061
- body = /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_jsx_runtime27.Fragment, { children: [
3062
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("table", { className: "w-full text-sm", children: [
3063
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("tr", { className: "border-b border-border text-start text-muted-foreground", children: [
3064
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("th", { scope: "col", className: "py-2 pe-4 font-medium", children: "User" }),
3065
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Email" }),
3066
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Phone" }),
3067
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Consents" }),
3068
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Created" }),
3069
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "sr-only", children: "Actions" }) })
3170
+ body = /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(import_jsx_runtime28.Fragment, { children: [
3171
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("table", { className: "w-full text-sm", children: [
3172
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("tr", { className: "border-b border-border text-start text-muted-foreground", children: [
3173
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("th", { scope: "col", className: "py-2 pe-4 font-medium", children: "User" }),
3174
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Email" }),
3175
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Phone" }),
3176
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Consents" }),
3177
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Created" }),
3178
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "sr-only", children: "Actions" }) })
3070
3179
  ] }) }),
3071
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("tbody", { children: rows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("tr", { className: "border-b border-border", children: [
3072
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("td", { className: "py-3 pe-4 font-mono text-foreground", children: row.userId }),
3073
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("td", { className: "px-4 py-3 text-foreground", children: row.email ?? "\u2014" }),
3074
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("td", { className: "px-4 py-3 text-foreground", children: row.phone ?? "\u2014" }),
3075
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("td", { className: "px-4 py-3 text-muted-foreground", children: consents(row) }),
3076
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("td", { className: "px-4 py-3 text-muted-foreground", children: (0, import_react_ui26.formatDateTime)(row.createdAt) }),
3077
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("td", { className: "px-4 py-3", children: /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex items-center gap-2", children: [
3078
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3180
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("tbody", { children: rows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("tr", { className: "border-b border-border", children: [
3181
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("td", { className: "py-3 pe-4 font-mono text-foreground", children: row.userId }),
3182
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("td", { className: "px-4 py-3 text-foreground", children: row.email ?? "\u2014" }),
3183
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("td", { className: "px-4 py-3 text-foreground", children: row.phone ?? "\u2014" }),
3184
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("td", { className: "px-4 py-3 text-muted-foreground", children: consents(row) }),
3185
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("td", { className: "px-4 py-3 text-muted-foreground", children: (0, import_react_ui27.formatDateTime)(row.createdAt) }),
3186
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("td", { className: "px-4 py-3", children: /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex items-center gap-2", children: [
3187
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
3079
3188
  "button",
3080
3189
  {
3081
3190
  type: "button",
@@ -3085,7 +3194,7 @@ function RecipientAdminPanel({ basePath = "/api", className }) {
3085
3194
  children: "Export"
3086
3195
  }
3087
3196
  ),
3088
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3197
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
3089
3198
  "button",
3090
3199
  {
3091
3200
  type: "button",
@@ -3098,8 +3207,8 @@ function RecipientAdminPanel({ basePath = "/api", className }) {
3098
3207
  ] }) })
3099
3208
  ] }, row.userId)) })
3100
3209
  ] }),
3101
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("nav", { "aria-label": "Recipient pagination", className: "flex items-center justify-between", children: [
3102
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3210
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("nav", { "aria-label": "Recipient pagination", className: "flex items-center justify-between", children: [
3211
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
3103
3212
  "button",
3104
3213
  {
3105
3214
  type: "button",
@@ -3109,13 +3218,13 @@ function RecipientAdminPanel({ basePath = "/api", className }) {
3109
3218
  children: "Previous"
3110
3219
  }
3111
3220
  ),
3112
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { className: "text-xs text-muted-foreground", children: [
3221
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { className: "text-xs text-muted-foreground", children: [
3113
3222
  "Page ",
3114
3223
  page,
3115
3224
  " of ",
3116
3225
  totalPages
3117
3226
  ] }),
3118
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3227
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
3119
3228
  "button",
3120
3229
  {
3121
3230
  type: "button",
@@ -3129,7 +3238,7 @@ function RecipientAdminPanel({ basePath = "/api", className }) {
3129
3238
  ] });
3130
3239
  }
3131
3240
  }
3132
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("section", { "aria-label": "Recipient administration", className: (0, import_react_ui26.cn)("flex flex-col gap-4", className), children: [
3241
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("section", { "aria-label": "Recipient administration", className: (0, import_react_ui27.cn)("flex flex-col gap-4", className), children: [
3133
3242
  searchForm,
3134
3243
  body
3135
3244
  ] });
@@ -3137,9 +3246,9 @@ function RecipientAdminPanel({ basePath = "/api", className }) {
3137
3246
 
3138
3247
  // src/webhook-endpoint-manager.tsx
3139
3248
  var import_react17 = require("react");
3140
- var import_react_ui27 = require("@quanticjs/react-ui");
3249
+ var import_react_ui28 = require("@quanticjs/react-ui");
3141
3250
  var import_react_query24 = require("@quanticjs/react-query");
3142
- var import_jsx_runtime28 = require("react/jsx-runtime");
3251
+ var import_jsx_runtime29 = require("react/jsx-runtime");
3143
3252
  var EVENT_TYPES = [
3144
3253
  "notification.sent",
3145
3254
  "notification.delivered",
@@ -3165,15 +3274,15 @@ function WebhookDeliveries({ endpointId, basePath }) {
3165
3274
  (client) => client.get(`${basePath}/webhook-endpoints/${endpointId}/deliveries`)
3166
3275
  );
3167
3276
  if (isLoading) {
3168
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { role: "status", "aria-label": "Loading deliveries", className: "flex flex-col gap-2 p-3", children: [
3169
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "sr-only", children: "Loading deliveries" }),
3170
- [0, 1].map((i) => /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { "aria-hidden": "true", className: "h-8 animate-pulse rounded bg-muted" }, i))
3277
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { role: "status", "aria-label": "Loading deliveries", className: "flex flex-col gap-2 p-3", children: [
3278
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "sr-only", children: "Loading deliveries" }),
3279
+ [0, 1].map((i) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { "aria-hidden": "true", className: "h-8 animate-pulse rounded bg-muted" }, i))
3171
3280
  ] });
3172
3281
  }
3173
3282
  if (isError) {
3174
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex flex-col items-start gap-2 p-3", children: [
3175
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("p", { className: "text-sm text-foreground", children: "Failed to load deliveries" }),
3176
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
3283
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "flex flex-col items-start gap-2 p-3", children: [
3284
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("p", { className: "text-sm text-foreground", children: "Failed to load deliveries" }),
3285
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
3177
3286
  "button",
3178
3287
  {
3179
3288
  type: "button",
@@ -3186,18 +3295,18 @@ function WebhookDeliveries({ endpointId, basePath }) {
3186
3295
  }
3187
3296
  const rows = normalizeDeliveries(data);
3188
3297
  if (rows.length === 0) {
3189
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "p-3 text-center text-sm text-muted-foreground", children: "No deliveries" });
3298
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "p-3 text-center text-sm text-muted-foreground", children: "No deliveries" });
3190
3299
  }
3191
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("table", { className: "w-full text-sm", children: [
3192
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("tr", { className: "border-b border-border text-start text-muted-foreground", children: [
3193
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("th", { scope: "col", className: "py-2 pe-4 font-medium", children: "Delivery" }),
3194
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Status" }),
3195
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Created" })
3300
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("table", { className: "w-full text-sm", children: [
3301
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("tr", { className: "border-b border-border text-start text-muted-foreground", children: [
3302
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("th", { scope: "col", className: "py-2 pe-4 font-medium", children: "Delivery" }),
3303
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Status" }),
3304
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Created" })
3196
3305
  ] }) }),
3197
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("tbody", { children: rows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("tr", { className: "border-b border-border", children: [
3198
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("td", { className: "py-2 pe-4 font-mono text-foreground", children: row.id }),
3199
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("td", { className: "px-4 py-2 text-muted-foreground", children: row.status }),
3200
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("td", { className: "px-4 py-2 text-muted-foreground", children: (0, import_react_ui27.formatDateTime)(row.createdAt) })
3306
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("tbody", { children: rows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("tr", { className: "border-b border-border", children: [
3307
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("td", { className: "py-2 pe-4 font-mono text-foreground", children: row.id }),
3308
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("td", { className: "px-4 py-2 text-muted-foreground", children: row.status }),
3309
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("td", { className: "px-4 py-2 text-muted-foreground", children: (0, import_react_ui28.formatDateTime)(row.createdAt) })
3201
3310
  ] }, row.id)) })
3202
3311
  ] });
3203
3312
  }
@@ -3205,7 +3314,7 @@ function WebhookEndpointManager({
3205
3314
  basePath = "/api",
3206
3315
  className
3207
3316
  }) {
3208
- const toast = (0, import_react_ui27.useToast)();
3317
+ const toast = (0, import_react_ui28.useToast)();
3209
3318
  const [url, setUrl] = (0, import_react17.useState)("");
3210
3319
  const [events, setEvents] = (0, import_react17.useState)([]);
3211
3320
  const [active, setActive] = (0, import_react17.useState)(true);
@@ -3272,10 +3381,10 @@ function WebhookEndpointManager({
3272
3381
  remove.mutate(id);
3273
3382
  }
3274
3383
  };
3275
- const createForm = /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("form", { onSubmit: onCreate, className: "flex flex-col gap-3", noValidate: true, children: [
3276
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex flex-col gap-1", children: [
3277
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("label", { htmlFor: "webhook-url", className: "text-sm font-medium text-foreground", children: "Endpoint URL" }),
3278
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
3384
+ const createForm = /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("form", { onSubmit: onCreate, className: "flex flex-col gap-3", noValidate: true, children: [
3385
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "flex flex-col gap-1", children: [
3386
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("label", { htmlFor: "webhook-url", className: "text-sm font-medium text-foreground", children: "Endpoint URL" }),
3387
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
3279
3388
  "input",
3280
3389
  {
3281
3390
  id: "webhook-url",
@@ -3287,19 +3396,19 @@ function WebhookEndpointManager({
3287
3396
  className: "rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring"
3288
3397
  }
3289
3398
  ),
3290
- urlError && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("p", { id: "webhook-url-error", className: "text-xs text-destructive", children: urlError })
3399
+ urlError && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("p", { id: "webhook-url-error", className: "text-xs text-destructive", children: urlError })
3291
3400
  ] }),
3292
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("fieldset", { className: "flex flex-col gap-2", children: [
3293
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("legend", { className: "text-sm font-medium text-foreground", children: "Events" }),
3294
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "flex flex-wrap gap-3", children: EVENT_TYPES.map((evt) => {
3401
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("fieldset", { className: "flex flex-col gap-2", children: [
3402
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("legend", { className: "text-sm font-medium text-foreground", children: "Events" }),
3403
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "flex flex-wrap gap-3", children: EVENT_TYPES.map((evt) => {
3295
3404
  const id = `webhook-event-${evt}`;
3296
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
3405
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
3297
3406
  "label",
3298
3407
  {
3299
3408
  htmlFor: id,
3300
3409
  className: "flex items-center gap-2 text-sm text-foreground",
3301
3410
  children: [
3302
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
3411
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
3303
3412
  "input",
3304
3413
  {
3305
3414
  id,
@@ -3316,8 +3425,8 @@ function WebhookEndpointManager({
3316
3425
  );
3317
3426
  }) })
3318
3427
  ] }),
3319
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("label", { htmlFor: "webhook-active", className: "flex items-center gap-2 text-sm text-foreground", children: [
3320
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
3428
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("label", { htmlFor: "webhook-active", className: "flex items-center gap-2 text-sm text-foreground", children: [
3429
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
3321
3430
  "input",
3322
3431
  {
3323
3432
  id: "webhook-active",
@@ -3329,7 +3438,7 @@ function WebhookEndpointManager({
3329
3438
  ),
3330
3439
  "Active"
3331
3440
  ] }),
3332
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
3441
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
3333
3442
  "button",
3334
3443
  {
3335
3444
  type: "submit",
@@ -3341,14 +3450,14 @@ function WebhookEndpointManager({
3341
3450
  ] });
3342
3451
  let body;
3343
3452
  if (isLoading) {
3344
- body = /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { role: "status", "aria-label": "Loading webhook endpoints", className: "flex flex-col gap-2 p-4", children: [
3345
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "sr-only", children: "Loading webhook endpoints" }),
3346
- [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { "aria-hidden": "true", className: "h-10 animate-pulse rounded bg-muted" }, i))
3453
+ body = /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { role: "status", "aria-label": "Loading webhook endpoints", className: "flex flex-col gap-2 p-4", children: [
3454
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "sr-only", children: "Loading webhook endpoints" }),
3455
+ [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { "aria-hidden": "true", className: "h-10 animate-pulse rounded bg-muted" }, i))
3347
3456
  ] });
3348
3457
  } else if (isError) {
3349
- body = /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex flex-col items-start gap-3 p-4", children: [
3350
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("p", { className: "text-sm text-foreground", children: "Failed to load webhook endpoints" }),
3351
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
3458
+ body = /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "flex flex-col items-start gap-3 p-4", children: [
3459
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("p", { className: "text-sm text-foreground", children: "Failed to load webhook endpoints" }),
3460
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
3352
3461
  "button",
3353
3462
  {
3354
3463
  type: "button",
@@ -3361,26 +3470,26 @@ function WebhookEndpointManager({
3361
3470
  } else {
3362
3471
  const rows = normalizeEndpoints(data);
3363
3472
  if (rows.length === 0) {
3364
- body = /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "p-6 text-center text-sm text-muted-foreground", children: "No webhook endpoints" });
3473
+ body = /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "p-6 text-center text-sm text-muted-foreground", children: "No webhook endpoints" });
3365
3474
  } else {
3366
- body = /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("table", { className: "w-full text-sm", children: [
3367
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("tr", { className: "border-b border-border text-start text-muted-foreground", children: [
3368
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("th", { scope: "col", className: "py-2 pe-4 font-medium", children: "URL" }),
3369
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Events" }),
3370
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Active" }),
3371
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Created" }),
3372
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "sr-only", children: "Actions" }) })
3475
+ body = /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("table", { className: "w-full text-sm", children: [
3476
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("tr", { className: "border-b border-border text-start text-muted-foreground", children: [
3477
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("th", { scope: "col", className: "py-2 pe-4 font-medium", children: "URL" }),
3478
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Events" }),
3479
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Active" }),
3480
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Created" }),
3481
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "sr-only", children: "Actions" }) })
3373
3482
  ] }) }),
3374
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("tbody", { children: rows.map((row) => {
3483
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("tbody", { children: rows.map((row) => {
3375
3484
  const isExpanded = expandedId === row.id;
3376
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(import_react17.Fragment, { children: [
3377
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("tr", { className: "border-b border-border", children: [
3378
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("td", { className: "py-3 pe-4 font-mono text-foreground", children: row.url }),
3379
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("td", { className: "px-4 py-3 text-muted-foreground", children: row.events.length > 0 ? row.events.join(", ") : "\u2014" }),
3380
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("td", { className: "px-4 py-3", children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_react_ui27.StatusBadge, { variant: row.active ? "success" : "neutral", appearance: "dot", children: row.active ? "active" : "inactive" }) }),
3381
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("td", { className: "px-4 py-3 text-muted-foreground", children: (0, import_react_ui27.formatDateTime)(row.createdAt) }),
3382
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("td", { className: "px-4 py-3", children: /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex items-center gap-2", children: [
3383
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
3485
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(import_react17.Fragment, { children: [
3486
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("tr", { className: "border-b border-border", children: [
3487
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("td", { className: "py-3 pe-4 font-mono text-foreground", children: row.url }),
3488
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("td", { className: "px-4 py-3 text-muted-foreground", children: row.events.length > 0 ? row.events.join(", ") : "\u2014" }),
3489
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("td", { className: "px-4 py-3", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_react_ui28.StatusBadge, { variant: row.active ? "success" : "neutral", appearance: "dot", children: row.active ? "active" : "inactive" }) }),
3490
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("td", { className: "px-4 py-3 text-muted-foreground", children: (0, import_react_ui28.formatDateTime)(row.createdAt) }),
3491
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("td", { className: "px-4 py-3", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "flex items-center gap-2", children: [
3492
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
3384
3493
  "button",
3385
3494
  {
3386
3495
  type: "button",
@@ -3390,7 +3499,7 @@ function WebhookEndpointManager({
3390
3499
  children: row.active ? "Disable" : "Enable"
3391
3500
  }
3392
3501
  ),
3393
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
3502
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
3394
3503
  "button",
3395
3504
  {
3396
3505
  type: "button",
@@ -3400,7 +3509,7 @@ function WebhookEndpointManager({
3400
3509
  children: "Deliveries"
3401
3510
  }
3402
3511
  ),
3403
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
3512
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
3404
3513
  "button",
3405
3514
  {
3406
3515
  type: "button",
@@ -3412,120 +3521,225 @@ function WebhookEndpointManager({
3412
3521
  )
3413
3522
  ] }) })
3414
3523
  ] }),
3415
- isExpanded && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("tr", { className: "border-b border-border", children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("td", { colSpan: 5, className: "bg-muted px-4 py-3", children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(WebhookDeliveries, { endpointId: row.id, basePath }) }) })
3524
+ isExpanded && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("tr", { className: "border-b border-border", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("td", { colSpan: 5, className: "bg-muted px-4 py-3", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(WebhookDeliveries, { endpointId: row.id, basePath }) }) })
3416
3525
  ] }, row.id);
3417
3526
  }) })
3418
3527
  ] });
3419
3528
  }
3420
3529
  }
3421
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("section", { "aria-label": "Webhook endpoints", className: (0, import_react_ui27.cn)("flex flex-col gap-4", className), children: [
3530
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("section", { "aria-label": "Webhook endpoints", className: (0, import_react_ui28.cn)("flex flex-col gap-4", className), children: [
3422
3531
  createForm,
3423
3532
  body
3424
3533
  ] });
3425
3534
  }
3426
3535
 
3427
3536
  // src/operations-overview.tsx
3428
- var import_react_ui28 = require("@quanticjs/react-ui");
3537
+ var import_react_ui29 = require("@quanticjs/react-ui");
3429
3538
  var import_react_query25 = require("@quanticjs/react-query");
3430
- var import_jsx_runtime29 = require("react/jsx-runtime");
3539
+ var import_jsx_runtime30 = require("react/jsx-runtime");
3431
3540
  function OperationsOverview({ basePath = "/api", className }) {
3432
3541
  const { data, isLoading, isError, refetch } = (0, import_react_query25.useApiQuery)(
3433
3542
  ["operations-overview"],
3434
3543
  (client) => client.get(`${basePath}/v1/admin/overview`)
3435
3544
  );
3436
3545
  if (isLoading) {
3437
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
3546
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(
3438
3547
  "div",
3439
3548
  {
3440
3549
  role: "status",
3441
3550
  "aria-label": "Loading operations overview",
3442
- className: (0, import_react_ui28.cn)("flex flex-col gap-2 p-4", className),
3551
+ className: (0, import_react_ui29.cn)("flex flex-col gap-4", className),
3443
3552
  children: [
3444
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "sr-only", children: "Loading operations overview" }),
3445
- [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { "aria-hidden": "true", className: "h-20 animate-pulse rounded bg-muted" }, i))
3553
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("span", { className: "sr-only", children: "Loading operations overview" }),
3554
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { "aria-hidden": "true", className: "grid grid-cols-1 gap-4 sm:grid-cols-3", children: [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_react_ui29.Card, { children: /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(import_react_ui29.CardContent, { className: "space-y-3 p-5", children: [
3555
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_react_ui29.Skeleton, { className: "h-3 w-24" }),
3556
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_react_ui29.Skeleton, { className: "h-7 w-28" })
3557
+ ] }) }, i)) }),
3558
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_react_ui29.Card, { "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_react_ui29.CardContent, { className: "space-y-2 p-5", children: [0, 1, 2, 3].map((i) => /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_react_ui29.Skeleton, { className: "h-10 w-full" }, i)) }) })
3446
3559
  ]
3447
3560
  }
3448
3561
  );
3449
3562
  }
3450
3563
  if (isError) {
3451
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: (0, import_react_ui28.cn)("flex flex-col items-start gap-3 p-4", className), children: [
3452
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("p", { className: "text-sm text-foreground", children: "Failed to load operations overview" }),
3453
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
3454
- "button",
3455
- {
3456
- type: "button",
3457
- onClick: () => void refetch(),
3458
- className: "rounded bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground hover:opacity-90 focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring",
3459
- children: "Try again"
3460
- }
3461
- )
3462
- ] });
3564
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_react_ui29.Card, { className, children: /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(import_react_ui29.CardContent, { className: "flex flex-col items-center gap-3 p-8 text-center", children: [
3565
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("p", { className: "text-sm text-foreground", children: "Failed to load operations overview" }),
3566
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_react_ui29.Button, { type: "button", onClick: () => void refetch(), children: "Try again" })
3567
+ ] }) });
3463
3568
  }
3464
3569
  if (!data) {
3465
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: (0, import_react_ui28.cn)("p-6 text-center text-sm text-muted-foreground", className), children: "No overview data" });
3570
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_react_ui29.EmptyState, { className, title: "No overview data" });
3466
3571
  }
3467
3572
  const windowHours = data.windowHours;
3468
3573
  const channels = data.channels ?? [];
3469
3574
  const dlqPending = data.dlqPending;
3470
- const cards = [
3471
- { label: `Sends (${windowHours}h)`, value: data.totalSends },
3472
- { label: `Delivered (${windowHours}h)`, value: data.totalDelivered },
3473
- { label: `Failed (${windowHours}h)`, value: data.totalFailed }
3474
- ];
3475
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("section", { "aria-label": "Operations overview", className: (0, import_react_ui28.cn)("flex flex-col gap-4", className), children: [
3476
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-3", children: cards.map((card) => /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "rounded-md border border-border bg-card p-4", children: [
3477
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("p", { className: "text-xs font-medium text-muted-foreground", children: card.label }),
3478
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("p", { className: "mt-1 text-2xl font-semibold text-foreground", children: card.value })
3479
- ] }, card.label)) }),
3480
- /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "flex flex-wrap items-center gap-4", children: [
3481
- /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "flex items-center gap-2", children: [
3482
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "text-sm text-muted-foreground", children: "DLQ pending" }),
3483
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_react_ui28.StatusBadge, { variant: dlqPending > 0 ? "destructive" : "success", children: dlqPending })
3484
- ] }),
3485
- /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "flex items-center gap-2", children: [
3486
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "text-sm text-muted-foreground", children: "Broadcasts in flight" }),
3487
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "text-sm font-medium text-foreground", children: data.broadcastsInFlight })
3575
+ const deliveryRate = ratePct(data.totalDelivered, data.totalSends);
3576
+ const failureRate = ratePct(data.totalFailed, data.totalSends);
3577
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("section", { "aria-label": "Operations overview", className: (0, import_react_ui29.cn)("flex flex-col gap-5", className), children: [
3578
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { className: "flex flex-wrap items-baseline justify-between gap-2", children: [
3579
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { children: [
3580
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("h2", { className: "text-base font-semibold tracking-tight text-foreground", children: "Operations overview" }),
3581
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("p", { className: "mt-0.5 text-xs text-muted-foreground", children: [
3582
+ "Last ",
3583
+ windowHours,
3584
+ "h"
3585
+ ] })
3488
3586
  ] }),
3489
- /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "flex items-center gap-2", children: [
3490
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "text-sm text-muted-foreground", children: "Queue" }),
3491
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_react_ui28.StatusBadge, { variant: data.queueHealthy ? "success" : "destructive", children: data.queueHealthy ? "Healthy" : "Unhealthy" })
3587
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("p", { className: "text-xs text-muted-foreground", children: [
3588
+ "Generated ",
3589
+ (0, import_react_ui29.formatDateTime)(data.generatedAt)
3492
3590
  ] })
3493
3591
  ] }),
3494
- /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("table", { className: "w-full text-sm", children: [
3495
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("tr", { className: "border-b border-border text-start text-muted-foreground", children: [
3496
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("th", { scope: "col", className: "py-2 pe-4 font-medium", children: "Channel" }),
3497
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Sends" }),
3498
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Delivered" }),
3499
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Failed" })
3500
- ] }) }),
3501
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("tbody", { children: channels.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("tr", { className: "border-b border-border", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("td", { colSpan: 4, className: "py-3 text-center text-muted-foreground", children: "No channel activity" }) }) : channels.map((row) => /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("tr", { className: "border-b border-border", children: [
3502
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("td", { className: "py-3 pe-4 text-foreground", children: row.channel }),
3503
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("td", { className: "px-4 py-3 text-muted-foreground", children: row.sends }),
3504
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("td", { className: "px-4 py-3 text-muted-foreground", children: row.delivered }),
3505
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("td", { className: "px-4 py-3 text-muted-foreground", children: row.failed })
3506
- ] }, row.channel)) })
3592
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-3", children: [
3593
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_react_ui29.StatCard, { label: `Sent (${windowHours}h)`, value: data.totalSends, icon: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(SendIcon, {}) }),
3594
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
3595
+ import_react_ui29.StatCard,
3596
+ {
3597
+ label: "Delivery rate",
3598
+ value: deliveryRate,
3599
+ icon: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(CheckIcon, {}),
3600
+ delta: `${data.totalDelivered} delivered`,
3601
+ trend: "up"
3602
+ }
3603
+ ),
3604
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
3605
+ import_react_ui29.StatCard,
3606
+ {
3607
+ label: "Failure rate",
3608
+ value: failureRate,
3609
+ icon: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(AlertIcon4, {}),
3610
+ delta: `${data.totalFailed} failed`,
3611
+ trend: data.totalFailed > 0 ? "down" : "flat"
3612
+ }
3613
+ )
3507
3614
  ] }),
3508
- /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("p", { className: "text-xs text-muted-foreground", children: [
3509
- "Generated ",
3510
- (0, import_react_ui28.formatDateTime)(data.generatedAt)
3615
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_react_ui29.Card, { children: /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(import_react_ui29.CardContent, { className: "flex flex-wrap items-center gap-x-8 gap-y-3 p-5", children: [
3616
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { className: "flex items-center gap-2", children: [
3617
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("span", { className: "text-sm text-muted-foreground", children: "DLQ pending" }),
3618
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_react_ui29.StatusBadge, { variant: dlqPending > 0 ? "destructive" : "success", appearance: "solid", children: dlqPending })
3619
+ ] }),
3620
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { className: "flex items-center gap-2", children: [
3621
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("span", { className: "text-sm text-muted-foreground", children: "Broadcasts in flight" }),
3622
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("span", { className: "text-sm font-semibold tabular-nums text-foreground", children: data.broadcastsInFlight })
3623
+ ] }),
3624
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { className: "flex items-center gap-2", children: [
3625
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("span", { className: "text-sm text-muted-foreground", children: "Queue" }),
3626
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_react_ui29.StatusBadge, { variant: data.queueHealthy ? "success" : "destructive", appearance: "solid", children: data.queueHealthy ? "Healthy" : "Unhealthy" })
3627
+ ] })
3628
+ ] }) }),
3629
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(import_react_ui29.Card, { children: [
3630
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(import_react_ui29.CardHeader, { children: [
3631
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_react_ui29.CardTitle, { children: "Channel health" }),
3632
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("p", { className: "text-sm text-muted-foreground", children: "Delivery success by channel" })
3633
+ ] }),
3634
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_react_ui29.CardContent, { className: "p-0", children: channels.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("p", { className: "px-5 py-8 text-center text-sm text-muted-foreground", children: "No channel activity" }) : /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("ul", { className: "divide-y divide-border", children: channels.map((row) => {
3635
+ const success = ratePct(row.delivered, row.sends);
3636
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("li", { className: "flex items-center gap-3 px-5 py-3.5", children: [
3637
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
3638
+ "span",
3639
+ {
3640
+ "aria-hidden": "true",
3641
+ className: "grid size-9 shrink-0 place-items-center rounded-md bg-primary/10 text-primary [&_svg]:size-4",
3642
+ children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ChannelIcon, { channel: row.channel })
3643
+ }
3644
+ ),
3645
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { className: "min-w-0 flex-1", children: [
3646
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { className: "flex items-center justify-between gap-2", children: [
3647
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("span", { className: "flex items-center gap-2 text-sm font-medium capitalize text-foreground", children: [
3648
+ row.channel,
3649
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(import_react_ui29.StatusBadge, { variant: row.failed > 0 ? "warning" : "success", appearance: "dot", children: [
3650
+ success,
3651
+ " success"
3652
+ ] })
3653
+ ] }),
3654
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("span", { className: "shrink-0 text-xs tabular-nums text-muted-foreground", children: [
3655
+ row.sends,
3656
+ " sent"
3657
+ ] })
3658
+ ] }),
3659
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { className: "mt-2 flex h-1.5 overflow-hidden rounded-full bg-muted", children: [
3660
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("span", { className: "bg-success", style: { width: `${barPct(row.delivered, row.sends)}%` } }),
3661
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("span", { className: "bg-destructive", style: { width: `${barPct(row.failed, row.sends)}%` } })
3662
+ ] }),
3663
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { className: "mt-1.5 text-xs tabular-nums text-muted-foreground", children: [
3664
+ row.delivered,
3665
+ " delivered \xB7 ",
3666
+ row.failed,
3667
+ " failed"
3668
+ ] })
3669
+ ] })
3670
+ ] }, row.channel);
3671
+ }) }) })
3511
3672
  ] })
3512
3673
  ] });
3513
3674
  }
3514
-
3515
- // src/delivery-log-explorer.tsx
3516
- var import_react18 = require("react");
3517
- var import_react_ui29 = require("@quanticjs/react-ui");
3518
- var import_react_query26 = require("@quanticjs/react-query");
3519
- var import_jsx_runtime30 = require("react/jsx-runtime");
3520
- var LIMIT6 = 20;
3521
- var EMPTY_FILTERS = {
3522
- channel: "",
3523
- status: "",
3524
- recipient: "",
3525
- userId: "",
3526
- from: "",
3527
- to: ""
3528
- };
3675
+ function ratePct(n, total) {
3676
+ if (total <= 0) return "\u2014";
3677
+ return `${(n / total * 100).toFixed(2)}%`;
3678
+ }
3679
+ function barPct(n, total) {
3680
+ if (total <= 0) return 0;
3681
+ return Math.min(100, n / total * 100);
3682
+ }
3683
+ function SendIcon() {
3684
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
3685
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("path", { d: "M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z" }),
3686
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("path", { d: "m21.854 2.147-10.94 10.939" })
3687
+ ] });
3688
+ }
3689
+ function CheckIcon() {
3690
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
3691
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("path", { d: "M21.801 10A10 10 0 1 1 17 3.335" }),
3692
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("path", { d: "m9 11 3 3L22 4" })
3693
+ ] });
3694
+ }
3695
+ function AlertIcon4() {
3696
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
3697
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
3698
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("path", { d: "m15 9-6 6" }),
3699
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("path", { d: "m9 9 6 6" })
3700
+ ] });
3701
+ }
3702
+ function ChannelIcon({ channel }) {
3703
+ switch (channel) {
3704
+ case "email":
3705
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
3706
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("rect", { width: "20", height: "16", x: "2", y: "4", rx: "2" }),
3707
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("path", { d: "m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" })
3708
+ ] });
3709
+ case "sms":
3710
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("path", { d: "M7.9 20A9 9 0 1 0 4 16.1L2 22Z" }) });
3711
+ case "push":
3712
+ case "inapp":
3713
+ case "in-app":
3714
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
3715
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("path", { d: "M10.268 21a2 2 0 0 0 3.464 0" }),
3716
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("path", { d: "M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326" })
3717
+ ] });
3718
+ case "webhook":
3719
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
3720
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("path", { d: "M10 6.5a3.5 3.5 0 1 1 6 2.45" }),
3721
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("path", { d: "M6 10.5a3.5 3.5 0 1 0 5 3.15" }),
3722
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("path", { d: "M14 17.5a3.5 3.5 0 1 0 1-6.85" })
3723
+ ] });
3724
+ default:
3725
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("circle", { cx: "12", cy: "12", r: "4" }) });
3726
+ }
3727
+ }
3728
+
3729
+ // src/delivery-log-explorer.tsx
3730
+ var import_react18 = require("react");
3731
+ var import_react_ui30 = require("@quanticjs/react-ui");
3732
+ var import_react_query26 = require("@quanticjs/react-query");
3733
+ var import_jsx_runtime31 = require("react/jsx-runtime");
3734
+ var LIMIT6 = 20;
3735
+ var EMPTY_FILTERS = {
3736
+ channel: "",
3737
+ status: "",
3738
+ recipient: "",
3739
+ userId: "",
3740
+ from: "",
3741
+ to: ""
3742
+ };
3529
3743
  function channelVariant(channel) {
3530
3744
  switch (channel) {
3531
3745
  case "email":
@@ -3569,186 +3783,98 @@ function DeliveryLogExplorer({ basePath = "/api", className }) {
3569
3783
  });
3570
3784
  };
3571
3785
  const setField = (key, value) => setDraft((prev) => ({ ...prev, [key]: value }));
3572
- const filterForm = /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("form", { onSubmit: onApply, className: "flex flex-wrap items-end gap-3", noValidate: true, children: [
3573
- /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { className: "flex flex-col gap-1", children: [
3574
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("label", { htmlFor: "dle-channel", className: "text-sm font-medium text-foreground", children: "Channel" }),
3575
- /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(
3576
- "select",
3577
- {
3578
- id: "dle-channel",
3579
- value: draft.channel,
3580
- onChange: (e) => setField("channel", e.target.value),
3581
- className: "rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring",
3582
- children: [
3583
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("option", { value: "", children: "All" }),
3584
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("option", { value: "email", children: "Email" }),
3585
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("option", { value: "sms", children: "SMS" })
3586
- ]
3587
- }
3588
- )
3589
- ] }),
3590
- /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { className: "flex flex-col gap-1", children: [
3591
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("label", { htmlFor: "dle-status", className: "text-sm font-medium text-foreground", children: "Status" }),
3592
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
3593
- "input",
3594
- {
3595
- id: "dle-status",
3596
- type: "text",
3597
- value: draft.status,
3598
- onChange: (e) => setField("status", e.target.value),
3599
- className: "rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring"
3600
- }
3601
- )
3602
- ] }),
3603
- /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { className: "flex flex-col gap-1", children: [
3604
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("label", { htmlFor: "dle-recipient", className: "text-sm font-medium text-foreground", children: "Recipient" }),
3605
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
3606
- "input",
3607
- {
3608
- id: "dle-recipient",
3609
- type: "text",
3610
- value: draft.recipient,
3611
- onChange: (e) => setField("recipient", e.target.value),
3612
- className: "rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring"
3613
- }
3614
- )
3615
- ] }),
3616
- /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { className: "flex flex-col gap-1", children: [
3617
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("label", { htmlFor: "dle-user", className: "text-sm font-medium text-foreground", children: "User ID" }),
3618
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
3619
- "input",
3620
- {
3621
- id: "dle-user",
3622
- type: "text",
3623
- value: draft.userId,
3624
- onChange: (e) => setField("userId", e.target.value),
3625
- className: "rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring"
3626
- }
3627
- )
3628
- ] }),
3629
- /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { className: "flex flex-col gap-1", children: [
3630
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("label", { htmlFor: "dle-from", className: "text-sm font-medium text-foreground", children: "From" }),
3631
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
3632
- "input",
3633
- {
3634
- id: "dle-from",
3635
- type: "date",
3636
- value: draft.from,
3637
- onChange: (e) => setField("from", e.target.value),
3638
- className: "rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring"
3639
- }
3640
- )
3641
- ] }),
3642
- /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { className: "flex flex-col gap-1", children: [
3643
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("label", { htmlFor: "dle-to", className: "text-sm font-medium text-foreground", children: "To" }),
3644
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
3645
- "input",
3646
- {
3647
- id: "dle-to",
3648
- type: "date",
3649
- value: draft.to,
3650
- onChange: (e) => setField("to", e.target.value),
3651
- className: "rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring"
3652
- }
3653
- )
3654
- ] }),
3655
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
3656
- "button",
3657
- {
3658
- type: "submit",
3659
- className: "rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:opacity-90 focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring",
3660
- children: "Apply"
3661
- }
3662
- )
3663
- ] });
3664
- let body;
3665
- if (isLoading) {
3666
- body = /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { role: "status", "aria-label": "Loading delivery logs", className: "flex flex-col gap-2 p-4", children: [
3667
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("span", { className: "sr-only", children: "Loading delivery logs" }),
3668
- [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { "aria-hidden": "true", className: "h-10 animate-pulse rounded bg-muted" }, i))
3669
- ] });
3670
- } else if (isError) {
3671
- body = /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { className: "flex flex-col items-start gap-3 p-4", children: [
3672
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("p", { className: "text-sm text-foreground", children: "Failed to load delivery logs" }),
3673
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
3674
- "button",
3675
- {
3676
- type: "button",
3677
- onClick: () => void refetch(),
3678
- className: "rounded bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground hover:opacity-90 focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring",
3679
- children: "Try again"
3680
- }
3681
- )
3682
- ] });
3683
- } else {
3684
- const rows = data?.items ?? [];
3685
- const totalPages = data?.totalPages ?? 1;
3686
- if (rows.length === 0) {
3687
- body = /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { className: "p-6 text-center text-sm text-muted-foreground", children: "No delivery logs" });
3688
- } else {
3689
- body = /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(import_jsx_runtime30.Fragment, { children: [
3690
- /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("table", { className: "w-full text-sm", children: [
3691
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("tr", { className: "border-b border-border text-start text-muted-foreground", children: [
3692
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("th", { scope: "col", className: "py-2 pe-4 font-medium", children: "Channel" }),
3693
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Recipient" }),
3694
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "User" }),
3695
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Status" }),
3696
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Provider" }),
3697
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Attempts" }),
3698
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Created" })
3786
+ const rows = data?.items ?? [];
3787
+ const totalPages = data?.totalPages ?? 1;
3788
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("section", { "aria-label": "Delivery log explorer", className: (0, import_react_ui30.cn)("flex flex-col gap-4", className), children: [
3789
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_react_ui30.Card, { children: /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("form", { onSubmit: onApply, className: "flex flex-wrap items-end gap-3 p-4", noValidate: true, children: [
3790
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("label", { htmlFor: "dle-channel", className: "flex flex-col gap-1 text-sm", children: [
3791
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "font-medium text-foreground", children: "Channel" }),
3792
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
3793
+ "select",
3794
+ {
3795
+ id: "dle-channel",
3796
+ value: draft.channel,
3797
+ onChange: (e) => setField("channel", e.target.value),
3798
+ className: FIELD_CLASS,
3799
+ children: [
3800
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("option", { value: "", children: "All" }),
3801
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("option", { value: "email", children: "Email" }),
3802
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("option", { value: "sms", children: "SMS" })
3803
+ ]
3804
+ }
3805
+ )
3806
+ ] }),
3807
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("label", { htmlFor: "dle-status", className: "flex flex-col gap-1 text-sm", children: [
3808
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "font-medium text-foreground", children: "Status" }),
3809
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("input", { id: "dle-status", type: "text", value: draft.status, onChange: (e) => setField("status", e.target.value), className: FIELD_CLASS })
3810
+ ] }),
3811
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("label", { htmlFor: "dle-recipient", className: "flex flex-col gap-1 text-sm", children: [
3812
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "font-medium text-foreground", children: "Recipient" }),
3813
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("input", { id: "dle-recipient", type: "text", value: draft.recipient, onChange: (e) => setField("recipient", e.target.value), className: FIELD_CLASS })
3814
+ ] }),
3815
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("label", { htmlFor: "dle-user", className: "flex flex-col gap-1 text-sm", children: [
3816
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "font-medium text-foreground", children: "User ID" }),
3817
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("input", { id: "dle-user", type: "text", value: draft.userId, onChange: (e) => setField("userId", e.target.value), className: FIELD_CLASS })
3818
+ ] }),
3819
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("label", { htmlFor: "dle-from", className: "flex flex-col gap-1 text-sm", children: [
3820
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "font-medium text-foreground", children: "From" }),
3821
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("input", { id: "dle-from", type: "date", value: draft.from, onChange: (e) => setField("from", e.target.value), className: FIELD_CLASS })
3822
+ ] }),
3823
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("label", { htmlFor: "dle-to", className: "flex flex-col gap-1 text-sm", children: [
3824
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "font-medium text-foreground", children: "To" }),
3825
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("input", { id: "dle-to", type: "date", value: draft.to, onChange: (e) => setField("to", e.target.value), className: FIELD_CLASS })
3826
+ ] }),
3827
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_react_ui30.Button, { type: "submit", className: "ms-auto", children: "Apply" })
3828
+ ] }) }),
3829
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(import_react_ui30.Card, { children: [
3830
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(PanelHeader, { title: "Attempts", subtitle: !isLoading && !isError ? `${data?.total ?? rows.length} matching entries` : void 0 }),
3831
+ isLoading ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(SkeletonRows, { label: "Loading delivery logs" }) : isError ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ErrorPanel, { message: "Failed to load delivery logs", onRetry: () => void refetch() }) : rows.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_react_ui30.EmptyState, { icon: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(InboxIcon, {}), title: "No delivery logs", description: "No attempts match the current filters." }) : /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(import_jsx_runtime31.Fragment, { children: [
3832
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("table", { className: "w-full text-sm", children: [
3833
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("tr", { className: "border-b border-border text-start text-muted-foreground", children: [
3834
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Channel" }),
3835
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Recipient" }),
3836
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "User" }),
3837
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Status" }),
3838
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Provider" }),
3839
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Attempts" }),
3840
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Created" })
3699
3841
  ] }) }),
3700
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("tbody", { children: rows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("tr", { className: "border-b border-border", children: [
3701
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("td", { className: "py-3 pe-4", children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_react_ui29.StatusBadge, { variant: channelVariant(row.channel), appearance: "dot", children: row.channel }) }),
3702
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("td", { className: "px-4 py-3 text-foreground", children: row.recipient ?? "\u2014" }),
3703
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("td", { className: "px-4 py-3 font-mono text-foreground", children: row.userId }),
3704
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("td", { className: "px-4 py-3 text-foreground", children: row.status }),
3705
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("td", { className: "px-4 py-3 text-muted-foreground", children: row.provider ?? "\u2014" }),
3706
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("td", { className: "px-4 py-3 text-muted-foreground", children: row.attempts }),
3707
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("td", { className: "px-4 py-3 text-muted-foreground", children: (0, import_react_ui29.formatDateTime)(row.createdAt) })
3842
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("tbody", { children: rows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("tr", { className: "border-b border-border last:border-0 hover:bg-muted/40", children: [
3843
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("td", { className: "px-4 py-3", children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_react_ui30.StatusBadge, { variant: channelVariant(row.channel), appearance: "dot", children: row.channel }) }),
3844
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("td", { className: "px-4 py-3 text-foreground", children: row.recipient ?? "\u2014" }),
3845
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("td", { className: "px-4 py-3 font-mono text-foreground", children: row.userId }),
3846
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("td", { className: "px-4 py-3 text-foreground", children: row.status }),
3847
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("td", { className: "px-4 py-3 text-muted-foreground", children: row.provider ?? "\u2014" }),
3848
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("td", { className: "px-4 py-3 tabular-nums text-muted-foreground", children: row.attempts }),
3849
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("td", { className: "px-4 py-3 text-muted-foreground", children: (0, import_react_ui30.formatDateTime)(row.createdAt) })
3708
3850
  ] }, row.id)) })
3709
3851
  ] }),
3710
- /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("nav", { "aria-label": "Delivery log pagination", className: "flex items-center justify-between", children: [
3711
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
3712
- "button",
3713
- {
3714
- type: "button",
3715
- onClick: () => setPage((p) => Math.max(1, p - 1)),
3716
- disabled: page <= 1,
3717
- className: "rounded-md border border-border px-3 py-1 text-sm text-foreground hover:bg-muted focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring disabled:opacity-50",
3718
- children: "Previous"
3719
- }
3720
- ),
3721
- /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("span", { className: "text-xs text-muted-foreground", children: [
3722
- "Page ",
3852
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
3853
+ Pager,
3854
+ {
3855
+ label: "Delivery log pagination",
3723
3856
  page,
3724
- " of ",
3725
- totalPages
3726
- ] }),
3727
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
3728
- "button",
3729
- {
3730
- type: "button",
3731
- onClick: () => setPage((p) => Math.min(totalPages, p + 1)),
3732
- disabled: page >= totalPages,
3733
- className: "rounded-md border border-border px-3 py-1 text-sm text-foreground hover:bg-muted focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring disabled:opacity-50",
3734
- children: "Next"
3735
- }
3736
- )
3737
- ] })
3738
- ] });
3739
- }
3740
- }
3741
- return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("section", { "aria-label": "Delivery log explorer", className: (0, import_react_ui29.cn)("flex flex-col gap-4", className), children: [
3742
- filterForm,
3743
- body
3857
+ totalPages,
3858
+ onPrev: () => setPage((p) => Math.max(1, p - 1)),
3859
+ onNext: () => setPage((p) => Math.min(totalPages, p + 1))
3860
+ }
3861
+ )
3862
+ ] })
3863
+ ] })
3864
+ ] });
3865
+ }
3866
+ function InboxIcon() {
3867
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
3868
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("path", { d: "M22 12h-6l-2 3h-4l-2-3H2" }),
3869
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("path", { d: "M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11Z" })
3744
3870
  ] });
3745
3871
  }
3746
3872
 
3747
3873
  // src/quiet-hours-form.tsx
3748
3874
  var import_react19 = require("react");
3749
- var import_react_ui30 = require("@quanticjs/react-ui");
3875
+ var import_react_ui31 = require("@quanticjs/react-ui");
3750
3876
  var import_react_query27 = require("@quanticjs/react-query");
3751
- var import_jsx_runtime31 = require("react/jsx-runtime");
3877
+ var import_jsx_runtime32 = require("react/jsx-runtime");
3752
3878
  var DEFAULTS = {
3753
3879
  enabled: false,
3754
3880
  start: "22:00",
@@ -3765,7 +3891,7 @@ function normalize5(raw) {
3765
3891
  };
3766
3892
  }
3767
3893
  function QuietHoursForm({ basePath = "/api", className }) {
3768
- const toast = (0, import_react_ui30.useToast)();
3894
+ const toast = (0, import_react_ui31.useToast)();
3769
3895
  const url = `${basePath}/notifications/config/quiet-hours`;
3770
3896
  const { data, isLoading, isError, refetch } = (0, import_react_query27.useApiQuery)(
3771
3897
  ["quiet-hours"],
@@ -3789,23 +3915,23 @@ function QuietHoursForm({ basePath = "/api", className }) {
3789
3915
  save.mutate(form);
3790
3916
  };
3791
3917
  if (isLoading) {
3792
- return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
3918
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
3793
3919
  "div",
3794
3920
  {
3795
3921
  role: "status",
3796
3922
  "aria-label": "Loading quiet hours",
3797
- className: (0, import_react_ui30.cn)("flex flex-col gap-2 p-4", className),
3923
+ className: (0, import_react_ui31.cn)("flex flex-col gap-2 p-4", className),
3798
3924
  children: [
3799
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "sr-only", children: "Loading quiet hours" }),
3800
- [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { "aria-hidden": "true", className: "h-10 animate-pulse rounded bg-muted" }, i))
3925
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "sr-only", children: "Loading quiet hours" }),
3926
+ [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { "aria-hidden": "true", className: "h-10 animate-pulse rounded bg-muted" }, i))
3801
3927
  ]
3802
3928
  }
3803
3929
  );
3804
3930
  }
3805
3931
  if (isError) {
3806
- return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: (0, import_react_ui30.cn)("flex flex-col items-start gap-3 p-4", className), children: [
3807
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("p", { className: "text-sm text-foreground", children: "Failed to load quiet hours" }),
3808
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
3932
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: (0, import_react_ui31.cn)("flex flex-col items-start gap-3 p-4", className), children: [
3933
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("p", { className: "text-sm text-foreground", children: "Failed to load quiet hours" }),
3934
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
3809
3935
  "button",
3810
3936
  {
3811
3937
  type: "button",
@@ -3816,10 +3942,10 @@ function QuietHoursForm({ basePath = "/api", className }) {
3816
3942
  )
3817
3943
  ] });
3818
3944
  }
3819
- return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("form", { onSubmit, className: (0, import_react_ui30.cn)("flex flex-col gap-4", className), noValidate: true, children: [
3820
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("h2", { className: "text-sm font-semibold text-foreground", children: "Quiet hours" }),
3821
- /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("label", { className: "flex items-center gap-2 text-sm text-foreground", children: [
3822
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
3945
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("form", { onSubmit, className: (0, import_react_ui31.cn)("flex flex-col gap-4", className), noValidate: true, children: [
3946
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("h2", { className: "text-sm font-semibold text-foreground", children: "Quiet hours" }),
3947
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("label", { className: "flex items-center gap-2 text-sm text-foreground", children: [
3948
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
3823
3949
  "input",
3824
3950
  {
3825
3951
  type: "checkbox",
@@ -3830,10 +3956,10 @@ function QuietHoursForm({ basePath = "/api", className }) {
3830
3956
  ),
3831
3957
  "Enable quiet hours"
3832
3958
  ] }),
3833
- /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "flex flex-wrap gap-4", children: [
3834
- /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "flex flex-col gap-1", children: [
3835
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("label", { htmlFor: "qh-start", className: "text-sm font-medium text-foreground", children: "Start" }),
3836
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
3959
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex flex-wrap gap-4", children: [
3960
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex flex-col gap-1", children: [
3961
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("label", { htmlFor: "qh-start", className: "text-sm font-medium text-foreground", children: "Start" }),
3962
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
3837
3963
  "input",
3838
3964
  {
3839
3965
  id: "qh-start",
@@ -3844,9 +3970,9 @@ function QuietHoursForm({ basePath = "/api", className }) {
3844
3970
  }
3845
3971
  )
3846
3972
  ] }),
3847
- /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "flex flex-col gap-1", children: [
3848
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("label", { htmlFor: "qh-end", className: "text-sm font-medium text-foreground", children: "End" }),
3849
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
3973
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex flex-col gap-1", children: [
3974
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("label", { htmlFor: "qh-end", className: "text-sm font-medium text-foreground", children: "End" }),
3975
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
3850
3976
  "input",
3851
3977
  {
3852
3978
  id: "qh-end",
@@ -3857,9 +3983,9 @@ function QuietHoursForm({ basePath = "/api", className }) {
3857
3983
  }
3858
3984
  )
3859
3985
  ] }),
3860
- /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "flex flex-col gap-1", children: [
3861
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("label", { htmlFor: "qh-tz", className: "text-sm font-medium text-foreground", children: "Timezone" }),
3862
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
3986
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex flex-col gap-1", children: [
3987
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("label", { htmlFor: "qh-tz", className: "text-sm font-medium text-foreground", children: "Timezone" }),
3988
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
3863
3989
  "input",
3864
3990
  {
3865
3991
  id: "qh-tz",
@@ -3871,7 +3997,7 @@ function QuietHoursForm({ basePath = "/api", className }) {
3871
3997
  )
3872
3998
  ] })
3873
3999
  ] }),
3874
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
4000
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
3875
4001
  "button",
3876
4002
  {
3877
4003
  type: "submit",
@@ -3885,9 +4011,9 @@ function QuietHoursForm({ basePath = "/api", className }) {
3885
4011
 
3886
4012
  // src/frequency-cap-table.tsx
3887
4013
  var import_react20 = require("react");
3888
- var import_react_ui31 = require("@quanticjs/react-ui");
4014
+ var import_react_ui32 = require("@quanticjs/react-ui");
3889
4015
  var import_react_query28 = require("@quanticjs/react-query");
3890
- var import_jsx_runtime32 = require("react/jsx-runtime");
4016
+ var import_jsx_runtime33 = require("react/jsx-runtime");
3891
4017
  function normalize6(raw) {
3892
4018
  const list = Array.isArray(raw) ? raw : Array.isArray(raw?.caps) ? raw.caps : [];
3893
4019
  return list.map((entry) => {
@@ -3897,7 +4023,7 @@ function normalize6(raw) {
3897
4023
  });
3898
4024
  }
3899
4025
  function FrequencyCapTable({ basePath = "/api", className }) {
3900
- const toast = (0, import_react_ui31.useToast)();
4026
+ const toast = (0, import_react_ui32.useToast)();
3901
4027
  const url = `${basePath}/notifications/config/frequency-cap`;
3902
4028
  const { data, isLoading, isError, refetch } = (0, import_react_query28.useApiQuery)(
3903
4029
  ["frequency-cap"],
@@ -3932,23 +4058,23 @@ function FrequencyCapTable({ basePath = "/api", className }) {
3932
4058
  setNewMax(0);
3933
4059
  };
3934
4060
  if (isLoading) {
3935
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
4061
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
3936
4062
  "div",
3937
4063
  {
3938
4064
  role: "status",
3939
4065
  "aria-label": "Loading frequency caps",
3940
- className: (0, import_react_ui31.cn)("flex flex-col gap-2 p-4", className),
4066
+ className: (0, import_react_ui32.cn)("flex flex-col gap-2 p-4", className),
3941
4067
  children: [
3942
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "sr-only", children: "Loading frequency caps" }),
3943
- [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { "aria-hidden": "true", className: "h-10 animate-pulse rounded bg-muted" }, i))
4068
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "sr-only", children: "Loading frequency caps" }),
4069
+ [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { "aria-hidden": "true", className: "h-10 animate-pulse rounded bg-muted" }, i))
3944
4070
  ]
3945
4071
  }
3946
4072
  );
3947
4073
  }
3948
4074
  if (isError) {
3949
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: (0, import_react_ui31.cn)("flex flex-col items-start gap-3 p-4", className), children: [
3950
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("p", { className: "text-sm text-foreground", children: "Failed to load frequency caps" }),
3951
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
4075
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { className: (0, import_react_ui32.cn)("flex flex-col items-start gap-3 p-4", className), children: [
4076
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("p", { className: "text-sm text-foreground", children: "Failed to load frequency caps" }),
4077
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
3952
4078
  "button",
3953
4079
  {
3954
4080
  type: "button",
@@ -3959,24 +4085,24 @@ function FrequencyCapTable({ basePath = "/api", className }) {
3959
4085
  )
3960
4086
  ] });
3961
4087
  }
3962
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("section", { "aria-label": "Frequency caps", className: (0, import_react_ui31.cn)("flex flex-col gap-4", className), children: [
3963
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("h2", { className: "text-sm font-semibold text-foreground", children: "Frequency caps" }),
3964
- /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("table", { className: "w-full text-sm", children: [
3965
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("tr", { className: "border-b border-border text-start text-muted-foreground", children: [
3966
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("th", { scope: "col", className: "py-2 pe-4 font-medium", children: "Type" }),
3967
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Max per day" }),
3968
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "sr-only", children: "Actions" }) })
4088
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("section", { "aria-label": "Frequency caps", className: (0, import_react_ui32.cn)("flex flex-col gap-4", className), children: [
4089
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("h2", { className: "text-sm font-semibold text-foreground", children: "Frequency caps" }),
4090
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("table", { className: "w-full text-sm", children: [
4091
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("tr", { className: "border-b border-border text-start text-muted-foreground", children: [
4092
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("th", { scope: "col", className: "py-2 pe-4 font-medium", children: "Type" }),
4093
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Max per day" }),
4094
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "sr-only", children: "Actions" }) })
3969
4095
  ] }) }),
3970
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("tbody", { children: caps.map((cap, index) => {
4096
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("tbody", { children: caps.map((cap, index) => {
3971
4097
  const inputId = `cap-${index}`;
3972
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("tr", { className: "border-b border-border", children: [
3973
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("td", { className: "py-3 pe-4 text-foreground", children: cap.type }),
3974
- /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("td", { className: "px-4 py-3", children: [
3975
- /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("label", { htmlFor: inputId, className: "sr-only", children: [
4098
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("tr", { className: "border-b border-border", children: [
4099
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("td", { className: "py-3 pe-4 text-foreground", children: cap.type }),
4100
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("td", { className: "px-4 py-3", children: [
4101
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("label", { htmlFor: inputId, className: "sr-only", children: [
3976
4102
  "Max per day for ",
3977
4103
  cap.type
3978
4104
  ] }),
3979
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
4105
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
3980
4106
  "input",
3981
4107
  {
3982
4108
  id: inputId,
@@ -3988,7 +4114,7 @@ function FrequencyCapTable({ basePath = "/api", className }) {
3988
4114
  }
3989
4115
  )
3990
4116
  ] }),
3991
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("td", { className: "px-4 py-3", children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
4117
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("td", { className: "px-4 py-3", children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
3992
4118
  "button",
3993
4119
  {
3994
4120
  type: "button",
@@ -4000,10 +4126,10 @@ function FrequencyCapTable({ basePath = "/api", className }) {
4000
4126
  ] }, `${cap.type}-${index}`);
4001
4127
  }) })
4002
4128
  ] }),
4003
- /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("form", { onSubmit: addRow, className: "flex flex-wrap items-end gap-3", noValidate: true, children: [
4004
- /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex flex-col gap-1", children: [
4005
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("label", { htmlFor: "cap-new-type", className: "text-sm font-medium text-foreground", children: "New type" }),
4006
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
4129
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("form", { onSubmit: addRow, className: "flex flex-wrap items-end gap-3", noValidate: true, children: [
4130
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { className: "flex flex-col gap-1", children: [
4131
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("label", { htmlFor: "cap-new-type", className: "text-sm font-medium text-foreground", children: "New type" }),
4132
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
4007
4133
  "input",
4008
4134
  {
4009
4135
  id: "cap-new-type",
@@ -4014,9 +4140,9 @@ function FrequencyCapTable({ basePath = "/api", className }) {
4014
4140
  }
4015
4141
  )
4016
4142
  ] }),
4017
- /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex flex-col gap-1", children: [
4018
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("label", { htmlFor: "cap-new-max", className: "text-sm font-medium text-foreground", children: "Max per day" }),
4019
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
4143
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { className: "flex flex-col gap-1", children: [
4144
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("label", { htmlFor: "cap-new-max", className: "text-sm font-medium text-foreground", children: "Max per day" }),
4145
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
4020
4146
  "input",
4021
4147
  {
4022
4148
  id: "cap-new-max",
@@ -4028,7 +4154,7 @@ function FrequencyCapTable({ basePath = "/api", className }) {
4028
4154
  }
4029
4155
  )
4030
4156
  ] }),
4031
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
4157
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
4032
4158
  "button",
4033
4159
  {
4034
4160
  type: "submit",
@@ -4037,7 +4163,7 @@ function FrequencyCapTable({ basePath = "/api", className }) {
4037
4163
  }
4038
4164
  )
4039
4165
  ] }),
4040
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
4166
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
4041
4167
  "button",
4042
4168
  {
4043
4169
  type: "button",
@@ -4052,9 +4178,9 @@ function FrequencyCapTable({ basePath = "/api", className }) {
4052
4178
 
4053
4179
  // src/tenant-config-form.tsx
4054
4180
  var import_react21 = require("react");
4055
- var import_react_ui32 = require("@quanticjs/react-ui");
4181
+ var import_react_ui33 = require("@quanticjs/react-ui");
4056
4182
  var import_react_query29 = require("@quanticjs/react-query");
4057
- var import_jsx_runtime33 = require("react/jsx-runtime");
4183
+ var import_jsx_runtime34 = require("react/jsx-runtime");
4058
4184
  function normalize7(raw) {
4059
4185
  const obj = raw ?? {};
4060
4186
  const toList = (value) => Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
@@ -4072,15 +4198,15 @@ function TagEditor({ id, label, values, onAdd, onRemove }) {
4072
4198
  onAdd(value);
4073
4199
  setDraft("");
4074
4200
  };
4075
- return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { className: "flex flex-col gap-2", children: [
4076
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "text-sm font-medium text-foreground", children: label }),
4077
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("ul", { className: "flex flex-wrap gap-2", children: values.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("li", { className: "text-sm text-muted-foreground", children: "None" }) : values.map((value) => /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
4201
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "flex flex-col gap-2", children: [
4202
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "text-sm font-medium text-foreground", children: label }),
4203
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("ul", { className: "flex flex-wrap gap-2", children: values.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("li", { className: "text-sm text-muted-foreground", children: "None" }) : values.map((value) => /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
4078
4204
  "li",
4079
4205
  {
4080
4206
  className: "flex items-center gap-1 rounded-md border border-border bg-card px-2 py-1 text-sm text-foreground",
4081
4207
  children: [
4082
4208
  value,
4083
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
4209
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
4084
4210
  "button",
4085
4211
  {
4086
4212
  type: "button",
@@ -4094,13 +4220,13 @@ function TagEditor({ id, label, values, onAdd, onRemove }) {
4094
4220
  },
4095
4221
  value
4096
4222
  )) }),
4097
- /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("form", { onSubmit: add, className: "flex items-end gap-2", noValidate: true, children: [
4098
- /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { className: "flex flex-col gap-1", children: [
4099
- /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("label", { htmlFor: id, className: "sr-only", children: [
4223
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("form", { onSubmit: add, className: "flex items-end gap-2", noValidate: true, children: [
4224
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "flex flex-col gap-1", children: [
4225
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("label", { htmlFor: id, className: "sr-only", children: [
4100
4226
  "Add to ",
4101
4227
  label
4102
4228
  ] }),
4103
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
4229
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
4104
4230
  "input",
4105
4231
  {
4106
4232
  id,
@@ -4111,7 +4237,7 @@ function TagEditor({ id, label, values, onAdd, onRemove }) {
4111
4237
  }
4112
4238
  )
4113
4239
  ] }),
4114
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
4240
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
4115
4241
  "button",
4116
4242
  {
4117
4243
  type: "submit",
@@ -4123,7 +4249,7 @@ function TagEditor({ id, label, values, onAdd, onRemove }) {
4123
4249
  ] });
4124
4250
  }
4125
4251
  function TenantConfigForm({ basePath = "/api", className }) {
4126
- const toast = (0, import_react_ui32.useToast)();
4252
+ const toast = (0, import_react_ui33.useToast)();
4127
4253
  const url = `${basePath}/admin/notification-config`;
4128
4254
  const { data, isLoading, isError, refetch } = (0, import_react_query29.useApiQuery)(
4129
4255
  ["tenant-config"],
@@ -4176,23 +4302,23 @@ function TenantConfigForm({ basePath = "/api", className }) {
4176
4302
  save.mutate(config);
4177
4303
  };
4178
4304
  if (isLoading) {
4179
- return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
4305
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
4180
4306
  "div",
4181
4307
  {
4182
4308
  role: "status",
4183
4309
  "aria-label": "Loading tenant configuration",
4184
- className: (0, import_react_ui32.cn)("flex flex-col gap-2 p-4", className),
4310
+ className: (0, import_react_ui33.cn)("flex flex-col gap-2 p-4", className),
4185
4311
  children: [
4186
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "sr-only", children: "Loading tenant configuration" }),
4187
- [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { "aria-hidden": "true", className: "h-10 animate-pulse rounded bg-muted" }, i))
4312
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "sr-only", children: "Loading tenant configuration" }),
4313
+ [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { "aria-hidden": "true", className: "h-10 animate-pulse rounded bg-muted" }, i))
4188
4314
  ]
4189
4315
  }
4190
4316
  );
4191
4317
  }
4192
4318
  if (isError) {
4193
- return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { className: (0, import_react_ui32.cn)("flex flex-col items-start gap-3 p-4", className), children: [
4194
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("p", { className: "text-sm text-foreground", children: "Failed to load tenant configuration" }),
4195
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
4319
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: (0, import_react_ui33.cn)("flex flex-col items-start gap-3 p-4", className), children: [
4320
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("p", { className: "text-sm text-foreground", children: "Failed to load tenant configuration" }),
4321
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
4196
4322
  "button",
4197
4323
  {
4198
4324
  type: "button",
@@ -4204,17 +4330,17 @@ function TenantConfigForm({ basePath = "/api", className }) {
4204
4330
  ] });
4205
4331
  }
4206
4332
  const errorId = "tenant-config-subset-error";
4207
- return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
4333
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
4208
4334
  "form",
4209
4335
  {
4210
4336
  onSubmit,
4211
- className: (0, import_react_ui32.cn)("flex flex-col gap-5", className),
4337
+ className: (0, import_react_ui33.cn)("flex flex-col gap-5", className),
4212
4338
  noValidate: true,
4213
4339
  "aria-invalid": subsetError ? "true" : void 0,
4214
4340
  "aria-describedby": subsetError ? errorId : void 0,
4215
4341
  children: [
4216
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("h2", { className: "text-sm font-semibold text-foreground", children: "Notification configuration" }),
4217
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
4342
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("h2", { className: "text-sm font-semibold text-foreground", children: "Notification configuration" }),
4343
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
4218
4344
  TagEditor,
4219
4345
  {
4220
4346
  id: "tc-notification-types",
@@ -4224,7 +4350,7 @@ function TenantConfigForm({ basePath = "/api", className }) {
4224
4350
  onRemove: removeType
4225
4351
  }
4226
4352
  ),
4227
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
4353
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
4228
4354
  TagEditor,
4229
4355
  {
4230
4356
  id: "tc-immediate-email-types",
@@ -4234,8 +4360,8 @@ function TenantConfigForm({ basePath = "/api", className }) {
4234
4360
  onRemove: removeImmediate
4235
4361
  }
4236
4362
  ),
4237
- subsetError && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("p", { id: errorId, className: "text-xs text-destructive", children: subsetError }),
4238
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
4363
+ subsetError && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("p", { id: errorId, className: "text-xs text-destructive", children: subsetError }),
4364
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
4239
4365
  "button",
4240
4366
  {
4241
4367
  type: "submit",
@@ -4251,9 +4377,9 @@ function TenantConfigForm({ basePath = "/api", className }) {
4251
4377
 
4252
4378
  // src/tracking-config-form.tsx
4253
4379
  var import_react22 = require("react");
4254
- var import_react_ui33 = require("@quanticjs/react-ui");
4380
+ var import_react_ui34 = require("@quanticjs/react-ui");
4255
4381
  var import_react_query30 = require("@quanticjs/react-query");
4256
- var import_jsx_runtime34 = require("react/jsx-runtime");
4382
+ var import_jsx_runtime35 = require("react/jsx-runtime");
4257
4383
  function normalize8(raw) {
4258
4384
  const obj = raw ?? {};
4259
4385
  const bool = (...keys) => {
@@ -4268,7 +4394,7 @@ function normalize8(raw) {
4268
4394
  };
4269
4395
  }
4270
4396
  function TrackingConfigForm({ basePath = "/api", className }) {
4271
- const toast = (0, import_react_ui33.useToast)();
4397
+ const toast = (0, import_react_ui34.useToast)();
4272
4398
  const url = `${basePath}/analytics/notifications/tracking-config`;
4273
4399
  const { data, isLoading, isError, refetch } = (0, import_react_query30.useApiQuery)(
4274
4400
  ["tracking-config"],
@@ -4298,23 +4424,23 @@ function TrackingConfigForm({ basePath = "/api", className }) {
4298
4424
  save.mutate(form);
4299
4425
  };
4300
4426
  if (isLoading) {
4301
- return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
4427
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
4302
4428
  "div",
4303
4429
  {
4304
4430
  role: "status",
4305
4431
  "aria-label": "Loading tracking configuration",
4306
- className: (0, import_react_ui33.cn)("flex flex-col gap-2 p-4", className),
4432
+ className: (0, import_react_ui34.cn)("flex flex-col gap-2 p-4", className),
4307
4433
  children: [
4308
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "sr-only", children: "Loading tracking configuration" }),
4309
- [0, 1].map((i) => /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { "aria-hidden": "true", className: "h-10 animate-pulse rounded bg-muted" }, i))
4434
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "sr-only", children: "Loading tracking configuration" }),
4435
+ [0, 1].map((i) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { "aria-hidden": "true", className: "h-10 animate-pulse rounded bg-muted" }, i))
4310
4436
  ]
4311
4437
  }
4312
4438
  );
4313
4439
  }
4314
4440
  if (isError) {
4315
- return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: (0, import_react_ui33.cn)("flex flex-col items-start gap-3 p-4", className), children: [
4316
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("p", { className: "text-sm text-foreground", children: "Failed to load tracking configuration" }),
4317
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
4441
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: (0, import_react_ui34.cn)("flex flex-col items-start gap-3 p-4", className), children: [
4442
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("p", { className: "text-sm text-foreground", children: "Failed to load tracking configuration" }),
4443
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
4318
4444
  "button",
4319
4445
  {
4320
4446
  type: "button",
@@ -4325,10 +4451,10 @@ function TrackingConfigForm({ basePath = "/api", className }) {
4325
4451
  )
4326
4452
  ] });
4327
4453
  }
4328
- return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("form", { onSubmit, className: (0, import_react_ui33.cn)("flex flex-col gap-4", className), noValidate: true, children: [
4329
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("h2", { className: "text-sm font-semibold text-foreground", children: "Tracking configuration" }),
4330
- /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("label", { className: "flex items-center gap-2 text-sm text-foreground", children: [
4331
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
4454
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("form", { onSubmit, className: (0, import_react_ui34.cn)("flex flex-col gap-4", className), noValidate: true, children: [
4455
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("h2", { className: "text-sm font-semibold text-foreground", children: "Tracking configuration" }),
4456
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("label", { className: "flex items-center gap-2 text-sm text-foreground", children: [
4457
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
4332
4458
  "input",
4333
4459
  {
4334
4460
  type: "checkbox",
@@ -4339,8 +4465,8 @@ function TrackingConfigForm({ basePath = "/api", className }) {
4339
4465
  ),
4340
4466
  "Open tracking"
4341
4467
  ] }),
4342
- /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("label", { className: "flex items-center gap-2 text-sm text-foreground", children: [
4343
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
4468
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("label", { className: "flex items-center gap-2 text-sm text-foreground", children: [
4469
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
4344
4470
  "input",
4345
4471
  {
4346
4472
  type: "checkbox",
@@ -4351,7 +4477,7 @@ function TrackingConfigForm({ basePath = "/api", className }) {
4351
4477
  ),
4352
4478
  "Click tracking"
4353
4479
  ] }),
4354
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
4480
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
4355
4481
  "button",
4356
4482
  {
4357
4483
  type: "submit",
@@ -4365,15 +4491,15 @@ function TrackingConfigForm({ basePath = "/api", className }) {
4365
4491
 
4366
4492
  // src/api-key-manager.tsx
4367
4493
  var import_react23 = require("react");
4368
- var import_react_ui34 = require("@quanticjs/react-ui");
4494
+ var import_react_ui35 = require("@quanticjs/react-ui");
4369
4495
  var import_react_query31 = require("@quanticjs/react-query");
4370
- var import_jsx_runtime35 = require("react/jsx-runtime");
4496
+ var import_jsx_runtime36 = require("react/jsx-runtime");
4371
4497
  function normalize9(raw) {
4372
4498
  const list = Array.isArray(raw) ? raw : Array.isArray(raw?.items) ? raw.items : [];
4373
4499
  return list;
4374
4500
  }
4375
4501
  function ApiKeyManager({ basePath = "/api", className }) {
4376
- const toast = (0, import_react_ui34.useToast)();
4502
+ const toast = (0, import_react_ui35.useToast)();
4377
4503
  const url = `${basePath}/v1/admin/api-keys`;
4378
4504
  const { data, isLoading, isError, refetch } = (0, import_react_query31.useApiQuery)(
4379
4505
  ["api-keys"],
@@ -4425,110 +4551,77 @@ function ApiKeyManager({ basePath = "/api", className }) {
4425
4551
  revoke.mutate(id);
4426
4552
  }
4427
4553
  };
4428
- const createForm = /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("form", { onSubmit: onCreate, className: "flex flex-wrap items-end gap-3", noValidate: true, children: [
4429
- /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "flex flex-col gap-1", children: [
4430
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("label", { htmlFor: "api-key-name", className: "text-sm font-medium text-foreground", children: "New key name" }),
4431
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
4432
- "input",
4433
- {
4434
- id: "api-key-name",
4435
- type: "text",
4436
- value: name,
4437
- onChange: (e) => setName(e.target.value),
4438
- className: "rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring"
4439
- }
4440
- )
4441
- ] }),
4442
- /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "flex flex-col gap-1", children: [
4443
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("label", { htmlFor: "api-key-app", className: "text-sm font-medium text-foreground", children: "Application (optional)" }),
4444
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
4445
- "input",
4446
- {
4447
- id: "api-key-app",
4448
- type: "text",
4449
- value: applicationKey,
4450
- onChange: (e) => setApplicationKey(e.target.value),
4451
- placeholder: "delivery-hub",
4452
- className: "rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring"
4453
- }
4454
- )
4455
- ] }),
4456
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
4457
- "button",
4458
- {
4459
- type: "submit",
4460
- disabled: create.isPending,
4461
- className: "rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:opacity-90 focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring disabled:opacity-50",
4462
- children: create.isPending ? "Creating\u2026" : "Create key"
4463
- }
4464
- )
4465
- ] });
4466
- let body;
4467
- if (isLoading) {
4468
- body = /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { role: "status", "aria-label": "Loading API keys", className: "flex flex-col gap-2 p-4", children: [
4469
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "sr-only", children: "Loading API keys" }),
4470
- [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { "aria-hidden": "true", className: "h-10 animate-pulse rounded bg-muted" }, i))
4471
- ] });
4472
- } else if (isError) {
4473
- body = /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "flex flex-col items-start gap-3 p-4", children: [
4474
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("p", { className: "text-sm text-foreground", children: "Failed to load API keys" }),
4475
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
4476
- "button",
4477
- {
4478
- type: "button",
4479
- onClick: () => void refetch(),
4480
- className: "rounded bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground hover:opacity-90 focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring",
4481
- children: "Try again"
4482
- }
4483
- )
4484
- ] });
4485
- } else {
4486
- const rows = normalize9(data);
4487
- if (rows.length === 0) {
4488
- body = /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "p-6 text-center text-sm text-muted-foreground", children: "No API keys" });
4489
- } else {
4490
- body = /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("table", { className: "w-full text-sm", children: [
4491
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("tr", { className: "border-b border-border text-start text-muted-foreground", children: [
4492
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("th", { scope: "col", className: "py-2 pe-4 font-medium", children: "Name" }),
4493
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Prefix" }),
4494
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Application" }),
4495
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Status" }),
4496
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Created" }),
4497
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Last used" }),
4498
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "sr-only", children: "Actions" }) })
4554
+ const rows = normalize9(data);
4555
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("section", { "aria-label": "API key management", className: (0, import_react_ui35.cn)("flex flex-col gap-4", className), children: [
4556
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_react_ui35.Card, { children: /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("form", { onSubmit: onCreate, className: "flex flex-wrap items-end gap-3 p-4", noValidate: true, children: [
4557
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("label", { className: "flex flex-1 flex-col gap-1 text-sm", children: [
4558
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "font-medium text-foreground", children: "New key name" }),
4559
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("input", { type: "text", value: name, onChange: (e) => setName(e.target.value), className: FIELD_CLASS })
4560
+ ] }),
4561
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("label", { className: "flex flex-1 flex-col gap-1 text-sm", children: [
4562
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "font-medium text-foreground", children: "Application (optional)" }),
4563
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
4564
+ "input",
4565
+ {
4566
+ type: "text",
4567
+ value: applicationKey,
4568
+ onChange: (e) => setApplicationKey(e.target.value),
4569
+ placeholder: "delivery-hub",
4570
+ className: FIELD_CLASS
4571
+ }
4572
+ )
4573
+ ] }),
4574
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_react_ui35.Button, { type: "submit", disabled: create.isPending, children: create.isPending ? "Creating\u2026" : "Create key" })
4575
+ ] }) }),
4576
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(import_react_ui35.Card, { children: [
4577
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(PanelHeader, { title: "Keys", subtitle: !isLoading && !isError ? `${rows.length} keys` : void 0 }),
4578
+ isLoading ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(SkeletonRows, { label: "Loading API keys" }) : isError ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ErrorPanel, { message: "Failed to load API keys", onRetry: () => void refetch() }) : rows.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_react_ui35.EmptyState, { icon: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(KeyIcon, {}), title: "No API keys", description: "Create a key to get started." }) : /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("table", { className: "w-full text-sm", children: [
4579
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("tr", { className: "border-b border-border text-start text-muted-foreground", children: [
4580
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Name" }),
4581
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Prefix" }),
4582
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Application" }),
4583
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Status" }),
4584
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Created" }),
4585
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: "Last used" }),
4586
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("th", { scope: "col", className: "px-4 py-2 font-medium", children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "sr-only", children: "Actions" }) })
4499
4587
  ] }) }),
4500
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("tbody", { children: rows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("tr", { className: "border-b border-border", children: [
4501
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("td", { className: "py-3 pe-4 text-foreground", children: row.name }),
4502
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("td", { className: "px-4 py-3 font-mono text-muted-foreground", children: row.prefix ?? "\u2014" }),
4503
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("td", { className: "px-4 py-3 font-mono text-muted-foreground", children: row.applicationKey ?? "\u2014" }),
4504
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("td", { className: "px-4 py-3", children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_react_ui34.StatusBadge, { variant: row.revoked ? "destructive" : "success", children: row.revoked ? "Revoked" : "Active" }) }),
4505
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("td", { className: "px-4 py-3 text-muted-foreground", children: (0, import_react_ui34.formatDateTime)(row.createdAt) }),
4506
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("td", { className: "px-4 py-3 text-muted-foreground", children: row.lastUsedAt ? (0, import_react_ui34.formatDateTime)(row.lastUsedAt) : "\u2014" }),
4507
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("td", { className: "px-4 py-3", children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
4508
- "button",
4588
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("tbody", { children: rows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("tr", { className: "border-b border-border last:border-0 hover:bg-muted/40", children: [
4589
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("td", { className: "px-4 py-3 text-foreground", children: row.name }),
4590
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("td", { className: "px-4 py-3 font-mono text-muted-foreground", children: row.prefix ?? "\u2014" }),
4591
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("td", { className: "px-4 py-3 font-mono text-muted-foreground", children: row.applicationKey ?? "\u2014" }),
4592
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("td", { className: "px-4 py-3", children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_react_ui35.StatusBadge, { variant: row.revoked ? "destructive" : "success", children: row.revoked ? "Revoked" : "Active" }) }),
4593
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("td", { className: "px-4 py-3 text-muted-foreground", children: (0, import_react_ui35.formatDateTime)(row.createdAt) }),
4594
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("td", { className: "px-4 py-3 text-muted-foreground", children: row.lastUsedAt ? (0, import_react_ui35.formatDateTime)(row.lastUsedAt) : "\u2014" }),
4595
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("td", { className: "px-4 py-3 text-end", children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
4596
+ import_react_ui35.Button,
4509
4597
  {
4510
4598
  type: "button",
4599
+ variant: "ghost",
4600
+ size: "sm",
4601
+ className: "text-destructive",
4511
4602
  disabled: revoke.isPending || row.revoked,
4512
4603
  onClick: () => onRevoke(row.id),
4513
- className: "rounded-md border border-border px-3 py-1 text-sm font-medium text-destructive hover:bg-muted focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring disabled:opacity-50",
4514
4604
  children: "Revoke"
4515
4605
  }
4516
4606
  ) })
4517
4607
  ] }, row.id)) })
4518
- ] });
4519
- }
4520
- }
4521
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("section", { "aria-label": "API key management", className: (0, import_react_ui34.cn)("flex flex-col gap-4", className), children: [
4522
- createForm,
4523
- body
4608
+ ] })
4609
+ ] })
4610
+ ] });
4611
+ }
4612
+ function KeyIcon() {
4613
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
4614
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("path", { d: "m15.5 7.5 2.3 2.3a1 1 0 0 0 1.4 0l2.1-2.1a1 1 0 0 0 0-1.4L19 4" }),
4615
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("path", { d: "m21 2-9.6 9.6" }),
4616
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("circle", { cx: "7.5", cy: "15.5", r: "5.5" })
4524
4617
  ] });
4525
4618
  }
4526
4619
 
4527
4620
  // src/application-registry-panel.tsx
4528
4621
  var import_react24 = require("react");
4529
- var import_react_ui35 = require("@quanticjs/react-ui");
4622
+ var import_react_ui36 = require("@quanticjs/react-ui");
4530
4623
  var import_react_query32 = require("@quanticjs/react-query");
4531
- var import_jsx_runtime36 = require("react/jsx-runtime");
4624
+ var import_jsx_runtime37 = require("react/jsx-runtime");
4532
4625
  function normalize10(raw) {
4533
4626
  const list = Array.isArray(raw) ? raw : Array.isArray(raw?.items) ? raw.items : [];
4534
4627
  return list;
@@ -4537,7 +4630,7 @@ function ApplicationRegistryPanel({
4537
4630
  basePath = "/api",
4538
4631
  className
4539
4632
  }) {
4540
- const toast = (0, import_react_ui35.useToast)();
4633
+ const toast = (0, import_react_ui36.useToast)();
4541
4634
  const url = `${basePath}/admin/applications`;
4542
4635
  const { data, isLoading, isError, refetch } = (0, import_react_query32.useApiQuery)(
4543
4636
  ["applications"],
@@ -4574,10 +4667,10 @@ function ApplicationRegistryPanel({
4574
4667
  if (!k || !name) return;
4575
4668
  register.mutate({ key: k, displayName: name, description: description.trim() || void 0 });
4576
4669
  };
4577
- const registerForm = /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("form", { onSubmit: onRegister, className: "flex flex-wrap items-end gap-3", noValidate: true, children: [
4578
- /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "flex flex-col gap-1", children: [
4579
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("label", { htmlFor: "application-key", className: "text-sm font-medium text-foreground", children: "Key (slug)" }),
4580
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
4670
+ const registerForm = /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("form", { onSubmit: onRegister, className: "flex flex-wrap items-end gap-3", noValidate: true, children: [
4671
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "flex flex-col gap-1", children: [
4672
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("label", { htmlFor: "application-key", className: "text-sm font-medium text-foreground", children: "Key (slug)" }),
4673
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
4581
4674
  "input",
4582
4675
  {
4583
4676
  id: "application-key",
@@ -4589,9 +4682,9 @@ function ApplicationRegistryPanel({
4589
4682
  }
4590
4683
  )
4591
4684
  ] }),
4592
- /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "flex flex-col gap-1", children: [
4593
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("label", { htmlFor: "application-name", className: "text-sm font-medium text-foreground", children: "Display name" }),
4594
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
4685
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "flex flex-col gap-1", children: [
4686
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("label", { htmlFor: "application-name", className: "text-sm font-medium text-foreground", children: "Display name" }),
4687
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
4595
4688
  "input",
4596
4689
  {
4597
4690
  id: "application-name",
@@ -4603,9 +4696,9 @@ function ApplicationRegistryPanel({
4603
4696
  }
4604
4697
  )
4605
4698
  ] }),
4606
- /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "flex flex-col gap-1", children: [
4607
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("label", { htmlFor: "application-description", className: "text-sm font-medium text-foreground", children: "Description (optional)" }),
4608
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
4699
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "flex flex-col gap-1", children: [
4700
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("label", { htmlFor: "application-description", className: "text-sm font-medium text-foreground", children: "Description (optional)" }),
4701
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
4609
4702
  "input",
4610
4703
  {
4611
4704
  id: "application-description",
@@ -4616,7 +4709,7 @@ function ApplicationRegistryPanel({
4616
4709
  }
4617
4710
  )
4618
4711
  ] }),
4619
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
4712
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
4620
4713
  "button",
4621
4714
  {
4622
4715
  type: "submit",
@@ -4627,23 +4720,23 @@ function ApplicationRegistryPanel({
4627
4720
  )
4628
4721
  ] });
4629
4722
  if (isLoading) {
4630
- return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
4723
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(
4631
4724
  "div",
4632
4725
  {
4633
4726
  role: "status",
4634
4727
  "aria-label": "Loading applications",
4635
- className: (0, import_react_ui35.cn)("flex flex-col gap-2 p-4", className),
4728
+ className: (0, import_react_ui36.cn)("flex flex-col gap-2 p-4", className),
4636
4729
  children: [
4637
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "sr-only", children: "Loading applications" }),
4638
- [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { "aria-hidden": "true", className: "h-10 animate-pulse rounded bg-muted" }, i))
4730
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "sr-only", children: "Loading applications" }),
4731
+ [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { "aria-hidden": "true", className: "h-10 animate-pulse rounded bg-muted" }, i))
4639
4732
  ]
4640
4733
  }
4641
4734
  );
4642
4735
  }
4643
4736
  if (isError) {
4644
- return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: (0, import_react_ui35.cn)("flex flex-col items-start gap-3 p-4", className), children: [
4645
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("p", { className: "text-sm text-foreground", children: "Failed to load applications" }),
4646
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
4737
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: (0, import_react_ui36.cn)("flex flex-col items-start gap-3 p-4", className), children: [
4738
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("p", { className: "text-sm text-foreground", children: "Failed to load applications" }),
4739
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
4647
4740
  "button",
4648
4741
  {
4649
4742
  type: "button",
@@ -4655,22 +4748,22 @@ function ApplicationRegistryPanel({
4655
4748
  ] });
4656
4749
  }
4657
4750
  const apps = normalize10(data);
4658
- return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("section", { "aria-label": "Applications", className: (0, import_react_ui35.cn)("flex flex-col gap-4 p-4", className), children: [
4751
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("section", { "aria-label": "Applications", className: (0, import_react_ui36.cn)("flex flex-col gap-4 p-4", className), children: [
4659
4752
  registerForm,
4660
- apps.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("p", { className: "p-6 text-center text-sm text-muted-foreground", children: "No applications registered" }) : /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("table", { className: "w-full text-sm", children: [
4661
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("tr", { className: "border-b border-border text-start text-muted-foreground", children: [
4662
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("th", { className: "py-2 font-medium", children: "Key" }),
4663
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("th", { className: "py-2 font-medium", children: "Name" }),
4664
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("th", { className: "py-2 font-medium", children: "Status" }),
4665
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("th", { className: "py-2 font-medium", children: "Created" }),
4666
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("th", { className: "py-2 font-medium", children: "Actions" })
4753
+ apps.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("p", { className: "p-6 text-center text-sm text-muted-foreground", children: "No applications registered" }) : /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("table", { className: "w-full text-sm", children: [
4754
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("tr", { className: "border-b border-border text-start text-muted-foreground", children: [
4755
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("th", { className: "py-2 font-medium", children: "Key" }),
4756
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("th", { className: "py-2 font-medium", children: "Name" }),
4757
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("th", { className: "py-2 font-medium", children: "Status" }),
4758
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("th", { className: "py-2 font-medium", children: "Created" }),
4759
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("th", { className: "py-2 font-medium", children: "Actions" })
4667
4760
  ] }) }),
4668
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("tbody", { children: apps.map((app) => /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("tr", { className: "border-b border-border", children: [
4669
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("td", { className: "py-2 font-mono text-foreground", children: app.key }),
4670
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("td", { className: "py-2 text-foreground", children: app.displayName }),
4671
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("td", { className: "py-2", children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_react_ui35.StatusBadge, { variant: app.status === "active" ? "success" : "neutral", children: app.status }) }),
4672
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("td", { className: "py-2 text-muted-foreground", children: (0, import_react_ui35.formatDateTime)(app.createdAt) }),
4673
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("td", { className: "py-2", children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
4761
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("tbody", { children: apps.map((app) => /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("tr", { className: "border-b border-border", children: [
4762
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("td", { className: "py-2 font-mono text-foreground", children: app.key }),
4763
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("td", { className: "py-2 text-foreground", children: app.displayName }),
4764
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("td", { className: "py-2", children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_react_ui36.StatusBadge, { variant: app.status === "active" ? "success" : "neutral", children: app.status }) }),
4765
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("td", { className: "py-2 text-muted-foreground", children: (0, import_react_ui36.formatDateTime)(app.createdAt) }),
4766
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("td", { className: "py-2", children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
4674
4767
  "button",
4675
4768
  {
4676
4769
  type: "button",