@orthacms/webhooks-admin 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +7 -0
  3. package/dist/index.d.ts +12 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +9 -0
  6. package/dist/lib/application/useContentTypeOptions/index.d.ts +18 -0
  7. package/dist/lib/application/useContentTypeOptions/index.d.ts.map +1 -0
  8. package/dist/lib/application/useContentTypeOptions/index.js +40 -0
  9. package/dist/lib/application/useWebhookDeliveries/index.d.ts +19 -0
  10. package/dist/lib/application/useWebhookDeliveries/index.d.ts.map +1 -0
  11. package/dist/lib/application/useWebhookDeliveries/index.js +48 -0
  12. package/dist/lib/application/useWebhookEndpoints/index.d.ts +12 -0
  13. package/dist/lib/application/useWebhookEndpoints/index.d.ts.map +1 -0
  14. package/dist/lib/application/useWebhookEndpoints/index.js +26 -0
  15. package/dist/lib/application/useWebhookEvents/index.d.ts +9 -0
  16. package/dist/lib/application/useWebhookEvents/index.d.ts.map +1 -0
  17. package/dist/lib/application/useWebhookEvents/index.js +20 -0
  18. package/dist/lib/application/useWebhookMutations/index.d.ts +25 -0
  19. package/dist/lib/application/useWebhookMutations/index.d.ts.map +1 -0
  20. package/dist/lib/application/useWebhookMutations/index.js +79 -0
  21. package/dist/lib/application/useWorkspaceOptions/index.d.ts +9 -0
  22. package/dist/lib/application/useWorkspaceOptions/index.d.ts.map +1 -0
  23. package/dist/lib/application/useWorkspaceOptions/index.js +19 -0
  24. package/dist/lib/domain/contentTypeChoices/index.d.ts +65 -0
  25. package/dist/lib/domain/contentTypeChoices/index.d.ts.map +1 -0
  26. package/dist/lib/domain/contentTypeChoices/index.js +70 -0
  27. package/dist/lib/domain/headerRules/index.d.ts +37 -0
  28. package/dist/lib/domain/headerRules/index.d.ts.map +1 -0
  29. package/dist/lib/domain/headerRules/index.js +66 -0
  30. package/dist/lib/domain/types/contentTypeOption/index.d.ts +14 -0
  31. package/dist/lib/domain/types/contentTypeOption/index.d.ts.map +1 -0
  32. package/dist/lib/domain/types/contentTypeOption/index.js +1 -0
  33. package/dist/lib/domain/types/webhook/index.d.ts +102 -0
  34. package/dist/lib/domain/types/webhook/index.d.ts.map +1 -0
  35. package/dist/lib/domain/types/webhook/index.js +18 -0
  36. package/dist/lib/domain/types/workspaceOption/index.d.ts +16 -0
  37. package/dist/lib/domain/types/workspaceOption/index.d.ts.map +1 -0
  38. package/dist/lib/domain/types/workspaceOption/index.js +1 -0
  39. package/dist/lib/infrastructure/httpWebhookGateway/index.d.ts +11 -0
  40. package/dist/lib/infrastructure/httpWebhookGateway/index.d.ts.map +1 -0
  41. package/dist/lib/infrastructure/httpWebhookGateway/index.js +138 -0
  42. package/dist/lib/infrastructure/serverMessageOf/index.d.ts +16 -0
  43. package/dist/lib/infrastructure/serverMessageOf/index.d.ts.map +1 -0
  44. package/dist/lib/infrastructure/serverMessageOf/index.js +24 -0
  45. package/dist/lib/infrastructure/webhookGateway/index.d.ts +62 -0
  46. package/dist/lib/infrastructure/webhookGateway/index.d.ts.map +1 -0
  47. package/dist/lib/infrastructure/webhookGateway/index.js +1 -0
  48. package/dist/lib/infrastructure/webhookMapper/index.d.ts +104 -0
  49. package/dist/lib/infrastructure/webhookMapper/index.d.ts.map +1 -0
  50. package/dist/lib/infrastructure/webhookMapper/index.js +49 -0
  51. package/dist/lib/infrastructure/webhooksKeys/index.d.ts +35 -0
  52. package/dist/lib/infrastructure/webhooksKeys/index.d.ts.map +1 -0
  53. package/dist/lib/infrastructure/webhooksKeys/index.js +23 -0
  54. package/dist/lib/presentation/components/DeliveryStatusBadge/index.d.ts +6 -0
  55. package/dist/lib/presentation/components/DeliveryStatusBadge/index.d.ts.map +1 -0
  56. package/dist/lib/presentation/components/DeliveryStatusBadge/index.js +30 -0
  57. package/dist/lib/presentation/components/RevealWebhookSecretDialog/index.d.ts +25 -0
  58. package/dist/lib/presentation/components/RevealWebhookSecretDialog/index.d.ts.map +1 -0
  59. package/dist/lib/presentation/components/RevealWebhookSecretDialog/index.js +107 -0
  60. package/dist/lib/presentation/components/WebhookDeliveriesPanel/DeliveryDetailSheet/index.d.ts +19 -0
  61. package/dist/lib/presentation/components/WebhookDeliveriesPanel/DeliveryDetailSheet/index.d.ts.map +1 -0
  62. package/dist/lib/presentation/components/WebhookDeliveriesPanel/DeliveryDetailSheet/index.js +72 -0
  63. package/dist/lib/presentation/components/WebhookDeliveriesPanel/index.d.ts +12 -0
  64. package/dist/lib/presentation/components/WebhookDeliveriesPanel/index.d.ts.map +1 -0
  65. package/dist/lib/presentation/components/WebhookDeliveriesPanel/index.js +132 -0
  66. package/dist/lib/presentation/components/WebhookForm/index.d.ts +46 -0
  67. package/dist/lib/presentation/components/WebhookForm/index.d.ts.map +1 -0
  68. package/dist/lib/presentation/components/WebhookForm/index.js +412 -0
  69. package/dist/lib/presentation/components/WebhooksEmpty/index.d.ts +5 -0
  70. package/dist/lib/presentation/components/WebhooksEmpty/index.d.ts.map +1 -0
  71. package/dist/lib/presentation/components/WebhooksEmpty/index.js +23 -0
  72. package/dist/lib/presentation/components/WebhooksNoAccess/index.d.ts +3 -0
  73. package/dist/lib/presentation/components/WebhooksNoAccess/index.d.ts.map +1 -0
  74. package/dist/lib/presentation/components/WebhooksNoAccess/index.js +18 -0
  75. package/dist/lib/presentation/components/WebhooksSkeleton/index.d.ts +20 -0
  76. package/dist/lib/presentation/components/WebhooksSkeleton/index.d.ts.map +1 -0
  77. package/dist/lib/presentation/components/WebhooksSkeleton/index.js +45 -0
  78. package/dist/lib/presentation/components/WebhooksTable/index.d.ts +12 -0
  79. package/dist/lib/presentation/components/WebhooksTable/index.d.ts.map +1 -0
  80. package/dist/lib/presentation/components/WebhooksTable/index.js +79 -0
  81. package/dist/lib/presentation/pages/WebhookDetailPage/index.d.ts +10 -0
  82. package/dist/lib/presentation/pages/WebhookDetailPage/index.d.ts.map +1 -0
  83. package/dist/lib/presentation/pages/WebhookDetailPage/index.js +191 -0
  84. package/dist/lib/presentation/pages/WebhookEditorPage/index.d.ts +28 -0
  85. package/dist/lib/presentation/pages/WebhookEditorPage/index.d.ts.map +1 -0
  86. package/dist/lib/presentation/pages/WebhookEditorPage/index.js +149 -0
  87. package/dist/lib/presentation/pages/WebhooksPage/index.d.ts +11 -0
  88. package/dist/lib/presentation/pages/WebhooksPage/index.d.ts.map +1 -0
  89. package/dist/lib/presentation/pages/WebhooksPage/index.js +65 -0
  90. package/dist/lib/utils/webhooksPlugin/index.d.ts +35 -0
  91. package/dist/lib/utils/webhooksPlugin/index.d.ts.map +1 -0
  92. package/dist/lib/utils/webhooksPlugin/index.js +88 -0
  93. package/package.json +46 -0
@@ -0,0 +1,49 @@
1
+ /** Maps an endpoint from the wire. */
2
+ export function toWebhookEndpoint(dto) {
3
+ return {
4
+ ...dto,
5
+ lastDelivery: dto.lastDelivery ? toLastDelivery(dto.lastDelivery) : null
6
+ };
7
+ }
8
+ /** Maps the last-delivery summary. */
9
+ function toLastDelivery(dto) {
10
+ return { ...dto, status: dto.status };
11
+ }
12
+ /** Maps one delivery row. */
13
+ export function toWebhookDelivery(dto) {
14
+ return { ...dto, status: dto.status };
15
+ }
16
+ /** Maps one delivery with its bodies. */
17
+ export function toWebhookDeliveryDetail(dto) {
18
+ return {
19
+ ...toWebhookDelivery(dto),
20
+ payload: dto.payload,
21
+ responseSnippet: dto.responseSnippet
22
+ };
23
+ }
24
+ /** Maps one event descriptor. */
25
+ export function toWebhookEventOption(dto) {
26
+ return { ...dto };
27
+ }
28
+ /** Maps a workspace row to the picker's option shape. */
29
+ export function toWorkspaceOption(dto) {
30
+ return {
31
+ id: dto.id,
32
+ name: dto.name,
33
+ description: dto.description,
34
+ // Absent is read as "granted nothing", which is what an empty grant set
35
+ // means on the server too. The picker degrades to free entry rather
36
+ // than to a wrong list.
37
+ contentTypes: dto.content ?? []
38
+ };
39
+ }
40
+ /**
41
+ * Maps one content type from the registry.
42
+ *
43
+ * Only the two fields the picker needs — the rest of a summary (kind, path,
44
+ * `i18n`, …) describes how the type behaves, which the subscription filter has
45
+ * no opinion about.
46
+ */
47
+ export function toContentTypeOption(dto) {
48
+ return { name: dto.name, label: dto.label };
49
+ }
@@ -0,0 +1,35 @@
1
+ /** Filters and paging accepted by `GET /api/webhooks/:id/deliveries`. */
2
+ export type DeliveriesListParams = {
3
+ /** Only deliveries in this state. */
4
+ status?: string;
5
+ /** Only deliveries of this event kind. */
6
+ eventKind?: string;
7
+ /** 1-based page number. */
8
+ page?: number;
9
+ /** Rows per page. */
10
+ pageSize?: number;
11
+ };
12
+ /**
13
+ * Query keys for the webhooks cache.
14
+ *
15
+ * Deliveries hang off their endpoint's key rather than a flat root, so
16
+ * refreshing one endpoint's log after a redelivery does not invalidate every
17
+ * other endpoint's — the log is the one query here that is actively polled.
18
+ */
19
+ export declare const webhooksKeys: {
20
+ /** Root key covering every webhooks query. */
21
+ all: readonly ["webhooks"];
22
+ /** The endpoint list. */
23
+ list: () => readonly ["webhooks", "list"];
24
+ /** One endpoint. */
25
+ detail: (id: string) => readonly ["webhooks", "detail", string];
26
+ /** Every delivery query for one endpoint. */
27
+ deliveriesOf: (id: string) => readonly ["webhooks", "deliveries", string];
28
+ /** One page of one endpoint's log. */
29
+ deliveries: (id: string, params: DeliveriesListParams) => readonly ["webhooks", "deliveries", string, DeliveriesListParams];
30
+ /** The subscribable event catalogue. */
31
+ events: () => readonly ["webhooks", "events"];
32
+ /** The code-defined content-type catalogue, for the editor's picker. */
33
+ contentTypes: () => readonly ["webhooks", "content-types"];
34
+ };
35
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/infrastructure/webhooksKeys/index.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,MAAM,MAAM,oBAAoB,GAAG;IAC/B,qCAAqC;IACrC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0CAA0C;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2BAA2B;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qBAAqB;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,YAAY;IACrB,8CAA8C;;IAE9C,yBAAyB;;IAEzB,oBAAoB;iBACP,MAAM;IACnB,6CAA6C;uBAC1B,MAAM;IACzB,sCAAsC;qBACrB,MAAM,UAAU,oBAAoB;IAErD,wCAAwC;;IAExC,wEAAwE;;CAE3E,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Query keys for the webhooks cache.
3
+ *
4
+ * Deliveries hang off their endpoint's key rather than a flat root, so
5
+ * refreshing one endpoint's log after a redelivery does not invalidate every
6
+ * other endpoint's — the log is the one query here that is actively polled.
7
+ */
8
+ export const webhooksKeys = {
9
+ /** Root key covering every webhooks query. */
10
+ all: ['webhooks'],
11
+ /** The endpoint list. */
12
+ list: () => ['webhooks', 'list'],
13
+ /** One endpoint. */
14
+ detail: (id) => ['webhooks', 'detail', id],
15
+ /** Every delivery query for one endpoint. */
16
+ deliveriesOf: (id) => ['webhooks', 'deliveries', id],
17
+ /** One page of one endpoint's log. */
18
+ deliveries: (id, params) => ['webhooks', 'deliveries', id, params],
19
+ /** The subscribable event catalogue. */
20
+ events: () => ['webhooks', 'events'],
21
+ /** The code-defined content-type catalogue, for the editor's picker. */
22
+ contentTypes: () => ['webhooks', 'content-types']
23
+ };
@@ -0,0 +1,6 @@
1
+ import type { DeliveryStatus } from '../../../domain/types/webhook';
2
+ /** The state of one delivery, as a chip. */
3
+ export declare function DeliveryStatusBadge({ status }: {
4
+ status: DeliveryStatus;
5
+ }): import("react").JSX.Element;
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/lib/presentation/components/DeliveryStatusBadge/index.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AA8BpE,4CAA4C;AAC5C,wBAAgB,mBAAmB,CAAC,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,cAAc,CAAA;CAAE,+BAOzE"}
@@ -0,0 +1,30 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { defineMessages, useIntl } from 'react-intl';
3
+ import { Badge } from '@orthacms/design-system';
4
+ const messages = defineMessages({
5
+ pending: { id: 'webhooks.status.pending', defaultMessage: 'Queued' },
6
+ delivering: { id: 'webhooks.status.delivering', defaultMessage: 'Sending' },
7
+ succeeded: { id: 'webhooks.status.succeeded', defaultMessage: 'Delivered' },
8
+ failed: { id: 'webhooks.status.failed', defaultMessage: 'Retrying' },
9
+ dead: { id: 'webhooks.status.dead', defaultMessage: 'Failed' }
10
+ });
11
+ /**
12
+ * How each state reads and looks.
13
+ *
14
+ * The wording is the point: `failed` on a row that will be tried again says
15
+ * **Retrying**, and only `dead` — the state nothing will act on again — says
16
+ * "Failed". Calling both of them "failed" is what makes an operator chase a
17
+ * delivery that was about to succeed on its own.
18
+ */
19
+ const LOOK = {
20
+ pending: { variant: 'outline' },
21
+ delivering: { variant: 'secondary' },
22
+ succeeded: { variant: 'default' },
23
+ failed: { variant: 'secondary' },
24
+ dead: { variant: 'destructive' }
25
+ };
26
+ /** The state of one delivery, as a chip. */
27
+ export function DeliveryStatusBadge({ status }) {
28
+ const intl = useIntl();
29
+ return (_jsx(Badge, { variant: LOOK[status].variant, children: intl.formatMessage(messages[status]) }));
30
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Shows a signing secret **once**.
3
+ *
4
+ * The secret cannot be hashed — it is used to sign, not to verify — so it lives
5
+ * in the parent's state until this dialog is dismissed and is never fetchable
6
+ * again. Three things follow, all of them load-bearing and all of them mirroring
7
+ * the API-token reveal:
8
+ *
9
+ * - the secret is a focusable, labelled `readOnly` input that selects itself on
10
+ * focus, so it can be read and copied without the Copy button;
11
+ * - the clipboard write is wrapped, because `writeText` rejects on an insecure
12
+ * origin, a denied permission, or an unfocused document — and a silent
13
+ * rejection loses the credential;
14
+ * - dismissing before copying asks first, because Esc, the overlay and the
15
+ * close button are all reached by reflex.
16
+ */
17
+ export declare function RevealWebhookSecretDialog({ secret, rotated, open, onOpenChange }: {
18
+ /** The plaintext secret, or `null` when there is nothing to reveal. */
19
+ secret: string | null;
20
+ /** Whether this replaced an existing secret rather than minting the first. */
21
+ rotated?: boolean;
22
+ open: boolean;
23
+ onOpenChange: (open: boolean) => void;
24
+ }): import("react").JSX.Element;
25
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/lib/presentation/components/RevealWebhookSecretDialog/index.tsx"],"names":[],"mappings":"AA+DA;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,yBAAyB,CAAC,EACtC,MAAM,EACN,OAAO,EACP,IAAI,EACJ,YAAY,EACf,EAAE;IACC,uEAAuE;IACvE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,8EAA8E;IAC9E,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;CACzC,+BA4HA"}
@@ -0,0 +1,107 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useEffect, useState } from 'react';
3
+ import { defineMessages, useIntl } from 'react-intl';
4
+ import { Check, Copy, TriangleAlert } from 'lucide-react';
5
+ import { Alert, AlertDescription, Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, toast } from '@orthacms/design-system';
6
+ const messages = defineMessages({
7
+ title: {
8
+ id: 'webhooks.reveal.title',
9
+ defaultMessage: 'Copy your signing secret'
10
+ },
11
+ description: {
12
+ id: 'webhooks.reveal.description',
13
+ defaultMessage: 'This is the only time the secret is shown. Your receiver needs it to verify that a delivery really came from this CMS.'
14
+ },
15
+ warning: {
16
+ id: 'webhooks.reveal.warning',
17
+ defaultMessage: 'Every delivery is signed with this secret. Anyone who has it can forge a delivery your receiver will accept.'
18
+ },
19
+ rotated: {
20
+ id: 'webhooks.reveal.rotated',
21
+ defaultMessage: 'The new secret is already in use, including for deliveries that were queued before the rotation. Put it in place on the receiver now.'
22
+ },
23
+ secretLabel: {
24
+ id: 'webhooks.reveal.secretLabel',
25
+ defaultMessage: 'Webhook signing secret'
26
+ },
27
+ copy: { id: 'webhooks.reveal.copy', defaultMessage: 'Copy' },
28
+ copied: {
29
+ id: 'webhooks.reveal.copied',
30
+ defaultMessage: 'Copied to clipboard'
31
+ },
32
+ copyFailed: {
33
+ id: 'webhooks.reveal.copyFailed',
34
+ defaultMessage: 'Couldn’t reach the clipboard. Select the secret above and copy it manually.'
35
+ },
36
+ done: { id: 'webhooks.reveal.done', defaultMessage: 'Done' },
37
+ uncopied: {
38
+ id: 'webhooks.reveal.uncopied',
39
+ defaultMessage: 'You haven’t copied the secret yet, and it can’t be shown again. Closing now means rotating to a new one.'
40
+ },
41
+ closeAnyway: {
42
+ id: 'webhooks.reveal.closeAnyway',
43
+ defaultMessage: 'Close without copying'
44
+ },
45
+ keepOpen: { id: 'webhooks.reveal.keepOpen', defaultMessage: 'Keep it open' }
46
+ });
47
+ /**
48
+ * Shows a signing secret **once**.
49
+ *
50
+ * The secret cannot be hashed — it is used to sign, not to verify — so it lives
51
+ * in the parent's state until this dialog is dismissed and is never fetchable
52
+ * again. Three things follow, all of them load-bearing and all of them mirroring
53
+ * the API-token reveal:
54
+ *
55
+ * - the secret is a focusable, labelled `readOnly` input that selects itself on
56
+ * focus, so it can be read and copied without the Copy button;
57
+ * - the clipboard write is wrapped, because `writeText` rejects on an insecure
58
+ * origin, a denied permission, or an unfocused document — and a silent
59
+ * rejection loses the credential;
60
+ * - dismissing before copying asks first, because Esc, the overlay and the
61
+ * close button are all reached by reflex.
62
+ */
63
+ export function RevealWebhookSecretDialog({ secret, rotated, open, onOpenChange }) {
64
+ const intl = useIntl();
65
+ const [copied, setCopied] = useState(false);
66
+ const [confirmingClose, setConfirmingClose] = useState(false);
67
+ // A new secret is a new chance to lose it — clear the guard whenever one
68
+ // arrives, so a rotation does not inherit the first one's "copied".
69
+ useEffect(() => {
70
+ if (secret !== null) {
71
+ setCopied(false);
72
+ setConfirmingClose(false);
73
+ }
74
+ }, [secret]);
75
+ const copy = async () => {
76
+ if (!secret)
77
+ return;
78
+ try {
79
+ await navigator.clipboard.writeText(secret);
80
+ setCopied(true);
81
+ toast.success(intl.formatMessage(messages.copied));
82
+ }
83
+ catch {
84
+ toast.error(intl.formatMessage(messages.copyFailed));
85
+ }
86
+ };
87
+ /** Close for real, clearing the guard. */
88
+ const close = () => {
89
+ setConfirmingClose(false);
90
+ onOpenChange(false);
91
+ };
92
+ /** Every dismissal path funnels through here so none can slip past. */
93
+ const requestClose = () => {
94
+ if (copied) {
95
+ close();
96
+ return;
97
+ }
98
+ setConfirmingClose(true);
99
+ };
100
+ return (_jsx(Dialog, { open: open, onOpenChange: (next) => {
101
+ if (next) {
102
+ onOpenChange(true);
103
+ return;
104
+ }
105
+ requestClose();
106
+ }, children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: intl.formatMessage(messages.title) }), _jsx(DialogDescription, { children: intl.formatMessage(messages.description) })] }), _jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [_jsx("input", { readOnly: true, value: secret ?? '', "aria-label": intl.formatMessage(messages.secretLabel), onFocus: (event) => event.currentTarget.select(), className: "min-w-0 flex-1 truncate rounded-md bg-muted px-3 py-2 font-mono text-sm" }), _jsxs(Button, { type: "button", variant: "outline", className: "shrink-0", onClick: copy, children: [copied ? _jsx(Check, { "aria-hidden": true }) : _jsx(Copy, { "aria-hidden": true }), intl.formatMessage(messages.copy)] })] }), _jsxs(Alert, { children: [_jsx(TriangleAlert, { "aria-hidden": true }), _jsx(AlertDescription, { children: intl.formatMessage(rotated ? messages.rotated : messages.warning) })] }), confirmingClose ? (_jsxs(Alert, { variant: "destructive", role: "alert", children: [_jsx(TriangleAlert, { "aria-hidden": true }), _jsx(AlertDescription, { children: intl.formatMessage(messages.uncopied) })] })) : null, _jsx(DialogFooter, { children: confirmingClose ? (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", onClick: () => setConfirmingClose(false), children: intl.formatMessage(messages.keepOpen) }), _jsx(Button, { variant: "destructive", onClick: close, children: intl.formatMessage(messages.closeAnyway) })] })) : (_jsx(Button, { onClick: requestClose, children: intl.formatMessage(messages.done) })) })] }) }));
107
+ }
@@ -0,0 +1,19 @@
1
+ import type { WebhookDeliveryDetail } from '../../../../domain/types/webhook';
2
+ /**
3
+ * One delivery in full — the body that was posted, the response that came back,
4
+ * and the button to send it again.
5
+ *
6
+ * The request body is shown verbatim rather than re-derived, because it is
7
+ * stored verbatim: what is on screen is byte-for-byte what the receiver was
8
+ * sent, which is the only version worth debugging against.
9
+ */
10
+ export declare function DeliveryDetailSheet({ delivery, open, onOpenChange, canRedeliver, redelivering, onRedeliver }: {
11
+ /** The delivery, or `undefined` while it loads. */
12
+ delivery: WebhookDeliveryDetail | undefined;
13
+ open: boolean;
14
+ onOpenChange: (open: boolean) => void;
15
+ canRedeliver: boolean;
16
+ redelivering: boolean;
17
+ onRedeliver: (deliveryId: string) => void;
18
+ }): import("react").JSX.Element;
19
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../src/lib/presentation/components/WebhookDeliveriesPanel/DeliveryDetailSheet/index.tsx"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,kCAAkC,CAAC;AAgE9E;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,EAChC,QAAQ,EACR,IAAI,EACJ,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,WAAW,EACd,EAAE;IACC,mDAAmD;IACnD,QAAQ,EAAE,qBAAqB,GAAG,SAAS,CAAC;IAC5C,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACtC,YAAY,EAAE,OAAO,CAAC;IACtB,YAAY,EAAE,OAAO,CAAC;IACtB,WAAW,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7C,+BAmHA"}
@@ -0,0 +1,72 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { defineMessages, useIntl } from 'react-intl';
3
+ import { RefreshCw } from 'lucide-react';
4
+ import { Button, Sheet, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, Spinner } from '@orthacms/design-system';
5
+ import { DeliveryStatusBadge } from '../../DeliveryStatusBadge';
6
+ const messages = defineMessages({
7
+ title: { id: 'webhooks.delivery.title', defaultMessage: 'Delivery' },
8
+ description: {
9
+ id: 'webhooks.delivery.description',
10
+ defaultMessage: 'What was sent, and what came back.'
11
+ },
12
+ request: {
13
+ id: 'webhooks.delivery.request',
14
+ defaultMessage: 'Request body'
15
+ },
16
+ response: {
17
+ id: 'webhooks.delivery.response',
18
+ defaultMessage: 'Response'
19
+ },
20
+ noResponse: {
21
+ id: 'webhooks.delivery.noResponse',
22
+ defaultMessage: 'The receiver sent no body.'
23
+ },
24
+ error: { id: 'webhooks.delivery.error', defaultMessage: 'Error' },
25
+ event: { id: 'webhooks.delivery.event', defaultMessage: 'Event' },
26
+ attempts: { id: 'webhooks.delivery.attempts', defaultMessage: 'Attempts' },
27
+ statusCode: {
28
+ id: 'webhooks.delivery.statusCode',
29
+ defaultMessage: 'HTTP status'
30
+ },
31
+ duration: { id: 'webhooks.delivery.duration', defaultMessage: 'Took' },
32
+ durationValue: {
33
+ id: 'webhooks.delivery.durationValue',
34
+ defaultMessage: '{ms} ms'
35
+ },
36
+ nextAttempt: {
37
+ id: 'webhooks.delivery.nextAttempt',
38
+ defaultMessage: 'Next attempt'
39
+ },
40
+ eventId: {
41
+ id: 'webhooks.delivery.eventId',
42
+ defaultMessage: 'Event id (deduplicate on this)'
43
+ },
44
+ redeliver: {
45
+ id: 'webhooks.delivery.redeliver',
46
+ defaultMessage: 'Send again'
47
+ },
48
+ none: { id: 'webhooks.delivery.none', defaultMessage: '—' }
49
+ });
50
+ /** One labelled fact in the summary grid. */
51
+ function Fact({ label, children }) {
52
+ return (_jsxs("div", { className: "flex flex-col gap-0.5", children: [_jsx("dt", { className: "text-xs text-muted-foreground", children: label }), _jsx("dd", { className: "text-sm", children: children })] }));
53
+ }
54
+ /**
55
+ * One delivery in full — the body that was posted, the response that came back,
56
+ * and the button to send it again.
57
+ *
58
+ * The request body is shown verbatim rather than re-derived, because it is
59
+ * stored verbatim: what is on screen is byte-for-byte what the receiver was
60
+ * sent, which is the only version worth debugging against.
61
+ */
62
+ export function DeliveryDetailSheet({ delivery, open, onOpenChange, canRedeliver, redelivering, onRedeliver }) {
63
+ const intl = useIntl();
64
+ return (_jsx(Sheet, { open: open, onOpenChange: onOpenChange, children: _jsxs(SheetContent, { className: "flex w-full flex-col gap-4 overflow-y-auto sm:max-w-xl", children: [_jsxs(SheetHeader, { children: [_jsx(SheetTitle, { children: intl.formatMessage(messages.title) }), _jsx(SheetDescription, { children: intl.formatMessage(messages.description) })] }), !delivery ? (_jsx("div", { className: "flex justify-center py-8", children: _jsx(Spinner, {}) })) : (_jsxs(_Fragment, { children: [_jsxs("dl", { className: "grid grid-cols-2 gap-3 px-4", children: [_jsx(Fact, { label: intl.formatMessage(messages.event), children: _jsx("span", { className: "font-mono text-xs", children: delivery.eventKind }) }), _jsx(Fact, { label: intl.formatMessage(messages.statusCode), children: _jsxs("span", { className: "flex items-center gap-2", children: [_jsx(DeliveryStatusBadge, { status: delivery.status }), delivery.statusCode ??
65
+ intl.formatMessage(messages.none)] }) }), _jsx(Fact, { label: intl.formatMessage(messages.attempts), children: delivery.attempts }), _jsx(Fact, { label: intl.formatMessage(messages.duration), children: delivery.durationMs === null
66
+ ? intl.formatMessage(messages.none)
67
+ : intl.formatMessage(messages.durationValue, { ms: delivery.durationMs }) }), delivery.nextAttemptAt ? (_jsx(Fact, { label: intl.formatMessage(messages.nextAttempt), children: intl.formatDate(delivery.nextAttemptAt, {
68
+ dateStyle: 'medium',
69
+ timeStyle: 'short'
70
+ }) })) : null, _jsx(Fact, { label: intl.formatMessage(messages.eventId), children: _jsx("span", { className: "font-mono text-xs break-all", children: delivery.eventId }) })] }), delivery.error ? (_jsxs("section", { className: "px-4", children: [_jsx("h3", { className: "mb-1 text-xs text-muted-foreground", children: intl.formatMessage(messages.error) }), _jsx("p", { className: "rounded-md bg-destructive/10 px-3 py-2 text-sm text-destructive", children: delivery.error })] })) : null, _jsxs("section", { className: "px-4", children: [_jsx("h3", { className: "mb-1 text-xs text-muted-foreground", children: intl.formatMessage(messages.request) }), _jsx("pre", { className: "max-h-72 overflow-auto rounded-md bg-muted p-3 font-mono text-xs", children: JSON.stringify(delivery.payload, null, 2) })] }), _jsxs("section", { className: "px-4", children: [_jsx("h3", { className: "mb-1 text-xs text-muted-foreground", children: intl.formatMessage(messages.response) }), _jsx("pre", { className: "max-h-48 overflow-auto rounded-md bg-muted p-3 font-mono text-xs", children: delivery.responseSnippet ??
71
+ intl.formatMessage(messages.noResponse) })] }), canRedeliver ? (_jsx(SheetFooter, { children: _jsxs(Button, { variant: "outline", disabled: redelivering, onClick: () => onRedeliver(delivery.id), children: [_jsx(RefreshCw, { "aria-hidden": true }), intl.formatMessage(messages.redeliver)] }) })) : null] }))] }) }));
72
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * One endpoint's delivery log.
3
+ *
4
+ * The list polls itself while anything in it is still in flight (see
5
+ * `useWebhookDeliveries`), so a queued delivery becomes a delivered one on
6
+ * screen without anybody pressing refresh.
7
+ */
8
+ export declare function WebhookDeliveriesPanel({ endpointId, canRedeliver }: {
9
+ endpointId: string;
10
+ canRedeliver: boolean;
11
+ }): import("react").JSX.Element;
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/lib/presentation/components/WebhookDeliveriesPanel/index.tsx"],"names":[],"mappings":"AAwGA;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,EACnC,UAAU,EACV,YAAY,EACf,EAAE;IACC,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,OAAO,CAAC;CACzB,+BAmNA"}
@@ -0,0 +1,132 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useState } from 'react';
3
+ import { defineMessages, useIntl } from 'react-intl';
4
+ import { Alert, AlertDescription, Button, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, toast } from '@orthacms/design-system';
5
+ import { DELIVERY_STATUSES } from '../../../domain/types/webhook';
6
+ import { DEFAULT_PAGE_SIZE, useWebhookDeliveries, useWebhookDelivery } from '../../../application/useWebhookDeliveries';
7
+ import { useRedeliverWebhook } from '../../../application/useWebhookMutations';
8
+ import { DeliveryStatusBadge } from '../DeliveryStatusBadge';
9
+ import { DeliveryDetailSheet } from './DeliveryDetailSheet';
10
+ const messages = defineMessages({
11
+ caption: {
12
+ id: 'webhooks.deliveries.caption',
13
+ defaultMessage: 'Delivery log'
14
+ },
15
+ event: { id: 'webhooks.deliveries.event', defaultMessage: 'Event' },
16
+ status: { id: 'webhooks.deliveries.status', defaultMessage: 'Status' },
17
+ code: { id: 'webhooks.deliveries.code', defaultMessage: 'Response' },
18
+ attempts: {
19
+ id: 'webhooks.deliveries.attempts',
20
+ defaultMessage: 'Attempts'
21
+ },
22
+ when: { id: 'webhooks.deliveries.when', defaultMessage: 'Queued' },
23
+ empty: {
24
+ id: 'webhooks.deliveries.empty',
25
+ defaultMessage: 'Nothing has been sent yet. A delivery appears here the moment content this endpoint subscribes to changes.'
26
+ },
27
+ error: {
28
+ id: 'webhooks.deliveries.error',
29
+ defaultMessage: 'Couldn’t load the delivery log. Please try again.'
30
+ },
31
+ retry: { id: 'webhooks.deliveries.retry', defaultMessage: 'Retry' },
32
+ filterAll: {
33
+ id: 'webhooks.deliveries.filterAll',
34
+ defaultMessage: 'All states'
35
+ },
36
+ filterLabel: {
37
+ id: 'webhooks.deliveries.filterLabel',
38
+ defaultMessage: 'Filter by state'
39
+ },
40
+ view: { id: 'webhooks.deliveries.view', defaultMessage: 'View' },
41
+ actions: { id: 'webhooks.deliveries.actions', defaultMessage: 'Actions' },
42
+ prev: { id: 'webhooks.deliveries.prev', defaultMessage: 'Previous' },
43
+ next: { id: 'webhooks.deliveries.next', defaultMessage: 'Next' },
44
+ pageOf: {
45
+ id: 'webhooks.deliveries.pageOf',
46
+ defaultMessage: 'Page {page} of {pageCount}'
47
+ },
48
+ redelivered: {
49
+ id: 'webhooks.deliveries.redelivered',
50
+ defaultMessage: 'Queued to send again'
51
+ },
52
+ redeliverFailed: {
53
+ id: 'webhooks.deliveries.redeliverFailed',
54
+ defaultMessage: 'Couldn’t queue it again. Please try again.'
55
+ },
56
+ statusPending: {
57
+ id: 'webhooks.status.pending',
58
+ defaultMessage: 'Queued'
59
+ },
60
+ statusDelivering: {
61
+ id: 'webhooks.status.delivering',
62
+ defaultMessage: 'Sending'
63
+ },
64
+ statusSucceeded: {
65
+ id: 'webhooks.status.succeeded',
66
+ defaultMessage: 'Delivered'
67
+ },
68
+ statusFailed: { id: 'webhooks.status.failed', defaultMessage: 'Retrying' },
69
+ statusDead: { id: 'webhooks.status.dead', defaultMessage: 'Failed' }
70
+ });
71
+ /** The filter dropdown's label for each state — same wording as the chips. */
72
+ const STATUS_LABELS = {
73
+ pending: messages.statusPending,
74
+ delivering: messages.statusDelivering,
75
+ succeeded: messages.statusSucceeded,
76
+ failed: messages.statusFailed,
77
+ dead: messages.statusDead
78
+ };
79
+ /** Sentinel for "no state filter" — a `Select` cannot hold an empty value. */
80
+ const ALL = 'all';
81
+ /**
82
+ * One endpoint's delivery log.
83
+ *
84
+ * The list polls itself while anything in it is still in flight (see
85
+ * `useWebhookDeliveries`), so a queued delivery becomes a delivered one on
86
+ * screen without anybody pressing refresh.
87
+ */
88
+ export function WebhookDeliveriesPanel({ endpointId, canRedeliver }) {
89
+ const intl = useIntl();
90
+ const [status, setStatus] = useState(ALL);
91
+ const [page, setPage] = useState(1);
92
+ const [openDeliveryId, setOpenDeliveryId] = useState(null);
93
+ const { data, isPending, isError, refetch } = useWebhookDeliveries(endpointId, {
94
+ page,
95
+ pageSize: DEFAULT_PAGE_SIZE,
96
+ ...(status === ALL ? {} : { status })
97
+ });
98
+ const { data: detail } = useWebhookDelivery(endpointId, openDeliveryId);
99
+ const redeliver = useRedeliverWebhook();
100
+ // The log shrinks under the reader — retention prunes it, a filter narrows
101
+ // it — so a page number that was valid a moment ago can point past the end.
102
+ // Following the server's clamp keeps the pager and the rows in agreement
103
+ // instead of showing an empty page with no way back.
104
+ useEffect(() => {
105
+ if (data && page > data.pageCount) {
106
+ setPage(data.pageCount);
107
+ }
108
+ }, [data, page]);
109
+ if (isError) {
110
+ return (_jsx(Alert, { variant: "destructive", role: "alert", className: "mt-4", children: _jsxs(AlertDescription, { className: "flex items-center justify-between gap-4", children: [intl.formatMessage(messages.error), _jsx(Button, { size: "sm", variant: "outline", onClick: () => void refetch(), children: intl.formatMessage(messages.retry) })] }) }));
111
+ }
112
+ const items = data?.items ?? [];
113
+ return (_jsxs("div", { className: "flex flex-col gap-3", children: [_jsx("div", { className: "flex items-center justify-end", children: _jsxs(Select, { value: status, onValueChange: (next) => {
114
+ setStatus(next);
115
+ setPage(1);
116
+ }, children: [_jsx(SelectTrigger, { className: "w-48", "aria-label": intl.formatMessage(messages.filterLabel), children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: ALL, children: intl.formatMessage(messages.filterAll) }), DELIVERY_STATUSES.map((value) => (_jsx(SelectItem, { value: value, children: intl.formatMessage(STATUS_LABELS[value]) }, value)))] })] }) }), !isPending && items.length === 0 ? (_jsx("p", { className: "py-8 text-center text-sm text-muted-foreground", children: intl.formatMessage(messages.empty) })) : (_jsx("div", { className: "overflow-hidden rounded-xl border bg-card shadow-xs", children: _jsxs(Table, { children: [_jsx("caption", { className: "sr-only", children: intl.formatMessage(messages.caption) }), _jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: intl.formatMessage(messages.event) }), _jsx(TableHead, { children: intl.formatMessage(messages.status) }), _jsx(TableHead, { children: intl.formatMessage(messages.code) }), _jsx(TableHead, { children: intl.formatMessage(messages.attempts) }), _jsx(TableHead, { children: intl.formatMessage(messages.when) }), _jsx(TableHead, { className: "w-20", children: _jsx("span", { className: "sr-only", children: intl.formatMessage(messages.actions) }) })] }) }), _jsx(TableBody, { children: items.map((delivery) => (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "font-mono text-xs", children: delivery.eventKind }), _jsx(TableCell, { children: _jsx(DeliveryStatusBadge, { status: delivery.status }) }), _jsx(TableCell, { className: "tabular-nums", children: delivery.statusCode ?? '—' }), _jsx(TableCell, { className: "tabular-nums", children: delivery.attempts }), _jsx(TableCell, { className: "text-sm text-muted-foreground", children: intl.formatDate(delivery.createdAt, {
117
+ dateStyle: 'medium',
118
+ timeStyle: 'short'
119
+ }) }), _jsx(TableCell, { className: "text-right", children: _jsx(Button, { size: "sm", variant: "ghost", onClick: () => setOpenDeliveryId(delivery.id), children: intl.formatMessage(messages.view) }) })] }, delivery.id))) })] }) })), data && data.pageCount > 1 ? (_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: "text-sm text-muted-foreground", children: intl.formatMessage(messages.pageOf, {
120
+ page: data.page,
121
+ pageCount: data.pageCount
122
+ }) }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { size: "sm", variant: "outline", disabled: data.page <= 1, onClick: () => setPage((prev) => prev - 1), children: intl.formatMessage(messages.prev) }), _jsx(Button, { size: "sm", variant: "outline", disabled: data.page >= data.pageCount, onClick: () => setPage((prev) => prev + 1), children: intl.formatMessage(messages.next) })] })] })) : null, _jsx(DeliveryDetailSheet, { delivery: detail, open: openDeliveryId !== null, onOpenChange: (next) => {
123
+ if (!next)
124
+ setOpenDeliveryId(null);
125
+ }, canRedeliver: canRedeliver, redelivering: redeliver.isPending, onRedeliver: (deliveryId) => redeliver.mutate({ endpointId, deliveryId }, {
126
+ onSuccess: () => {
127
+ toast.success(intl.formatMessage(messages.redelivered));
128
+ setOpenDeliveryId(null);
129
+ },
130
+ onError: () => toast.error(intl.formatMessage(messages.redeliverFailed))
131
+ }) })] }));
132
+ }
@@ -0,0 +1,46 @@
1
+ import type { WebhookEndpoint, WebhookEventOption } from '../../../domain/types/webhook';
2
+ import type { ContentTypeOption } from '../../../domain/types/contentTypeOption';
3
+ import type { WorkspaceOption } from '../../../domain/types/workspaceOption';
4
+ import { type HeaderRow } from '../../../domain/headerRules';
5
+ import type { SaveWebhookInput } from '../../../infrastructure/webhookGateway';
6
+ /** What the dialog is currently editing. */
7
+ export type WebhookFormValues = {
8
+ name: string;
9
+ url: string;
10
+ enabled: boolean;
11
+ allWorkspaces: boolean;
12
+ workspaceIds: string[];
13
+ allEvents: boolean;
14
+ eventKinds: string[];
15
+ allContentTypes: boolean;
16
+ contentTypes: string[];
17
+ headers: HeaderRow[];
18
+ };
19
+ /**
20
+ * Create and edit an endpoint.
21
+ *
22
+ * The three filters are each an "All…" toggle over a picker, and the toggle is
23
+ * not cosmetic: it is the difference between "everything, including what does
24
+ * not exist yet" and an enumerated list that silently stops covering the next
25
+ * workspace or event kind. Turning one on sends **no** value for that filter,
26
+ * which is exactly how the server reads "all".
27
+ */
28
+ export declare function WebhookForm({ endpoint, events, workspaces, contentTypes, pending, error, onSubmit, onCancel }: {
29
+ /** The endpoint being edited, or `undefined` when creating one. */
30
+ endpoint?: WebhookEndpoint;
31
+ events: WebhookEventOption[];
32
+ workspaces: WorkspaceOption[];
33
+ /**
34
+ * The code-defined content types. Empty is a legitimate state — an
35
+ * unreachable catalogue, or a build with no types yet — and the picker
36
+ * still accepts a typed-in machine name.
37
+ */
38
+ contentTypes: ContentTypeOption[];
39
+ pending: boolean;
40
+ /** A server-side refusal to show above the footer (a rejected URL, say). */
41
+ error: string | null;
42
+ onSubmit: (input: SaveWebhookInput) => void;
43
+ /** Leaves without saving — the page decides where that goes. */
44
+ onCancel: () => void;
45
+ }): import("react").JSX.Element;
46
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/lib/presentation/components/WebhookForm/index.tsx"],"names":[],"mappings":"AAcA,OAAO,KAAK,EACR,eAAe,EACf,kBAAkB,EACrB,MAAM,+BAA+B,CAAC;AAEvC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yCAAyC,CAAC;AACjF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uCAAuC,CAAC;AAC7E,OAAO,EAIH,KAAK,SAAS,EACjB,MAAM,6BAA6B,CAAC;AAOrC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wCAAwC,CAAC;AA4K/E,4CAA4C;AAC5C,MAAM,MAAM,iBAAiB,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,OAAO,CAAC;IACvB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,eAAe,EAAE,OAAO,CAAC;IACzB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,OAAO,EAAE,SAAS,EAAE,CAAC;CACxB,CAAC;AAwCF;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,EACxB,QAAQ,EACR,MAAM,EACN,UAAU,EACV,YAAY,EACZ,OAAO,EACP,KAAK,EACL,QAAQ,EACR,QAAQ,EACX,EAAE;IACC,mEAAmE;IACnE,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,MAAM,EAAE,kBAAkB,EAAE,CAAC;IAC7B,UAAU,EAAE,eAAe,EAAE,CAAC;IAC9B;;;;OAIG;IACH,YAAY,EAAE,iBAAiB,EAAE,CAAC;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,4EAA4E;IAC5E,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,QAAQ,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAC5C,gEAAgE;IAChE,QAAQ,EAAE,MAAM,IAAI,CAAC;CACxB,+BAkmBA"}