@aglyn/plugins-commerce 1.0.0-beta.287 → 1.0.0-beta.289

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 (109) hide show
  1. package/README.md +2 -2
  2. package/package.json +13 -13
  3. package/src/lib/components/commerce-console-page.js +29 -2
  4. package/src/lib/components/commerce-console-page.js.map +1 -1
  5. package/src/lib/components/console/commerce-analytics-card.component.js +4 -2
  6. package/src/lib/components/console/commerce-analytics-card.component.js.map +1 -1
  7. package/src/lib/components/console/host-orders-card.component.js +5 -1
  8. package/src/lib/components/console/host-orders-card.component.js.map +1 -1
  9. package/src/lib/components/console/host-quotes-card.component.d.ts +34 -0
  10. package/src/lib/components/console/host-quotes-card.component.js +247 -0
  11. package/src/lib/components/console/host-quotes-card.component.js.map +1 -0
  12. package/src/lib/components/console/order-webhooks-card.component.js +1 -1
  13. package/src/lib/components/console/order-webhooks-card.component.js.map +1 -1
  14. package/src/lib/components/console/pos-ops/pos-check-ins-card.component.js +33 -28
  15. package/src/lib/components/console/pos-ops/pos-check-ins-card.component.js.map +1 -1
  16. package/src/lib/components/console/quote-editor-dialog.component.d.ts +45 -0
  17. package/src/lib/components/console/quote-editor-dialog.component.js +1266 -0
  18. package/src/lib/components/console/quote-editor-dialog.component.js.map +1 -0
  19. package/src/lib/components/console/quote-pay-link.component.d.ts +38 -0
  20. package/src/lib/components/console/quote-pay-link.component.js +213 -0
  21. package/src/lib/components/console/quote-pay-link.component.js.map +1 -0
  22. package/src/lib/components/console/quote-settings-card.component.d.ts +29 -0
  23. package/src/lib/components/console/quote-settings-card.component.js +121 -0
  24. package/src/lib/components/console/quote-settings-card.component.js.map +1 -0
  25. package/src/lib/components/console/tax-settings-card.component.js +4 -1
  26. package/src/lib/components/console/tax-settings-card.component.js.map +1 -1
  27. package/src/lib/components/quote-page-body.d.ts +33 -0
  28. package/src/lib/components/quote-page-body.js +696 -0
  29. package/src/lib/components/quote-page-body.js.map +1 -0
  30. package/src/lib/components/quote-page.d.ts +29 -0
  31. package/src/lib/components/quote-page.js +96 -0
  32. package/src/lib/components/quote-page.js.map +1 -0
  33. package/src/lib/components/storefront-payment-element.d.ts +7 -1
  34. package/src/lib/components/storefront-payment-element.js +8 -3
  35. package/src/lib/components/storefront-payment-element.js.map +1 -1
  36. package/src/lib/constants/orders-list-query.js +6 -1
  37. package/src/lib/constants/orders-list-query.js.map +1 -1
  38. package/src/lib/constants/quote-page.d.ts +30 -0
  39. package/src/lib/constants/quote-page.js +35 -0
  40. package/src/lib/constants/quote-page.js.map +1 -0
  41. package/src/lib/declarations.js +5 -1
  42. package/src/lib/declarations.js.map +1 -1
  43. package/src/lib/model/commerce-orders.d.ts +25 -2
  44. package/src/lib/model/commerce-orders.js +10 -1
  45. package/src/lib/model/commerce-orders.js.map +1 -1
  46. package/src/lib/model/commerce-quote-pay.d.ts +229 -0
  47. package/src/lib/model/commerce-quote-pay.js +393 -0
  48. package/src/lib/model/commerce-quote-pay.js.map +1 -0
  49. package/src/lib/model/commerce-quotes.d.ts +279 -0
  50. package/src/lib/model/commerce-quotes.js +522 -0
  51. package/src/lib/model/commerce-quotes.js.map +1 -0
  52. package/src/lib/model/index.d.ts +2 -0
  53. package/src/lib/model/index.js +2 -0
  54. package/src/lib/model/index.js.map +1 -1
  55. package/src/lib/model/plugin-permissions.d.ts +8 -1
  56. package/src/lib/model/plugin-permissions.js +21 -1
  57. package/src/lib/model/plugin-permissions.js.map +1 -1
  58. package/src/lib/model/workspace-site-permissions.d.ts +26 -0
  59. package/src/lib/model/workspace-site-permissions.js +81 -0
  60. package/src/lib/model/workspace-site-permissions.js.map +1 -0
  61. package/src/lib/server/api-v1/orders-and-products.js +6 -2
  62. package/src/lib/server/api-v1/orders-and-products.js.map +1 -1
  63. package/src/lib/server/billing-webhook.js +127 -52
  64. package/src/lib/server/billing-webhook.js.map +1 -1
  65. package/src/lib/server/checkout-session-expiry.d.ts +36 -0
  66. package/src/lib/server/checkout-session-expiry.js +63 -0
  67. package/src/lib/server/checkout-session-expiry.js.map +1 -0
  68. package/src/lib/server/commerce-site-keys.d.ts +19 -0
  69. package/src/lib/server/commerce-site-keys.js +23 -2
  70. package/src/lib/server/commerce-site-keys.js.map +1 -1
  71. package/src/lib/server/draft-order.js +16 -8
  72. package/src/lib/server/draft-order.js.map +1 -1
  73. package/src/lib/server/order-webhooks.js +8 -8
  74. package/src/lib/server/order-webhooks.js.map +1 -1
  75. package/src/lib/server/person-eraser.d.ts +7 -0
  76. package/src/lib/server/person-eraser.js +24 -2
  77. package/src/lib/server/person-eraser.js.map +1 -1
  78. package/src/lib/server/pos-ops-gate.d.ts +13 -5
  79. package/src/lib/server/pos-ops-gate.js +12 -14
  80. package/src/lib/server/pos-ops-gate.js.map +1 -1
  81. package/src/lib/server/pos-refund-authority.d.ts +3 -1
  82. package/src/lib/server/pos-refund-authority.js +3 -2
  83. package/src/lib/server/pos-refund-authority.js.map +1 -1
  84. package/src/lib/server/pos-return.js +6 -7
  85. package/src/lib/server/pos-return.js.map +1 -1
  86. package/src/lib/server/pos-staff-pin.js +7 -7
  87. package/src/lib/server/pos-staff-pin.js.map +1 -1
  88. package/src/lib/server/quote-link-token.d.ts +39 -0
  89. package/src/lib/server/quote-link-token.js +97 -0
  90. package/src/lib/server/quote-link-token.js.map +1 -0
  91. package/src/lib/server/quote-pay.d.ts +83 -0
  92. package/src/lib/server/quote-pay.js +606 -0
  93. package/src/lib/server/quote-pay.js.map +1 -0
  94. package/src/lib/server/quote.d.ts +49 -0
  95. package/src/lib/server/quote.js +513 -0
  96. package/src/lib/server/quote.js.map +1 -0
  97. package/src/lib/server/refund.js +26 -36
  98. package/src/lib/server/refund.js.map +1 -1
  99. package/src/lib/server/related.js +2 -0
  100. package/src/lib/server/related.js.map +1 -1
  101. package/src/lib/server/site-page-resolver.d.ts +2 -0
  102. package/src/lib/server/site-page-resolver.js +75 -0
  103. package/src/lib/server/site-page-resolver.js.map +1 -1
  104. package/src/lib/server.js +14 -0
  105. package/src/lib/server.js.map +1 -1
  106. package/src/lib/site.js +6 -0
  107. package/src/lib/site.js.map +1 -1
  108. package/src/lib/transfer/tracking.server.js +6 -0
  109. package/src/lib/transfer/tracking.server.js.map +1 -1
@@ -0,0 +1,247 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ 'use client';
17
+ import { _ as _extends } from "@swc/helpers/_/_extends";
18
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
19
+ import * as CommerceModel from "../../model/index.js";
20
+ import { posMoney } from "../../model/commerce-pos-ops.js";
21
+ import { commerceSitePermissionRefusal } from "../../model/plugin-permissions.js";
22
+ import { pluginDocsHelp } from "@aglyn/aglyn";
23
+ import { CardDisplay } from "@aglyn/shared-ui-jsx";
24
+ import { BlockedControl } from "@aglyn/shared-ui-jsx/components/blocked-control.component";
25
+ import { ListTable } from "@aglyn/shared-ui-jsx/components/list-table.component";
26
+ import { ceilingedWindow, collectionCeiling, useFirestore, useFirestoreCollection } from "@aglyn/tenant-feature-instance";
27
+ import { Button, Chip, Stack, Typography } from "@mui/material";
28
+ import { collection, limit, query, where } from "firebase/firestore";
29
+ import { useMemo, useState } from "react";
30
+ import QuoteEditorDialog from "./quote-editor-dialog.component.js";
31
+ /**
32
+ * How many quotes the list reads. The whole set is read and the grid sorts
33
+ * it by any column; a store past this many is told the list stops short.
34
+ */ const QUOTE_CEILING = 500;
35
+ /** The products the editor's picker offers. */ const PRODUCT_WINDOW = 200;
36
+ const REFUSAL = commerceSitePermissionRefusal('commerce.quotes');
37
+ /** One quote as its row draws and sorts it. */ function quoteListRow(quote) {
38
+ var _quote_lineItems, _ref, _ref1, _ref2;
39
+ var _quote_quote, _items_, _quote_totals, _quote_quote1;
40
+ const customer = (_quote_quote = quote.quote) == null ? void 0 : _quote_quote.customer;
41
+ const status = CommerceModel.quoteDisplayStatus(quote);
42
+ const items = (_quote_lineItems = quote.lineItems) != null ? _quote_lineItems : [];
43
+ return _extends({}, quote, {
44
+ customerLabel: (customer == null ? void 0 : customer.name) || (customer == null ? void 0 : customer.company) || (customer == null ? void 0 : customer.email) || '',
45
+ firstItem: (_ref = (_items_ = items[0]) == null ? void 0 : _items_.name) != null ? _ref : '',
46
+ moreItems: Math.max(0, items.length - 1),
47
+ statusKey: status.key,
48
+ statusLabel: status.label,
49
+ totalCents: Number((_ref1 = (_quote_totals = quote.totals) == null ? void 0 : _quote_totals.totalCents) != null ? _ref1 : 0),
50
+ updatedAtMs: Number((_ref2 = (_quote_quote1 = quote.quote) == null ? void 0 : _quote_quote1.updatedAtMs) != null ? _ref2 : 0)
51
+ });
52
+ }
53
+ /**
54
+ * Quotes (AGL-4093): prices put to a customer before they buy, beside the
55
+ * orders they become. A quote is an unpaid order on the `quote` channel, so
56
+ * this reads `hosts/{hostId}/orders` for that channel alone; the Orders list
57
+ * above never shows one until it is paid.
58
+ */ export function HostQuotesCard(props) {
59
+ var _quotes_rows_find;
60
+ const { hostId, canEdit, canSend } = props;
61
+ const firestore = useFirestore();
62
+ const { data: quoteDocs, status } = useFirestoreCollection(()=>query(collection(firestore, 'hosts', hostId, 'orders'), where('channel', '==', 'quote'), limit(QUOTE_CEILING + 1)), [
63
+ firestore,
64
+ hostId
65
+ ], {
66
+ idField: '$id'
67
+ });
68
+ const quotes = useMemo(()=>ceilingedWindow(quoteDocs != null ? quoteDocs : undefined, QUOTE_CEILING), [
69
+ quoteDocs
70
+ ]);
71
+ const rows = useMemo(()=>Array.from(quotes.rows, quoteListRow), [
72
+ quotes
73
+ ]);
74
+ const { data: productDocs } = useFirestoreCollection(()=>collectionCeiling(collection(firestore, 'hosts', hostId, 'products'), PRODUCT_WINDOW), [
75
+ firestore,
76
+ hostId
77
+ ], {
78
+ idField: '$id'
79
+ });
80
+ const products = useMemo(()=>ceilingedWindow(productDocs != null ? productDocs : undefined, PRODUCT_WINDOW).rows.filter((product)=>!product['deletedAt']), [
81
+ productDocs
82
+ ]);
83
+ /** The quote open in the editor: its id, or `new`. */ const [openId, setOpenId] = useState(null);
84
+ const openQuote = openId && openId !== 'new' ? (_quotes_rows_find = quotes.rows.find((row)=>row.$id === openId)) != null ? _quotes_rows_find : null : null;
85
+ const columns = useMemo(()=>[
86
+ {
87
+ field: 'number',
88
+ headerName: 'Quote',
89
+ flex: 1,
90
+ minWidth: 160,
91
+ renderCell: ({ row })=>/*#__PURE__*/ _jsxs(Stack, {
92
+ sx: {
93
+ minWidth: 0
94
+ },
95
+ children: [
96
+ /*#__PURE__*/ _jsx(Typography, {
97
+ variant: "body2",
98
+ noWrap: true,
99
+ children: CommerceModel.formatQuoteNumber(row, row.$id)
100
+ }),
101
+ /*#__PURE__*/ _jsx(Typography, {
102
+ variant: "caption",
103
+ color: "text.secondary",
104
+ noWrap: true,
105
+ children: row.moreItems ? `${row.firstItem} and more` : row.firstItem
106
+ })
107
+ ]
108
+ })
109
+ },
110
+ {
111
+ field: 'customerLabel',
112
+ headerName: 'Customer',
113
+ flex: 1,
114
+ minWidth: 160,
115
+ renderCell: ({ row })=>/*#__PURE__*/ _jsx(Typography, {
116
+ variant: "body2",
117
+ noWrap: true,
118
+ children: row.customerLabel || '—'
119
+ })
120
+ },
121
+ {
122
+ field: 'totalCents',
123
+ headerName: 'Total',
124
+ type: 'number',
125
+ width: 140,
126
+ align: 'right',
127
+ headerAlign: 'right',
128
+ renderCell: ({ row })=>{
129
+ var _row_quote_tax, _row_quote;
130
+ return /*#__PURE__*/ _jsxs(Stack, {
131
+ sx: {
132
+ alignItems: 'flex-end',
133
+ minWidth: 0,
134
+ width: 1
135
+ },
136
+ children: [
137
+ /*#__PURE__*/ _jsx(Typography, {
138
+ variant: "body2",
139
+ children: posMoney(row.totalCents)
140
+ }),
141
+ ((_row_quote = row.quote) == null ? void 0 : (_row_quote_tax = _row_quote.tax) == null ? void 0 : _row_quote_tax.kind) === 'at-payment' && CommerceModel.isUnpaidQuote(row) ? /*#__PURE__*/ _jsx(Typography, {
142
+ variant: "caption",
143
+ color: "text.secondary",
144
+ noWrap: true,
145
+ children: 'plus tax at payment'
146
+ }) : null
147
+ ]
148
+ });
149
+ }
150
+ },
151
+ {
152
+ field: 'statusLabel',
153
+ headerName: 'Status',
154
+ width: 180,
155
+ renderCell: ({ row })=>/*#__PURE__*/ _jsx(Chip, {
156
+ size: "small",
157
+ variant: "outlined",
158
+ label: row.statusLabel,
159
+ color: CommerceModel.QUOTE_STATUS_COLOR[row.statusKey]
160
+ })
161
+ },
162
+ {
163
+ field: 'updatedAtMs',
164
+ headerName: 'Updated',
165
+ type: 'number',
166
+ width: 140,
167
+ align: 'left',
168
+ headerAlign: 'left',
169
+ renderCell: ({ row })=>/*#__PURE__*/ _jsx(Typography, {
170
+ variant: "body2",
171
+ noWrap: true,
172
+ children: row.updatedAtMs ? new Date(row.updatedAtMs).toLocaleDateString() : '—'
173
+ })
174
+ }
175
+ ], []);
176
+ const empty = status === 'success' && rows.length === 0;
177
+ return /*#__PURE__*/ _jsxs(CardDisplay, {
178
+ header: 'Quotes',
179
+ subheader: 'Prices for a customer’s order before they buy',
180
+ help: pluginDocsHelp('quotes', {
181
+ anchor: '#make-a-quote',
182
+ title: 'Quotes',
183
+ excerpt: 'Put a price to a customer with your products, items typed for the job and anything ' + 'included. A quote becomes an order when it is paid.'
184
+ }),
185
+ HeaderProps: {
186
+ action: /*#__PURE__*/ _jsx(BlockedControl, {
187
+ blocked: !canEdit,
188
+ reason: REFUSAL,
189
+ children: /*#__PURE__*/ _jsx(Button, {
190
+ size: "small",
191
+ variant: "contained",
192
+ onClick: ()=>setOpenId('new'),
193
+ children: 'New quote'
194
+ })
195
+ })
196
+ },
197
+ contentGutterX: true,
198
+ contentGutterY: true,
199
+ children: [
200
+ empty ? /*#__PURE__*/ _jsx(Typography, {
201
+ variant: "body2",
202
+ color: "text.secondary",
203
+ children: 'No quotes yet. A quote prices a customer’s order before they buy: your products, ' + 'items typed for the job, and anything you include at no charge.'
204
+ }) : /*#__PURE__*/ _jsxs(Stack, {
205
+ spacing: 1,
206
+ children: [
207
+ quotes.truncated ? /*#__PURE__*/ _jsx(Typography, {
208
+ variant: "caption",
209
+ color: "text.secondary",
210
+ children: 'This store has more quotes than the list shows at once.'
211
+ }) : null,
212
+ /*#__PURE__*/ _jsx(ListTable, {
213
+ "aria-label": "Quotes",
214
+ rows: rows,
215
+ columns: columns,
216
+ loading: status === 'loading',
217
+ onOpen: (id)=>setOpenId(id),
218
+ quickFilter: true,
219
+ initialState: {
220
+ sorting: {
221
+ sortModel: [
222
+ {
223
+ field: 'updatedAtMs',
224
+ sort: 'desc'
225
+ }
226
+ ]
227
+ }
228
+ },
229
+ noRowsLabel: "No quotes match"
230
+ })
231
+ ]
232
+ }),
233
+ openId === 'new' || openQuote ? /*#__PURE__*/ _jsx(QuoteEditorDialog, {
234
+ hostId: hostId,
235
+ quote: openQuote,
236
+ products: products,
237
+ canEdit: canEdit,
238
+ canSend: canSend,
239
+ onClose: ()=>setOpenId(null)
240
+ }, openId) : null
241
+ ]
242
+ });
243
+ }
244
+ HostQuotesCard.displayName = 'HostQuotesCard';
245
+ export default HostQuotesCard;
246
+
247
+ //# sourceMappingURL=host-quotes-card.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/host-quotes-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use client'\n\nimport * as CommerceModel from '../../model'\nimport { posMoney } from '../../model/commerce-pos-ops'\nimport { commerceSitePermissionRefusal } from '../../model/plugin-permissions'\nimport { pluginDocsHelp } from '@aglyn/aglyn'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { BlockedControl } from '@aglyn/shared-ui-jsx/components/blocked-control.component'\nimport { ListTable } from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport {\n ceilingedWindow,\n collectionCeiling,\n useFirestore,\n useFirestoreCollection,\n} from '@aglyn/tenant-feature-instance'\nimport { Button, Chip, Stack, Typography } from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport { collection, limit, query, where } from 'firebase/firestore'\nimport { useMemo, useState } from 'react'\nimport QuoteEditorDialog, { type QuoteRow } from './quote-editor-dialog.component'\n\nexport interface HostQuotesCardProps {\n hostId: string\n /** Holds \"Create and edit quotes\" (`commerce.quotes`). */\n canEdit: boolean\n /** Holds \"Send quotes and invoices to customers\" (`commerce.quotes.send`). */\n canSend: boolean\n}\n\n/**\n * How many quotes the list reads. The whole set is read and the grid sorts\n * it by any column; a store past this many is told the list stops short.\n */\nconst QUOTE_CEILING = 500\n\n/** The products the editor's picker offers. */\nconst PRODUCT_WINDOW = 200\n\nconst REFUSAL = commerceSitePermissionRefusal('commerce.quotes')\n\ninterface QuoteListRow extends QuoteRow {\n customerLabel: string\n firstItem: string\n moreItems: number\n statusKey: ReturnType<typeof CommerceModel.quoteDisplayStatus>['key']\n statusLabel: string\n totalCents: number\n updatedAtMs: number\n}\n\n/** One quote as its row draws and sorts it. */\nfunction quoteListRow(quote: QuoteRow): QuoteListRow {\n const customer = quote.quote?.customer\n const status = CommerceModel.quoteDisplayStatus(quote)\n const items = quote.lineItems ?? []\n return {\n ...quote,\n customerLabel: customer?.name || customer?.company || customer?.email || '',\n firstItem: items[0]?.name ?? '',\n moreItems: Math.max(0, items.length - 1),\n statusKey: status.key,\n statusLabel: status.label,\n totalCents: Number(quote.totals?.totalCents ?? 0),\n updatedAtMs: Number(quote.quote?.updatedAtMs ?? 0),\n }\n}\n\n/**\n * Quotes (AGL-4093): prices put to a customer before they buy, beside the\n * orders they become. A quote is an unpaid order on the `quote` channel, so\n * this reads `hosts/{hostId}/orders` for that channel alone; the Orders list\n * above never shows one until it is paid.\n */\nexport function HostQuotesCard(props: HostQuotesCardProps) {\n const { hostId, canEdit, canSend } = props\n const firestore = useFirestore()\n const { data: quoteDocs, status } = useFirestoreCollection<QuoteRow>(\n () =>\n query(\n collection(firestore, 'hosts', hostId, 'orders'),\n where('channel', '==', 'quote'),\n limit(QUOTE_CEILING + 1),\n ),\n [firestore, hostId],\n { idField: '$id' },\n )\n const quotes = useMemo(() => ceilingedWindow<QuoteRow>(quoteDocs ?? undefined, QUOTE_CEILING), [quoteDocs])\n const rows = useMemo(() => Array.from(quotes.rows, quoteListRow), [quotes])\n\n const { data: productDocs } = useFirestoreCollection<Record<string, any>>(\n () => collectionCeiling(collection(firestore, 'hosts', hostId, 'products'), PRODUCT_WINDOW),\n [firestore, hostId],\n { idField: '$id' },\n )\n const products = useMemo(\n () => ceilingedWindow<Record<string, any>>(productDocs ?? undefined, PRODUCT_WINDOW).rows.filter((product) => !product['deletedAt']),\n [productDocs],\n )\n\n /** The quote open in the editor: its id, or `new`. */\n const [openId, setOpenId] = useState<string | null>(null)\n const openQuote = openId && openId !== 'new' ? (quotes.rows.find((row) => row.$id === openId) ?? null) : null\n\n const columns = useMemo<GridColDef[]>(\n () => [\n {\n field: 'number',\n headerName: 'Quote',\n flex: 1,\n minWidth: 160,\n renderCell: ({ row }: { row: QuoteListRow }) => (\n <Stack sx={{ minWidth: 0 }}>\n <Typography variant=\"body2\" noWrap>\n {CommerceModel.formatQuoteNumber(row, row.$id)}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {row.moreItems ? `${row.firstItem} and more` : row.firstItem}\n </Typography>\n </Stack>\n ),\n },\n {\n field: 'customerLabel',\n headerName: 'Customer',\n flex: 1,\n minWidth: 160,\n renderCell: ({ row }: { row: QuoteListRow }) => (\n <Typography variant=\"body2\" noWrap>\n {row.customerLabel || '—'}\n </Typography>\n ),\n },\n {\n field: 'totalCents',\n headerName: 'Total',\n type: 'number',\n width: 140,\n align: 'right',\n headerAlign: 'right',\n renderCell: ({ row }: { row: QuoteListRow }) => (\n <Stack sx={{ alignItems: 'flex-end', minWidth: 0, width: 1 }}>\n <Typography variant=\"body2\">{posMoney(row.totalCents)}</Typography>\n {row.quote?.tax?.kind === 'at-payment' && CommerceModel.isUnpaidQuote(row) ? (\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {'plus tax at payment'}\n </Typography>\n ) : null}\n </Stack>\n ),\n },\n {\n field: 'statusLabel',\n headerName: 'Status',\n width: 180,\n renderCell: ({ row }: { row: QuoteListRow }) => (\n <Chip\n size=\"small\"\n variant=\"outlined\"\n label={row.statusLabel}\n color={CommerceModel.QUOTE_STATUS_COLOR[row.statusKey]}\n />\n ),\n },\n {\n field: 'updatedAtMs',\n headerName: 'Updated',\n type: 'number',\n width: 140,\n align: 'left',\n headerAlign: 'left',\n renderCell: ({ row }: { row: QuoteListRow }) => (\n <Typography variant=\"body2\" noWrap>\n {row.updatedAtMs ? new Date(row.updatedAtMs).toLocaleDateString() : '—'}\n </Typography>\n ),\n },\n ],\n [],\n )\n\n const empty = status === 'success' && rows.length === 0\n\n return (\n <CardDisplay\n header={'Quotes'}\n subheader={'Prices for a customer’s order before they buy'}\n help={pluginDocsHelp('quotes', {\n anchor: '#make-a-quote',\n title: 'Quotes',\n excerpt:\n 'Put a price to a customer with your products, items typed for the job and anything ' +\n 'included. A quote becomes an order when it is paid.',\n })}\n HeaderProps={{\n action: (\n <BlockedControl blocked={!canEdit} reason={REFUSAL}>\n <Button size=\"small\" variant=\"contained\" onClick={() => setOpenId('new')}>\n {'New quote'}\n </Button>\n </BlockedControl>\n ),\n }}\n contentGutterX\n contentGutterY\n >\n {empty ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No quotes yet. A quote prices a customer’s order before they buy: your products, ' +\n 'items typed for the job, and anything you include at no charge.'}\n </Typography>\n ) : (\n <Stack spacing={1}>\n {quotes.truncated ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'This store has more quotes than the list shows at once.'}\n </Typography>\n ) : null}\n <ListTable\n aria-label=\"Quotes\"\n rows={rows}\n columns={columns}\n loading={status === 'loading'}\n onOpen={(id) => setOpenId(id)}\n quickFilter\n initialState={{ sorting: { sortModel: [{ field: 'updatedAtMs', sort: 'desc' }] } }}\n noRowsLabel=\"No quotes match\"\n />\n </Stack>\n )}\n {/* A quote deleted while open closes its editor rather than turn it into a new one. */}\n {openId === 'new' || openQuote ? (\n <QuoteEditorDialog\n key={openId}\n hostId={hostId}\n quote={openQuote}\n products={products}\n canEdit={canEdit}\n canSend={canSend}\n onClose={() => setOpenId(null)}\n />\n ) : null}\n </CardDisplay>\n )\n}\nHostQuotesCard.displayName = 'HostQuotesCard'\n\nexport default HostQuotesCard\n"],"names":["CommerceModel","posMoney","commerceSitePermissionRefusal","pluginDocsHelp","CardDisplay","BlockedControl","ListTable","ceilingedWindow","collectionCeiling","useFirestore","useFirestoreCollection","Button","Chip","Stack","Typography","collection","limit","query","where","useMemo","useState","QuoteEditorDialog","QUOTE_CEILING","PRODUCT_WINDOW","REFUSAL","quoteListRow","quote","items","customer","status","quoteDisplayStatus","lineItems","customerLabel","name","company","email","firstItem","moreItems","Math","max","length","statusKey","key","statusLabel","label","totalCents","Number","totals","updatedAtMs","HostQuotesCard","props","quotes","hostId","canEdit","canSend","firestore","data","quoteDocs","idField","undefined","rows","Array","from","productDocs","products","filter","product","openId","setOpenId","openQuote","find","row","$id","columns","field","headerName","flex","minWidth","renderCell","sx","variant","noWrap","formatQuoteNumber","color","type","width","align","headerAlign","alignItems","tax","kind","isUnpaidQuote","size","QUOTE_STATUS_COLOR","Date","toLocaleDateString","empty","header","subheader","help","anchor","title","excerpt","HeaderProps","action","blocked","reason","onClick","contentGutterX","contentGutterY","spacing","truncated","aria-label","loading","onOpen","id","quickFilter","initialState","sorting","sortModel","sort","noRowsLabel","onClose","displayName"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;AAEA,YAAYA,mBAAmB,uBAAa;AAC5C,SAASC,QAAQ,QAAQ,kCAA8B;AACvD,SAASC,6BAA6B,QAAQ,oCAAgC;AAC9E,SAASC,cAAc,QAAQ,eAAc;AAC7C,SAASC,WAAW,QAAQ,uBAAsB;AAClD,SAASC,cAAc,QAAQ,4DAA2D;AAC1F,SAASC,SAAS,QAAQ,uDAAsD;AAChF,SACEC,eAAe,EACfC,iBAAiB,EACjBC,YAAY,EACZC,sBAAsB,QACjB,iCAAgC;AACvC,SAASC,MAAM,EAAEC,IAAI,EAAEC,KAAK,EAAEC,UAAU,QAAQ,gBAAe;AAE/D,SAASC,UAAU,EAAEC,KAAK,EAAEC,KAAK,EAAEC,KAAK,QAAQ,qBAAoB;AACpE,SAASC,OAAO,EAAEC,QAAQ,QAAQ,QAAO;AACzC,OAAOC,uBAA0C,qCAAiC;AAUlF;;;CAGC,GACD,MAAMC,gBAAgB;AAEtB,6CAA6C,GAC7C,MAAMC,iBAAiB;AAEvB,MAAMC,UAAUtB,8BAA8B;AAY9C,6CAA6C,GAC7C,SAASuB,aAAaC,KAAe;QAGrBA;QAFGA,cAMJC,SAIQD,eACCA;IAXtB,MAAME,YAAWF,eAAAA,MAAMA,KAAK,qBAAXA,aAAaE,QAAQ;IACtC,MAAMC,SAAS7B,cAAc8B,kBAAkB,CAACJ;IAChD,MAAMC,SAAQD,mBAAAA,MAAMK,SAAS,YAAfL,mBAAmB,EAAE;IACnC,OAAO,aACFA;QACHM,eAAeJ,CAAAA,4BAAAA,SAAUK,IAAI,MAAIL,4BAAAA,SAAUM,OAAO,MAAIN,4BAAAA,SAAUO,KAAK,KAAI;QACzEC,SAAS,WAAET,UAAAA,KAAK,CAAC,EAAE,qBAARA,QAAUM,IAAI,mBAAI;QAC7BI,WAAWC,KAAKC,GAAG,CAAC,GAAGZ,MAAMa,MAAM,GAAG;QACtCC,WAAWZ,OAAOa,GAAG;QACrBC,aAAad,OAAOe,KAAK;QACzBC,YAAYC,iBAAOpB,gBAAAA,MAAMqB,MAAM,qBAAZrB,cAAcmB,UAAU,oBAAI;QAC/CG,aAAaF,iBAAOpB,gBAAAA,MAAMA,KAAK,qBAAXA,cAAasB,WAAW,oBAAI;;AAEpD;AAEA;;;;;CAKC,GACD,OAAO,SAASC,eAAeC,KAA0B;QA4BPC;IA3BhD,MAAM,EAAEC,MAAM,EAAEC,OAAO,EAAEC,OAAO,EAAE,GAAGJ;IACrC,MAAMK,YAAY9C;IAClB,MAAM,EAAE+C,MAAMC,SAAS,EAAE5B,MAAM,EAAE,GAAGnB,uBAClC,IACEO,MACEF,WAAWwC,WAAW,SAASH,QAAQ,WACvClC,MAAM,WAAW,MAAM,UACvBF,MAAMM,gBAAgB,KAE1B;QAACiC;QAAWH;KAAO,EACnB;QAAEM,SAAS;IAAM;IAEnB,MAAMP,SAAShC,QAAQ,IAAMZ,gBAA0BkD,oBAAAA,YAAaE,WAAWrC,gBAAgB;QAACmC;KAAU;IAC1G,MAAMG,OAAOzC,QAAQ,IAAM0C,MAAMC,IAAI,CAACX,OAAOS,IAAI,EAAEnC,eAAe;QAAC0B;KAAO;IAE1E,MAAM,EAAEK,MAAMO,WAAW,EAAE,GAAGrD,uBAC5B,IAAMF,kBAAkBO,WAAWwC,WAAW,SAASH,QAAQ,aAAa7B,iBAC5E;QAACgC;QAAWH;KAAO,EACnB;QAAEM,SAAS;IAAM;IAEnB,MAAMM,WAAW7C,QACf,IAAMZ,gBAAqCwD,sBAAAA,cAAeJ,WAAWpC,gBAAgBqC,IAAI,CAACK,MAAM,CAAC,CAACC,UAAY,CAACA,OAAO,CAAC,YAAY,GACnI;QAACH;KAAY;IAGf,oDAAoD,GACpD,MAAM,CAACI,QAAQC,UAAU,GAAGhD,SAAwB;IACpD,MAAMiD,YAAYF,UAAUA,WAAW,SAAShB,oBAAAA,OAAOS,IAAI,CAACU,IAAI,CAAC,CAACC,MAAQA,IAAIC,GAAG,KAAKL,mBAAtChB,oBAAiD,OAAQ;IAEzG,MAAMsB,UAAUtD,QACd,IAAM;YACJ;gBACEuD,OAAO;gBACPC,YAAY;gBACZC,MAAM;gBACNC,UAAU;gBACVC,YAAY,CAAC,EAAEP,GAAG,EAAyB,iBACzC,MAAC1D;wBAAMkE,IAAI;4BAAEF,UAAU;wBAAE;;0CACvB,KAAC/D;gCAAWkE,SAAQ;gCAAQC,MAAM;0CAC/BjF,cAAckF,iBAAiB,CAACX,KAAKA,IAAIC,GAAG;;0CAE/C,KAAC1D;gCAAWkE,SAAQ;gCAAUG,OAAM;gCAAiBF,MAAM;0CACxDV,IAAIlC,SAAS,GAAG,GAAGkC,IAAInC,SAAS,CAAC,SAAS,CAAC,GAAGmC,IAAInC,SAAS;;;;YAIpE;YACA;gBACEsC,OAAO;gBACPC,YAAY;gBACZC,MAAM;gBACNC,UAAU;gBACVC,YAAY,CAAC,EAAEP,GAAG,EAAyB,iBACzC,KAACzD;wBAAWkE,SAAQ;wBAAQC,MAAM;kCAC/BV,IAAIvC,aAAa,IAAI;;YAG5B;YACA;gBACE0C,OAAO;gBACPC,YAAY;gBACZS,MAAM;gBACNC,OAAO;gBACPC,OAAO;gBACPC,aAAa;gBACbT,YAAY,CAAC,EAAEP,GAAG,EAAyB;wBAGtCA,gBAAAA;yCAFH,MAAC1D;wBAAMkE,IAAI;4BAAES,YAAY;4BAAYX,UAAU;4BAAGQ,OAAO;wBAAE;;0CACzD,KAACvE;gCAAWkE,SAAQ;0CAAS/E,SAASsE,IAAI1B,UAAU;;4BACnD0B,EAAAA,aAAAA,IAAI7C,KAAK,sBAAT6C,iBAAAA,WAAWkB,GAAG,qBAAdlB,eAAgBmB,IAAI,MAAK,gBAAgB1F,cAAc2F,aAAa,CAACpB,qBACpE,KAACzD;gCAAWkE,SAAQ;gCAAUG,OAAM;gCAAiBF,MAAM;0CACxD;iCAED;;;;YAGV;YACA;gBACEP,OAAO;gBACPC,YAAY;gBACZU,OAAO;gBACPP,YAAY,CAAC,EAAEP,GAAG,EAAyB,iBACzC,KAAC3D;wBACCgF,MAAK;wBACLZ,SAAQ;wBACRpC,OAAO2B,IAAI5B,WAAW;wBACtBwC,OAAOnF,cAAc6F,kBAAkB,CAACtB,IAAI9B,SAAS,CAAC;;YAG5D;YACA;gBACEiC,OAAO;gBACPC,YAAY;gBACZS,MAAM;gBACNC,OAAO;gBACPC,OAAO;gBACPC,aAAa;gBACbT,YAAY,CAAC,EAAEP,GAAG,EAAyB,iBACzC,KAACzD;wBAAWkE,SAAQ;wBAAQC,MAAM;kCAC/BV,IAAIvB,WAAW,GAAG,IAAI8C,KAAKvB,IAAIvB,WAAW,EAAE+C,kBAAkB,KAAK;;YAG1E;SACD,EACD,EAAE;IAGJ,MAAMC,QAAQnE,WAAW,aAAa+B,KAAKpB,MAAM,KAAK;IAEtD,qBACE,MAACpC;QACC6F,QAAQ;QACRC,WAAW;QACXC,MAAMhG,eAAe,UAAU;YAC7BiG,QAAQ;YACRC,OAAO;YACPC,SACE,wFACA;QACJ;QACAC,aAAa;YACXC,sBACE,KAACnG;gBAAeoG,SAAS,CAACpD;gBAASqD,QAAQlF;0BACzC,cAAA,KAACb;oBAAOiF,MAAK;oBAAQZ,SAAQ;oBAAY2B,SAAS,IAAMvC,UAAU;8BAC/D;;;QAIT;QACAwC,cAAc;QACdC,cAAc;;YAEbb,sBACC,KAAClF;gBAAWkE,SAAQ;gBAAQG,OAAM;0BAC/B,sFACC;+BAGJ,MAACtE;gBAAMiG,SAAS;;oBACb3D,OAAO4D,SAAS,iBACf,KAACjG;wBAAWkE,SAAQ;wBAAUG,OAAM;kCACjC;yBAED;kCACJ,KAAC7E;wBACC0G,cAAW;wBACXpD,MAAMA;wBACNa,SAASA;wBACTwC,SAASpF,WAAW;wBACpBqF,QAAQ,CAACC,KAAO/C,UAAU+C;wBAC1BC,WAAW;wBACXC,cAAc;4BAAEC,SAAS;gCAAEC,WAAW;oCAAC;wCAAE7C,OAAO;wCAAe8C,MAAM;oCAAO;iCAAE;4BAAC;wBAAE;wBACjFC,aAAY;;;;YAKjBtD,WAAW,SAASE,0BACnB,KAAChD;gBAEC+B,QAAQA;gBACR1B,OAAO2C;gBACPL,UAAUA;gBACVX,SAASA;gBACTC,SAASA;gBACToE,SAAS,IAAMtD,UAAU;eANpBD,UAQL;;;AAGV;AACAlB,eAAe0E,WAAW,GAAG;AAE7B,eAAe1E,eAAc"}
@@ -68,7 +68,7 @@ const ROUTE = '/api/commerce/order-webhooks';
68
68
  }).then((payload)=>{
69
69
  if (active) setStatus(payload);
70
70
  }).catch(()=>{
71
- // Not an admin of the whole workspace, or the route is down: the
71
+ // Without "Manage order webhooks" (AGL-4047), or the route is down: the
72
72
  // card is not this person's to use.
73
73
  if (active) setStatus(null);
74
74
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/order-webhooks-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use client'\n\nimport { pluginDocsHelp } from '@aglyn/aglyn'\nimport { CardDisplay, useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport {\n useFirestore,\n useFirestoreCollection,\n usePagedCollection,\n useUser,\n} from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n Checkbox,\n Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n FormControlLabel,\n FormGroup,\n MenuItem,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { collection, limit, orderBy, query, where } from 'firebase/firestore'\nimport { useCallback, useEffect, useState } from 'react'\nimport * as Webhooks from '../../model/order-webhooks'\n\nexport interface OrderWebhooksCardProps {\n hostId: string\n}\n\ninterface WebhookEventOption {\n event: string\n label: string\n description: string\n}\n\ninterface WebhooksStatus {\n configured: boolean\n maxEndpoints: number\n events: WebhookEventOption[]\n}\n\ntype EndpointRow = Webhooks.OrderWebhookEndpoint & { $id: string }\ntype DeliveryRow = Webhooks.OrderWebhookDelivery & { $id: string }\n\nconst ROUTE = '/api/commerce/order-webhooks'\n\n/** POSTs one action to the webhooks route; answers the body or throws its error. */\nexport async function callOrderWebhooks(\n user: Parameters<typeof authorizedFetch>[0],\n hostId: string,\n body: Record<string, unknown>,\n): Promise<Record<string, any>> {\n const response = await authorizedFetch(user, ROUTE, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ hostId, ...body }),\n })\n const payload = (await response.json().catch(() => ({}))) as Record<string, any>\n if (!response.ok) throw Object.assign(new Error(String(payload.error ?? 'Request failed')), { status: response.status })\n return payload\n}\n\n/**\n * Order webhooks (AGL-3611): the store's endpoints for order and return\n * events, each with its signing secret, a test event and a delivery log. Shown\n * only to the people who may manage them — the route answers who — and only\n * where the deployment can sign.\n */\nexport function OrderWebhooksCard(props: OrderWebhooksCardProps) {\n const { hostId } = props\n const firestore = useFirestore()\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const { confirm } = useConfirmationContext()\n const [status, setStatus] = useState<WebhooksStatus | null>(null)\n const [editing, setEditing] = useState<EndpointRow | 'new' | null>(null)\n const [secret, setSecret] = useState<string | null>(null)\n const [logFor, setLogFor] = useState<EndpointRow | null>(null)\n const [busy, setBusy] = useState(false)\n\n useEffect(() => {\n if (!user) return\n let active = true\n callOrderWebhooks(user, hostId, { action: 'status' })\n .then((payload) => {\n if (active) setStatus(payload as WebhooksStatus)\n })\n .catch(() => {\n // Not an admin of the whole workspace, or the route is down: the\n // card is not this person's to use.\n if (active) setStatus(null)\n })\n return () => {\n active = false\n }\n }, [user, hostId])\n\n const { data: endpointDocs } = useFirestoreCollection<EndpointRow>(\n () =>\n query(\n collection(firestore, 'hosts', hostId, 'orderWebhooks'),\n orderBy('createdAtMs', 'desc'),\n limit(Webhooks.ORDER_WEBHOOK_MAX_ENDPOINTS),\n ),\n [firestore, hostId],\n { idField: '$id' },\n )\n const endpoints = endpointDocs ?? []\n\n const run = useCallback(\n async (body: Record<string, unknown>, success?: string) => {\n setBusy(true)\n try {\n const payload = await callOrderWebhooks(user, hostId, body)\n if (success) enqueueSnackbar(success, { variant: 'success' })\n return payload\n } catch (error) {\n enqueueSnackbar(String((error as Error).message), { variant: 'error' })\n return null\n } finally {\n setBusy(false)\n }\n },\n [user, hostId, enqueueSnackbar],\n )\n\n const handleTest = useCallback(\n async (endpoint: EndpointRow) => {\n const payload = await run({ action: 'test', endpointId: endpoint.$id })\n if (!payload) return\n enqueueSnackbar(\n payload.delivered ? 'Test event delivered' : `Test event not delivered: ${payload.attempt?.error ?? 'no answer'}`,\n { variant: payload.delivered ? 'success' : 'warning' },\n )\n },\n [run, enqueueSnackbar],\n )\n\n const handleRoll = useCallback(\n async (endpoint: EndpointRow) => {\n const confirmed = await confirm({\n title: 'Roll the signing secret?',\n description:\n 'A new secret signs every event from now on. Update your endpoint before ' +\n 'the next order, or it will reject what it is sent.',\n confirmationText: 'Roll secret',\n })\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n const payload = await run({ action: 'roll-secret', endpointId: endpoint.$id })\n if (payload?.secret) setSecret(String(payload.secret))\n },\n [confirm, run],\n )\n\n const handleDelete = useCallback(\n async (endpoint: EndpointRow) => {\n const confirmed = await confirm({\n title: 'Delete this endpoint?',\n description: `${endpoint.url} stops receiving order events. Its delivery log is kept for 30 days.`,\n confirmationText: 'Delete',\n confirmationButtonProps: { color: 'error' },\n })\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n await run({ action: 'delete', endpointId: endpoint.$id }, 'Endpoint deleted')\n },\n [confirm, run],\n )\n\n if (!status?.configured) return null\n const labelFor = (event: string) => status.events.find((entry) => entry.event === event)?.label ?? event\n const atMax = endpoints.length >= status.maxEndpoints\n\n return (\n <CardDisplay\n header={'Order webhooks'}\n help={pluginDocsHelp('ordersAndReturns', { anchor: '#order-webhooks' })}\n contentGutterX\n contentGutterY\n HeaderProps={{\n action: (\n <Button size=\"small\" disabled={atMax || busy} onClick={() => setEditing('new')}>\n {'Add endpoint'}\n </Button>\n ),\n }}\n >\n <Stack spacing={2}>\n {endpoints.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Send order and return events to your own systems — a warehouse, an ERP or a ' +\n 'script. Each event is signed so your endpoint can check it came from this store.'}\n </Typography>\n ) : (\n endpoints.map((endpoint) => (\n <Stack key={endpoint.$id} spacing={1} data-testid=\"order-webhook-endpoint\">\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap' }}>\n <Typography variant=\"subtitle2\" sx={{ flex: 1, minWidth: 0, wordBreak: 'break-all' }}>\n {endpoint.description ? `${endpoint.description} · ${endpoint.url}` : endpoint.url}\n </Typography>\n <Chip\n size=\"small\"\n label={endpoint.enabled ? 'On' : 'Paused'}\n color={endpoint.enabled ? 'success' : 'default'}\n variant=\"outlined\"\n />\n {endpoint.lastDeliveryStatus ? (\n <Chip\n size=\"small\"\n label={`Last: ${Webhooks.ORDER_WEBHOOK_DELIVERY_STATUS_LABELS[endpoint.lastDeliveryStatus]}`}\n color={Webhooks.ORDER_WEBHOOK_DELIVERY_STATUS_COLOR[endpoint.lastDeliveryStatus]}\n variant=\"outlined\"\n />\n ) : null}\n </Stack>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`${endpoint.events.map(labelFor).join(', ')} · secret ends ${endpoint.secretHint}`}\n </Typography>\n <Stack direction=\"row\" spacing={1} sx={{ flexWrap: 'wrap' }}>\n <Button size=\"small\" disabled={busy || !endpoint.enabled} onClick={() => handleTest(endpoint)}>\n {'Send test event'}\n </Button>\n <Button size=\"small\" onClick={() => setLogFor(endpoint)}>\n {'Deliveries'}\n </Button>\n <Button size=\"small\" disabled={busy} onClick={() => setEditing(endpoint)}>\n {'Edit'}\n </Button>\n <Button\n size=\"small\"\n disabled={busy}\n onClick={() => run({ action: 'update', endpointId: endpoint.$id, enabled: !endpoint.enabled })}\n >\n {endpoint.enabled ? 'Pause' : 'Resume'}\n </Button>\n <Button size=\"small\" disabled={busy} onClick={() => handleRoll(endpoint)}>\n {'Roll secret'}\n </Button>\n <Button size=\"small\" color=\"error\" disabled={busy} onClick={() => handleDelete(endpoint)}>\n {'Delete'}\n </Button>\n </Stack>\n </Stack>\n ))\n )}\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`${endpoints.length}/${status.maxEndpoints} endpoints. A failed delivery is retried for about a day.`}\n </Typography>\n </Stack>\n {editing ? (\n <EndpointDialog\n endpoint={editing === 'new' ? null : editing}\n events={status.events}\n busy={busy}\n onClose={() => setEditing(null)}\n onSave={async (values) => {\n const payload =\n editing === 'new'\n ? await run({ action: 'create', ...values })\n : await run({ action: 'update', endpointId: editing.$id, ...values }, 'Endpoint saved')\n if (!payload) return\n setEditing(null)\n if (payload.secret) setSecret(String(payload.secret))\n }}\n />\n ) : null}\n {secret ? <SecretDialog secret={secret} onClose={() => setSecret(null)} /> : null}\n {logFor ? (\n <DeliveriesDialog\n hostId={hostId}\n endpoint={logFor}\n labelFor={labelFor}\n busy={busy}\n onResend={async (delivery) => {\n const payload = await run({ action: 'resend', deliveryId: delivery.$id })\n if (payload) {\n enqueueSnackbar(payload.delivered ? 'Delivered' : `Not delivered: ${payload.attempt?.error ?? 'no answer'}`, {\n variant: payload.delivered ? 'success' : 'warning',\n })\n }\n }}\n onClose={() => setLogFor(null)}\n />\n ) : null}\n </CardDisplay>\n )\n}\nOrderWebhooksCard.displayName = 'OrderWebhooksCard'\n\ninterface EndpointDialogProps {\n endpoint: EndpointRow | null\n events: WebhookEventOption[]\n busy: boolean\n onClose: () => void\n onSave: (values: { url: string; description: string; events: string[] }) => void | Promise<void>\n}\n\nfunction EndpointDialog(props: EndpointDialogProps) {\n const { endpoint, events, busy, onClose, onSave } = props\n const [url, setUrl] = useState(endpoint?.url ?? '')\n const [description, setDescription] = useState(endpoint?.description ?? '')\n const [chosen, setChosen] = useState<string[]>(endpoint?.events ?? events.map((entry) => entry.event))\n const toggle = (event: string) =>\n setChosen((current) => (current.includes(event) ? current.filter((entry) => entry !== event) : [...current, event]))\n const valid = /^https:\\/\\//i.test(url.trim()) && chosen.length > 0\n return (\n <Dialog open onClose={onClose} fullWidth maxWidth=\"sm\">\n <DialogTitle>{endpoint ? 'Edit endpoint' : 'Add endpoint'}</DialogTitle>\n <DialogContent>\n <Stack spacing={2} sx={{ pt: 1 }}>\n <TextField\n label=\"Endpoint URL\"\n value={url}\n onChange={(event) => setUrl(event.target.value)}\n placeholder=\"https://example.com/aglyn-webhooks\"\n helperText=\"An https address on a public server. Redirects are not followed.\"\n fullWidth\n autoFocus\n />\n <TextField\n label=\"Description\"\n value={description}\n onChange={(event) => setDescription(event.target.value)}\n placeholder=\"Warehouse\"\n fullWidth\n />\n <FormGroup>\n <Typography variant=\"subtitle2\">{'Events'}</Typography>\n {events.map((entry) => (\n <FormControlLabel\n key={entry.event}\n control={<Checkbox checked={chosen.includes(entry.event)} onChange={() => toggle(entry.event)} />}\n label={\n <Stack>\n <Typography variant=\"body2\">{`${entry.label} (${entry.event})`}</Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {entry.description}\n </Typography>\n </Stack>\n }\n />\n ))}\n </FormGroup>\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={onClose}>{'Cancel'}</Button>\n <Button\n variant=\"contained\"\n disabled={!valid || busy}\n onClick={() => onSave({ url: url.trim(), description: description.trim(), events: chosen })}\n >\n {endpoint ? 'Save' : 'Add endpoint'}\n </Button>\n </DialogActions>\n </Dialog>\n )\n}\n\nfunction SecretDialog(props: { secret: string; onClose: () => void }) {\n const { secret, onClose } = props\n const { enqueueSnackbar } = useSnackbar()\n return (\n <Dialog open onClose={onClose} fullWidth maxWidth=\"sm\">\n <DialogTitle>{'Signing secret'}</DialogTitle>\n <DialogContent>\n <Stack spacing={2}>\n <Alert severity=\"warning\">{'Copy it now. It is not shown again; roll a new one if it is lost.'}</Alert>\n <TextField value={secret} fullWidth slotProps={{ input: { readOnly: true } }} label=\"Secret\" />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`Each request carries an ${Webhooks.ORDER_WEBHOOK_SIGNATURE_HEADER} header, t=<timestamp>,v1=<signature>. ` +\n 'The signature is the HMAC-SHA256, in hex, of the timestamp, a period and the raw body, ' +\n `keyed with this secret. ${Webhooks.ORDER_WEBHOOK_EVENT_ID_HEADER} is the same on every retry.`}\n </Typography>\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button\n onClick={() => {\n void navigator.clipboard\n ?.writeText(secret)\n .then(() => enqueueSnackbar('Secret copied', { variant: 'success' }))\n }}\n >\n {'Copy'}\n </Button>\n <Button variant=\"contained\" onClick={onClose}>\n {'Done'}\n </Button>\n </DialogActions>\n </Dialog>\n )\n}\n\ninterface DeliveriesDialogProps {\n hostId: string\n endpoint: EndpointRow\n labelFor: (event: string) => string\n busy: boolean\n onResend: (delivery: DeliveryRow) => void | Promise<void>\n onClose: () => void\n}\n\n/** The endpoint's delivery log, newest first, filtered and paged by a Firestore query. */\nexport function DeliveriesDialog(props: DeliveriesDialogProps) {\n const { hostId, endpoint, labelFor, busy, onResend, onClose } = props\n const firestore = useFirestore()\n const [statusFilter, setStatusFilter] = useState<Webhooks.OrderWebhookDeliveryStatus | ''>('')\n const [openBody, setOpenBody] = useState<string | null>(null)\n const {\n rows: deliveries,\n hasMore,\n page,\n setPage,\n pageSize,\n setPageSize,\n } = usePagedCollection<DeliveryRow>(\n (pageLimit) =>\n query(\n collection(firestore, 'hosts', hostId, 'orderWebhookDeliveries'),\n where('endpointId', '==', endpoint.$id),\n ...(statusFilter ? [where('status', '==', statusFilter)] : []),\n orderBy('createdAtMs', 'desc'),\n limit(pageLimit),\n ),\n [firestore, hostId, endpoint.$id, statusFilter],\n { idField: '$id' },\n )\n return (\n <Dialog open onClose={onClose} fullWidth maxWidth=\"md\">\n <DialogTitle>{`Deliveries · ${endpoint.url}`}</DialogTitle>\n <DialogContent>\n <Stack spacing={2} sx={{ pt: 1 }}>\n <TextField\n select\n size=\"small\"\n label=\"Status\"\n value={statusFilter}\n onChange={(event) => setStatusFilter(event.target.value as Webhooks.OrderWebhookDeliveryStatus | '')}\n sx={{ maxWidth: 200 }}\n >\n <MenuItem value=\"\">{'All'}</MenuItem>\n {(Object.keys(Webhooks.ORDER_WEBHOOK_DELIVERY_STATUS_LABELS) as Webhooks.OrderWebhookDeliveryStatus[]).map((key) => (\n <MenuItem key={key} value={key}>\n {Webhooks.ORDER_WEBHOOK_DELIVERY_STATUS_LABELS[key]}\n </MenuItem>\n ))}\n </TextField>\n {deliveries.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No deliveries in the last 30 days.'}\n </Typography>\n ) : (\n deliveries.map((delivery) => {\n const last = delivery.attempts?.[delivery.attempts.length - 1]\n return (\n <Stack key={delivery.$id} spacing={0.5} data-testid=\"order-webhook-delivery\">\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap' }}>\n <Typography variant=\"body2\" sx={{ flex: 1 }}>\n {`${delivery.test ? 'Test event' : labelFor(delivery.event)} · ${new Date(delivery.createdAtMs).toLocaleString()}`}\n </Typography>\n <Chip\n size=\"small\"\n variant=\"outlined\"\n label={Webhooks.ORDER_WEBHOOK_DELIVERY_STATUS_LABELS[delivery.status]}\n color={Webhooks.ORDER_WEBHOOK_DELIVERY_STATUS_COLOR[delivery.status]}\n />\n <Button size=\"small\" onClick={() => setOpenBody(openBody === delivery.$id ? null : delivery.$id)}>\n {openBody === delivery.$id ? 'Hide body' : 'Body'}\n </Button>\n <Button size=\"small\" disabled={busy} onClick={() => onResend(delivery)}>\n {'Resend'}\n </Button>\n </Stack>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`${delivery.attempts?.length ?? 0} attempt${delivery.attempts?.length === 1 ? '' : 's'}` +\n (last ? ` · last ${last.httpStatus ?? 'no answer'}${last.error ? ` — ${last.error}` : ''}` : '')}\n </Typography>\n {openBody === delivery.$id ? (\n <TextField\n value={delivery.body}\n multiline\n maxRows={12}\n fullWidth\n slotProps={{ input: { readOnly: true } }}\n />\n ) : null}\n </Stack>\n )\n })\n )}\n <ListPagination\n page={page}\n pageSize={pageSize}\n rowCount={deliveries.length}\n hasMore={hasMore}\n onPageChange={setPage}\n onPageSizeChange={setPageSize}\n />\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={onClose}>{'Close'}</Button>\n </DialogActions>\n </Dialog>\n )\n}\n\nexport default OrderWebhooksCard\n"],"names":["pluginDocsHelp","CardDisplay","useConfirmationContext","ListPagination","useSnackbar","authorizedFetch","useFirestore","useFirestoreCollection","usePagedCollection","useUser","Alert","Button","Checkbox","Chip","Dialog","DialogActions","DialogContent","DialogTitle","FormControlLabel","FormGroup","MenuItem","Stack","TextField","Typography","collection","limit","orderBy","query","where","useCallback","useEffect","useState","Webhooks","ROUTE","callOrderWebhooks","user","hostId","body","payload","response","method","headers","JSON","stringify","json","catch","ok","Object","assign","Error","String","error","status","OrderWebhooksCard","props","firestore","data","enqueueSnackbar","confirm","setStatus","editing","setEditing","secret","setSecret","logFor","setLogFor","busy","setBusy","active","action","then","endpointDocs","ORDER_WEBHOOK_MAX_ENDPOINTS","idField","endpoints","run","success","variant","message","handleTest","endpoint","endpointId","$id","delivered","attempt","handleRoll","confirmed","title","description","confirmationText","handleDelete","url","confirmationButtonProps","color","configured","labelFor","event","events","find","entry","label","atMax","length","maxEndpoints","header","help","anchor","contentGutterX","contentGutterY","HeaderProps","size","disabled","onClick","spacing","map","data-testid","direction","sx","alignItems","flexWrap","flex","minWidth","wordBreak","enabled","lastDeliveryStatus","ORDER_WEBHOOK_DELIVERY_STATUS_LABELS","ORDER_WEBHOOK_DELIVERY_STATUS_COLOR","join","secretHint","EndpointDialog","onClose","onSave","values","SecretDialog","DeliveriesDialog","onResend","delivery","deliveryId","displayName","setUrl","setDescription","chosen","setChosen","toggle","current","includes","filter","valid","test","trim","open","fullWidth","maxWidth","pt","value","onChange","target","placeholder","helperText","autoFocus","control","checked","severity","slotProps","input","readOnly","ORDER_WEBHOOK_SIGNATURE_HEADER","ORDER_WEBHOOK_EVENT_ID_HEADER","navigator","clipboard","writeText","statusFilter","setStatusFilter","openBody","setOpenBody","rows","deliveries","hasMore","page","setPage","pageSize","setPageSize","pageLimit","select","keys","key","last","attempts","Date","createdAtMs","toLocaleString","httpStatus","multiline","maxRows","rowCount","onPageChange","onPageSizeChange"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/order-webhooks-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use client'\n\nimport { pluginDocsHelp } from '@aglyn/aglyn'\nimport { CardDisplay, useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport {\n useFirestore,\n useFirestoreCollection,\n usePagedCollection,\n useUser,\n} from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n Checkbox,\n Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n FormControlLabel,\n FormGroup,\n MenuItem,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { collection, limit, orderBy, query, where } from 'firebase/firestore'\nimport { useCallback, useEffect, useState } from 'react'\nimport * as Webhooks from '../../model/order-webhooks'\n\nexport interface OrderWebhooksCardProps {\n hostId: string\n}\n\ninterface WebhookEventOption {\n event: string\n label: string\n description: string\n}\n\ninterface WebhooksStatus {\n configured: boolean\n maxEndpoints: number\n events: WebhookEventOption[]\n}\n\ntype EndpointRow = Webhooks.OrderWebhookEndpoint & { $id: string }\ntype DeliveryRow = Webhooks.OrderWebhookDelivery & { $id: string }\n\nconst ROUTE = '/api/commerce/order-webhooks'\n\n/** POSTs one action to the webhooks route; answers the body or throws its error. */\nexport async function callOrderWebhooks(\n user: Parameters<typeof authorizedFetch>[0],\n hostId: string,\n body: Record<string, unknown>,\n): Promise<Record<string, any>> {\n const response = await authorizedFetch(user, ROUTE, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ hostId, ...body }),\n })\n const payload = (await response.json().catch(() => ({}))) as Record<string, any>\n if (!response.ok) throw Object.assign(new Error(String(payload.error ?? 'Request failed')), { status: response.status })\n return payload\n}\n\n/**\n * Order webhooks (AGL-3611): the store's endpoints for order and return\n * events, each with its signing secret, a test event and a delivery log. Shown\n * only to the people who may manage them — the route answers who — and only\n * where the deployment can sign.\n */\nexport function OrderWebhooksCard(props: OrderWebhooksCardProps) {\n const { hostId } = props\n const firestore = useFirestore()\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const { confirm } = useConfirmationContext()\n const [status, setStatus] = useState<WebhooksStatus | null>(null)\n const [editing, setEditing] = useState<EndpointRow | 'new' | null>(null)\n const [secret, setSecret] = useState<string | null>(null)\n const [logFor, setLogFor] = useState<EndpointRow | null>(null)\n const [busy, setBusy] = useState(false)\n\n useEffect(() => {\n if (!user) return\n let active = true\n callOrderWebhooks(user, hostId, { action: 'status' })\n .then((payload) => {\n if (active) setStatus(payload as WebhooksStatus)\n })\n .catch(() => {\n // Without \"Manage order webhooks\" (AGL-4047), or the route is down: the\n // card is not this person's to use.\n if (active) setStatus(null)\n })\n return () => {\n active = false\n }\n }, [user, hostId])\n\n const { data: endpointDocs } = useFirestoreCollection<EndpointRow>(\n () =>\n query(\n collection(firestore, 'hosts', hostId, 'orderWebhooks'),\n orderBy('createdAtMs', 'desc'),\n limit(Webhooks.ORDER_WEBHOOK_MAX_ENDPOINTS),\n ),\n [firestore, hostId],\n { idField: '$id' },\n )\n const endpoints = endpointDocs ?? []\n\n const run = useCallback(\n async (body: Record<string, unknown>, success?: string) => {\n setBusy(true)\n try {\n const payload = await callOrderWebhooks(user, hostId, body)\n if (success) enqueueSnackbar(success, { variant: 'success' })\n return payload\n } catch (error) {\n enqueueSnackbar(String((error as Error).message), { variant: 'error' })\n return null\n } finally {\n setBusy(false)\n }\n },\n [user, hostId, enqueueSnackbar],\n )\n\n const handleTest = useCallback(\n async (endpoint: EndpointRow) => {\n const payload = await run({ action: 'test', endpointId: endpoint.$id })\n if (!payload) return\n enqueueSnackbar(\n payload.delivered ? 'Test event delivered' : `Test event not delivered: ${payload.attempt?.error ?? 'no answer'}`,\n { variant: payload.delivered ? 'success' : 'warning' },\n )\n },\n [run, enqueueSnackbar],\n )\n\n const handleRoll = useCallback(\n async (endpoint: EndpointRow) => {\n const confirmed = await confirm({\n title: 'Roll the signing secret?',\n description:\n 'A new secret signs every event from now on. Update your endpoint before ' +\n 'the next order, or it will reject what it is sent.',\n confirmationText: 'Roll secret',\n })\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n const payload = await run({ action: 'roll-secret', endpointId: endpoint.$id })\n if (payload?.secret) setSecret(String(payload.secret))\n },\n [confirm, run],\n )\n\n const handleDelete = useCallback(\n async (endpoint: EndpointRow) => {\n const confirmed = await confirm({\n title: 'Delete this endpoint?',\n description: `${endpoint.url} stops receiving order events. Its delivery log is kept for 30 days.`,\n confirmationText: 'Delete',\n confirmationButtonProps: { color: 'error' },\n })\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n await run({ action: 'delete', endpointId: endpoint.$id }, 'Endpoint deleted')\n },\n [confirm, run],\n )\n\n if (!status?.configured) return null\n const labelFor = (event: string) => status.events.find((entry) => entry.event === event)?.label ?? event\n const atMax = endpoints.length >= status.maxEndpoints\n\n return (\n <CardDisplay\n header={'Order webhooks'}\n help={pluginDocsHelp('ordersAndReturns', { anchor: '#order-webhooks' })}\n contentGutterX\n contentGutterY\n HeaderProps={{\n action: (\n <Button size=\"small\" disabled={atMax || busy} onClick={() => setEditing('new')}>\n {'Add endpoint'}\n </Button>\n ),\n }}\n >\n <Stack spacing={2}>\n {endpoints.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Send order and return events to your own systems — a warehouse, an ERP or a ' +\n 'script. Each event is signed so your endpoint can check it came from this store.'}\n </Typography>\n ) : (\n endpoints.map((endpoint) => (\n <Stack key={endpoint.$id} spacing={1} data-testid=\"order-webhook-endpoint\">\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap' }}>\n <Typography variant=\"subtitle2\" sx={{ flex: 1, minWidth: 0, wordBreak: 'break-all' }}>\n {endpoint.description ? `${endpoint.description} · ${endpoint.url}` : endpoint.url}\n </Typography>\n <Chip\n size=\"small\"\n label={endpoint.enabled ? 'On' : 'Paused'}\n color={endpoint.enabled ? 'success' : 'default'}\n variant=\"outlined\"\n />\n {endpoint.lastDeliveryStatus ? (\n <Chip\n size=\"small\"\n label={`Last: ${Webhooks.ORDER_WEBHOOK_DELIVERY_STATUS_LABELS[endpoint.lastDeliveryStatus]}`}\n color={Webhooks.ORDER_WEBHOOK_DELIVERY_STATUS_COLOR[endpoint.lastDeliveryStatus]}\n variant=\"outlined\"\n />\n ) : null}\n </Stack>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`${endpoint.events.map(labelFor).join(', ')} · secret ends ${endpoint.secretHint}`}\n </Typography>\n <Stack direction=\"row\" spacing={1} sx={{ flexWrap: 'wrap' }}>\n <Button size=\"small\" disabled={busy || !endpoint.enabled} onClick={() => handleTest(endpoint)}>\n {'Send test event'}\n </Button>\n <Button size=\"small\" onClick={() => setLogFor(endpoint)}>\n {'Deliveries'}\n </Button>\n <Button size=\"small\" disabled={busy} onClick={() => setEditing(endpoint)}>\n {'Edit'}\n </Button>\n <Button\n size=\"small\"\n disabled={busy}\n onClick={() => run({ action: 'update', endpointId: endpoint.$id, enabled: !endpoint.enabled })}\n >\n {endpoint.enabled ? 'Pause' : 'Resume'}\n </Button>\n <Button size=\"small\" disabled={busy} onClick={() => handleRoll(endpoint)}>\n {'Roll secret'}\n </Button>\n <Button size=\"small\" color=\"error\" disabled={busy} onClick={() => handleDelete(endpoint)}>\n {'Delete'}\n </Button>\n </Stack>\n </Stack>\n ))\n )}\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`${endpoints.length}/${status.maxEndpoints} endpoints. A failed delivery is retried for about a day.`}\n </Typography>\n </Stack>\n {editing ? (\n <EndpointDialog\n endpoint={editing === 'new' ? null : editing}\n events={status.events}\n busy={busy}\n onClose={() => setEditing(null)}\n onSave={async (values) => {\n const payload =\n editing === 'new'\n ? await run({ action: 'create', ...values })\n : await run({ action: 'update', endpointId: editing.$id, ...values }, 'Endpoint saved')\n if (!payload) return\n setEditing(null)\n if (payload.secret) setSecret(String(payload.secret))\n }}\n />\n ) : null}\n {secret ? <SecretDialog secret={secret} onClose={() => setSecret(null)} /> : null}\n {logFor ? (\n <DeliveriesDialog\n hostId={hostId}\n endpoint={logFor}\n labelFor={labelFor}\n busy={busy}\n onResend={async (delivery) => {\n const payload = await run({ action: 'resend', deliveryId: delivery.$id })\n if (payload) {\n enqueueSnackbar(payload.delivered ? 'Delivered' : `Not delivered: ${payload.attempt?.error ?? 'no answer'}`, {\n variant: payload.delivered ? 'success' : 'warning',\n })\n }\n }}\n onClose={() => setLogFor(null)}\n />\n ) : null}\n </CardDisplay>\n )\n}\nOrderWebhooksCard.displayName = 'OrderWebhooksCard'\n\ninterface EndpointDialogProps {\n endpoint: EndpointRow | null\n events: WebhookEventOption[]\n busy: boolean\n onClose: () => void\n onSave: (values: { url: string; description: string; events: string[] }) => void | Promise<void>\n}\n\nfunction EndpointDialog(props: EndpointDialogProps) {\n const { endpoint, events, busy, onClose, onSave } = props\n const [url, setUrl] = useState(endpoint?.url ?? '')\n const [description, setDescription] = useState(endpoint?.description ?? '')\n const [chosen, setChosen] = useState<string[]>(endpoint?.events ?? events.map((entry) => entry.event))\n const toggle = (event: string) =>\n setChosen((current) => (current.includes(event) ? current.filter((entry) => entry !== event) : [...current, event]))\n const valid = /^https:\\/\\//i.test(url.trim()) && chosen.length > 0\n return (\n <Dialog open onClose={onClose} fullWidth maxWidth=\"sm\">\n <DialogTitle>{endpoint ? 'Edit endpoint' : 'Add endpoint'}</DialogTitle>\n <DialogContent>\n <Stack spacing={2} sx={{ pt: 1 }}>\n <TextField\n label=\"Endpoint URL\"\n value={url}\n onChange={(event) => setUrl(event.target.value)}\n placeholder=\"https://example.com/aglyn-webhooks\"\n helperText=\"An https address on a public server. Redirects are not followed.\"\n fullWidth\n autoFocus\n />\n <TextField\n label=\"Description\"\n value={description}\n onChange={(event) => setDescription(event.target.value)}\n placeholder=\"Warehouse\"\n fullWidth\n />\n <FormGroup>\n <Typography variant=\"subtitle2\">{'Events'}</Typography>\n {events.map((entry) => (\n <FormControlLabel\n key={entry.event}\n control={<Checkbox checked={chosen.includes(entry.event)} onChange={() => toggle(entry.event)} />}\n label={\n <Stack>\n <Typography variant=\"body2\">{`${entry.label} (${entry.event})`}</Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {entry.description}\n </Typography>\n </Stack>\n }\n />\n ))}\n </FormGroup>\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={onClose}>{'Cancel'}</Button>\n <Button\n variant=\"contained\"\n disabled={!valid || busy}\n onClick={() => onSave({ url: url.trim(), description: description.trim(), events: chosen })}\n >\n {endpoint ? 'Save' : 'Add endpoint'}\n </Button>\n </DialogActions>\n </Dialog>\n )\n}\n\nfunction SecretDialog(props: { secret: string; onClose: () => void }) {\n const { secret, onClose } = props\n const { enqueueSnackbar } = useSnackbar()\n return (\n <Dialog open onClose={onClose} fullWidth maxWidth=\"sm\">\n <DialogTitle>{'Signing secret'}</DialogTitle>\n <DialogContent>\n <Stack spacing={2}>\n <Alert severity=\"warning\">{'Copy it now. It is not shown again; roll a new one if it is lost.'}</Alert>\n <TextField value={secret} fullWidth slotProps={{ input: { readOnly: true } }} label=\"Secret\" />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`Each request carries an ${Webhooks.ORDER_WEBHOOK_SIGNATURE_HEADER} header, t=<timestamp>,v1=<signature>. ` +\n 'The signature is the HMAC-SHA256, in hex, of the timestamp, a period and the raw body, ' +\n `keyed with this secret. ${Webhooks.ORDER_WEBHOOK_EVENT_ID_HEADER} is the same on every retry.`}\n </Typography>\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button\n onClick={() => {\n void navigator.clipboard\n ?.writeText(secret)\n .then(() => enqueueSnackbar('Secret copied', { variant: 'success' }))\n }}\n >\n {'Copy'}\n </Button>\n <Button variant=\"contained\" onClick={onClose}>\n {'Done'}\n </Button>\n </DialogActions>\n </Dialog>\n )\n}\n\ninterface DeliveriesDialogProps {\n hostId: string\n endpoint: EndpointRow\n labelFor: (event: string) => string\n busy: boolean\n onResend: (delivery: DeliveryRow) => void | Promise<void>\n onClose: () => void\n}\n\n/** The endpoint's delivery log, newest first, filtered and paged by a Firestore query. */\nexport function DeliveriesDialog(props: DeliveriesDialogProps) {\n const { hostId, endpoint, labelFor, busy, onResend, onClose } = props\n const firestore = useFirestore()\n const [statusFilter, setStatusFilter] = useState<Webhooks.OrderWebhookDeliveryStatus | ''>('')\n const [openBody, setOpenBody] = useState<string | null>(null)\n const {\n rows: deliveries,\n hasMore,\n page,\n setPage,\n pageSize,\n setPageSize,\n } = usePagedCollection<DeliveryRow>(\n (pageLimit) =>\n query(\n collection(firestore, 'hosts', hostId, 'orderWebhookDeliveries'),\n where('endpointId', '==', endpoint.$id),\n ...(statusFilter ? [where('status', '==', statusFilter)] : []),\n orderBy('createdAtMs', 'desc'),\n limit(pageLimit),\n ),\n [firestore, hostId, endpoint.$id, statusFilter],\n { idField: '$id' },\n )\n return (\n <Dialog open onClose={onClose} fullWidth maxWidth=\"md\">\n <DialogTitle>{`Deliveries · ${endpoint.url}`}</DialogTitle>\n <DialogContent>\n <Stack spacing={2} sx={{ pt: 1 }}>\n <TextField\n select\n size=\"small\"\n label=\"Status\"\n value={statusFilter}\n onChange={(event) => setStatusFilter(event.target.value as Webhooks.OrderWebhookDeliveryStatus | '')}\n sx={{ maxWidth: 200 }}\n >\n <MenuItem value=\"\">{'All'}</MenuItem>\n {(Object.keys(Webhooks.ORDER_WEBHOOK_DELIVERY_STATUS_LABELS) as Webhooks.OrderWebhookDeliveryStatus[]).map((key) => (\n <MenuItem key={key} value={key}>\n {Webhooks.ORDER_WEBHOOK_DELIVERY_STATUS_LABELS[key]}\n </MenuItem>\n ))}\n </TextField>\n {deliveries.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No deliveries in the last 30 days.'}\n </Typography>\n ) : (\n deliveries.map((delivery) => {\n const last = delivery.attempts?.[delivery.attempts.length - 1]\n return (\n <Stack key={delivery.$id} spacing={0.5} data-testid=\"order-webhook-delivery\">\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap' }}>\n <Typography variant=\"body2\" sx={{ flex: 1 }}>\n {`${delivery.test ? 'Test event' : labelFor(delivery.event)} · ${new Date(delivery.createdAtMs).toLocaleString()}`}\n </Typography>\n <Chip\n size=\"small\"\n variant=\"outlined\"\n label={Webhooks.ORDER_WEBHOOK_DELIVERY_STATUS_LABELS[delivery.status]}\n color={Webhooks.ORDER_WEBHOOK_DELIVERY_STATUS_COLOR[delivery.status]}\n />\n <Button size=\"small\" onClick={() => setOpenBody(openBody === delivery.$id ? null : delivery.$id)}>\n {openBody === delivery.$id ? 'Hide body' : 'Body'}\n </Button>\n <Button size=\"small\" disabled={busy} onClick={() => onResend(delivery)}>\n {'Resend'}\n </Button>\n </Stack>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`${delivery.attempts?.length ?? 0} attempt${delivery.attempts?.length === 1 ? '' : 's'}` +\n (last ? ` · last ${last.httpStatus ?? 'no answer'}${last.error ? ` — ${last.error}` : ''}` : '')}\n </Typography>\n {openBody === delivery.$id ? (\n <TextField\n value={delivery.body}\n multiline\n maxRows={12}\n fullWidth\n slotProps={{ input: { readOnly: true } }}\n />\n ) : null}\n </Stack>\n )\n })\n )}\n <ListPagination\n page={page}\n pageSize={pageSize}\n rowCount={deliveries.length}\n hasMore={hasMore}\n onPageChange={setPage}\n onPageSizeChange={setPageSize}\n />\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={onClose}>{'Close'}</Button>\n </DialogActions>\n </Dialog>\n )\n}\n\nexport default OrderWebhooksCard\n"],"names":["pluginDocsHelp","CardDisplay","useConfirmationContext","ListPagination","useSnackbar","authorizedFetch","useFirestore","useFirestoreCollection","usePagedCollection","useUser","Alert","Button","Checkbox","Chip","Dialog","DialogActions","DialogContent","DialogTitle","FormControlLabel","FormGroup","MenuItem","Stack","TextField","Typography","collection","limit","orderBy","query","where","useCallback","useEffect","useState","Webhooks","ROUTE","callOrderWebhooks","user","hostId","body","payload","response","method","headers","JSON","stringify","json","catch","ok","Object","assign","Error","String","error","status","OrderWebhooksCard","props","firestore","data","enqueueSnackbar","confirm","setStatus","editing","setEditing","secret","setSecret","logFor","setLogFor","busy","setBusy","active","action","then","endpointDocs","ORDER_WEBHOOK_MAX_ENDPOINTS","idField","endpoints","run","success","variant","message","handleTest","endpoint","endpointId","$id","delivered","attempt","handleRoll","confirmed","title","description","confirmationText","handleDelete","url","confirmationButtonProps","color","configured","labelFor","event","events","find","entry","label","atMax","length","maxEndpoints","header","help","anchor","contentGutterX","contentGutterY","HeaderProps","size","disabled","onClick","spacing","map","data-testid","direction","sx","alignItems","flexWrap","flex","minWidth","wordBreak","enabled","lastDeliveryStatus","ORDER_WEBHOOK_DELIVERY_STATUS_LABELS","ORDER_WEBHOOK_DELIVERY_STATUS_COLOR","join","secretHint","EndpointDialog","onClose","onSave","values","SecretDialog","DeliveriesDialog","onResend","delivery","deliveryId","displayName","setUrl","setDescription","chosen","setChosen","toggle","current","includes","filter","valid","test","trim","open","fullWidth","maxWidth","pt","value","onChange","target","placeholder","helperText","autoFocus","control","checked","severity","slotProps","input","readOnly","ORDER_WEBHOOK_SIGNATURE_HEADER","ORDER_WEBHOOK_EVENT_ID_HEADER","navigator","clipboard","writeText","statusFilter","setStatusFilter","openBody","setOpenBody","rows","deliveries","hasMore","page","setPage","pageSize","setPageSize","pageLimit","select","keys","key","last","attempts","Date","createdAtMs","toLocaleString","httpStatus","multiline","maxRows","rowCount","onPageChange","onPageSizeChange"],"mappings":"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"}
@@ -165,6 +165,39 @@ function PosCheckInsBody(props) {
165
165
  minWidth: 150,
166
166
  valueGetter: (_value, row)=>row.customerName || row.customerEmail || ''
167
167
  },
168
+ {
169
+ // What a pass waiting at the window can still have done to it: a
170
+ // rain check, or one taken back; and a stay's guest count (AGL-4043).
171
+ // Not a value, so nothing to sort.
172
+ //
173
+ // Second, beside the name it acts on, rather than trailing. The grid
174
+ // has no column pinning, so a trailing column sat past the right edge
175
+ // of any window narrower than every column put together — on a
176
+ // laptop, and always on a phone — and the window could not see it.
177
+ field: 'rainCheckAction',
178
+ headerName: 'Actions',
179
+ minWidth: 150,
180
+ sortable: false,
181
+ filterable: false,
182
+ hideable: false,
183
+ disableColumnMenu: true,
184
+ renderCell: ({ row })=>row.source === 'reservation' && row.reservationId ? /*#__PURE__*/ _jsx(Button, {
185
+ size: "small",
186
+ disabled: !canManagePos,
187
+ onClick: ()=>setGuestsFor(row.reservationId),
188
+ children: 'Guests'
189
+ }) : row.source !== 'order' || row.status !== 'expected' ? null : row.rainCheck ? /*#__PURE__*/ _jsx(Button, {
190
+ size: "small",
191
+ disabled: !canManagePos,
192
+ onClick: ()=>void undoRainCheck(row),
193
+ children: 'Undo rain check'
194
+ }) : /*#__PURE__*/ _jsx(Button, {
195
+ size: "small",
196
+ disabled: !canManagePos || !todayKey,
197
+ onClick: ()=>setRainCheckRow(row),
198
+ children: 'Rain check'
199
+ })
200
+ },
168
201
  {
169
202
  field: 'itemName',
170
203
  headerName: 'Item',
@@ -225,34 +258,6 @@ function PosCheckInsBody(props) {
225
258
  headerName: 'Wristband',
226
259
  minWidth: 120,
227
260
  valueGetter: (_value, row)=>describeWristband(row.wristband)
228
- },
229
- {
230
- // What a pass waiting at the window can still have done to it: a
231
- // rain check, or one taken back; and a stay's guest count (AGL-4043).
232
- // Not a value, so nothing to sort.
233
- field: 'rainCheckAction',
234
- headerName: '',
235
- minWidth: 150,
236
- sortable: false,
237
- filterable: false,
238
- hideable: false,
239
- disableColumnMenu: true,
240
- renderCell: ({ row })=>row.source === 'reservation' && row.reservationId ? /*#__PURE__*/ _jsx(Button, {
241
- size: "small",
242
- disabled: !canManagePos,
243
- onClick: ()=>setGuestsFor(row.reservationId),
244
- children: 'Guests'
245
- }) : row.source !== 'order' || row.status !== 'expected' ? null : row.rainCheck ? /*#__PURE__*/ _jsx(Button, {
246
- size: "small",
247
- disabled: !canManagePos,
248
- onClick: ()=>void undoRainCheck(row),
249
- children: 'Undo rain check'
250
- }) : /*#__PURE__*/ _jsx(Button, {
251
- size: "small",
252
- disabled: !canManagePos || !todayKey,
253
- onClick: ()=>setRainCheckRow(row),
254
- children: 'Rain check'
255
- })
256
261
  }
257
262
  ], [
258
263
  list,