@burdenoff/microfe-planmagnet 2026.1001.5 → 2026.1001.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/dist/planmagnet/PlanMagnetRoutes.d.ts.map +1 -1
  2. package/dist/planmagnet/PlanMagnetRoutes.js +10 -6
  3. package/dist/planmagnet/PlanMagnetRoutes.js.map +1 -1
  4. package/dist/planmagnet/automation/automationRuleTypes.d.ts +31 -19
  5. package/dist/planmagnet/automation/automationRuleTypes.d.ts.map +1 -1
  6. package/dist/planmagnet/automation/automationRuleTypes.js +4 -8
  7. package/dist/planmagnet/automation/automationRuleTypes.js.map +1 -1
  8. package/dist/planmagnet/controls/CompetitorControls.d.ts +71 -0
  9. package/dist/planmagnet/controls/CompetitorControls.d.ts.map +1 -1
  10. package/dist/planmagnet/controls/CompetitorControls.js +346 -12
  11. package/dist/planmagnet/controls/CompetitorControls.js.map +1 -1
  12. package/dist/planmagnet/controls/FeatureControls.d.ts +23 -1
  13. package/dist/planmagnet/controls/FeatureControls.d.ts.map +1 -1
  14. package/dist/planmagnet/controls/FeatureControls.js +431 -229
  15. package/dist/planmagnet/controls/FeatureControls.js.map +1 -1
  16. package/dist/planmagnet/controls/FeedbackControls.d.ts +70 -0
  17. package/dist/planmagnet/controls/FeedbackControls.d.ts.map +1 -0
  18. package/dist/planmagnet/controls/FeedbackControls.js +153 -0
  19. package/dist/planmagnet/controls/FeedbackControls.js.map +1 -0
  20. package/dist/planmagnet/controls/Pickers.d.ts +15 -0
  21. package/dist/planmagnet/controls/Pickers.d.ts.map +1 -1
  22. package/dist/planmagnet/controls/Pickers.js +61 -1
  23. package/dist/planmagnet/controls/Pickers.js.map +1 -1
  24. package/dist/planmagnet/controls/PrioritizationControls.d.ts +36 -0
  25. package/dist/planmagnet/controls/PrioritizationControls.d.ts.map +1 -1
  26. package/dist/planmagnet/controls/PrioritizationControls.js +118 -1
  27. package/dist/planmagnet/controls/PrioritizationControls.js.map +1 -1
  28. package/dist/planmagnet/controls/SyncControls.d.ts.map +1 -1
  29. package/dist/planmagnet/controls/SyncControls.js +1 -0
  30. package/dist/planmagnet/controls/SyncControls.js.map +1 -1
  31. package/dist/planmagnet/controls/WebhookSubscriptionControls.d.ts +47 -0
  32. package/dist/planmagnet/controls/WebhookSubscriptionControls.d.ts.map +1 -0
  33. package/dist/planmagnet/controls/WebhookSubscriptionControls.js +542 -0
  34. package/dist/planmagnet/controls/WebhookSubscriptionControls.js.map +1 -0
  35. package/dist/planmagnet/dashboards/DashboardUI.js +8 -8
  36. package/dist/planmagnet/dashboards/cards/RiskSeverityCard.js +4 -4
  37. package/dist/planmagnet/dashboards/cards/SprintCapacityCard.d.ts.map +1 -1
  38. package/dist/planmagnet/dashboards/cards/SprintCapacityCard.js +67 -59
  39. package/dist/planmagnet/dashboards/cards/SprintCapacityCard.js.map +1 -1
  40. package/dist/planmagnet/forecast/DeliveryForecastCard.js +8 -8
  41. package/dist/planmagnet/forecast/ProjectOutlookCard.js +10 -10
  42. package/dist/planmagnet/forecast/ProjectRiskSummaryCard.js +9 -9
  43. package/dist/planmagnet/forecast/ResourceForecastCard.js +6 -6
  44. package/dist/planmagnet/integrations/syncTypes.d.ts +2 -1
  45. package/dist/planmagnet/integrations/syncTypes.d.ts.map +1 -1
  46. package/dist/planmagnet/integrations/syncTypes.js +19 -9
  47. package/dist/planmagnet/integrations/syncTypes.js.map +1 -1
  48. package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.d.ts.map +1 -1
  49. package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.js +290 -283
  50. package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.js.map +1 -1
  51. package/dist/planmagnet/pages/CompetitorsCompetitorDetailPagePage.d.ts.map +1 -1
  52. package/dist/planmagnet/pages/CompetitorsCompetitorDetailPagePage.js +126 -63
  53. package/dist/planmagnet/pages/CompetitorsCompetitorDetailPagePage.js.map +1 -1
  54. package/dist/planmagnet/pages/CompetitorsCompetitorsPagePage.d.ts.map +1 -1
  55. package/dist/planmagnet/pages/CompetitorsCompetitorsPagePage.js +14 -0
  56. package/dist/planmagnet/pages/CompetitorsCompetitorsPagePage.js.map +1 -1
  57. package/dist/planmagnet/pages/CompetitorsCreateCompetitorPagePage.d.ts.map +1 -1
  58. package/dist/planmagnet/pages/CompetitorsCreateCompetitorPagePage.js +93 -57
  59. package/dist/planmagnet/pages/CompetitorsCreateCompetitorPagePage.js.map +1 -1
  60. package/dist/planmagnet/pages/CompetitorsEditCompetitorPagePage.d.ts.map +1 -1
  61. package/dist/planmagnet/pages/CompetitorsEditCompetitorPagePage.js +125 -83
  62. package/dist/planmagnet/pages/CompetitorsEditCompetitorPagePage.js.map +1 -1
  63. package/dist/planmagnet/pages/DashboardDashboardPagePage.js +23 -23
  64. package/dist/planmagnet/pages/DocsDocDetailPagePage.d.ts.map +1 -1
  65. package/dist/planmagnet/pages/DocsDocDetailPagePage.js +340 -248
  66. package/dist/planmagnet/pages/DocsDocDetailPagePage.js.map +1 -1
  67. package/dist/planmagnet/pages/IntegrationsSyncDetailPagePage.d.ts.map +1 -1
  68. package/dist/planmagnet/pages/IntegrationsSyncDetailPagePage.js +172 -167
  69. package/dist/planmagnet/pages/IntegrationsSyncDetailPagePage.js.map +1 -1
  70. package/dist/planmagnet/pages/IntegrationsSyncsPagePage.d.ts.map +1 -1
  71. package/dist/planmagnet/pages/IntegrationsSyncsPagePage.js +92 -83
  72. package/dist/planmagnet/pages/IntegrationsSyncsPagePage.js.map +1 -1
  73. package/dist/planmagnet/pages/IntegrationsWebhooksPagePage.d.ts +2 -0
  74. package/dist/planmagnet/pages/IntegrationsWebhooksPagePage.d.ts.map +1 -0
  75. package/dist/planmagnet/pages/IntegrationsWebhooksPagePage.js +175 -0
  76. package/dist/planmagnet/pages/IntegrationsWebhooksPagePage.js.map +1 -0
  77. package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.d.ts.map +1 -1
  78. package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.js +226 -126
  79. package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.js.map +1 -1
  80. package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.d.ts.map +1 -1
  81. package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js +151 -126
  82. package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js.map +1 -1
  83. package/dist/planmagnet/pages/ProductsFeaturesPagePage.d.ts.map +1 -1
  84. package/dist/planmagnet/pages/ProductsFeaturesPagePage.js +169 -122
  85. package/dist/planmagnet/pages/ProductsFeaturesPagePage.js.map +1 -1
  86. package/dist/planmagnet/pages/ProductsNewFeaturePagePage.d.ts.map +1 -1
  87. package/dist/planmagnet/pages/ProductsNewFeaturePagePage.js +165 -109
  88. package/dist/planmagnet/pages/ProductsNewFeaturePagePage.js.map +1 -1
  89. package/dist/planmagnet/pages/ProductsProductsPagePage.js +13 -13
  90. package/dist/planmagnet/pages/ProjectsAtRiskPagePage.js +9 -9
  91. package/dist/planmagnet/pages/ProjectsBudgetsPagePage.js +8 -8
  92. package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.d.ts.map +1 -1
  93. package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js +15 -3
  94. package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js.map +1 -1
  95. package/dist/planmagnet/pages/ProjectsProjectsPagePage.js +12 -12
  96. package/dist/planmagnet/pages/ReportsReportsPagePage.js +23 -23
  97. package/dist/planmagnet/pages/TasksMyDashboardPagePage.js +18 -18
  98. package/dist/planmagnet/pages/TasksMyWorkPagePage.js +10 -10
  99. package/dist/planmagnet/pages/TasksTimeEntriesPagePage.js +5 -5
  100. package/dist/planmagnet/pages/TeamTeamPagePage.d.ts.map +1 -1
  101. package/dist/planmagnet/pages/TeamTeamPagePage.js +59 -53
  102. package/dist/planmagnet/pages/TeamTeamPagePage.js.map +1 -1
  103. package/dist/planmagnet/pages/analytics/DeliveryHealthCards.js +8 -8
  104. package/dist/planmagnet/pages/analytics/DeliveryKpisCard.js +6 -6
  105. package/dist/planmagnet/pages/analytics/MetricsCard.d.ts +8 -0
  106. package/dist/planmagnet/pages/analytics/MetricsCard.d.ts.map +1 -0
  107. package/dist/planmagnet/pages/analytics/MetricsCard.js +164 -0
  108. package/dist/planmagnet/pages/analytics/MetricsCard.js.map +1 -0
  109. package/dist/planmagnet/pages/analytics/QualityMetricsCard.js +6 -6
  110. package/dist/planmagnet/pages/analytics/SprintDeliveryCards.js +8 -8
  111. package/dist/planmagnet/pages/analytics/StatusFlowCard.js +7 -7
  112. package/dist/planmagnet/pages/analytics/ThroughputCard.js +7 -7
  113. package/dist/planmagnet/pages/analytics/WorkBreakdownCard.js +7 -7
  114. package/dist/planmagnet/people/People.d.ts +14 -0
  115. package/dist/planmagnet/people/People.d.ts.map +1 -1
  116. package/dist/planmagnet/people/People.js +36 -22
  117. package/dist/planmagnet/people/People.js.map +1 -1
  118. package/dist/planmagnet/team/UnmatchedCrewIdentities.d.ts +2 -0
  119. package/dist/planmagnet/team/UnmatchedCrewIdentities.d.ts.map +1 -0
  120. package/dist/planmagnet/team/UnmatchedCrewIdentities.js +155 -0
  121. package/dist/planmagnet/team/UnmatchedCrewIdentities.js.map +1 -0
  122. package/dist/planmagnet/webhooks/webhookTypes.d.ts +54 -0
  123. package/dist/planmagnet/webhooks/webhookTypes.d.ts.map +1 -0
  124. package/dist/planmagnet/webhooks/webhookTypes.js +17 -0
  125. package/dist/planmagnet/webhooks/webhookTypes.js.map +1 -0
  126. package/package.json +1 -1
@@ -0,0 +1,542 @@
1
+ import { isForbidden as e, mutationErrorMessage as t } from "../utils/graphqlErrors.js";
2
+ import { throwOnError as n } from "../utils/mutationResult.js";
3
+ import { ConfirmButton as r, ErrorLine as i } from "./ConfirmButton.js";
4
+ import { TRIGGER_TYPES as a, triggerTypeLabel as o } from "../automation/automationRuleTypes.js";
5
+ import { TruncatedText as s } from "./SyncControls.js";
6
+ import { isPlausibleWebhookUrl as c, webhookEventOptionSuffix as l } from "../webhooks/webhookTypes.js";
7
+ import { useState as u } from "react";
8
+ import { Check as d, Copy as f, KeyRound as p, Loader2 as m, Pause as h, Play as g, Plus as _, Power as v, X as y } from "lucide-react";
9
+ import { gql as b } from "@apollo/client";
10
+ import { useMutation as x, useQuery as S } from "@apollo/client/react";
11
+ import { Fragment as C, jsx as w, jsxs as T } from "react/jsx-runtime";
12
+ import { Badge as E, Button as D, Checkbox as O, Input as k, ResponsiveDialog as A, ResponsiveDialogContent as j, ResponsiveDialogDescription as M, ResponsiveDialogFooter as N, ResponsiveDialogHeader as P, ResponsiveDialogTitle as F, ResponsiveDialogTrigger as I } from "@burdenoff/fe-libs/ui";
13
+ import { copyTextToClipboard as L } from "@burdenoff/fe-libs/sharing";
14
+ var R = { refetchQueries: ["WebhookSubscriptionsList"] }, ee = {
15
+ ACTIVE: "success",
16
+ PAUSED: "warning",
17
+ DISABLED: "outline"
18
+ };
19
+ function z({ status: e }) {
20
+ return /* @__PURE__ */ w(E, {
21
+ variant: ee[e] ?? "outline",
22
+ className: "text-xs",
23
+ "data-testid": "webhook-status-badge",
24
+ children: e === "ACTIVE" ? "Active" : e === "PAUSED" ? "Paused" : "Disabled"
25
+ });
26
+ }
27
+ var B = {
28
+ SUCCESS: "success",
29
+ FAILED: "destructive"
30
+ };
31
+ function V({ status: e }) {
32
+ return /* @__PURE__ */ w(E, {
33
+ variant: B[e] ?? "outline",
34
+ className: "text-xs",
35
+ children: e === "SUCCESS" ? "Success" : "Failed"
36
+ });
37
+ }
38
+ function H({ events: e }) {
39
+ return e.length === 0 ? /* @__PURE__ */ w("span", {
40
+ className: "text-xs text-muted-foreground",
41
+ children: "No events selected"
42
+ }) : /* @__PURE__ */ w("div", {
43
+ className: "flex flex-wrap gap-1",
44
+ "data-testid": "webhook-event-badges",
45
+ children: e.map((e) => /* @__PURE__ */ T(E, {
46
+ variant: "outline",
47
+ className: "text-xs",
48
+ children: [o(e), l(e)]
49
+ }, e))
50
+ });
51
+ }
52
+ function U({ idPrefix: e, selected: t, onChange: n, disabled: r = !1 }) {
53
+ let i = (e) => n(t.includes(e) ? t.filter((t) => t !== e) : [...t, e]);
54
+ return /* @__PURE__ */ w("div", {
55
+ role: "group",
56
+ "aria-label": "Events",
57
+ className: "grid grid-cols-1 gap-1.5 sm:grid-cols-2",
58
+ "data-testid": "webhook-events-checklist",
59
+ children: a.map((n) => /* @__PURE__ */ T("label", {
60
+ htmlFor: `${e}-event-${n}`,
61
+ className: "flex items-center gap-2 rounded-md border border-input px-2 py-1.5 text-sm",
62
+ children: [
63
+ /* @__PURE__ */ w(O, {
64
+ id: `${e}-event-${n}`,
65
+ "data-testid": `webhook-event-${n}`,
66
+ checked: t.includes(n),
67
+ onCheckedChange: () => i(n),
68
+ disabled: r
69
+ }),
70
+ o(n),
71
+ l(n) && /* @__PURE__ */ w("span", {
72
+ className: "text-xs text-muted-foreground",
73
+ children: l(n)
74
+ })
75
+ ]
76
+ }, n))
77
+ });
78
+ }
79
+ var W = b`
80
+ mutation WebhookSubscriptionCreate($input: PlanmagnetCreateWebhookSubscriptionInput!) {
81
+ planmagnetCreateWebhookSubscription(input: $input) {
82
+ secret
83
+ subscription {
84
+ id
85
+ url
86
+ events
87
+ status
88
+ consecutiveFailures
89
+ lastDeliveryAt
90
+ lastDeliveryStatus
91
+ createdAt
92
+ createdBy
93
+ updatedAt
94
+ updatedBy
95
+ disabledAt
96
+ }
97
+ }
98
+ }
99
+ `;
100
+ function G({ secret: e }) {
101
+ let [t, n] = u(!1), r = async () => {
102
+ try {
103
+ await L(e), n(!0);
104
+ } catch {
105
+ n(!1);
106
+ }
107
+ };
108
+ return /* @__PURE__ */ T("div", {
109
+ className: "space-y-2 rounded-md border border-primary bg-primary/5 p-3",
110
+ "data-testid": "webhook-secret-reveal",
111
+ children: [
112
+ /* @__PURE__ */ w("p", {
113
+ className: "text-sm font-medium",
114
+ children: "Signing secret — copy it now."
115
+ }),
116
+ /* @__PURE__ */ w("p", {
117
+ className: "text-xs text-muted-foreground",
118
+ children: "You will not see this again. If you lose it, rotate the secret to get a new one."
119
+ }),
120
+ /* @__PURE__ */ T("div", {
121
+ className: "flex items-center gap-2",
122
+ children: [/* @__PURE__ */ w(k, {
123
+ readOnly: !0,
124
+ value: e,
125
+ className: "font-mono text-xs",
126
+ "data-testid": "webhook-secret-value"
127
+ }), /* @__PURE__ */ T(D, {
128
+ size: "sm",
129
+ variant: "outline",
130
+ onClick: () => void r(),
131
+ "data-testid": "webhook-secret-copy",
132
+ children: [w(t ? d : f, { className: "h-4 w-4" }), t ? "Copied" : "Copy"]
133
+ })]
134
+ })
135
+ ]
136
+ });
137
+ }
138
+ function K() {
139
+ let [r, a] = u(!1), [o, s] = u(""), [l, d] = u([]), [f, p] = u(null), [m, h] = u(null), [g, v] = x(W, R), y = c(o), b = y && l.length > 0 && !v.loading, S = () => {
140
+ a(!1), s(""), d([]), h(null), p(null);
141
+ }, E = async () => {
142
+ if (b) {
143
+ p(null);
144
+ try {
145
+ let e = (await n(g({ variables: { input: {
146
+ url: o.trim(),
147
+ events: l
148
+ } } }))).data?.planmagnetCreateWebhookSubscription;
149
+ e && h(e);
150
+ } catch (n) {
151
+ p(e(n) ? "Your role can view webhook subscriptions but not create them." : t(n, "The subscription could not be created."));
152
+ }
153
+ }
154
+ };
155
+ return /* @__PURE__ */ T(A, {
156
+ open: r,
157
+ onOpenChange: (e) => e ? a(!0) : S(),
158
+ children: [/* @__PURE__ */ w(I, {
159
+ asChild: !0,
160
+ children: /* @__PURE__ */ T(D, {
161
+ size: "sm",
162
+ "data-testid": "webhook-create-open",
163
+ children: [/* @__PURE__ */ w(_, { className: "mr-1 h-4 w-4" }), "New subscription"]
164
+ })
165
+ }), /* @__PURE__ */ T(j, {
166
+ className: "max-h-[85vh] max-w-lg overflow-y-auto",
167
+ children: [
168
+ /* @__PURE__ */ T(P, { children: [/* @__PURE__ */ w(F, { children: "New webhook subscription" }), /* @__PURE__ */ w(M, { children: "PlanMagnet POSTs a signed event payload to this URL as the events below happen." })] }),
169
+ m ? /* @__PURE__ */ w(G, { secret: m.secret }) : /* @__PURE__ */ T("div", {
170
+ className: "space-y-3",
171
+ children: [
172
+ /* @__PURE__ */ T("div", { children: [
173
+ /* @__PURE__ */ w("label", {
174
+ htmlFor: "webhook-create-url",
175
+ className: "text-sm font-medium",
176
+ children: "URL"
177
+ }),
178
+ /* @__PURE__ */ w(k, {
179
+ id: "webhook-create-url",
180
+ "data-testid": "webhook-create-url",
181
+ className: "mt-1",
182
+ placeholder: "https://example.com/webhooks/planmagnet",
183
+ value: o,
184
+ "aria-invalid": o !== "" && !y,
185
+ onChange: (e) => s(e.target.value)
186
+ }),
187
+ o !== "" && !y && /* @__PURE__ */ w(i, { message: "Must be an absolute https:// URL." })
188
+ ] }),
189
+ /* @__PURE__ */ T("div", { children: [
190
+ /* @__PURE__ */ w("p", {
191
+ className: "text-sm font-medium",
192
+ children: "Events"
193
+ }),
194
+ /* @__PURE__ */ w("div", {
195
+ className: "mt-1",
196
+ children: /* @__PURE__ */ w(U, {
197
+ idPrefix: "webhook-create",
198
+ selected: l,
199
+ onChange: d
200
+ })
201
+ }),
202
+ l.length === 0 && /* @__PURE__ */ w("p", {
203
+ className: "mt-1 text-xs text-muted-foreground",
204
+ children: "Choose at least one event."
205
+ })
206
+ ] }),
207
+ f && /* @__PURE__ */ w(i, { message: f })
208
+ ]
209
+ }),
210
+ /* @__PURE__ */ w(N, { children: m ? /* @__PURE__ */ w(D, {
211
+ onClick: S,
212
+ "data-testid": "webhook-create-done",
213
+ children: "Done"
214
+ }) : /* @__PURE__ */ T(C, { children: [/* @__PURE__ */ w(D, {
215
+ variant: "outline",
216
+ onClick: S,
217
+ children: "Cancel"
218
+ }), /* @__PURE__ */ w(D, {
219
+ onClick: () => void E(),
220
+ disabled: !b,
221
+ "data-testid": "webhook-create-submit",
222
+ children: v.loading ? "Creating…" : "Create subscription"
223
+ })] }) })
224
+ ]
225
+ })]
226
+ });
227
+ }
228
+ var q = b`
229
+ mutation WebhookSubscriptionRotateSecret($id: ID!) {
230
+ planmagnetRotateWebhookSubscriptionSecret(id: $id) {
231
+ secret
232
+ subscription {
233
+ id
234
+ status
235
+ consecutiveFailures
236
+ updatedAt
237
+ }
238
+ }
239
+ }
240
+ `;
241
+ function J({ subscriptionId: r }) {
242
+ let [a, o] = u(!1), [s, c] = u(null), [l, d] = u(null), [f, m] = x(q, R), h = () => {
243
+ o(!1), d(null), c(null);
244
+ }, g = async () => {
245
+ c(null);
246
+ try {
247
+ let e = (await n(f({ variables: { id: r } }))).data?.planmagnetRotateWebhookSubscriptionSecret;
248
+ e && d(e);
249
+ } catch (n) {
250
+ c(e(n) ? "Your role can view webhook subscriptions but not rotate their secret." : t(n, "The secret could not be rotated."));
251
+ }
252
+ };
253
+ return /* @__PURE__ */ T(A, {
254
+ open: a,
255
+ onOpenChange: (e) => e ? o(!0) : h(),
256
+ children: [/* @__PURE__ */ w(I, {
257
+ asChild: !0,
258
+ children: /* @__PURE__ */ T(D, {
259
+ size: "sm",
260
+ variant: "outline",
261
+ "data-testid": "webhook-rotate-open",
262
+ children: [/* @__PURE__ */ w(p, { className: "mr-1 h-4 w-4" }), "Rotate secret"]
263
+ })
264
+ }), /* @__PURE__ */ T(j, {
265
+ className: "max-w-md",
266
+ children: [
267
+ /* @__PURE__ */ T(P, { children: [/* @__PURE__ */ w(F, { children: "Rotate signing secret?" }), /* @__PURE__ */ w(M, { children: "The current secret stops verifying deliveries immediately. Update the receiving endpoint with the new one before closing this dialog." })] }),
268
+ l ? /* @__PURE__ */ w(G, { secret: l.secret }) : /* @__PURE__ */ w("div", {
269
+ className: "space-y-2",
270
+ children: s && /* @__PURE__ */ w(i, { message: s })
271
+ }),
272
+ /* @__PURE__ */ w(N, { children: l ? /* @__PURE__ */ w(D, {
273
+ onClick: h,
274
+ "data-testid": "webhook-rotate-done",
275
+ children: "Done"
276
+ }) : /* @__PURE__ */ T(C, { children: [/* @__PURE__ */ w(D, {
277
+ variant: "outline",
278
+ onClick: h,
279
+ children: "Cancel"
280
+ }), /* @__PURE__ */ w(D, {
281
+ onClick: () => void g(),
282
+ disabled: m.loading,
283
+ "data-testid": "webhook-rotate-submit",
284
+ children: m.loading ? "Rotating…" : "Rotate secret"
285
+ })] }) })
286
+ ]
287
+ })]
288
+ });
289
+ }
290
+ var Y = b`
291
+ mutation WebhookSubscriptionUpdateStatus(
292
+ $id: ID!
293
+ $input: PlanmagnetUpdateWebhookSubscriptionInput!
294
+ ) {
295
+ planmagnetUpdateWebhookSubscription(id: $id, input: $input) {
296
+ id
297
+ status
298
+ consecutiveFailures
299
+ disabledAt
300
+ updatedAt
301
+ updatedBy
302
+ }
303
+ }
304
+ `;
305
+ function X({ subscription: r }) {
306
+ let [a, o] = x(Y, R), [s, c] = u(null), l = async (i) => {
307
+ c(null);
308
+ try {
309
+ await n(a({ variables: {
310
+ id: r.id,
311
+ input: { status: i }
312
+ } }));
313
+ } catch (n) {
314
+ c(e(n) ? "Your role can view webhook subscriptions but not change them." : t(n, "The status could not be changed."));
315
+ }
316
+ };
317
+ return /* @__PURE__ */ T("div", {
318
+ className: "flex flex-col items-start gap-1",
319
+ children: [/* @__PURE__ */ T("div", {
320
+ className: "flex flex-wrap gap-2",
321
+ children: [
322
+ r.status === "ACTIVE" && /* @__PURE__ */ T(D, {
323
+ size: "sm",
324
+ variant: "outline",
325
+ disabled: o.loading,
326
+ onClick: () => void l("PAUSED"),
327
+ "data-testid": "webhook-pause",
328
+ children: [/* @__PURE__ */ w(h, { className: "mr-1 h-4 w-4" }), "Pause"]
329
+ }),
330
+ r.status === "PAUSED" && /* @__PURE__ */ T(D, {
331
+ size: "sm",
332
+ variant: "outline",
333
+ disabled: o.loading,
334
+ onClick: () => void l("ACTIVE"),
335
+ "data-testid": "webhook-resume",
336
+ children: [/* @__PURE__ */ w(g, { className: "mr-1 h-4 w-4" }), "Resume"]
337
+ }),
338
+ r.status === "DISABLED" ? /* @__PURE__ */ T(D, {
339
+ size: "sm",
340
+ variant: "outline",
341
+ disabled: o.loading,
342
+ onClick: () => void l("ACTIVE"),
343
+ "data-testid": "webhook-reenable",
344
+ children: [/* @__PURE__ */ w(v, { className: "mr-1 h-4 w-4" }), "Re-enable"]
345
+ }) : /* @__PURE__ */ T(D, {
346
+ size: "sm",
347
+ variant: "outline",
348
+ disabled: o.loading,
349
+ onClick: () => void l("DISABLED"),
350
+ "data-testid": "webhook-disable",
351
+ children: [/* @__PURE__ */ w(v, { className: "mr-1 h-4 w-4" }), "Disable"]
352
+ }),
353
+ o.loading && /* @__PURE__ */ w(m, { className: "h-4 w-4 animate-spin text-muted-foreground" })
354
+ ]
355
+ }), s && /* @__PURE__ */ w(i, { message: s })]
356
+ });
357
+ }
358
+ var Z = b`
359
+ mutation WebhookSubscriptionDelete($id: ID!) {
360
+ planmagnetDeleteWebhookSubscription(id: $id)
361
+ }
362
+ `;
363
+ function Q({ subscriptionId: a }) {
364
+ let [o, s] = x(Z, R), [c, l] = u(null), d = async () => {
365
+ l(null);
366
+ try {
367
+ await n(o({ variables: { id: a } }));
368
+ } catch (n) {
369
+ l(e(n) ? "Your role can view webhook subscriptions but not delete them." : t(n, "The subscription could not be deleted."));
370
+ }
371
+ };
372
+ return /* @__PURE__ */ T("div", {
373
+ className: "flex flex-col items-end gap-1",
374
+ children: [/* @__PURE__ */ w(r, {
375
+ label: "Delete",
376
+ variant: "outline",
377
+ icon: /* @__PURE__ */ w(y, { className: "mr-1 h-4 w-4" }),
378
+ title: "Delete this webhook subscription?",
379
+ description: "Deliveries stop immediately. Its delivery log is removed with it. This cannot be undone.",
380
+ confirmLabel: "Delete subscription",
381
+ destructive: !0,
382
+ disabled: s.loading,
383
+ onConfirm: () => void d(),
384
+ testId: "webhook-delete"
385
+ }), c && /* @__PURE__ */ w(i, { message: c })]
386
+ });
387
+ }
388
+ var te = b`
389
+ query WebhookSubscriptionDeliveries(
390
+ $subscriptionId: ID!
391
+ $pagination: PlanmagnetPaginationInput
392
+ ) {
393
+ planmagnetWebhookSubscriptionDeliveries(
394
+ subscriptionId: $subscriptionId
395
+ pagination: $pagination
396
+ ) {
397
+ totalCount
398
+ pageInfo {
399
+ hasNextPage
400
+ endCursor
401
+ }
402
+ items {
403
+ id
404
+ event
405
+ status
406
+ responseStatus
407
+ attempts
408
+ error
409
+ deliveredAt
410
+ }
411
+ }
412
+ }
413
+ `, $ = 20;
414
+ function ne({ subscriptionId: e }) {
415
+ let [t, n] = u(!1), { data: r, loading: i, error: a, fetchMore: c } = S(te, { variables: {
416
+ subscriptionId: e,
417
+ pagination: { first: $ }
418
+ } }), l = r?.planmagnetWebhookSubscriptionDeliveries, d = l?.items ?? [], f = async () => {
419
+ let e = l?.pageInfo.endCursor;
420
+ if (e) {
421
+ n(!0);
422
+ try {
423
+ await c({
424
+ variables: { pagination: {
425
+ first: $,
426
+ after: e
427
+ } },
428
+ updateQuery: (e, { fetchMoreResult: t }) => ({ planmagnetWebhookSubscriptionDeliveries: {
429
+ ...t.planmagnetWebhookSubscriptionDeliveries,
430
+ items: [...e.planmagnetWebhookSubscriptionDeliveries.items, ...t.planmagnetWebhookSubscriptionDeliveries.items]
431
+ } })
432
+ });
433
+ } finally {
434
+ n(!1);
435
+ }
436
+ }
437
+ };
438
+ return /* @__PURE__ */ T("div", {
439
+ className: "space-y-2",
440
+ "data-testid": "webhook-deliveries",
441
+ children: [
442
+ a && /* @__PURE__ */ T("p", {
443
+ role: "alert",
444
+ className: "text-sm text-destructive",
445
+ children: ["Deliveries could not be loaded: ", a.message]
446
+ }),
447
+ i && !r && /* @__PURE__ */ w("p", {
448
+ className: "text-sm text-muted-foreground",
449
+ children: "Loading deliveries…"
450
+ }),
451
+ l && d.length === 0 && /* @__PURE__ */ w("p", {
452
+ className: "py-4 text-center text-sm text-muted-foreground",
453
+ children: "No deliveries yet."
454
+ }),
455
+ d.length > 0 && /* @__PURE__ */ w("div", {
456
+ className: "overflow-x-auto",
457
+ children: /* @__PURE__ */ T("table", {
458
+ className: "w-full text-sm",
459
+ children: [/* @__PURE__ */ w("thead", { children: /* @__PURE__ */ T("tr", {
460
+ className: "text-left text-muted-foreground",
461
+ children: [
462
+ /* @__PURE__ */ w("th", {
463
+ className: "py-1 pr-3 font-medium",
464
+ children: "Time"
465
+ }),
466
+ /* @__PURE__ */ w("th", {
467
+ className: "py-1 pr-3 font-medium",
468
+ children: "Event"
469
+ }),
470
+ /* @__PURE__ */ w("th", {
471
+ className: "py-1 pr-3 font-medium",
472
+ children: "Status"
473
+ }),
474
+ /* @__PURE__ */ w("th", {
475
+ className: "py-1 pr-3 font-medium",
476
+ children: "Response"
477
+ }),
478
+ /* @__PURE__ */ w("th", {
479
+ className: "py-1 pr-3 font-medium",
480
+ children: "Attempts"
481
+ }),
482
+ /* @__PURE__ */ w("th", {
483
+ className: "py-1 font-medium",
484
+ children: "Error"
485
+ })
486
+ ]
487
+ }) }), /* @__PURE__ */ w("tbody", { children: d.map((e) => /* @__PURE__ */ T("tr", {
488
+ className: "border-t align-top",
489
+ "data-testid": "webhook-delivery-row",
490
+ children: [
491
+ /* @__PURE__ */ w("td", {
492
+ className: "py-1.5 pr-3 whitespace-nowrap",
493
+ children: new Date(e.deliveredAt).toLocaleString()
494
+ }),
495
+ /* @__PURE__ */ w("td", {
496
+ className: "py-1.5 pr-3 whitespace-nowrap",
497
+ children: o(e.event)
498
+ }),
499
+ /* @__PURE__ */ w("td", {
500
+ className: "py-1.5 pr-3",
501
+ children: /* @__PURE__ */ w(V, { status: e.status })
502
+ }),
503
+ /* @__PURE__ */ w("td", {
504
+ className: "py-1.5 pr-3 whitespace-nowrap",
505
+ children: e.responseStatus ?? /* @__PURE__ */ w("span", {
506
+ className: "text-muted-foreground",
507
+ children: "—"
508
+ })
509
+ }),
510
+ /* @__PURE__ */ w("td", {
511
+ className: "py-1.5 pr-3",
512
+ children: e.attempts
513
+ }),
514
+ /* @__PURE__ */ w("td", {
515
+ className: "py-1.5 text-status-error-text",
516
+ children: e.error ? /* @__PURE__ */ w(s, {
517
+ text: e.error,
518
+ max: 120,
519
+ className: "text-status-error-text"
520
+ }) : /* @__PURE__ */ w("span", {
521
+ className: "text-muted-foreground",
522
+ children: "—"
523
+ })
524
+ })
525
+ ]
526
+ }, e.id)) })]
527
+ })
528
+ }),
529
+ l?.pageInfo.hasNextPage && /* @__PURE__ */ w(D, {
530
+ size: "sm",
531
+ variant: "outline",
532
+ onClick: () => void f(),
533
+ disabled: t,
534
+ children: t ? "Loading…" : `Show more (${d.length} of ${l.totalCount})`
535
+ })
536
+ ]
537
+ });
538
+ }
539
+ //#endregion
540
+ export { K as CreateWebhookSubscriptionDialog, Q as DeleteWebhookSubscriptionButton, J as RotateWebhookSecretButton, ne as WebhookDeliveryLog, V as WebhookDeliveryStatusBadge, H as WebhookEventBadges, X as WebhookStatusActions, z as WebhookStatusBadge };
541
+
542
+ //# sourceMappingURL=WebhookSubscriptionControls.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebhookSubscriptionControls.js","names":[],"sources":["../../../src/planmagnet/controls/WebhookSubscriptionControls.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport {\n Badge,\n Button,\n Checkbox,\n Input,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogTrigger,\n} from '@burdenoff/fe-libs/ui';\nimport { copyTextToClipboard } from '@burdenoff/fe-libs/sharing';\nimport { Check, Copy, KeyRound, Loader2, Pause, Play, Plus, Power, X } from 'lucide-react';\nimport { throwOnError } from '../utils/mutationResult';\nimport { isForbidden, mutationErrorMessage } from '../utils/graphqlErrors';\nimport { ConfirmButton, ErrorLine } from './ConfirmButton';\nimport { TruncatedText } from './SyncControls';\nimport { TRIGGER_TYPES, triggerTypeLabel } from '../automation/automationRuleTypes';\nimport {\n isPlausibleWebhookUrl,\n webhookEventOptionSuffix,\n type WebhookDeliveryStatus,\n type WebhookSubscriptionRow,\n type WebhookSubscriptionStatus,\n} from '../webhooks/webhookTypes';\n\n/**\n * Outbound webhook subscriptions (AUT-33) — create/rotate/status/delete controls, shared by\n * `pages/IntegrationsWebhooksPagePage.tsx`. See `webhooks/webhookTypes.ts` for the backing shapes and the real\n * SDL doc comments they are grounded in.\n */\n\nexport const WEBHOOK_LIST_QUERIES = ['WebhookSubscriptionsList'];\nconst options = { refetchQueries: WEBHOOK_LIST_QUERIES };\n\nconst STATUS_VARIANT: Record<WebhookSubscriptionStatus, 'success' | 'warning' | 'outline'> = {\n ACTIVE: 'success',\n PAUSED: 'warning',\n DISABLED: 'outline',\n};\n\nexport function WebhookStatusBadge({ status }: { status: string }) {\n return (\n <Badge\n variant={STATUS_VARIANT[status as WebhookSubscriptionStatus] ?? 'outline'}\n className=\"text-xs\"\n data-testid=\"webhook-status-badge\"\n >\n {status === 'ACTIVE' ? 'Active' : status === 'PAUSED' ? 'Paused' : 'Disabled'}\n </Badge>\n );\n}\n\nconst DELIVERY_STATUS_VARIANT: Record<WebhookDeliveryStatus, 'success' | 'destructive'> = {\n SUCCESS: 'success',\n FAILED: 'destructive',\n};\n\nexport function WebhookDeliveryStatusBadge({ status }: { status: string }) {\n return (\n <Badge\n variant={DELIVERY_STATUS_VARIANT[status as WebhookDeliveryStatus] ?? 'outline'}\n className=\"text-xs\"\n >\n {status === 'SUCCESS' ? 'Success' : 'Failed'}\n </Badge>\n );\n}\n\n/** Which `PlanmagnetAutomationTriggerType` values this subscription is delivered for, as small badges. */\nexport function WebhookEventBadges({ events }: { events: string[] }) {\n if (events.length === 0) {\n return <span className=\"text-xs text-muted-foreground\">No events selected</span>;\n }\n return (\n <div className=\"flex flex-wrap gap-1\" data-testid=\"webhook-event-badges\">\n {events.map((event) => (\n <Badge key={event} variant=\"outline\" className=\"text-xs\">\n {triggerTypeLabel(event)}\n {webhookEventOptionSuffix(event)}\n </Badge>\n ))}\n </div>\n );\n}\n\n/** The events checklist shared by the create dialog — every `PlanmagnetAutomationTriggerType` value, with\n * `WEBHOOK` visibly marked \"not live yet\" rather than hidden (a subscription can be prepared for it ahead of\n * BOFF-8238 shipping, same precedent the automation rule builder's own trigger select follows). */\nfunction EventsChecklist({\n idPrefix,\n selected,\n onChange,\n disabled = false,\n}: {\n idPrefix: string;\n selected: string[];\n onChange: (next: string[]) => void;\n disabled?: boolean;\n}) {\n const toggle = (event: string) =>\n onChange(selected.includes(event) ? selected.filter((e) => e !== event) : [...selected, event]);\n return (\n <div\n role=\"group\"\n aria-label=\"Events\"\n className=\"grid grid-cols-1 gap-1.5 sm:grid-cols-2\"\n data-testid=\"webhook-events-checklist\"\n >\n {TRIGGER_TYPES.map((event) => (\n <label\n key={event}\n htmlFor={`${idPrefix}-event-${event}`}\n className=\"flex items-center gap-2 rounded-md border border-input px-2 py-1.5 text-sm\"\n >\n <Checkbox\n id={`${idPrefix}-event-${event}`}\n data-testid={`webhook-event-${event}`}\n checked={selected.includes(event)}\n onCheckedChange={() => toggle(event)}\n disabled={disabled}\n />\n {triggerTypeLabel(event)}\n {webhookEventOptionSuffix(event) && (\n <span className=\"text-xs text-muted-foreground\">{webhookEventOptionSuffix(event)}</span>\n )}\n </label>\n ))}\n </div>\n );\n}\n\nconst CREATE_WEBHOOK_SUBSCRIPTION = gql`\n mutation WebhookSubscriptionCreate($input: PlanmagnetCreateWebhookSubscriptionInput!) {\n planmagnetCreateWebhookSubscription(input: $input) {\n secret\n subscription {\n id\n url\n events\n status\n consecutiveFailures\n lastDeliveryAt\n lastDeliveryStatus\n createdAt\n createdBy\n updatedAt\n updatedBy\n disabledAt\n }\n }\n }\n`;\n\ninterface SubscriptionWithSecret {\n secret: string;\n subscription: WebhookSubscriptionRow;\n}\n\n/** The secret-shown-once panel, identical in the create dialog and the rotate dialog below: copy it now, it\n * cannot be read back from the API again. */\nfunction SecretRevealPanel({ secret }: { secret: string }) {\n const [copied, setCopied] = useState(false);\n const copy = async () => {\n try {\n await copyTextToClipboard(secret);\n setCopied(true);\n } catch {\n setCopied(false);\n }\n };\n return (\n <div\n className=\"space-y-2 rounded-md border border-primary bg-primary/5 p-3\"\n data-testid=\"webhook-secret-reveal\"\n >\n <p className=\"text-sm font-medium\">Signing secret — copy it now.</p>\n <p className=\"text-xs text-muted-foreground\">\n You will not see this again. If you lose it, rotate the secret to get a new one.\n </p>\n <div className=\"flex items-center gap-2\">\n <Input\n readOnly\n value={secret}\n className=\"font-mono text-xs\"\n data-testid=\"webhook-secret-value\"\n />\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => void copy()}\n data-testid=\"webhook-secret-copy\"\n >\n {copied ? <Check className=\"h-4 w-4\" /> : <Copy className=\"h-4 w-4\" />}\n {copied ? 'Copied' : 'Copy'}\n </Button>\n </div>\n </div>\n );\n}\n\n/**\n * \"New subscription\": https:// URL + events checklist, then the signing secret shown exactly once. Every way of\n * closing (the footer button, the X, Escape, an outside click) goes through one `close()` — see\n * `reports/ShareDialog.tsx`'s own fix (#45) for why setting `open` directly on the controlled dialog is not\n * enough: the footer button used to skip the reset there, and the just-created secret reappeared on reopen.\n */\nexport function CreateWebhookSubscriptionDialog() {\n const [open, setOpen] = useState(false);\n const [url, setUrl] = useState('');\n const [events, setEvents] = useState<string[]>([]);\n const [error, setError] = useState<string | null>(null);\n const [justCreated, setJustCreated] = useState<SubscriptionWithSecret | null>(null);\n const [create, state] = useMutation<{\n planmagnetCreateWebhookSubscription: SubscriptionWithSecret;\n }>(CREATE_WEBHOOK_SUBSCRIPTION, options);\n\n const urlValid = isPlausibleWebhookUrl(url);\n const canSave = urlValid && events.length > 0 && !state.loading;\n\n const close = () => {\n setOpen(false);\n setUrl('');\n setEvents([]);\n setJustCreated(null);\n setError(null);\n };\n\n const submit = async () => {\n if (!canSave) return;\n setError(null);\n try {\n const result = await throwOnError(\n create({ variables: { input: { url: url.trim(), events } } })\n );\n const created = result.data?.planmagnetCreateWebhookSubscription;\n if (created) setJustCreated(created);\n } catch (e) {\n setError(\n isForbidden(e)\n ? 'Your role can view webhook subscriptions but not create them.'\n : mutationErrorMessage(e, 'The subscription could not be created.')\n );\n }\n };\n\n return (\n <ResponsiveDialog open={open} onOpenChange={(next) => (next ? setOpen(true) : close())}>\n <ResponsiveDialogTrigger asChild>\n <Button size=\"sm\" data-testid=\"webhook-create-open\">\n <Plus className=\"mr-1 h-4 w-4\" />\n New subscription\n </Button>\n </ResponsiveDialogTrigger>\n <ResponsiveDialogContent className=\"max-h-[85vh] max-w-lg overflow-y-auto\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>New webhook subscription</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n PlanMagnet POSTs a signed event payload to this URL as the events below happen.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n {!justCreated ? (\n <div className=\"space-y-3\">\n <div>\n <label htmlFor=\"webhook-create-url\" className=\"text-sm font-medium\">\n URL\n </label>\n <Input\n id=\"webhook-create-url\"\n data-testid=\"webhook-create-url\"\n className=\"mt-1\"\n placeholder=\"https://example.com/webhooks/planmagnet\"\n value={url}\n aria-invalid={url !== '' && !urlValid}\n onChange={(e) => setUrl(e.target.value)}\n />\n {url !== '' && !urlValid && <ErrorLine message=\"Must be an absolute https:// URL.\" />}\n </div>\n <div>\n <p className=\"text-sm font-medium\">Events</p>\n <div className=\"mt-1\">\n <EventsChecklist idPrefix=\"webhook-create\" selected={events} onChange={setEvents} />\n </div>\n {events.length === 0 && (\n <p className=\"mt-1 text-xs text-muted-foreground\">Choose at least one event.</p>\n )}\n </div>\n {error && <ErrorLine message={error} />}\n </div>\n ) : (\n <SecretRevealPanel secret={justCreated.secret} />\n )}\n\n <ResponsiveDialogFooter>\n {!justCreated ? (\n <>\n <Button variant=\"outline\" onClick={close}>\n Cancel\n </Button>\n <Button\n onClick={() => void submit()}\n disabled={!canSave}\n data-testid=\"webhook-create-submit\"\n >\n {state.loading ? 'Creating…' : 'Create subscription'}\n </Button>\n </>\n ) : (\n <Button onClick={close} data-testid=\"webhook-create-done\">\n Done\n </Button>\n )}\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n\nconst ROTATE_WEBHOOK_SUBSCRIPTION_SECRET = gql`\n mutation WebhookSubscriptionRotateSecret($id: ID!) {\n planmagnetRotateWebhookSubscriptionSecret(id: $id) {\n secret\n subscription {\n id\n status\n consecutiveFailures\n updatedAt\n }\n }\n }\n`;\n\n/**\n * \"Rotate secret\": the old secret stops verifying deliveries immediately, the new one is shown exactly once.\n * Same one-`close()`-for-every-exit discipline as the create dialog above.\n */\nexport function RotateWebhookSecretButton({ subscriptionId }: { subscriptionId: string }) {\n const [open, setOpen] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [justRotated, setJustRotated] = useState<SubscriptionWithSecret | null>(null);\n const [rotate, state] = useMutation<{\n planmagnetRotateWebhookSubscriptionSecret: SubscriptionWithSecret;\n }>(ROTATE_WEBHOOK_SUBSCRIPTION_SECRET, options);\n\n const close = () => {\n setOpen(false);\n setJustRotated(null);\n setError(null);\n };\n\n const submit = async () => {\n setError(null);\n try {\n const result = await throwOnError(rotate({ variables: { id: subscriptionId } }));\n const rotated = result.data?.planmagnetRotateWebhookSubscriptionSecret;\n if (rotated) setJustRotated(rotated);\n } catch (e) {\n setError(\n isForbidden(e)\n ? 'Your role can view webhook subscriptions but not rotate their secret.'\n : mutationErrorMessage(e, 'The secret could not be rotated.')\n );\n }\n };\n\n return (\n <ResponsiveDialog open={open} onOpenChange={(next) => (next ? setOpen(true) : close())}>\n <ResponsiveDialogTrigger asChild>\n <Button size=\"sm\" variant=\"outline\" data-testid=\"webhook-rotate-open\">\n <KeyRound className=\"mr-1 h-4 w-4\" />\n Rotate secret\n </Button>\n </ResponsiveDialogTrigger>\n <ResponsiveDialogContent className=\"max-w-md\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Rotate signing secret?</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n The current secret stops verifying deliveries immediately. Update the receiving endpoint\n with the new one before closing this dialog.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n {!justRotated ? (\n <div className=\"space-y-2\">{error && <ErrorLine message={error} />}</div>\n ) : (\n <SecretRevealPanel secret={justRotated.secret} />\n )}\n <ResponsiveDialogFooter>\n {!justRotated ? (\n <>\n <Button variant=\"outline\" onClick={close}>\n Cancel\n </Button>\n <Button\n onClick={() => void submit()}\n disabled={state.loading}\n data-testid=\"webhook-rotate-submit\"\n >\n {state.loading ? 'Rotating…' : 'Rotate secret'}\n </Button>\n </>\n ) : (\n <Button onClick={close} data-testid=\"webhook-rotate-done\">\n Done\n </Button>\n )}\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n\nconst UPDATE_WEBHOOK_SUBSCRIPTION_STATUS = gql`\n mutation WebhookSubscriptionUpdateStatus(\n $id: ID!\n $input: PlanmagnetUpdateWebhookSubscriptionInput!\n ) {\n planmagnetUpdateWebhookSubscription(id: $id, input: $input) {\n id\n status\n consecutiveFailures\n disabledAt\n updatedAt\n updatedBy\n }\n }\n`;\n\n/** Pause/Resume/Disable/Re-enable — every transition `PlanmagnetWebhookSubscriptionStatus` allows, via the one\n * update mutation (its own doc comment: \"PAUSED/ACTIVE are the owner's own choice; setting DISABLED by hand is\n * also accepted\"). Re-activating (PAUSED or DISABLED -> ACTIVE) resets `consecutiveFailures` server-side. */\nexport function WebhookStatusActions({ subscription }: { subscription: WebhookSubscriptionRow }) {\n const [update, state] = useMutation(UPDATE_WEBHOOK_SUBSCRIPTION_STATUS, options);\n const [error, setError] = useState<string | null>(null);\n\n const setStatus = async (status: WebhookSubscriptionStatus) => {\n setError(null);\n try {\n await throwOnError(update({ variables: { id: subscription.id, input: { status } } }));\n } catch (e) {\n setError(\n isForbidden(e)\n ? 'Your role can view webhook subscriptions but not change them.'\n : mutationErrorMessage(e, 'The status could not be changed.')\n );\n }\n };\n\n return (\n <div className=\"flex flex-col items-start gap-1\">\n <div className=\"flex flex-wrap gap-2\">\n {subscription.status === 'ACTIVE' && (\n <Button\n size=\"sm\"\n variant=\"outline\"\n disabled={state.loading}\n onClick={() => void setStatus('PAUSED')}\n data-testid=\"webhook-pause\"\n >\n <Pause className=\"mr-1 h-4 w-4\" />\n Pause\n </Button>\n )}\n {subscription.status === 'PAUSED' && (\n <Button\n size=\"sm\"\n variant=\"outline\"\n disabled={state.loading}\n onClick={() => void setStatus('ACTIVE')}\n data-testid=\"webhook-resume\"\n >\n <Play className=\"mr-1 h-4 w-4\" />\n Resume\n </Button>\n )}\n {subscription.status === 'DISABLED' ? (\n <Button\n size=\"sm\"\n variant=\"outline\"\n disabled={state.loading}\n onClick={() => void setStatus('ACTIVE')}\n data-testid=\"webhook-reenable\"\n >\n <Power className=\"mr-1 h-4 w-4\" />\n Re-enable\n </Button>\n ) : (\n <Button\n size=\"sm\"\n variant=\"outline\"\n disabled={state.loading}\n onClick={() => void setStatus('DISABLED')}\n data-testid=\"webhook-disable\"\n >\n <Power className=\"mr-1 h-4 w-4\" />\n Disable\n </Button>\n )}\n {state.loading && <Loader2 className=\"h-4 w-4 animate-spin text-muted-foreground\" />}\n </div>\n {error && <ErrorLine message={error} />}\n </div>\n );\n}\n\nconst DELETE_WEBHOOK_SUBSCRIPTION = gql`\n mutation WebhookSubscriptionDelete($id: ID!) {\n planmagnetDeleteWebhookSubscription(id: $id)\n }\n`;\n\nexport function DeleteWebhookSubscriptionButton({ subscriptionId }: { subscriptionId: string }) {\n const [remove, state] = useMutation(DELETE_WEBHOOK_SUBSCRIPTION, options);\n const [error, setError] = useState<string | null>(null);\n\n const onDelete = async () => {\n setError(null);\n try {\n await throwOnError(remove({ variables: { id: subscriptionId } }));\n } catch (e) {\n setError(\n isForbidden(e)\n ? 'Your role can view webhook subscriptions but not delete them.'\n : mutationErrorMessage(e, 'The subscription could not be deleted.')\n );\n }\n };\n\n return (\n <div className=\"flex flex-col items-end gap-1\">\n <ConfirmButton\n label=\"Delete\"\n variant=\"outline\"\n icon={<X className=\"mr-1 h-4 w-4\" />}\n title=\"Delete this webhook subscription?\"\n description=\"Deliveries stop immediately. Its delivery log is removed with it. This cannot be undone.\"\n confirmLabel=\"Delete subscription\"\n destructive\n disabled={state.loading}\n onConfirm={() => void onDelete()}\n testId=\"webhook-delete\"\n />\n {error && <ErrorLine message={error} />}\n </div>\n );\n}\n\nconst WEBHOOK_SUBSCRIPTION_DELIVERIES = gql`\n query WebhookSubscriptionDeliveries(\n $subscriptionId: ID!\n $pagination: PlanmagnetPaginationInput\n ) {\n planmagnetWebhookSubscriptionDeliveries(\n subscriptionId: $subscriptionId\n pagination: $pagination\n ) {\n totalCount\n pageInfo {\n hasNextPage\n endCursor\n }\n items {\n id\n event\n status\n responseStatus\n attempts\n error\n deliveredAt\n }\n }\n }\n`;\n\nconst DELIVERIES_PAGE = 20;\n\n/** The delivery log for one subscription (AUT-33): event, status, response status, attempts, error, time —\n * newest first, loaded only once expanded. */\nexport function WebhookDeliveryLog({ subscriptionId }: { subscriptionId: string }) {\n const [loadingMore, setLoadingMore] = useState(false);\n const { data, loading, error, fetchMore } = useQuery<{\n planmagnetWebhookSubscriptionDeliveries: {\n totalCount: number;\n pageInfo: { hasNextPage: boolean; endCursor: string | null };\n items: {\n id: string;\n event: string;\n status: string;\n responseStatus: number | null;\n attempts: number;\n error: string | null;\n deliveredAt: string;\n }[];\n };\n }>(WEBHOOK_SUBSCRIPTION_DELIVERIES, {\n variables: { subscriptionId, pagination: { first: DELIVERIES_PAGE } },\n });\n\n const list = data?.planmagnetWebhookSubscriptionDeliveries;\n const deliveries = list?.items ?? [];\n\n const loadMore = async () => {\n const after = list?.pageInfo.endCursor;\n if (!after) return;\n setLoadingMore(true);\n try {\n await fetchMore({\n variables: { pagination: { first: DELIVERIES_PAGE, after } },\n updateQuery: (prev, { fetchMoreResult }) => ({\n planmagnetWebhookSubscriptionDeliveries: {\n ...fetchMoreResult.planmagnetWebhookSubscriptionDeliveries,\n items: [\n ...prev.planmagnetWebhookSubscriptionDeliveries.items,\n ...fetchMoreResult.planmagnetWebhookSubscriptionDeliveries.items,\n ],\n },\n }),\n });\n } finally {\n setLoadingMore(false);\n }\n };\n\n return (\n <div className=\"space-y-2\" data-testid=\"webhook-deliveries\">\n {error && (\n <p role=\"alert\" className=\"text-sm text-destructive\">\n Deliveries could not be loaded: {error.message}\n </p>\n )}\n {loading && !data && <p className=\"text-sm text-muted-foreground\">Loading deliveries…</p>}\n {list && deliveries.length === 0 && (\n <p className=\"py-4 text-center text-sm text-muted-foreground\">No deliveries yet.</p>\n )}\n {deliveries.length > 0 && (\n <div className=\"overflow-x-auto\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"text-left text-muted-foreground\">\n <th className=\"py-1 pr-3 font-medium\">Time</th>\n <th className=\"py-1 pr-3 font-medium\">Event</th>\n <th className=\"py-1 pr-3 font-medium\">Status</th>\n <th className=\"py-1 pr-3 font-medium\">Response</th>\n <th className=\"py-1 pr-3 font-medium\">Attempts</th>\n <th className=\"py-1 font-medium\">Error</th>\n </tr>\n </thead>\n <tbody>\n {deliveries.map((delivery) => (\n <tr\n key={delivery.id}\n className=\"border-t align-top\"\n data-testid=\"webhook-delivery-row\"\n >\n <td className=\"py-1.5 pr-3 whitespace-nowrap\">\n {new Date(delivery.deliveredAt).toLocaleString()}\n </td>\n <td className=\"py-1.5 pr-3 whitespace-nowrap\">\n {triggerTypeLabel(delivery.event)}\n </td>\n <td className=\"py-1.5 pr-3\">\n <WebhookDeliveryStatusBadge status={delivery.status} />\n </td>\n <td className=\"py-1.5 pr-3 whitespace-nowrap\">\n {delivery.responseStatus ?? <span className=\"text-muted-foreground\">—</span>}\n </td>\n <td className=\"py-1.5 pr-3\">{delivery.attempts}</td>\n <td className=\"py-1.5 text-status-error-text\">\n {delivery.error ? (\n <TruncatedText\n text={delivery.error}\n max={120}\n className=\"text-status-error-text\"\n />\n ) : (\n <span className=\"text-muted-foreground\">—</span>\n )}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )}\n {list?.pageInfo.hasNextPage && (\n <Button size=\"sm\" variant=\"outline\" onClick={() => void loadMore()} disabled={loadingMore}>\n {loadingMore ? 'Loading…' : `Show more (${deliveries.length} of ${list.totalCount})`}\n </Button>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  import { isForbidden as e } from "../utils/graphqlErrors.js";
2
2
  import { ArrowRight as t } from "lucide-react";
3
3
  import { jsx as n, jsxs as r } from "react/jsx-runtime";
4
- import { useI18n as i } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
5
- import { Card as a, CardContent as o, CardHeader as s, CardTitle as c } from "@burdenoff/fe-libs/ui";
4
+ import { Card as i, CardContent as a, CardHeader as o, CardTitle as s } from "@burdenoff/fe-libs/ui";
5
+ import { useI18n as c } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
6
6
  //#region src/planmagnet/dashboards/DashboardUI.tsx
7
7
  function l({ label: e, onClick: i }) {
8
8
  return /* @__PURE__ */ r("button", {
@@ -16,7 +16,7 @@ function l({ label: e, onClick: i }) {
16
16
  });
17
17
  }
18
18
  function u({ icon: t, title: u, definition: d, loading: f, error: p, empty: m, emptyMessage: h, drillDown: g, children: _ }) {
19
- let { t: v } = i(), y = e(p), b = y ? "dashboard-card-forbidden" : "dashboard-card", x;
19
+ let { t: v } = c(), y = e(p), b = y ? "dashboard-card-forbidden" : "dashboard-card", x;
20
20
  return x = p ? /* @__PURE__ */ n("p", {
21
21
  className: "text-sm text-muted-foreground",
22
22
  children: y ? v("planmagnet.dashboards.forbidden", "You do not have access to {{title}}.", { title: u }) : v("planmagnet.dashboards.error", "Could not be loaded: {{message}}", { message: p instanceof Error ? p.message : "unknown error" })
@@ -26,18 +26,18 @@ function u({ icon: t, title: u, definition: d, loading: f, error: p, empty: m, e
26
26
  }) : m ? /* @__PURE__ */ n("p", {
27
27
  className: "text-sm text-muted-foreground",
28
28
  children: h ?? v("planmagnet.dashboards.empty", "Nothing to show yet.")
29
- }) : _, /* @__PURE__ */ r(a, {
29
+ }) : _, /* @__PURE__ */ r(i, {
30
30
  "data-testid": b,
31
- children: [/* @__PURE__ */ r(s, {
31
+ children: [/* @__PURE__ */ r(o, {
32
32
  className: "space-y-1",
33
- children: [/* @__PURE__ */ r(c, {
33
+ children: [/* @__PURE__ */ r(s, {
34
34
  className: "flex items-center gap-2 text-base",
35
35
  children: [t, u]
36
36
  }), /* @__PURE__ */ n("p", {
37
37
  className: "text-xs text-muted-foreground",
38
38
  children: d
39
39
  })]
40
- }), /* @__PURE__ */ r(o, {
40
+ }), /* @__PURE__ */ r(a, {
41
41
  className: "space-y-3",
42
42
  children: [x, g && !f ? /* @__PURE__ */ n(l, { ...g }) : null]
43
43
  })]
@@ -121,6 +121,6 @@ function _(e, t, n) {
121
121
  return t > e ? `Showing ${e} of ${t} ${n}` : `${t} ${n}`;
122
122
  }
123
123
  //#endregion
124
- export { u as DashboardCard, d as DashboardCardShell, f as NoSkillsNote, m as PercentBar, p as StatTile, _ as cappedSubtitle, g as formatHours, h as ratePercentLabel };
124
+ export { u as DashboardCard, d as DashboardCardShell, l as DrillDownLink, f as NoSkillsNote, m as PercentBar, p as StatTile, _ as cappedSubtitle, g as formatHours, h as ratePercentLabel };
125
125
 
126
126
  //# sourceMappingURL=DashboardUI.js.map