@aglyn/plugins-commerce 1.0.0-beta.277 → 1.0.0-beta.282

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 (212) hide show
  1. package/package.json +17 -14
  2. package/src/lib/components/commerce-console-page.js +33 -1
  3. package/src/lib/components/commerce-console-page.js.map +1 -1
  4. package/src/lib/components/console/payments-settings-card.component.js +1 -1
  5. package/src/lib/components/console/payments-settings-card.component.js.map +1 -1
  6. package/src/lib/components/console/pos/pos-hardware-api.d.ts +122 -0
  7. package/src/lib/components/console/pos/pos-hardware-api.js +90 -0
  8. package/src/lib/components/console/pos/pos-hardware-api.js.map +1 -0
  9. package/src/lib/components/console/pos/pos-reader-splash-api.d.ts +58 -0
  10. package/src/lib/components/console/pos/pos-reader-splash-api.js +101 -0
  11. package/src/lib/components/console/pos/pos-reader-splash-api.js.map +1 -0
  12. package/src/lib/components/console/pos-devices-card.component.js +5 -0
  13. package/src/lib/components/console/pos-devices-card.component.js.map +1 -1
  14. package/src/lib/components/console/pos-hardware-card.component.d.ts +26 -0
  15. package/src/lib/components/console/pos-hardware-card.component.js +456 -0
  16. package/src/lib/components/console/pos-hardware-card.component.js.map +1 -0
  17. package/src/lib/components/console/pos-hardware-orders-staff-page.component.d.ts +14 -0
  18. package/src/lib/components/console/pos-hardware-orders-staff-page.component.js +519 -0
  19. package/src/lib/components/console/pos-hardware-orders-staff-page.component.js.map +1 -0
  20. package/src/lib/components/console/pos-ops/pos-check-in-dialog.component.d.ts +27 -0
  21. package/src/lib/components/console/pos-ops/pos-check-in-dialog.component.js +444 -0
  22. package/src/lib/components/console/pos-ops/pos-check-in-dialog.component.js.map +1 -0
  23. package/src/lib/components/console/pos-ops/pos-check-in-zone.component.d.ts +9 -0
  24. package/src/lib/components/console/pos-ops/pos-check-in-zone.component.js +37 -0
  25. package/src/lib/components/console/pos-ops/pos-check-in-zone.component.js.map +1 -0
  26. package/src/lib/components/console/pos-ops/pos-check-ins-card.component.d.ts +24 -0
  27. package/src/lib/components/console/pos-ops/pos-check-ins-card.component.js +346 -0
  28. package/src/lib/components/console/pos-ops/pos-check-ins-card.component.js.map +1 -0
  29. package/src/lib/components/console/pos-ops/pos-customer-lookup.component.d.ts +5 -1
  30. package/src/lib/components/console/pos-ops/pos-customer-lookup.component.js +83 -4
  31. package/src/lib/components/console/pos-ops/pos-customer-lookup.component.js.map +1 -1
  32. package/src/lib/components/console/pos-ops/pos-day-totals-card.component.d.ts +35 -0
  33. package/src/lib/components/console/pos-ops/pos-day-totals-card.component.js +546 -0
  34. package/src/lib/components/console/pos-ops/pos-day-totals-card.component.js.map +1 -0
  35. package/src/lib/components/console/pos-ops/pos-operations-bar.component.d.ts +2 -1
  36. package/src/lib/components/console/pos-ops/pos-operations-bar.component.js +20 -1
  37. package/src/lib/components/console/pos-ops/pos-operations-bar.component.js.map +1 -1
  38. package/src/lib/components/console/pos-ops/pos-ops-api.d.ts +1 -1
  39. package/src/lib/components/console/pos-ops/pos-ops-api.js.map +1 -1
  40. package/src/lib/components/console/pos-page.component.js +6 -3
  41. package/src/lib/components/console/pos-page.component.js.map +1 -1
  42. package/src/lib/components/console/pos-reader-splash-setting.component.d.ts +22 -0
  43. package/src/lib/components/console/pos-reader-splash-setting.component.js +275 -0
  44. package/src/lib/components/console/pos-reader-splash-setting.component.js.map +1 -0
  45. package/src/lib/components/console/pos-reader-splash-staff-page.component.d.ts +14 -0
  46. package/src/lib/components/console/pos-reader-splash-staff-page.component.js +301 -0
  47. package/src/lib/components/console/pos-reader-splash-staff-page.component.js.map +1 -0
  48. package/src/lib/components/console/pos-zones.d.ts +37 -3
  49. package/src/lib/components/console/pos-zones.js +11 -1
  50. package/src/lib/components/console/pos-zones.js.map +1 -1
  51. package/src/lib/components/console/product-check-in-fields.component.d.ts +20 -0
  52. package/src/lib/components/console/product-check-in-fields.component.js +172 -0
  53. package/src/lib/components/console/product-check-in-fields.component.js.map +1 -0
  54. package/src/lib/components/console/product-editor-dialog.component.js +48 -9
  55. package/src/lib/components/console/product-editor-dialog.component.js.map +1 -1
  56. package/src/lib/components/console/product-register-fields.component.d.ts +12 -0
  57. package/src/lib/components/console/product-register-fields.component.js +39 -1
  58. package/src/lib/components/console/product-register-fields.component.js.map +1 -1
  59. package/src/lib/components/console/products-hub-card.component.js +21 -6
  60. package/src/lib/components/console/products-hub-card.component.js.map +1 -1
  61. package/src/lib/components/console/reservations-card.component.js +239 -70
  62. package/src/lib/components/console/reservations-card.component.js.map +1 -1
  63. package/src/lib/components/reservation-widget.js +16 -0
  64. package/src/lib/components/reservation-widget.js.map +1 -1
  65. package/src/lib/constants/operator-alerts.d.ts +6 -0
  66. package/src/lib/constants/operator-alerts.js +20 -1
  67. package/src/lib/constants/operator-alerts.js.map +1 -1
  68. package/src/lib/declarations.console-server.js +7 -0
  69. package/src/lib/declarations.console-server.js.map +1 -1
  70. package/src/lib/declarations.d.ts +26 -0
  71. package/src/lib/declarations.js +34 -0
  72. package/src/lib/declarations.js.map +1 -0
  73. package/src/lib/model/commerce-check-in-product.d.ts +37 -0
  74. package/src/lib/model/commerce-check-in-product.js +49 -0
  75. package/src/lib/model/commerce-check-in-product.js.map +1 -0
  76. package/src/lib/model/commerce-check-in.d.ts +187 -0
  77. package/src/lib/model/commerce-check-in.js +468 -0
  78. package/src/lib/model/commerce-check-in.js.map +1 -0
  79. package/src/lib/model/commerce-pos-day-report.d.ts +209 -0
  80. package/src/lib/model/commerce-pos-day-report.js +572 -0
  81. package/src/lib/model/commerce-pos-day-report.js.map +1 -0
  82. package/src/lib/model/commerce-pos-offline.d.ts +65 -3
  83. package/src/lib/model/commerce-pos-offline.js +104 -7
  84. package/src/lib/model/commerce-pos-offline.js.map +1 -1
  85. package/src/lib/model/commerce-pos-ops.d.ts +3 -0
  86. package/src/lib/model/commerce-pos-ops.js +1 -0
  87. package/src/lib/model/commerce-pos-ops.js.map +1 -1
  88. package/src/lib/model/commerce-pos.d.ts +43 -3
  89. package/src/lib/model/commerce-pos.js +73 -5
  90. package/src/lib/model/commerce-pos.js.map +1 -1
  91. package/src/lib/model/commerce-reservations.d.ts +73 -7
  92. package/src/lib/model/commerce-reservations.js +90 -10
  93. package/src/lib/model/commerce-reservations.js.map +1 -1
  94. package/src/lib/model/commerce.d.ts +40 -0
  95. package/src/lib/model/commerce.js +22 -4
  96. package/src/lib/model/commerce.js.map +1 -1
  97. package/src/lib/model/index.d.ts +1 -0
  98. package/src/lib/model/index.js +1 -0
  99. package/src/lib/model/index.js.map +1 -1
  100. package/src/lib/model/plugin-permissions.d.ts +1 -1
  101. package/src/lib/model/plugin-permissions.js +2 -1
  102. package/src/lib/model/plugin-permissions.js.map +1 -1
  103. package/src/lib/model/pos-hardware.d.ts +288 -0
  104. package/src/lib/model/pos-hardware.js +499 -0
  105. package/src/lib/model/pos-hardware.js.map +1 -0
  106. package/src/lib/model/pos-offline-card-limits.d.ts +20 -0
  107. package/src/lib/model/pos-offline-card-limits.js +28 -0
  108. package/src/lib/model/pos-offline-card-limits.js.map +1 -0
  109. package/src/lib/model/pos-reader-splash.d.ts +114 -0
  110. package/src/lib/model/pos-reader-splash.js +141 -0
  111. package/src/lib/model/pos-reader-splash.js.map +1 -0
  112. package/src/lib/model/product-write.d.ts +6 -0
  113. package/src/lib/plugin-config.d.ts +10 -0
  114. package/src/lib/plugin-config.js +40 -0
  115. package/src/lib/plugin-config.js.map +1 -1
  116. package/src/lib/plugin.js +28 -0
  117. package/src/lib/plugin.js.map +1 -1
  118. package/src/lib/server/cart-checkout.js +3 -1
  119. package/src/lib/server/cart-checkout.js.map +1 -1
  120. package/src/lib/server/cart.js +12 -1
  121. package/src/lib/server/cart.js.map +1 -1
  122. package/src/lib/server/catalog.js +49 -12
  123. package/src/lib/server/catalog.js.map +1 -1
  124. package/src/lib/server/checkout-extras.js +1 -1
  125. package/src/lib/server/checkout-extras.js.map +1 -1
  126. package/src/lib/server/checkout.js +7 -0
  127. package/src/lib/server/checkout.js.map +1 -1
  128. package/src/lib/server/person-eraser.d.ts +8 -1
  129. package/src/lib/server/person-eraser.js +23 -1
  130. package/src/lib/server/person-eraser.js.map +1 -1
  131. package/src/lib/server/pos-check-in.d.ts +49 -0
  132. package/src/lib/server/pos-check-in.js +652 -0
  133. package/src/lib/server/pos-check-in.js.map +1 -0
  134. package/src/lib/server/pos-day-report.d.ts +46 -0
  135. package/src/lib/server/pos-day-report.js +299 -0
  136. package/src/lib/server/pos-day-report.js.map +1 -0
  137. package/src/lib/server/pos-hardware-admin.d.ts +50 -0
  138. package/src/lib/server/pos-hardware-admin.js +366 -0
  139. package/src/lib/server/pos-hardware-admin.js.map +1 -0
  140. package/src/lib/server/pos-hardware-notify.d.ts +27 -0
  141. package/src/lib/server/pos-hardware-notify.js +142 -0
  142. package/src/lib/server/pos-hardware-notify.js.map +1 -0
  143. package/src/lib/server/pos-hardware-user-eraser.d.ts +26 -0
  144. package/src/lib/server/pos-hardware-user-eraser.js +67 -0
  145. package/src/lib/server/pos-hardware-user-eraser.js.map +1 -0
  146. package/src/lib/server/pos-hardware-webhook.d.ts +31 -0
  147. package/src/lib/server/pos-hardware-webhook.js +176 -0
  148. package/src/lib/server/pos-hardware-webhook.js.map +1 -0
  149. package/src/lib/server/pos-hardware.d.ts +58 -0
  150. package/src/lib/server/pos-hardware.js +234 -0
  151. package/src/lib/server/pos-hardware.js.map +1 -0
  152. package/src/lib/server/pos-offline-card-reconcile.d.ts +38 -0
  153. package/src/lib/server/pos-offline-card-reconcile.js +58 -0
  154. package/src/lib/server/pos-offline-card-reconcile.js.map +1 -0
  155. package/src/lib/server/pos-offline-sync.d.ts +32 -0
  156. package/src/lib/server/pos-offline-sync.js +191 -15
  157. package/src/lib/server/pos-offline-sync.js.map +1 -1
  158. package/src/lib/server/pos-ops-routes.d.ts +4 -2
  159. package/src/lib/server/pos-ops-routes.js +14 -2
  160. package/src/lib/server/pos-ops-routes.js.map +1 -1
  161. package/src/lib/server/pos-order.js +39 -51
  162. package/src/lib/server/pos-order.js.map +1 -1
  163. package/src/lib/server/pos-payment.js +8 -4
  164. package/src/lib/server/pos-payment.js.map +1 -1
  165. package/src/lib/server/pos-reader-splash-admin.d.ts +36 -0
  166. package/src/lib/server/pos-reader-splash-admin.js +295 -0
  167. package/src/lib/server/pos-reader-splash-admin.js.map +1 -0
  168. package/src/lib/server/pos-reader-splash-render.d.ts +40 -0
  169. package/src/lib/server/pos-reader-splash-render.js +138 -0
  170. package/src/lib/server/pos-reader-splash-render.js.map +1 -0
  171. package/src/lib/server/pos-reader-splash-server.d.ts +32 -0
  172. package/src/lib/server/pos-reader-splash-server.js +32 -0
  173. package/src/lib/server/pos-reader-splash-server.js.map +1 -0
  174. package/src/lib/server/pos-reader-splash.d.ts +105 -0
  175. package/src/lib/server/pos-reader-splash.js +548 -0
  176. package/src/lib/server/pos-reader-splash.js.map +1 -0
  177. package/src/lib/server/pos-sale.d.ts +2 -1
  178. package/src/lib/server/pos-sale.js +3 -1
  179. package/src/lib/server/pos-sale.js.map +1 -1
  180. package/src/lib/server/pos-terminal.d.ts +67 -4
  181. package/src/lib/server/pos-terminal.js +239 -32
  182. package/src/lib/server/pos-terminal.js.map +1 -1
  183. package/src/lib/server/process-restock.js +4 -0
  184. package/src/lib/server/process-restock.js.map +1 -1
  185. package/src/lib/server/product-card.js +2 -2
  186. package/src/lib/server/product-card.js.map +1 -1
  187. package/src/lib/server/product-catalog.d.ts +2 -0
  188. package/src/lib/server/product-catalog.js +11 -4
  189. package/src/lib/server/product-catalog.js.map +1 -1
  190. package/src/lib/server/product.js +3 -1
  191. package/src/lib/server/product.js.map +1 -1
  192. package/src/lib/server/related.js +8 -1
  193. package/src/lib/server/related.js.map +1 -1
  194. package/src/lib/server/reservation-availability-query.d.ts +35 -0
  195. package/src/lib/server/reservation-availability-query.js +34 -0
  196. package/src/lib/server/reservation-availability-query.js.map +1 -0
  197. package/src/lib/server/reservation-availability.d.ts +21 -0
  198. package/src/lib/server/reservation-availability.js +64 -10
  199. package/src/lib/server/reservation-availability.js.map +1 -1
  200. package/src/lib/server/reservation-walk-in.d.ts +28 -0
  201. package/src/lib/server/reservation-walk-in.js +166 -0
  202. package/src/lib/server/reservation-walk-in.js.map +1 -0
  203. package/src/lib/server/reserve.js +13 -10
  204. package/src/lib/server/reserve.js.map +1 -1
  205. package/src/lib/server/reviews.js +3 -1
  206. package/src/lib/server/reviews.js.map +1 -1
  207. package/src/lib/server/site-page-resolver.js +5 -2
  208. package/src/lib/server/site-page-resolver.js.map +1 -1
  209. package/src/lib/server.js +26 -0
  210. package/src/lib/server.js.map +1 -1
  211. package/src/lib/testing/fake-firestore.js +72 -0
  212. package/src/lib/testing/fake-firestore.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/reservations-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'use client'\n\nimport * as Aglyn from '@aglyn/aglyn'\nimport * as CommerceModel from '../../model'\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 {\n Button,\n Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n MenuItem,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport {\n collection,\n deleteDoc,\n doc,\n getDocs,\n limit,\n orderBy,\n query,\n setDoc,\n updateDoc,\n where,\n} from 'firebase/firestore'\nimport { useCallback, useMemo, useState } from 'react'\nimport { useFirestore, useMerchantPayments } from '@aglyn/tenant-feature-instance'\nimport { PrerequisiteNotice } from '@aglyn/shared-ui-jsx/components/prerequisite-notice.component'\nimport {\n useFirestoreCollection,\n usePagedCollection,\n writeGuardedBySeed,\n} from '@aglyn/tenant-feature-instance'\nimport { pluginDocsHelp } from '@aglyn/aglyn'\n\nexport interface ReservationsCardProps {\n hostId: string\n}\n\nconst STATUS_COLOR: Record<\n string,\n 'default' | 'success' | 'warning' | 'error' | 'info'\n> = {\n pending: 'warning',\n confirmed: 'info',\n checked_in: 'success',\n checked_out: 'default',\n cancelled: 'default',\n no_show: 'error',\n}\n\nconst usd = (cents: number | undefined) =>\n `$${((cents ?? 0) / 100).toFixed(2)}`\nconst day = (dayMs: number | undefined) =>\n dayMs ? new Date(dayMs).toISOString().slice(0, 10) : '—'\n\n/**\n * Reservations console (AGL-311): resources CRUD (nightly pricing,\n * seasons via multipliers, deposits) plus the reservations list with\n * check-in/out, no-show, cancel, and walk-in creation. The storefront\n * widget books against the same docs.\n */\nexport function ReservationsCard(props: ReservationsCardProps) {\n const { hostId } = props\n // Every reservation is paid when it is booked, so a store that cannot\n // charge turns each guest away; the card says so up front.\n const payments = useMerchantPayments(hostId)\n const firestore = useFirestore()\n const { enqueueSnackbar } = useSnackbar()\n const { confirm } = useConfirmationContext()\n const {\n data: resourceDocs,\n status: resourcesStatus,\n /**\n * The rows this editor is seeded from are unconfirmed by the server\n * (AGL-1358). Editing spreads a whole stored resource into\n * `resourceDraft` and writes all of it back, so `merge: true` protects\n * nothing. `blocks` is the field that costs more than money: it is the\n * manually blocked date ranges `isRangeAvailable` reads, so a cached seed\n * re-opens dates that were closed and lets the next guest book a room\n * that is already spoken for.\n */\n fromCache: resourcesFromCache,\n } = useFirestoreCollection<any>(\n () => query(collection(firestore, 'hosts', hostId, 'resources'), limit(50)),\n [firestore, hostId],\n { idField: '$id' },\n )\n /*\n * Ordered by the server, and a growing window rather than a fixed 300.\n *\n * `limit(300)` carried no `orderBy`, so Firestore returned DOCUMENT-ID\n * order and the client sort below arranged that pseudo-random sample by\n * check-in. The ordering is unchanged — earliest check-in first, exactly\n * what the sort did — it is now the QUERY's, so the window is the real\n * front of the list and the rest can be reached.\n */\n const {\n rows: reservations,\n hasMore,\n page,\n setPage,\n pageSize,\n setPageSize,\n } = usePagedCollection<any>(\n (pageLimit) =>\n query(\n collection(firestore, 'hosts', hostId, 'reservations'),\n orderBy('checkInDayMs', 'asc'),\n limit(pageLimit),\n ),\n [firestore, hostId],\n { idField: '$id' },\n )\n const resourceNames = useMemo(() => {\n const map: Record<string, string> = {}\n for (const resource of resourceDocs ?? []) {\n map[resource.$id] = resource.name\n }\n return map\n }, [resourceDocs])\n\n const [resourceDraft, setResourceDraft] = useState<\n (Partial<CommerceModel.HostResource> & { id: string | null }) | null\n >(null)\n const [walkIn, setWalkIn] = useState<{\n resourceId: string\n checkIn: string\n checkOut: string\n guestName: string\n } | null>(null)\n\n const handleResourceSave = useCallback(async () => {\n if (!resourceDraft?.name?.trim() || !(resourceDraft.nightlyRateUsd! > 0)) {\n return\n }\n /**\n * `$id` comes off with `id` (AGL-1374).\n *\n * The editor is seeded `{ id: resource.$id, ...resource }`, and that\n * spread carries the listener's SYNTHETIC `$id` — `idField: '$id'`\n * stamps the document id onto the in-memory row, where nothing persists\n * it. Destructuring only `id` left `$id` in `data`, so every save wrote\n * it into the stored document as a real field. Nothing reads it; it is\n * the listener's bookkeeping leaking into storage, and once there no\n * reader can tell it from a field the card meant to write.\n */\n const seeded = resourceDraft as typeof resourceDraft & { $id?: string }\n const { id, $id: _syntheticId, ...data } = seeded\n /**\n * Refuse an EDIT whose seed the server never confirmed (AGL-1358).\n * `data` is the whole stored row spread out of the listener, so\n * `merge: true` has nothing left to protect — every field is present,\n * `blocks` and the seasonal multipliers included.\n *\n * Only the edit path. A NEW resource is built from defaults at a fresh\n * uid and can overwrite nothing, and the first snapshot of any listener\n * is `fromCache: true`, so guarding a create would refuse a save that was\n * never unsafe.\n *\n * The guard WRAPS the write — an early return is a shape you can keep\n * while losing the protection.\n */\n const verdict = await writeGuardedBySeed(\n {\n subject: 'resource',\n unreadable: Boolean(id) && resourcesStatus === 'error',\n fromCache: Boolean(id) && resourcesFromCache,\n },\n async () => {\n await setDoc(\n doc(\n firestore,\n 'hosts',\n hostId,\n 'resources',\n id ?? Aglyn.createResourceUid(),\n ),\n {\n ...data,\n name: resourceDraft.name!.trim().slice(0, 120),\n },\n { merge: true },\n )\n },\n )\n // Before `setResourceDraft(null)`, so a refusal keeps the dialog open\n // with what was typed.\n if (!verdict.ok) {\n return void enqueueSnackbar(verdict.message, {\n variant: 'warning',\n persist: false,\n })\n }\n setResourceDraft(null)\n enqueueSnackbar('Resource saved', { variant: 'success', persist: false })\n }, [\n resourceDraft,\n firestore,\n hostId,\n enqueueSnackbar,\n resourcesStatus,\n resourcesFromCache,\n ])\n\n const handleStatus = useCallback(\n (reservation: any, status: CommerceModel.ReservationStatus) => async () => {\n await updateDoc(\n doc(firestore, 'hosts', hostId, 'reservations', reservation.$id),\n { status },\n )\n },\n [firestore, hostId],\n )\n\n const handleWalkIn = useCallback(async () => {\n if (!walkIn?.resourceId || !walkIn.checkIn || !walkIn.checkOut) return\n const resource = (resourceDocs ?? []).find(\n (item: any) => item.$id === walkIn.resourceId,\n ) as CommerceModel.HostResource | undefined\n if (!resource) return\n const checkInDayMs = Date.parse(`${walkIn.checkIn}T00:00:00Z`)\n const checkOutDayMs = Date.parse(`${walkIn.checkOut}T00:00:00Z`)\n /*\n * The stay is priced FIRST, and its `problem` stops the walk-in: the quote\n * is the one rule for what can be booked (no nights, under the minimum\n * stay), shared with the booking widget and both native apps. Ignoring it\n * wrote a confirmed zero-night, $0 stay for a check-out on the arrival\n * day (AGL-3794).\n */\n const quote = CommerceModel.computeReservationQuote(\n resource,\n checkInDayMs,\n checkOutDayMs,\n )\n if (quote.problem) {\n return void enqueueSnackbar(quote.problem, {\n variant: 'warning',\n persist: false,\n })\n }\n /*\n * Every stay of THIS resource that ends after the walk-in arrives — the\n * same query `reserve.ts` checks a booking against, on the composite it\n * already has (AGL-3321). This used to test the rows on the card's\n * current page, so a stay on the next page did not block the dates and\n * the desk could double-book the room.\n */\n const overlapping = await getDocs(\n query(\n collection(firestore, 'hosts', hostId, 'reservations'),\n where('resourceId', '==', walkIn.resourceId),\n where('checkOutDayMs', '>', checkInDayMs),\n orderBy('checkOutDayMs'),\n limit(500),\n ),\n ).catch(() => null)\n if (!overlapping) {\n return void enqueueSnackbar('Could not check those dates — try again', {\n variant: 'warning',\n persist: false,\n })\n }\n // `createdAtMs` carried through because the pending hold lapses on it\n // (`reservationHoldsDates`). Dropping it made every unpaid `pending` row\n // block forever, so the front desk was refused a walk-in into a room that\n // an abandoned online checkout had left empty.\n const existing = overlapping.docs\n .map((snapshot) => snapshot.data() as any)\n .map((item: any) => ({\n checkInDayMs: item.checkInDayMs,\n checkOutDayMs: item.checkOutDayMs,\n status: item.status,\n createdAtMs: item.createdAtMs,\n }))\n if (\n !CommerceModel.isRangeAvailable(resource, existing, checkInDayMs, checkOutDayMs)\n ) {\n return void enqueueSnackbar('Those dates are taken', {\n variant: 'warning',\n persist: false,\n })\n }\n await setDoc(\n doc(\n firestore,\n 'hosts',\n hostId,\n 'reservations',\n Aglyn.createResourceUid(),\n ),\n {\n resourceId: walkIn.resourceId,\n status: 'confirmed',\n checkInDayMs,\n checkOutDayMs,\n guestName: walkIn.guestName || null,\n guestEmail: null,\n nights: quote.nights,\n totalCents: quote.totalCents,\n depositCents: 0,\n paidCents: 0,\n createdAtMs: Date.now(),\n } satisfies CommerceModel.HostReservation,\n )\n setWalkIn(null)\n enqueueSnackbar('Walk-in reserved — collect payment at POS', {\n variant: 'success',\n persist: false,\n })\n }, [walkIn, resourceDocs, firestore, hostId, enqueueSnackbar])\n\n return (\n <CardDisplay\n header={'Reservations'}\n help={pluginDocsHelp('pos', { anchor: '#reservations' })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1}>\n {payments.blocked ? (\n <PrerequisiteNotice severity=\"warning\" href={payments.href ?? undefined} actionLabel=\"Open Payments\">\n {`${payments.reason ?? ''} Guests pay when they reserve, so they can't reserve until it is set up.`}\n </PrerequisiteNotice>\n ) : null}\n <Typography variant=\"subtitle2\">{'Resources'}</Typography>\n {(resourceDocs ?? []).map((resource: any) => (\n <Stack\n key={resource.$id}\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center' }}\n >\n <Stack sx={{ flex: 1, minWidth: 0 }}>\n <Typography variant=\"body2\" noWrap>\n {`${resource.name} · $${resource.nightlyRateUsd}/night`}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {`id: ${resource.$id}`}\n </Typography>\n </Stack>\n <Button\n size=\"small\"\n onClick={() => setResourceDraft({ id: resource.$id, ...resource })}\n >\n {'Edit'}\n </Button>\n <Button\n size=\"small\"\n color=\"error\"\n onClick={async () => {\n const confirmed = await confirm({\n title: 'Remove this resource?',\n description:\n 'Existing reservations keep their records; the widget ' +\n 'stops accepting new stays.',\n confirmationText: 'Remove',\n confirmationButtonProps: { color: 'error' },\n })\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n await deleteDoc(\n doc(firestore, 'hosts', hostId, 'resources', resource.$id),\n )\n }}\n >\n {'Remove'}\n </Button>\n </Stack>\n ))}\n <Stack direction=\"row\" spacing={1}>\n <Button\n size=\"small\"\n onClick={() =>\n setResourceDraft({\n id: null,\n name: '',\n nightlyRateUsd: 100,\n depositPct: 30,\n minNights: 1,\n })\n }\n >\n {'Add resource'}\n </Button>\n <Button\n size=\"small\"\n disabled={(resourceDocs?.length ?? 0) === 0}\n onClick={() =>\n setWalkIn({\n resourceId: resourceDocs?.[0]?.$id ?? '',\n checkIn: '',\n checkOut: '',\n guestName: '',\n })\n }\n >\n {'Walk-in'}\n </Button>\n </Stack>\n\n <Typography variant=\"subtitle2\" sx={{ mt: 1 }}>\n {'Upcoming & recent stays'}\n </Typography>\n {reservations.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Reservations from the widget (and walk-ins) appear here.'}\n </Typography>\n ) : (\n reservations.map((reservation: any) => (\n <Stack\n key={reservation.$id}\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center' }}\n >\n <Stack sx={{ flex: 1, minWidth: 0 }}>\n <Typography variant=\"body2\" noWrap>\n {`${resourceNames[reservation.resourceId] ?? '?'} · ` +\n `${day(reservation.checkInDayMs)} → ${day(reservation.checkOutDayMs)}`}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {[\n reservation.guestName ?? reservation.guestEmail ?? 'Guest',\n `${usd(reservation.paidCents)} / ${usd(reservation.totalCents)} paid`,\n ].join(' · ')}\n </Typography>\n </Stack>\n <Chip\n label={String(reservation.status).replace('_', ' ')}\n size=\"small\"\n variant=\"outlined\"\n color={STATUS_COLOR[reservation.status] ?? 'default'}\n />\n {reservation.status === 'confirmed' ? (\n <>\n <Button size=\"small\" onClick={handleStatus(reservation, 'checked_in')}>\n {'Check in'}\n </Button>\n <Button\n size=\"small\"\n color=\"error\"\n onClick={handleStatus(reservation, 'no_show')}\n >\n {'No-show'}\n </Button>\n </>\n ) : null}\n {reservation.status === 'checked_in' ? (\n <Button\n size=\"small\"\n onClick={async () => {\n // Folio settlement (AGL-317): surface unpaid room\n // charges before the guest leaves.\n const folio = (reservation.folio ?? []) as Array<{\n amountCents: number\n }>\n const folioCents = folio.reduce(\n (sum, entry) => sum + (entry.amountCents ?? 0),\n 0,\n )\n const balanceCents =\n (reservation.totalCents ?? 0) -\n (reservation.paidCents ?? 0)\n if (folioCents > 0 || balanceCents > 0) {\n const confirmed = await confirm({\n title: 'Check out with open balance?',\n description:\n `${folioCents > 0 ? `Room charges: ${usd(folioCents)} (already recorded as paid POS orders). ` : ''}` +\n `${balanceCents > 0 ? `Stay balance due: ${usd(balanceCents)} — collect at the register.` : ''}`,\n confirmationText: 'Check out',\n })\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n }\n await handleStatus(reservation, 'checked_out')()\n }}\n >\n {'Check out'}\n </Button>\n ) : null}\n {['pending', 'confirmed'].includes(reservation.status) ? (\n <Button\n size=\"small\"\n color=\"error\"\n onClick={handleStatus(reservation, 'cancelled')}\n >\n {'Cancel'}\n </Button>\n ) : null}\n </Stack>\n ))\n )}\n <ListPagination\n page={page}\n pageSize={pageSize}\n rowCount={reservations.length}\n hasMore={hasMore}\n onPageChange={setPage}\n onPageSizeChange={setPageSize}\n />\n </Stack>\n\n <Dialog\n open={Boolean(resourceDraft)}\n onClose={() => setResourceDraft(null)}\n maxWidth=\"xs\"\n fullWidth\n >\n <DialogTitle>\n {resourceDraft?.id ? 'Edit resource' : 'New resource'}\n </DialogTitle>\n <DialogContent sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>\n <TextField\n label=\"Name\"\n value={resourceDraft?.name ?? ''}\n onChange={(event) =>\n setResourceDraft((prev) =>\n prev ? { ...prev, name: event.target.value } : prev,\n )\n }\n size=\"small\"\n autoFocus\n sx={{ mt: 1 }}\n placeholder=\"Lakeside cabin\"\n />\n <Stack direction=\"row\" spacing={1}>\n <TextField\n label=\"Nightly rate ($)\"\n value={resourceDraft?.nightlyRateUsd ?? ''}\n onChange={(event) =>\n setResourceDraft((prev) =>\n prev\n ? {\n ...prev,\n nightlyRateUsd: Number(event.target.value) || 0,\n }\n : prev,\n )\n }\n size=\"small\"\n slotProps={{ htmlInput: { inputMode: 'decimal' } }}\n />\n <TextField\n label=\"Weekend ×\"\n placeholder=\"1\"\n value={resourceDraft?.weekendMultiplier ?? ''}\n onChange={(event) =>\n setResourceDraft((prev) =>\n prev\n ? {\n ...prev,\n weekendMultiplier:\n Number(event.target.value) || undefined,\n }\n : prev,\n )\n }\n size=\"small\"\n slotProps={{ htmlInput: { inputMode: 'decimal' } }}\n />\n </Stack>\n <Stack direction=\"row\" spacing={1}>\n <TextField\n label=\"Min nights\"\n value={resourceDraft?.minNights ?? ''}\n onChange={(event) =>\n setResourceDraft((prev) =>\n prev\n ? {\n ...prev,\n minNights:\n Math.round(Number(event.target.value)) || undefined,\n }\n : prev,\n )\n }\n size=\"small\"\n slotProps={{ htmlInput: { inputMode: 'numeric' } }}\n />\n <TextField\n label=\"Deposit %\"\n placeholder=\"100 = pay in full\"\n value={resourceDraft?.depositPct ?? ''}\n onChange={(event) =>\n setResourceDraft((prev) =>\n prev\n ? {\n ...prev,\n depositPct:\n Math.round(Number(event.target.value)) || undefined,\n }\n : prev,\n )\n }\n size=\"small\"\n slotProps={{ htmlInput: { inputMode: 'numeric' } }}\n />\n <TextField\n label=\"Free cancel (hrs)\"\n value={resourceDraft?.cancellationHours ?? ''}\n onChange={(event) =>\n setResourceDraft((prev) =>\n prev\n ? {\n ...prev,\n cancellationHours:\n Math.round(Number(event.target.value)) || undefined,\n }\n : prev,\n )\n }\n size=\"small\"\n slotProps={{ htmlInput: { inputMode: 'numeric' } }}\n />\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setResourceDraft(null)}>{'Cancel'}</Button>\n <Button\n variant=\"contained\"\n color=\"primary\"\n disabled={\n !resourceDraft?.name?.trim() ||\n !((resourceDraft?.nightlyRateUsd ?? 0) > 0)\n }\n onClick={handleResourceSave}\n >\n {'Save'}\n </Button>\n </DialogActions>\n </Dialog>\n\n <Dialog\n open={Boolean(walkIn)}\n onClose={() => setWalkIn(null)}\n maxWidth=\"xs\"\n fullWidth\n >\n <DialogTitle>{'Walk-in reservation'}</DialogTitle>\n <DialogContent sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>\n <TextField\n label=\"Resource\"\n value={walkIn?.resourceId ?? ''}\n onChange={(event) =>\n setWalkIn((prev) =>\n prev ? { ...prev, resourceId: event.target.value } : prev,\n )\n }\n size=\"small\"\n select\n sx={{ mt: 1 }}\n >\n {(resourceDocs ?? []).map((resource: any) => (\n <MenuItem key={resource.$id} value={resource.$id}>\n {resource.name}\n </MenuItem>\n ))}\n </TextField>\n <Stack direction=\"row\" spacing={1}>\n <TextField\n label=\"Check-in\"\n type=\"date\"\n value={walkIn?.checkIn ?? ''}\n onChange={(event) =>\n setWalkIn((prev) =>\n prev ? { ...prev, checkIn: event.target.value } : prev,\n )\n }\n size=\"small\"\n fullWidth\n slotProps={{ inputLabel: { shrink: true } }}\n />\n <TextField\n label=\"Check-out\"\n type=\"date\"\n value={walkIn?.checkOut ?? ''}\n onChange={(event) =>\n setWalkIn((prev) =>\n prev ? { ...prev, checkOut: event.target.value } : prev,\n )\n }\n size=\"small\"\n fullWidth\n slotProps={{ inputLabel: { shrink: true } }}\n />\n </Stack>\n <TextField\n label=\"Guest name\"\n value={walkIn?.guestName ?? ''}\n onChange={(event) =>\n setWalkIn((prev) =>\n prev ? { ...prev, guestName: event.target.value } : prev,\n )\n }\n size=\"small\"\n />\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setWalkIn(null)}>{'Cancel'}</Button>\n <Button\n variant=\"contained\"\n color=\"primary\"\n disabled={!walkIn?.resourceId || !walkIn?.checkIn || !walkIn?.checkOut}\n onClick={handleWalkIn}\n >\n {'Reserve'}\n </Button>\n </DialogActions>\n </Dialog>\n </CardDisplay>\n )\n}\nReservationsCard.displayName = 'ReservationsCard'\n\nexport default ReservationsCard\n"],"names":["Aglyn","CommerceModel","CardDisplay","useConfirmationContext","ListPagination","useSnackbar","Button","Chip","Dialog","DialogActions","DialogContent","DialogTitle","MenuItem","Stack","TextField","Typography","collection","deleteDoc","doc","getDocs","limit","orderBy","query","setDoc","updateDoc","where","useCallback","useMemo","useState","useFirestore","useMerchantPayments","PrerequisiteNotice","useFirestoreCollection","usePagedCollection","writeGuardedBySeed","pluginDocsHelp","STATUS_COLOR","pending","confirmed","checked_in","checked_out","cancelled","no_show","usd","cents","toFixed","day","dayMs","Date","toISOString","slice","ReservationsCard","props","payments","resourceDraft","hostId","firestore","enqueueSnackbar","confirm","data","resourceDocs","status","resourcesStatus","fromCache","resourcesFromCache","idField","rows","reservations","hasMore","page","setPage","pageSize","setPageSize","pageLimit","resourceNames","map","resource","$id","name","setResourceDraft","walkIn","setWalkIn","handleResourceSave","trim","nightlyRateUsd","seeded","id","_syntheticId","verdict","subject","unreadable","Boolean","createResourceUid","merge","ok","message","variant","persist","handleStatus","reservation","handleWalkIn","resourceId","checkIn","checkOut","find","item","checkInDayMs","parse","checkOutDayMs","quote","computeReservationQuote","problem","overlapping","catch","existing","docs","snapshot","createdAtMs","isRangeAvailable","guestName","guestEmail","nights","totalCents","depositCents","paidCents","now","header","help","anchor","contentGutterX","contentGutterY","spacing","blocked","severity","href","undefined","actionLabel","reason","direction","sx","alignItems","flex","minWidth","noWrap","color","size","onClick","title","description","confirmationText","confirmationButtonProps","then","depositPct","minNights","disabled","length","mt","join","label","String","replace","folio","folioCents","reduce","sum","entry","amountCents","balanceCents","includes","rowCount","onPageChange","onPageSizeChange","open","onClose","maxWidth","fullWidth","display","flexDirection","gap","value","onChange","event","prev","target","autoFocus","placeholder","Number","slotProps","htmlInput","inputMode","weekendMultiplier","Math","round","cancellationHours","select","type","inputLabel","shrink","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/reservations-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'use client'\n\nimport * as Aglyn from '@aglyn/aglyn'\nimport * as CommerceModel from '../../model'\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 {\n Button,\n Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n MenuItem,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport {\n collection,\n deleteDoc,\n doc,\n getDocs,\n limit,\n orderBy,\n query,\n setDoc,\n updateDoc,\n where,\n} from 'firebase/firestore'\nimport { useCallback, useMemo, useState } from 'react'\nimport { useFirestore, useMerchantPayments, useUser } from '@aglyn/tenant-feature-instance'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { PrerequisiteNotice } from '@aglyn/shared-ui-jsx/components/prerequisite-notice.component'\nimport {\n useFirestoreCollection,\n usePagedCollection,\n writeGuardedBySeed,\n} from '@aglyn/tenant-feature-instance'\nimport { pluginDocsHelp } from '@aglyn/aglyn'\n\nexport interface ReservationsCardProps {\n hostId: string\n}\n\nconst STATUS_COLOR: Record<\n string,\n 'default' | 'success' | 'warning' | 'error' | 'info'\n> = {\n pending: 'warning',\n confirmed: 'info',\n checked_in: 'success',\n checked_out: 'default',\n cancelled: 'default',\n no_show: 'error',\n}\n\nconst usd = (cents: number | undefined) =>\n `$${((cents ?? 0) / 100).toFixed(2)}`\nconst day = (dayMs: number | undefined) =>\n dayMs ? new Date(dayMs).toISOString().slice(0, 10) : '—'\nconst nightLabel = (dayMs: number) =>\n new Date(dayMs).toLocaleDateString('en-US', {\n weekday: 'short',\n month: 'short',\n day: 'numeric',\n timeZone: 'UTC',\n })\n\n/** The Reservations card's look-ahead for a several-unit resource. */\nconst UNITS_LOOKAHEAD_NIGHTS = 14\n\n/** The desk's calendar day as the UTC-midnight `dayMs` stays are stored in. */\nfunction localTodayDayMs(): number {\n const now = new Date()\n return Date.UTC(now.getFullYear(), now.getMonth(), now.getDate())\n}\n\n/** The units field's problem, or null when the typed value is a valid count. */\nfunction unitsProblem(units: number | undefined): string | null {\n if (units === undefined) return null\n if (\n !Number.isInteger(units) ||\n units < 1 ||\n units > CommerceModel.MAX_RESOURCE_UNITS\n ) {\n return `A whole number from 1 to ${CommerceModel.MAX_RESOURCE_UNITS}`\n }\n return null\n}\n\n/**\n * Reservations console (AGL-311): resources CRUD (nightly pricing,\n * seasons via multipliers, deposits) plus the reservations list with\n * check-in/out, no-show, cancel, and walk-in creation. The storefront\n * widget books against the same docs.\n */\nexport function ReservationsCard(props: ReservationsCardProps) {\n const { hostId } = props\n // Every reservation is paid when it is booked, so a store that cannot\n // charge turns each guest away; the card says so up front.\n const payments = useMerchantPayments(hostId)\n const firestore = useFirestore()\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const { confirm } = useConfirmationContext()\n const {\n data: resourceDocs,\n status: resourcesStatus,\n /**\n * The rows this editor is seeded from are unconfirmed by the server\n * (AGL-1358). Editing spreads a whole stored resource into\n * `resourceDraft` and writes all of it back, so `merge: true` protects\n * nothing. `blocks` is the field that costs more than money: it is the\n * manually blocked date ranges `isRangeAvailable` reads, so a cached seed\n * re-opens dates that were closed and lets the next guest book a room\n * that is already spoken for.\n */\n fromCache: resourcesFromCache,\n } = useFirestoreCollection<any>(\n () => query(collection(firestore, 'hosts', hostId, 'resources'), limit(50)),\n [firestore, hostId],\n { idField: '$id' },\n )\n /*\n * Ordered by the server, and a growing window rather than a fixed 300.\n *\n * `limit(300)` carried no `orderBy`, so Firestore returned DOCUMENT-ID\n * order and the client sort below arranged that pseudo-random sample by\n * check-in. The ordering is unchanged — earliest check-in first, exactly\n * what the sort did — it is now the QUERY's, so the window is the real\n * front of the list and the rest can be reached.\n */\n const {\n rows: reservations,\n hasMore,\n page,\n setPage,\n pageSize,\n setPageSize,\n } = usePagedCollection<any>(\n (pageLimit) =>\n query(\n collection(firestore, 'hosts', hostId, 'reservations'),\n orderBy('checkInDayMs', 'asc'),\n limit(pageLimit),\n ),\n [firestore, hostId],\n { idField: '$id' },\n )\n /*\n * Units left on each of the next nights, for resources with several units\n * (AGL-4044). One listen across the site's resources, on the stays that\n * have not ended, nearest check-out first: a range and an order on the same\n * field, which Firestore's automatic single-field index serves. Not read at\n * all while every resource has one unit, so a cabin-only site pays nothing.\n *\n * The 500 cap is the booking door's: it can only leave out stays that end\n * after 500 others that are all still running, which would be a stay far\n * past the two weeks this draws.\n */\n const hasSeveralUnits = (resourceDocs ?? []).some(\n (resource: any) => CommerceModel.resourceUnits(resource) > 1,\n )\n const todayDayMs = localTodayDayMs()\n const { data: currentStays } = useFirestoreCollection<any>(\n () =>\n hasSeveralUnits\n ? query(\n collection(firestore, 'hosts', hostId, 'reservations'),\n where('checkOutDayMs', '>', todayDayMs),\n orderBy('checkOutDayMs'),\n limit(500),\n )\n : null,\n [firestore, hostId, hasSeveralUnits, todayDayMs],\n { idField: '$id' },\n )\n const unitsAhead = useMemo(() => {\n const byResource: Record<string, CommerceModel.ResourceNightUnits[]> = {}\n if (!hasSeveralUnits) return byResource\n const nowMs = Date.now()\n for (const resource of resourceDocs ?? []) {\n if (CommerceModel.resourceUnits(resource) === 1) continue\n const stays = (currentStays ?? [])\n .filter((stay: any) => stay.resourceId === resource.$id)\n .map((stay: any) => ({\n checkInDayMs: Number(stay.checkInDayMs),\n checkOutDayMs: Number(stay.checkOutDayMs),\n status: stay.status as CommerceModel.ReservationStatus,\n createdAtMs: Number(stay.createdAtMs ?? 0),\n }))\n byResource[resource.$id] = CommerceModel.unitsLeftByNight(\n resource,\n stays,\n todayDayMs,\n UNITS_LOOKAHEAD_NIGHTS,\n nowMs,\n )\n }\n return byResource\n }, [hasSeveralUnits, resourceDocs, currentStays, todayDayMs])\n\n const resourceNames = useMemo(() => {\n const map: Record<string, string> = {}\n for (const resource of resourceDocs ?? []) {\n map[resource.$id] = resource.name\n }\n return map\n }, [resourceDocs])\n\n const [resourceDraft, setResourceDraft] = useState<\n (Partial<CommerceModel.HostResource> & { id: string | null }) | null\n >(null)\n const [walkIn, setWalkIn] = useState<{\n resourceId: string\n checkIn: string\n checkOut: string\n guestName: string\n /** One walk-in attempt: a retry of it finds its own stay (AGL-4046). */\n attemptId: string\n } | null>(null)\n const [walkInBusy, setWalkInBusy] = useState(false)\n\n const handleResourceSave = useCallback(async () => {\n if (!resourceDraft?.name?.trim() || !(resourceDraft.nightlyRateUsd! > 0)) {\n return\n }\n /**\n * `$id` comes off with `id` (AGL-1374).\n *\n * The editor is seeded `{ id: resource.$id, ...resource }`, and that\n * spread carries the listener's SYNTHETIC `$id` — `idField: '$id'`\n * stamps the document id onto the in-memory row, where nothing persists\n * it. Destructuring only `id` left `$id` in `data`, so every save wrote\n * it into the stored document as a real field. Nothing reads it; it is\n * the listener's bookkeeping leaking into storage, and once there no\n * reader can tell it from a field the card meant to write.\n */\n if (unitsProblem(resourceDraft.units)) return\n const seeded = resourceDraft as typeof resourceDraft & { $id?: string }\n const { id, $id: _syntheticId, units, ...fields } = seeded\n // Units are written as the count the model reads (AGL-4044), and a\n // cleared field as 1 rather than left out: `merge: true` would otherwise\n // keep a stored 12 the merchant just removed.\n const data = 'units' in seeded\n ? { ...fields, units: CommerceModel.resourceUnits({ units }) }\n : fields\n /**\n * Refuse an EDIT whose seed the server never confirmed (AGL-1358).\n * `data` is the whole stored row spread out of the listener, so\n * `merge: true` has nothing left to protect — every field is present,\n * `blocks` and the seasonal multipliers included.\n *\n * Only the edit path. A NEW resource is built from defaults at a fresh\n * uid and can overwrite nothing, and the first snapshot of any listener\n * is `fromCache: true`, so guarding a create would refuse a save that was\n * never unsafe.\n *\n * The guard WRAPS the write — an early return is a shape you can keep\n * while losing the protection.\n */\n const verdict = await writeGuardedBySeed(\n {\n subject: 'resource',\n unreadable: Boolean(id) && resourcesStatus === 'error',\n fromCache: Boolean(id) && resourcesFromCache,\n },\n async () => {\n await setDoc(\n doc(\n firestore,\n 'hosts',\n hostId,\n 'resources',\n id ?? Aglyn.createResourceUid(),\n ),\n {\n ...data,\n name: resourceDraft.name!.trim().slice(0, 120),\n },\n { merge: true },\n )\n },\n )\n // Before `setResourceDraft(null)`, so a refusal keeps the dialog open\n // with what was typed.\n if (!verdict.ok) {\n return void enqueueSnackbar(verdict.message, {\n variant: 'warning',\n persist: false,\n })\n }\n setResourceDraft(null)\n enqueueSnackbar('Resource saved', { variant: 'success', persist: false })\n }, [\n resourceDraft,\n firestore,\n hostId,\n enqueueSnackbar,\n resourcesStatus,\n resourcesFromCache,\n ])\n\n const handleStatus = useCallback(\n (reservation: any, status: CommerceModel.ReservationStatus) => async () => {\n await updateDoc(\n doc(firestore, 'hosts', hostId, 'reservations', reservation.$id),\n { status },\n )\n },\n [firestore, hostId],\n )\n\n const handleWalkIn = useCallback(async () => {\n if (!walkIn?.resourceId || !walkIn.checkIn || !walkIn.checkOut) return\n const resource = (resourceDocs ?? []).find(\n (item: any) => item.$id === walkIn.resourceId,\n ) as CommerceModel.HostResource | undefined\n if (!resource) return\n const checkInDayMs = Date.parse(`${walkIn.checkIn}T00:00:00Z`)\n const checkOutDayMs = Date.parse(`${walkIn.checkOut}T00:00:00Z`)\n /*\n * The stay is priced FIRST, and its `problem` stops the walk-in: the quote\n * is the one rule for what can be booked (no nights, under the minimum\n * stay), shared with the booking widget and both native apps. Ignoring it\n * wrote a confirmed zero-night, $0 stay for a check-out on the arrival\n * day (AGL-3794).\n */\n const quote = CommerceModel.computeReservationQuote(\n resource,\n checkInDayMs,\n checkOutDayMs,\n )\n if (quote.problem) {\n return void enqueueSnackbar(quote.problem, {\n variant: 'warning',\n persist: false,\n })\n }\n /*\n * Every stay of THIS resource that ends after the walk-in arrives — the\n * same query `reserve.ts` checks a booking against, on the composite it\n * already has (AGL-3321). This used to test the rows on the card's\n * current page, so a stay on the next page did not block the dates and\n * the desk could double-book the room.\n */\n const overlapping = await getDocs(\n query(\n collection(firestore, 'hosts', hostId, 'reservations'),\n where('resourceId', '==', walkIn.resourceId),\n where('checkOutDayMs', '>', checkInDayMs),\n orderBy('checkOutDayMs'),\n limit(500),\n ),\n ).catch(() => null)\n if (!overlapping) {\n return void enqueueSnackbar('Could not check those dates — try again', {\n variant: 'warning',\n persist: false,\n })\n }\n // `createdAtMs` carried through because the pending hold lapses on it\n // (`reservationHoldsDates`). Dropping it made every unpaid `pending` row\n // block forever, so the front desk was refused a walk-in into a room that\n // an abandoned online checkout had left empty.\n const existing = overlapping.docs\n .map((snapshot) => snapshot.data() as any)\n .map((item: any) => ({\n checkInDayMs: item.checkInDayMs,\n checkOutDayMs: item.checkOutDayMs,\n status: item.status,\n createdAtMs: item.createdAtMs,\n }))\n // Count-based for a several-unit resource (AGL-4044): the walk-in takes\n // any unit that is free on every night, and is refused only when some\n // night has none left.\n if (\n !CommerceModel.isRangeAvailable(resource, existing, checkInDayMs, checkOutDayMs)\n ) {\n const message =\n CommerceModel.resourceUnits(resource) > 1\n ? 'Every unit is taken on at least one of those nights'\n : 'Those dates are taken'\n return void enqueueSnackbar(message, {\n variant: 'warning',\n persist: false,\n })\n }\n /*\n * The check above is the instant answer; the WRITE goes through\n * `/api/commerce/reservations/walk-in` (AGL-4046), which re-asks the same\n * question inside the transaction that books the stay. A client\n * read-then-write let two staff on two devices both take the last unit.\n */\n setWalkInBusy(true)\n try {\n const response = await authorizedFetch(\n user,\n '/api/commerce/reservations/walk-in',\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n hostId,\n resourceId: walkIn.resourceId,\n checkInDayMs,\n checkOutDayMs,\n guestName: walkIn.guestName || undefined,\n reservationId: walkIn.attemptId,\n }),\n },\n )\n const answer = await response.json().catch(() => ({}))\n if (!response.ok) {\n return void enqueueSnackbar(\n answer?.error ?? 'Could not reserve the walk-in — try again',\n {\n variant: response.status === 409 || response.status === 400\n ? 'warning'\n : 'error',\n persist: false,\n },\n )\n }\n } catch {\n return void enqueueSnackbar('Could not reserve the walk-in — try again', {\n variant: 'error',\n persist: false,\n })\n } finally {\n setWalkInBusy(false)\n }\n setWalkIn(null)\n enqueueSnackbar('Walk-in reserved — collect payment at POS', {\n variant: 'success',\n persist: false,\n })\n }, [walkIn, resourceDocs, firestore, hostId, user, enqueueSnackbar])\n\n return (\n <CardDisplay\n header={'Reservations'}\n help={pluginDocsHelp('pos', { anchor: '#reservations' })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1}>\n {payments.blocked ? (\n <PrerequisiteNotice severity=\"warning\" href={payments.href ?? undefined} actionLabel=\"Open Payments\">\n {`${payments.reason ?? ''} Guests pay when they reserve, so they can't reserve until it is set up.`}\n </PrerequisiteNotice>\n ) : null}\n <Typography variant=\"subtitle2\">{'Resources'}</Typography>\n {(resourceDocs ?? []).map((resource: any) => (\n <Stack key={resource.$id} spacing={0.5}>\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center' }}\n >\n <Stack sx={{ flex: 1, minWidth: 0 }}>\n <Typography variant=\"body2\" noWrap>\n {[\n resource.name,\n `$${resource.nightlyRateUsd}/night`,\n ...(CommerceModel.resourceUnits(resource) > 1\n ? [`${CommerceModel.resourceUnits(resource)} units`]\n : []),\n ].join(' · ')}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {`id: ${resource.$id}`}\n </Typography>\n </Stack>\n <Button\n size=\"small\"\n onClick={() => setResourceDraft({ id: resource.$id, ...resource })}\n >\n {'Edit'}\n </Button>\n <Button\n size=\"small\"\n color=\"error\"\n onClick={async () => {\n const confirmed = await confirm({\n title: 'Remove this resource?',\n description:\n 'Existing reservations keep their records; the widget ' +\n 'stops accepting new stays.',\n confirmationText: 'Remove',\n confirmationButtonProps: { color: 'error' },\n })\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n await deleteDoc(\n doc(firestore, 'hosts', hostId, 'resources', resource.$id),\n )\n }}\n >\n {'Remove'}\n </Button>\n </Stack>\n {unitsAhead[resource.$id] ? (\n <Stack\n direction=\"row\"\n useFlexGap\n aria-label={`Units left on the next ${UNITS_LOOKAHEAD_NIGHTS} nights`}\n sx={{ flexWrap: 'wrap', gap: 0.5 }}\n >\n {unitsAhead[resource.$id].map((night) => (\n <Chip\n key={night.dayMs}\n size=\"small\"\n variant=\"outlined\"\n color={\n night.unitsLeft === 0\n ? 'error'\n : night.unitsLeft <= CommerceModel.FEW_UNITS_LEFT\n ? 'warning'\n : 'default'\n }\n label={`${nightLabel(night.dayMs)} · ${night.unitsLeft} of ${night.units} left`}\n />\n ))}\n </Stack>\n ) : null}\n </Stack>\n ))}\n <Stack direction=\"row\" spacing={1}>\n <Button\n size=\"small\"\n onClick={() =>\n setResourceDraft({\n id: null,\n name: '',\n nightlyRateUsd: 100,\n depositPct: 30,\n minNights: 1,\n })\n }\n >\n {'Add resource'}\n </Button>\n <Button\n size=\"small\"\n disabled={(resourceDocs?.length ?? 0) === 0}\n onClick={() =>\n setWalkIn({\n resourceId: resourceDocs?.[0]?.$id ?? '',\n checkIn: '',\n checkOut: '',\n guestName: '',\n attemptId: Aglyn.createResourceUid(),\n })\n }\n >\n {'Walk-in'}\n </Button>\n </Stack>\n\n <Typography variant=\"subtitle2\" sx={{ mt: 1 }}>\n {'Upcoming & recent stays'}\n </Typography>\n {reservations.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Reservations from the widget (and walk-ins) appear here.'}\n </Typography>\n ) : (\n reservations.map((reservation: any) => (\n <Stack\n key={reservation.$id}\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center' }}\n >\n <Stack sx={{ flex: 1, minWidth: 0 }}>\n <Typography variant=\"body2\" noWrap>\n {`${resourceNames[reservation.resourceId] ?? '?'} · ` +\n `${day(reservation.checkInDayMs)} → ${day(reservation.checkOutDayMs)}`}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {[\n reservation.guestName ?? reservation.guestEmail ?? 'Guest',\n `${usd(reservation.paidCents)} / ${usd(reservation.totalCents)} paid`,\n ].join(' · ')}\n </Typography>\n </Stack>\n <Chip\n label={String(reservation.status).replace('_', ' ')}\n size=\"small\"\n variant=\"outlined\"\n color={STATUS_COLOR[reservation.status] ?? 'default'}\n />\n {reservation.status === 'confirmed' ? (\n <>\n <Button size=\"small\" onClick={handleStatus(reservation, 'checked_in')}>\n {'Check in'}\n </Button>\n <Button\n size=\"small\"\n color=\"error\"\n onClick={handleStatus(reservation, 'no_show')}\n >\n {'No-show'}\n </Button>\n </>\n ) : null}\n {reservation.status === 'checked_in' ? (\n <Button\n size=\"small\"\n onClick={async () => {\n // Folio settlement (AGL-317): surface unpaid room\n // charges before the guest leaves.\n const folio = (reservation.folio ?? []) as Array<{\n amountCents: number\n }>\n const folioCents = folio.reduce(\n (sum, entry) => sum + (entry.amountCents ?? 0),\n 0,\n )\n const balanceCents =\n (reservation.totalCents ?? 0) -\n (reservation.paidCents ?? 0)\n if (folioCents > 0 || balanceCents > 0) {\n const confirmed = await confirm({\n title: 'Check out with open balance?',\n description:\n `${folioCents > 0 ? `Room charges: ${usd(folioCents)} (already recorded as paid POS orders). ` : ''}` +\n `${balanceCents > 0 ? `Stay balance due: ${usd(balanceCents)} — collect at the register.` : ''}`,\n confirmationText: 'Check out',\n })\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n }\n await handleStatus(reservation, 'checked_out')()\n }}\n >\n {'Check out'}\n </Button>\n ) : null}\n {['pending', 'confirmed'].includes(reservation.status) ? (\n <Button\n size=\"small\"\n color=\"error\"\n onClick={handleStatus(reservation, 'cancelled')}\n >\n {'Cancel'}\n </Button>\n ) : null}\n </Stack>\n ))\n )}\n <ListPagination\n page={page}\n pageSize={pageSize}\n rowCount={reservations.length}\n hasMore={hasMore}\n onPageChange={setPage}\n onPageSizeChange={setPageSize}\n />\n </Stack>\n\n <Dialog\n open={Boolean(resourceDraft)}\n onClose={() => setResourceDraft(null)}\n maxWidth=\"xs\"\n fullWidth\n >\n <DialogTitle>\n {resourceDraft?.id ? 'Edit resource' : 'New resource'}\n </DialogTitle>\n <DialogContent sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>\n <TextField\n label=\"Name\"\n value={resourceDraft?.name ?? ''}\n onChange={(event) =>\n setResourceDraft((prev) =>\n prev ? { ...prev, name: event.target.value } : prev,\n )\n }\n size=\"small\"\n autoFocus\n sx={{ mt: 1 }}\n placeholder=\"Lakeside cabin\"\n />\n <Stack direction=\"row\" spacing={1}>\n <TextField\n label=\"Spots or units available\"\n placeholder=\"1\"\n value={resourceDraft?.units ?? ''}\n onChange={(event) =>\n setResourceDraft((prev) =>\n prev\n ? {\n ...prev,\n units:\n event.target.value.replace(/\\D/g, '') === ''\n ? undefined\n : Number(event.target.value.replace(/\\D/g, '')),\n }\n : prev,\n )\n }\n error={Boolean(unitsProblem(resourceDraft?.units))}\n helperText={\n unitsProblem(resourceDraft?.units) ??\n 'How many identical ones you have, like 12 RV hookups'\n }\n size=\"small\"\n fullWidth\n slotProps={{ htmlInput: { inputMode: 'numeric' } }}\n />\n <TextField\n label=\"Sleeps\"\n value={resourceDraft?.capacity ?? ''}\n onChange={(event) =>\n setResourceDraft((prev) =>\n prev\n ? {\n ...prev,\n capacity:\n Math.round(Number(event.target.value)) || undefined,\n }\n : prev,\n )\n }\n helperText=\"Guests per unit, for display\"\n size=\"small\"\n fullWidth\n slotProps={{ htmlInput: { inputMode: 'numeric' } }}\n />\n </Stack>\n <Stack direction=\"row\" spacing={1}>\n <TextField\n label=\"Nightly rate ($)\"\n value={resourceDraft?.nightlyRateUsd ?? ''}\n onChange={(event) =>\n setResourceDraft((prev) =>\n prev\n ? {\n ...prev,\n nightlyRateUsd: Number(event.target.value) || 0,\n }\n : prev,\n )\n }\n size=\"small\"\n slotProps={{ htmlInput: { inputMode: 'decimal' } }}\n />\n <TextField\n label=\"Weekend ×\"\n placeholder=\"1\"\n value={resourceDraft?.weekendMultiplier ?? ''}\n onChange={(event) =>\n setResourceDraft((prev) =>\n prev\n ? {\n ...prev,\n weekendMultiplier:\n Number(event.target.value) || undefined,\n }\n : prev,\n )\n }\n size=\"small\"\n slotProps={{ htmlInput: { inputMode: 'decimal' } }}\n />\n </Stack>\n <Stack direction=\"row\" spacing={1}>\n <TextField\n label=\"Min nights\"\n value={resourceDraft?.minNights ?? ''}\n onChange={(event) =>\n setResourceDraft((prev) =>\n prev\n ? {\n ...prev,\n minNights:\n Math.round(Number(event.target.value)) || undefined,\n }\n : prev,\n )\n }\n size=\"small\"\n slotProps={{ htmlInput: { inputMode: 'numeric' } }}\n />\n <TextField\n label=\"Deposit %\"\n placeholder=\"100 = pay in full\"\n value={resourceDraft?.depositPct ?? ''}\n onChange={(event) =>\n setResourceDraft((prev) =>\n prev\n ? {\n ...prev,\n depositPct:\n Math.round(Number(event.target.value)) || undefined,\n }\n : prev,\n )\n }\n size=\"small\"\n slotProps={{ htmlInput: { inputMode: 'numeric' } }}\n />\n <TextField\n label=\"Free cancel (hrs)\"\n value={resourceDraft?.cancellationHours ?? ''}\n onChange={(event) =>\n setResourceDraft((prev) =>\n prev\n ? {\n ...prev,\n cancellationHours:\n Math.round(Number(event.target.value)) || undefined,\n }\n : prev,\n )\n }\n size=\"small\"\n slotProps={{ htmlInput: { inputMode: 'numeric' } }}\n />\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setResourceDraft(null)}>{'Cancel'}</Button>\n <Button\n variant=\"contained\"\n color=\"primary\"\n disabled={\n !resourceDraft?.name?.trim() ||\n !((resourceDraft?.nightlyRateUsd ?? 0) > 0) ||\n Boolean(unitsProblem(resourceDraft?.units))\n }\n onClick={handleResourceSave}\n >\n {'Save'}\n </Button>\n </DialogActions>\n </Dialog>\n\n <Dialog\n open={Boolean(walkIn)}\n onClose={() => setWalkIn(null)}\n maxWidth=\"xs\"\n fullWidth\n >\n <DialogTitle>{'Walk-in reservation'}</DialogTitle>\n <DialogContent sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>\n <TextField\n label=\"Resource\"\n value={walkIn?.resourceId ?? ''}\n onChange={(event) =>\n setWalkIn((prev) =>\n prev ? { ...prev, resourceId: event.target.value } : prev,\n )\n }\n size=\"small\"\n select\n sx={{ mt: 1 }}\n >\n {(resourceDocs ?? []).map((resource: any) => (\n <MenuItem key={resource.$id} value={resource.$id}>\n {resource.name}\n </MenuItem>\n ))}\n </TextField>\n <Stack direction=\"row\" spacing={1}>\n <TextField\n label=\"Check-in\"\n type=\"date\"\n value={walkIn?.checkIn ?? ''}\n onChange={(event) =>\n setWalkIn((prev) =>\n prev ? { ...prev, checkIn: event.target.value } : prev,\n )\n }\n size=\"small\"\n fullWidth\n slotProps={{ inputLabel: { shrink: true } }}\n />\n <TextField\n label=\"Check-out\"\n type=\"date\"\n value={walkIn?.checkOut ?? ''}\n onChange={(event) =>\n setWalkIn((prev) =>\n prev ? { ...prev, checkOut: event.target.value } : prev,\n )\n }\n size=\"small\"\n fullWidth\n slotProps={{ inputLabel: { shrink: true } }}\n />\n </Stack>\n <TextField\n label=\"Guest name\"\n value={walkIn?.guestName ?? ''}\n onChange={(event) =>\n setWalkIn((prev) =>\n prev ? { ...prev, guestName: event.target.value } : prev,\n )\n }\n size=\"small\"\n />\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setWalkIn(null)}>{'Cancel'}</Button>\n <Button\n variant=\"contained\"\n color=\"primary\"\n disabled={\n walkInBusy ||\n !walkIn?.resourceId ||\n !walkIn?.checkIn ||\n !walkIn?.checkOut\n }\n onClick={handleWalkIn}\n >\n {'Reserve'}\n </Button>\n </DialogActions>\n </Dialog>\n </CardDisplay>\n )\n}\nReservationsCard.displayName = 'ReservationsCard'\n\nexport default ReservationsCard\n"],"names":["Aglyn","CommerceModel","CardDisplay","useConfirmationContext","ListPagination","useSnackbar","Button","Chip","Dialog","DialogActions","DialogContent","DialogTitle","MenuItem","Stack","TextField","Typography","collection","deleteDoc","doc","getDocs","limit","orderBy","query","setDoc","updateDoc","where","useCallback","useMemo","useState","useFirestore","useMerchantPayments","useUser","authorizedFetch","PrerequisiteNotice","useFirestoreCollection","usePagedCollection","writeGuardedBySeed","pluginDocsHelp","STATUS_COLOR","pending","confirmed","checked_in","checked_out","cancelled","no_show","usd","cents","toFixed","day","dayMs","Date","toISOString","slice","nightLabel","toLocaleDateString","weekday","month","timeZone","UNITS_LOOKAHEAD_NIGHTS","localTodayDayMs","now","UTC","getFullYear","getMonth","getDate","unitsProblem","units","undefined","Number","isInteger","MAX_RESOURCE_UNITS","ReservationsCard","props","payments","resourceDraft","hostId","firestore","data","user","enqueueSnackbar","confirm","resourceDocs","status","resourcesStatus","fromCache","resourcesFromCache","idField","rows","reservations","hasMore","page","setPage","pageSize","setPageSize","pageLimit","hasSeveralUnits","some","resource","resourceUnits","todayDayMs","currentStays","unitsAhead","byResource","nowMs","stays","filter","stay","resourceId","$id","map","checkInDayMs","checkOutDayMs","createdAtMs","unitsLeftByNight","resourceNames","name","setResourceDraft","walkIn","setWalkIn","walkInBusy","setWalkInBusy","handleResourceSave","trim","nightlyRateUsd","seeded","id","_syntheticId","fields","verdict","subject","unreadable","Boolean","createResourceUid","merge","ok","message","variant","persist","handleStatus","reservation","handleWalkIn","checkIn","checkOut","find","item","parse","quote","computeReservationQuote","problem","overlapping","catch","existing","docs","snapshot","isRangeAvailable","response","method","headers","body","JSON","stringify","guestName","reservationId","attemptId","answer","json","error","header","help","anchor","contentGutterX","contentGutterY","spacing","blocked","severity","href","actionLabel","reason","direction","sx","alignItems","flex","minWidth","noWrap","join","color","size","onClick","title","description","confirmationText","confirmationButtonProps","then","useFlexGap","aria-label","flexWrap","gap","night","unitsLeft","FEW_UNITS_LEFT","label","depositPct","minNights","disabled","length","mt","guestEmail","paidCents","totalCents","String","replace","folio","folioCents","reduce","sum","entry","amountCents","balanceCents","includes","rowCount","onPageChange","onPageSizeChange","open","onClose","maxWidth","fullWidth","display","flexDirection","value","onChange","event","prev","target","autoFocus","placeholder","helperText","slotProps","htmlInput","inputMode","capacity","Math","round","weekendMultiplier","cancellationHours","select","type","inputLabel","shrink","displayName"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;;;AAEA,YAAYA,WAAW,eAAc;AACrC,YAAYC,mBAAmB,uBAAa;AAC5C,SAASC,WAAW,EAAEC,sBAAsB,QAAQ,uBAAsB;AAC1E,SAASC,cAAc,QAAQ,4DAA2D;AAC1F,SAASC,WAAW,QAAQ,8BAA6B;AACzD,SACEC,MAAM,EACNC,IAAI,EACJC,MAAM,EACNC,aAAa,EACbC,aAAa,EACbC,WAAW,EACXC,QAAQ,EACRC,KAAK,EACLC,SAAS,EACTC,UAAU,QACL,gBAAe;AACtB,SACEC,UAAU,EACVC,SAAS,EACTC,GAAG,EACHC,OAAO,EACPC,KAAK,EACLC,OAAO,EACPC,KAAK,EACLC,MAAM,EACNC,SAAS,EACTC,KAAK,QACA,qBAAoB;AAC3B,SAASC,WAAW,EAAEC,OAAO,EAAEC,QAAQ,QAAQ,QAAO;AACtD,SAASC,YAAY,EAAEC,mBAAmB,EAAEC,OAAO,QAAQ,iCAAgC;AAC3F,SAASC,eAAe,QAAQ,2CAA0C;AAC1E,SAASC,kBAAkB,QAAQ,gEAA+D;AAClG,SACEC,sBAAsB,EACtBC,kBAAkB,EAClBC,kBAAkB,QACb,iCAAgC;AACvC,SAASC,cAAc,QAAQ,eAAc;AAM7C,MAAMC,eAGF;IACFC,SAAS;IACTC,WAAW;IACXC,YAAY;IACZC,aAAa;IACbC,WAAW;IACXC,SAAS;AACX;AAEA,MAAMC,MAAM,CAACC,QACX,CAAC,CAAC,EAAE,AAAC,CAAA,CAACA,gBAAAA,QAAS,KAAK,GAAE,EAAGC,OAAO,CAAC,IAAI;AACvC,MAAMC,MAAM,CAACC,QACXA,QAAQ,IAAIC,KAAKD,OAAOE,WAAW,GAAGC,KAAK,CAAC,GAAG,MAAM;AACvD,MAAMC,aAAa,CAACJ,QAClB,IAAIC,KAAKD,OAAOK,kBAAkB,CAAC,SAAS;QAC1CC,SAAS;QACTC,OAAO;QACPR,KAAK;QACLS,UAAU;IACZ;AAEF,oEAAoE,GACpE,MAAMC,yBAAyB;AAE/B,6EAA6E,GAC7E,SAASC;IACP,MAAMC,MAAM,IAAIV;IAChB,OAAOA,KAAKW,GAAG,CAACD,IAAIE,WAAW,IAAIF,IAAIG,QAAQ,IAAIH,IAAII,OAAO;AAChE;AAEA,8EAA8E,GAC9E,SAASC,aAAaC,KAAyB;IAC7C,IAAIA,UAAUC,WAAW,OAAO;IAChC,IACE,CAACC,OAAOC,SAAS,CAACH,UAClBA,QAAQ,KACRA,QAAQjE,cAAcqE,kBAAkB,EACxC;QACA,OAAO,CAAC,yBAAyB,EAAErE,cAAcqE,kBAAkB,EAAE;IACvE;IACA,OAAO;AACT;AAEA;;;;;CAKC,GACD,OAAO,SAASC,iBAAiBC,KAA4B;QAgWNC,gBACvCA,sCAkQAR;QA4HDS;IA9tBb,MAAM,EAAEC,MAAM,EAAE,GAAGH;IACnB,sEAAsE;IACtE,2DAA2D;IAC3D,MAAMC,WAAW3C,oBAAoB6C;IACrC,MAAMC,YAAY/C;IAClB,MAAM,EAAEgD,MAAMC,IAAI,EAAE,GAAG/C;IACvB,MAAM,EAAEgD,eAAe,EAAE,GAAG1E;IAC5B,MAAM,EAAE2E,OAAO,EAAE,GAAG7E;IACpB,MAAM,EACJ0E,MAAMI,YAAY,EAClBC,QAAQC,eAAe,EACvB;;;;;;;;KAQC,GACDC,WAAWC,kBAAkB,EAC9B,GAAGnD,uBACF,IAAMZ,MAAMN,WAAW4D,WAAW,SAASD,QAAQ,cAAcvD,MAAM,MACvE;QAACwD;QAAWD;KAAO,EACnB;QAAEW,SAAS;IAAM;IAEnB;;;;;;;;GAQC,GACD,MAAM,EACJC,MAAMC,YAAY,EAClBC,OAAO,EACPC,IAAI,EACJC,OAAO,EACPC,QAAQ,EACRC,WAAW,EACZ,GAAG1D,mBACF,CAAC2D,YACCxE,MACEN,WAAW4D,WAAW,SAASD,QAAQ,iBACvCtD,QAAQ,gBAAgB,QACxBD,MAAM0E,aAEV;QAAClB;QAAWD;KAAO,EACnB;QAAEW,SAAS;IAAM;IAEnB;;;;;;;;;;GAUC,GACD,MAAMS,kBAAkB,CAACd,uBAAAA,eAAgB,EAAE,EAAEe,IAAI,CAC/C,CAACC,WAAkBhG,cAAciG,aAAa,CAACD,YAAY;IAE7D,MAAME,aAAaxC;IACnB,MAAM,EAAEkB,MAAMuB,YAAY,EAAE,GAAGlE,uBAC7B,IACE6D,kBACIzE,MACEN,WAAW4D,WAAW,SAASD,QAAQ,iBACvClD,MAAM,iBAAiB,KAAK0E,aAC5B9E,QAAQ,kBACRD,MAAM,QAER,MACN;QAACwD;QAAWD;QAAQoB;QAAiBI;KAAW,EAChD;QAAEb,SAAS;IAAM;IAEnB,MAAMe,aAAa1E,QAAQ;QACzB,MAAM2E,aAAiE,CAAC;QACxE,IAAI,CAACP,iBAAiB,OAAOO;QAC7B,MAAMC,QAAQrD,KAAKU,GAAG;QACtB,KAAK,MAAMqC,YAAYhB,uBAAAA,eAAgB,EAAE,CAAE;YACzC,IAAIhF,cAAciG,aAAa,CAACD,cAAc,GAAG;YACjD,MAAMO,QAAQ,CAACJ,uBAAAA,eAAgB,EAAE,EAC9BK,MAAM,CAAC,CAACC,OAAcA,KAAKC,UAAU,KAAKV,SAASW,GAAG,EACtDC,GAAG,CAAC,CAACH;oBAIgBA;uBAJD;oBACnBI,cAAc1C,OAAOsC,KAAKI,YAAY;oBACtCC,eAAe3C,OAAOsC,KAAKK,aAAa;oBACxC7B,QAAQwB,KAAKxB,MAAM;oBACnB8B,aAAa5C,QAAOsC,oBAAAA,KAAKM,WAAW,YAAhBN,oBAAoB;gBAC1C;;YACFJ,UAAU,CAACL,SAASW,GAAG,CAAC,GAAG3G,cAAcgH,gBAAgB,CACvDhB,UACAO,OACAL,YACAzC,wBACA6C;QAEJ;QACA,OAAOD;IACT,GAAG;QAACP;QAAiBd;QAAcmB;QAAcD;KAAW;IAE5D,MAAMe,gBAAgBvF,QAAQ;QAC5B,MAAMkF,MAA8B,CAAC;QACrC,KAAK,MAAMZ,YAAYhB,uBAAAA,eAAgB,EAAE,CAAE;YACzC4B,GAAG,CAACZ,SAASW,GAAG,CAAC,GAAGX,SAASkB,IAAI;QACnC;QACA,OAAON;IACT,GAAG;QAAC5B;KAAa;IAEjB,MAAM,CAACP,eAAe0C,iBAAiB,GAAGxF,SAExC;IACF,MAAM,CAACyF,QAAQC,UAAU,GAAG1F,SAOlB;IACV,MAAM,CAAC2F,YAAYC,cAAc,GAAG5F,SAAS;IAE7C,MAAM6F,qBAAqB/F,YAAY;YAChCgD;QAAL,IAAI,EAACA,kCAAAA,sBAAAA,cAAeyC,IAAI,qBAAnBzC,oBAAqBgD,IAAI,OAAM,CAAEhD,CAAAA,cAAciD,cAAc,GAAI,CAAA,GAAI;YACxE;QACF;QACA;;;;;;;;;;KAUC,GACD,IAAI1D,aAAaS,cAAcR,KAAK,GAAG;QACvC,MAAM0D,SAASlD;QACf,MAAM,EAAEmD,EAAE,EAAEjB,KAAKkB,YAAY,EAAE5D,KAAK,EAAa,GAAG0D,QAAXG,0CAAWH;;;;;QACpD,mEAAmE;QACnE,yEAAyE;QACzE,8CAA8C;QAC9C,MAAM/C,OAAO,WAAW+C,SACpB,aAAKG;YAAQ7D,OAAOjE,cAAciG,aAAa,CAAC;gBAAEhC;YAAM;aACxD6D;QACJ;;;;;;;;;;;;;KAaC,GACD,MAAMC,UAAU,MAAM5F,mBACpB;YACE6F,SAAS;YACTC,YAAYC,QAAQN,OAAO1C,oBAAoB;YAC/CC,WAAW+C,QAAQN,OAAOxC;QAC5B,GACA;YACE,MAAM9D,OACJL,IACE0D,WACA,SACAD,QACA,aACAkD,aAAAA,KAAM7H,MAAMoI,iBAAiB,KAE/B,aACKvD;gBACHsC,MAAMzC,cAAcyC,IAAI,CAAEO,IAAI,GAAGtE,KAAK,CAAC,GAAG;gBAE5C;gBAAEiF,OAAO;YAAK;QAElB;QAEF,sEAAsE;QACtE,uBAAuB;QACvB,IAAI,CAACL,QAAQM,EAAE,EAAE;YACf,OAAO,KAAKvD,gBAAgBiD,QAAQO,OAAO,EAAE;gBAC3CC,SAAS;gBACTC,SAAS;YACX;QACF;QACArB,iBAAiB;QACjBrC,gBAAgB,kBAAkB;YAAEyD,SAAS;YAAWC,SAAS;QAAM;IACzE,GAAG;QACD/D;QACAE;QACAD;QACAI;QACAI;QACAE;KACD;IAED,MAAMqD,eAAehH,YACnB,CAACiH,aAAkBzD,SAA4C;YAC7D,MAAM1D,UACJN,IAAI0D,WAAW,SAASD,QAAQ,gBAAgBgE,YAAY/B,GAAG,GAC/D;gBAAE1B;YAAO;QAEb,GACA;QAACN;QAAWD;KAAO;IAGrB,MAAMiE,eAAelH,YAAY;QAC/B,IAAI,EAAC2F,0BAAAA,OAAQV,UAAU,KAAI,CAACU,OAAOwB,OAAO,IAAI,CAACxB,OAAOyB,QAAQ,EAAE;QAChE,MAAM7C,WAAW,CAAChB,uBAAAA,eAAgB,EAAE,EAAE8D,IAAI,CACxC,CAACC,OAAcA,KAAKpC,GAAG,KAAKS,OAAOV,UAAU;QAE/C,IAAI,CAACV,UAAU;QACf,MAAMa,eAAe5D,KAAK+F,KAAK,CAAC,GAAG5B,OAAOwB,OAAO,CAAC,UAAU,CAAC;QAC7D,MAAM9B,gBAAgB7D,KAAK+F,KAAK,CAAC,GAAG5B,OAAOyB,QAAQ,CAAC,UAAU,CAAC;QAC/D;;;;;;KAMC,GACD,MAAMI,QAAQjJ,cAAckJ,uBAAuB,CACjDlD,UACAa,cACAC;QAEF,IAAImC,MAAME,OAAO,EAAE;YACjB,OAAO,KAAKrE,gBAAgBmE,MAAME,OAAO,EAAE;gBACzCZ,SAAS;gBACTC,SAAS;YACX;QACF;QACA;;;;;;KAMC,GACD,MAAMY,cAAc,MAAMlI,QACxBG,MACEN,WAAW4D,WAAW,SAASD,QAAQ,iBACvClD,MAAM,cAAc,MAAM4F,OAAOV,UAAU,GAC3ClF,MAAM,iBAAiB,KAAKqF,eAC5BzF,QAAQ,kBACRD,MAAM,OAERkI,KAAK,CAAC,IAAM;QACd,IAAI,CAACD,aAAa;YAChB,OAAO,KAAKtE,gBAAgB,2CAA2C;gBACrEyD,SAAS;gBACTC,SAAS;YACX;QACF;QACA,sEAAsE;QACtE,yEAAyE;QACzE,0EAA0E;QAC1E,+CAA+C;QAC/C,MAAMc,WAAWF,YAAYG,IAAI,CAC9B3C,GAAG,CAAC,CAAC4C,WAAaA,SAAS5E,IAAI,IAC/BgC,GAAG,CAAC,CAACmC,OAAe,CAAA;gBACnBlC,cAAckC,KAAKlC,YAAY;gBAC/BC,eAAeiC,KAAKjC,aAAa;gBACjC7B,QAAQ8D,KAAK9D,MAAM;gBACnB8B,aAAagC,KAAKhC,WAAW;YAC/B,CAAA;QACF,wEAAwE;QACxE,sEAAsE;QACtE,uBAAuB;QACvB,IACE,CAAC/G,cAAcyJ,gBAAgB,CAACzD,UAAUsD,UAAUzC,cAAcC,gBAClE;YACA,MAAMwB,UACJtI,cAAciG,aAAa,CAACD,YAAY,IACpC,wDACA;YACN,OAAO,KAAKlB,gBAAgBwD,SAAS;gBACnCC,SAAS;gBACTC,SAAS;YACX;QACF;QACA;;;;;KAKC,GACDjB,cAAc;QACd,IAAI;YACF,MAAMmC,WAAW,MAAM3H,gBACrB8C,MACA,sCACA;gBACE8E,QAAQ;gBACRC,SAAS;oBAAE,gBAAgB;gBAAmB;gBAC9CC,MAAMC,KAAKC,SAAS,CAAC;oBACnBrF;oBACAgC,YAAYU,OAAOV,UAAU;oBAC7BG;oBACAC;oBACAkD,WAAW5C,OAAO4C,SAAS,IAAI9F;oBAC/B+F,eAAe7C,OAAO8C,SAAS;gBACjC;YACF;YAEF,MAAMC,SAAS,MAAMT,SAASU,IAAI,GAAGf,KAAK,CAAC,IAAO,CAAA,CAAC,CAAA;YACnD,IAAI,CAACK,SAASrB,EAAE,EAAE;;gBAChB,OAAO,KAAKvD,wBACVqF,0BAAAA,OAAQE,KAAK,mBAAI,6CACjB;oBACE9B,SAASmB,SAASzE,MAAM,KAAK,OAAOyE,SAASzE,MAAM,KAAK,MACpD,YACA;oBACJuD,SAAS;gBACX;YAEJ;QACF,EAAE,eAAM;YACN,OAAO,KAAK1D,gBAAgB,6CAA6C;gBACvEyD,SAAS;gBACTC,SAAS;YACX;QACF,SAAU;YACRjB,cAAc;QAChB;QACAF,UAAU;QACVvC,gBAAgB,6CAA6C;YAC3DyD,SAAS;YACTC,SAAS;QACX;IACF,GAAG;QAACpB;QAAQpC;QAAcL;QAAWD;QAAQG;QAAMC;KAAgB;IAEnE,qBACE,MAAC7E;QACCqK,QAAQ;QACRC,MAAMnI,eAAe,OAAO;YAAEoI,QAAQ;QAAgB;QACtDC,cAAc;QACdC,cAAc;;0BAEd,MAAC9J;gBAAM+J,SAAS;;oBACbnG,SAASoG,OAAO,iBACf,KAAC5I;wBAAmB6I,UAAS;wBAAUC,IAAI,GAAEtG,iBAAAA,SAASsG,IAAI,YAAbtG,iBAAiBN;wBAAW6G,aAAY;kCAClF,IAAGvG,mBAAAA,SAASwG,MAAM,YAAfxG,mBAAmB,GAAG,wEAAwE,CAAC;yBAEnG;kCACJ,KAAC1D;wBAAWyH,SAAQ;kCAAa;;oBAChC,CAACvD,uBAAAA,eAAgB,EAAE,EAAE4B,GAAG,CAAC,CAACZ,yBACzB,MAACpF;4BAAyB+J,SAAS;;8CACjC,MAAC/J;oCACCqK,WAAU;oCACVN,SAAS;oCACTO,IAAI;wCAAEC,YAAY;oCAAS;;sDAE3B,MAACvK;4CAAMsK,IAAI;gDAAEE,MAAM;gDAAGC,UAAU;4CAAE;;8DAChC,KAACvK;oDAAWyH,SAAQ;oDAAQ+C,MAAM;8DAC/B;wDACCtF,SAASkB,IAAI;wDACb,CAAC,CAAC,EAAElB,SAAS0B,cAAc,CAAC,MAAM,CAAC;2DAC/B1H,cAAciG,aAAa,CAACD,YAAY,IACxC;4DAAC,GAAGhG,cAAciG,aAAa,CAACD,UAAU,MAAM,CAAC;yDAAC,GAClD,EAAE;qDACP,CAACuF,IAAI,CAAC;;8DAET,KAACzK;oDAAWyH,SAAQ;oDAAUiD,OAAM;oDAAiBF,MAAM;8DACxD,CAAC,IAAI,EAAEtF,SAASW,GAAG,EAAE;;;;sDAG1B,KAACtG;4CACCoL,MAAK;4CACLC,SAAS,IAAMvE,iBAAiB;oDAAES,IAAI5B,SAASW,GAAG;mDAAKX;sDAEtD;;sDAEH,KAAC3F;4CACCoL,MAAK;4CACLD,OAAM;4CACNE,SAAS;gDACP,MAAMnJ,YAAY,MAAMwC,QAAQ;oDAC9B4G,OAAO;oDACPC,aACE,0DACA;oDACFC,kBAAkB;oDAClBC,yBAAyB;wDAAEN,OAAO;oDAAQ;gDAC5C,GACGO,IAAI,CAAC,IAAM,MACX1C,KAAK,CAAC,IAAM;gDACf,IAAI,CAAC9G,WAAW;gDAChB,MAAMvB,UACJC,IAAI0D,WAAW,SAASD,QAAQ,aAAasB,SAASW,GAAG;4CAE7D;sDAEC;;;;gCAGJP,UAAU,CAACJ,SAASW,GAAG,CAAC,iBACvB,KAAC/F;oCACCqK,WAAU;oCACVe,UAAU;oCACVC,cAAY,CAAC,uBAAuB,EAAExI,uBAAuB,OAAO,CAAC;oCACrEyH,IAAI;wCAAEgB,UAAU;wCAAQC,KAAK;oCAAI;8CAEhC/F,UAAU,CAACJ,SAASW,GAAG,CAAC,CAACC,GAAG,CAAC,CAACwF,sBAC7B,KAAC9L;4CAECmL,MAAK;4CACLlD,SAAQ;4CACRiD,OACEY,MAAMC,SAAS,KAAK,IAChB,UACAD,MAAMC,SAAS,IAAIrM,cAAcsM,cAAc,GAC7C,YACA;4CAERC,OAAO,GAAGnJ,WAAWgJ,MAAMpJ,KAAK,EAAE,GAAG,EAAEoJ,MAAMC,SAAS,CAAC,IAAI,EAAED,MAAMnI,KAAK,CAAC,KAAK,CAAC;2CAV1EmI,MAAMpJ,KAAK;qCAcpB;;2BAxEMgD,SAASW,GAAG;kCA2E1B,MAAC/F;wBAAMqK,WAAU;wBAAMN,SAAS;;0CAC9B,KAACtK;gCACCoL,MAAK;gCACLC,SAAS,IACPvE,iBAAiB;wCACfS,IAAI;wCACJV,MAAM;wCACNQ,gBAAgB;wCAChB8E,YAAY;wCACZC,WAAW;oCACb;0CAGD;;0CAEH,KAACpM;gCACCoL,MAAK;gCACLiB,UAAU,SAAC1H,gCAAAA,aAAc2H,MAAM,mBAAI,OAAO;gCAC1CjB,SAAS;;wCAEO1G;2CADdqC,UAAU;wCACRX,UAAU,UAAE1B,iCAAAA,iBAAAA,YAAc,CAAC,EAAE,qBAAjBA,eAAmB2B,GAAG,mBAAI;wCACtCiC,SAAS;wCACTC,UAAU;wCACVmB,WAAW;wCACXE,WAAWnK,MAAMoI,iBAAiB;oCACpC;;0CAGD;;;;kCAIL,KAACrH;wBAAWyH,SAAQ;wBAAY2C,IAAI;4BAAE0B,IAAI;wBAAE;kCACzC;;oBAEFrH,aAAaoH,MAAM,KAAK,kBACvB,KAAC7L;wBAAWyH,SAAQ;wBAAQiD,OAAM;kCAC/B;yBAGHjG,aAAaqB,GAAG,CAAC,CAAC8B;4BASNzB,uCAKFyB,MAAAA,wBASGrG;6CAtBX,MAACzB;4BAECqK,WAAU;4BACVN,SAAS;4BACTO,IAAI;gCAAEC,YAAY;4BAAS;;8CAE3B,MAACvK;oCAAMsK,IAAI;wCAAEE,MAAM;wCAAGC,UAAU;oCAAE;;sDAChC,KAACvK;4CAAWyH,SAAQ;4CAAQ+C,MAAM;sDAC/B,IAAGrE,wCAAAA,aAAa,CAACyB,YAAYhC,UAAU,CAAC,YAArCO,wCAAyC,IAAI,GAAG,CAAC,GACnD,GAAGlE,IAAI2F,YAAY7B,YAAY,EAAE,GAAG,EAAE9D,IAAI2F,YAAY5B,aAAa,GAAG;;sDAE1E,KAAChG;4CAAWyH,SAAQ;4CAAUiD,OAAM;4CAAiBF,MAAM;sDACxD;iDACC5C,QAAAA,yBAAAA,YAAYsB,SAAS,YAArBtB,yBAAyBA,YAAYmE,UAAU,YAA/CnE,OAAmD;gDACnD,GAAG9F,IAAI8F,YAAYoE,SAAS,EAAE,GAAG,EAAElK,IAAI8F,YAAYqE,UAAU,EAAE,KAAK,CAAC;6CACtE,CAACxB,IAAI,CAAC;;;;8CAGX,KAACjL;oCACCiM,OAAOS,OAAOtE,YAAYzD,MAAM,EAAEgI,OAAO,CAAC,KAAK;oCAC/CxB,MAAK;oCACLlD,SAAQ;oCACRiD,KAAK,GAAEnJ,mCAAAA,YAAY,CAACqG,YAAYzD,MAAM,CAAC,YAAhC5C,mCAAoC;;gCAE5CqG,YAAYzD,MAAM,KAAK,4BACtB;;sDACE,KAAC5E;4CAAOoL,MAAK;4CAAQC,SAASjD,aAAaC,aAAa;sDACrD;;sDAEH,KAACrI;4CACCoL,MAAK;4CACLD,OAAM;4CACNE,SAASjD,aAAaC,aAAa;sDAElC;;;qCAGH;gCACHA,YAAYzD,MAAM,KAAK,6BACtB,KAAC5E;oCACCoL,MAAK;oCACLC,SAAS;4CAGQhD,oBAQZA,yBACAA;wCAXH,kDAAkD;wCAClD,mCAAmC;wCACnC,MAAMwE,SAASxE,qBAAAA,YAAYwE,KAAK,YAAjBxE,qBAAqB,EAAE;wCAGtC,MAAMyE,aAAaD,MAAME,MAAM,CAC7B,CAACC,KAAKC;gDAAiBA;mDAAPD,QAAOC,qBAAAA,MAAMC,WAAW,YAAjBD,qBAAqB;2CAC5C;wCAEF,MAAME,eACJ,EAAC9E,0BAAAA,YAAYqE,UAAU,YAAtBrE,0BAA0B,OAC1BA,yBAAAA,YAAYoE,SAAS,YAArBpE,yBAAyB;wCAC5B,IAAIyE,aAAa,KAAKK,eAAe,GAAG;4CACtC,MAAMjL,YAAY,MAAMwC,QAAQ;gDAC9B4G,OAAO;gDACPC,aACE,GAAGuB,aAAa,IAAI,CAAC,cAAc,EAAEvK,IAAIuK,YAAY,wCAAwC,CAAC,GAAG,IAAI,GACrG,GAAGK,eAAe,IAAI,CAAC,kBAAkB,EAAE5K,IAAI4K,cAAc,2BAA2B,CAAC,GAAG,IAAI;gDAClG3B,kBAAkB;4CACpB,GACGE,IAAI,CAAC,IAAM,MACX1C,KAAK,CAAC,IAAM;4CACf,IAAI,CAAC9G,WAAW;wCAClB;wCACA,MAAMkG,aAAaC,aAAa;oCAClC;8CAEC;qCAED;gCACH;oCAAC;oCAAW;iCAAY,CAAC+E,QAAQ,CAAC/E,YAAYzD,MAAM,kBACnD,KAAC5E;oCACCoL,MAAK;oCACLD,OAAM;oCACNE,SAASjD,aAAaC,aAAa;8CAElC;qCAED;;2BA/ECA,YAAY/B,GAAG;;kCAmF1B,KAACxG;wBACCsF,MAAMA;wBACNE,UAAUA;wBACV+H,UAAUnI,aAAaoH,MAAM;wBAC7BnH,SAASA;wBACTmI,cAAcjI;wBACdkI,kBAAkBhI;;;;0BAItB,MAACrF;gBACCsN,MAAM3F,QAAQzD;gBACdqJ,SAAS,IAAM3G,iBAAiB;gBAChC4G,UAAS;gBACTC,SAAS;;kCAET,KAACtN;kCACE+D,CAAAA,iCAAAA,cAAemD,EAAE,IAAG,kBAAkB;;kCAEzC,MAACnH;wBAAcyK,IAAI;4BAAE+C,SAAS;4BAAQC,eAAe;4BAAU/B,KAAK;wBAAE;;0CACpE,KAACtL;gCACC0L,OAAM;gCACN4B,KAAK,WAAE1J,iCAAAA,cAAeyC,IAAI,oBAAI;gCAC9BkH,UAAU,CAACC,QACTlH,iBAAiB,CAACmH,OAChBA,OAAO,aAAKA;4CAAMpH,MAAMmH,MAAME,MAAM,CAACJ,KAAK;6CAAKG;gCAGnD7C,MAAK;gCACL+C,SAAS;gCACTtD,IAAI;oCAAE0B,IAAI;gCAAE;gCACZ6B,aAAY;;0CAEd,MAAC7N;gCAAMqK,WAAU;gCAAMN,SAAS;;kDAC9B,KAAC9J;wCACC0L,OAAM;wCACNkC,aAAY;wCACZN,KAAK,WAAE1J,iCAAAA,cAAeR,KAAK,oBAAI;wCAC/BmK,UAAU,CAACC,QACTlH,iBAAiB,CAACmH,OAChBA,OACI,aACKA;oDACHrK,OACEoK,MAAME,MAAM,CAACJ,KAAK,CAAClB,OAAO,CAAC,OAAO,QAAQ,KACtC/I,YACAC,OAAOkK,MAAME,MAAM,CAACJ,KAAK,CAAClB,OAAO,CAAC,OAAO;qDAEjDqB;wCAGRjE,OAAOnC,QAAQlE,aAAaS,iCAAAA,cAAeR,KAAK;wCAChDyK,UAAU,GACR1K,gBAAAA,aAAaS,iCAAAA,cAAeR,KAAK,aAAjCD,gBACA;wCAEFyH,MAAK;wCACLuC,SAAS;wCACTW,WAAW;4CAAEC,WAAW;gDAAEC,WAAW;4CAAU;wCAAE;;kDAEnD,KAAChO;wCACC0L,OAAM;wCACN4B,KAAK,WAAE1J,iCAAAA,cAAeqK,QAAQ,oBAAI;wCAClCV,UAAU,CAACC,QACTlH,iBAAiB,CAACmH,OAChBA,OACI,aACKA;oDACHQ,UACEC,KAAKC,KAAK,CAAC7K,OAAOkK,MAAME,MAAM,CAACJ,KAAK,MAAMjK;qDAE9CoK;wCAGRI,YAAW;wCACXjD,MAAK;wCACLuC,SAAS;wCACTW,WAAW;4CAAEC,WAAW;gDAAEC,WAAW;4CAAU;wCAAE;;;;0CAGrD,MAACjO;gCAAMqK,WAAU;gCAAMN,SAAS;;kDAC9B,KAAC9J;wCACC0L,OAAM;wCACN4B,KAAK,WAAE1J,iCAAAA,cAAeiD,cAAc,oBAAI;wCACxC0G,UAAU,CAACC,QACTlH,iBAAiB,CAACmH,OAChBA,OACI,aACKA;oDACH5G,gBAAgBvD,OAAOkK,MAAME,MAAM,CAACJ,KAAK,KAAK;qDAEhDG;wCAGR7C,MAAK;wCACLkD,WAAW;4CAAEC,WAAW;gDAAEC,WAAW;4CAAU;wCAAE;;kDAEnD,KAAChO;wCACC0L,OAAM;wCACNkC,aAAY;wCACZN,KAAK,WAAE1J,iCAAAA,cAAewK,iBAAiB,oBAAI;wCAC3Cb,UAAU,CAACC,QACTlH,iBAAiB,CAACmH,OAChBA,OACI,aACKA;oDACHW,mBACE9K,OAAOkK,MAAME,MAAM,CAACJ,KAAK,KAAKjK;qDAElCoK;wCAGR7C,MAAK;wCACLkD,WAAW;4CAAEC,WAAW;gDAAEC,WAAW;4CAAU;wCAAE;;;;0CAGrD,MAACjO;gCAAMqK,WAAU;gCAAMN,SAAS;;kDAC9B,KAAC9J;wCACC0L,OAAM;wCACN4B,KAAK,WAAE1J,iCAAAA,cAAegI,SAAS,oBAAI;wCACnC2B,UAAU,CAACC,QACTlH,iBAAiB,CAACmH,OAChBA,OACI,aACKA;oDACH7B,WACEsC,KAAKC,KAAK,CAAC7K,OAAOkK,MAAME,MAAM,CAACJ,KAAK,MAAMjK;qDAE9CoK;wCAGR7C,MAAK;wCACLkD,WAAW;4CAAEC,WAAW;gDAAEC,WAAW;4CAAU;wCAAE;;kDAEnD,KAAChO;wCACC0L,OAAM;wCACNkC,aAAY;wCACZN,KAAK,WAAE1J,iCAAAA,cAAe+H,UAAU,oBAAI;wCACpC4B,UAAU,CAACC,QACTlH,iBAAiB,CAACmH,OAChBA,OACI,aACKA;oDACH9B,YACEuC,KAAKC,KAAK,CAAC7K,OAAOkK,MAAME,MAAM,CAACJ,KAAK,MAAMjK;qDAE9CoK;wCAGR7C,MAAK;wCACLkD,WAAW;4CAAEC,WAAW;gDAAEC,WAAW;4CAAU;wCAAE;;kDAEnD,KAAChO;wCACC0L,OAAM;wCACN4B,KAAK,WAAE1J,iCAAAA,cAAeyK,iBAAiB,oBAAI;wCAC3Cd,UAAU,CAACC,QACTlH,iBAAiB,CAACmH,OAChBA,OACI,aACKA;oDACHY,mBACEH,KAAKC,KAAK,CAAC7K,OAAOkK,MAAME,MAAM,CAACJ,KAAK,MAAMjK;qDAE9CoK;wCAGR7C,MAAK;wCACLkD,WAAW;4CAAEC,WAAW;gDAAEC,WAAW;4CAAU;wCAAE;;;;;;kCAIvD,MAACrO;;0CACC,KAACH;gCAAOqL,SAAS,IAAMvE,iBAAiB;0CAAQ;;0CAChD,KAAC9G;gCACCkI,SAAQ;gCACRiD,OAAM;gCACNkB,UACE,EAACjI,kCAAAA,sBAAAA,cAAeyC,IAAI,qBAAnBzC,oBAAqBgD,IAAI,OAC1B,CAAE,CAAA,UAAChD,iCAAAA,cAAeiD,cAAc,oBAAI,KAAK,CAAA,KACzCQ,QAAQlE,aAAaS,iCAAAA,cAAeR,KAAK;gCAE3CyH,SAASlE;0CAER;;;;;;0BAKP,MAACjH;gBACCsN,MAAM3F,QAAQd;gBACd0G,SAAS,IAAMzG,UAAU;gBACzB0G,UAAS;gBACTC,SAAS;;kCAET,KAACtN;kCAAa;;kCACd,MAACD;wBAAcyK,IAAI;4BAAE+C,SAAS;4BAAQC,eAAe;4BAAU/B,KAAK;wBAAE;;0CACpE,KAACtL;gCACC0L,OAAM;gCACN4B,KAAK,YAAE/G,0BAAAA,OAAQV,UAAU,qBAAI;gCAC7B0H,UAAU,CAACC,QACThH,UAAU,CAACiH,OACTA,OAAO,aAAKA;4CAAM5H,YAAY2H,MAAME,MAAM,CAACJ,KAAK;6CAAKG;gCAGzD7C,MAAK;gCACL0D,MAAM;gCACNjE,IAAI;oCAAE0B,IAAI;gCAAE;0CAEX,CAAC5H,uBAAAA,eAAgB,EAAE,EAAE4B,GAAG,CAAC,CAACZ,yBACzB,KAACrF;wCAA4BwN,OAAOnI,SAASW,GAAG;kDAC7CX,SAASkB,IAAI;uCADDlB,SAASW,GAAG;;0CAK/B,MAAC/F;gCAAMqK,WAAU;gCAAMN,SAAS;;kDAC9B,KAAC9J;wCACC0L,OAAM;wCACN6C,MAAK;wCACLjB,KAAK,YAAE/G,0BAAAA,OAAQwB,OAAO,qBAAI;wCAC1BwF,UAAU,CAACC,QACThH,UAAU,CAACiH,OACTA,OAAO,aAAKA;oDAAM1F,SAASyF,MAAME,MAAM,CAACJ,KAAK;qDAAKG;wCAGtD7C,MAAK;wCACLuC,SAAS;wCACTW,WAAW;4CAAEU,YAAY;gDAAEC,QAAQ;4CAAK;wCAAE;;kDAE5C,KAACzO;wCACC0L,OAAM;wCACN6C,MAAK;wCACLjB,KAAK,YAAE/G,0BAAAA,OAAQyB,QAAQ,qBAAI;wCAC3BuF,UAAU,CAACC,QACThH,UAAU,CAACiH,OACTA,OAAO,aAAKA;oDAAMzF,UAAUwF,MAAME,MAAM,CAACJ,KAAK;qDAAKG;wCAGvD7C,MAAK;wCACLuC,SAAS;wCACTW,WAAW;4CAAEU,YAAY;gDAAEC,QAAQ;4CAAK;wCAAE;;;;0CAG9C,KAACzO;gCACC0L,OAAM;gCACN4B,KAAK,YAAE/G,0BAAAA,OAAQ4C,SAAS,qBAAI;gCAC5BoE,UAAU,CAACC,QACThH,UAAU,CAACiH,OACTA,OAAO,aAAKA;4CAAMtE,WAAWqE,MAAME,MAAM,CAACJ,KAAK;6CAAKG;gCAGxD7C,MAAK;;;;kCAGT,MAACjL;;0CACC,KAACH;gCAAOqL,SAAS,IAAMrE,UAAU;0CAAQ;;0CACzC,KAAChH;gCACCkI,SAAQ;gCACRiD,OAAM;gCACNkB,UACEpF,cACA,EAACF,0BAAAA,OAAQV,UAAU,KACnB,EAACU,0BAAAA,OAAQwB,OAAO,KAChB,EAACxB,0BAAAA,OAAQyB,QAAQ;gCAEnB6C,SAAS/C;0CAER;;;;;;;;AAMb;AACArE,iBAAiBiL,WAAW,GAAG;AAE/B,eAAejL,iBAAgB"}
@@ -91,6 +91,17 @@ const usd = (cents)=>`$${(cents / 100).toFixed(2)}`;
91
91
  checkInDayMs,
92
92
  checkOutDayMs
93
93
  ]);
94
+ // "Only 2 left" (AGL-4044): the fewest units free on any night of the
95
+ // stay, when that is a few. Never on a one-unit resource, which sends no
96
+ // `fewLeft`.
97
+ const unitsLeft = useMemo(()=>{
98
+ if (!availability || !checkInDayMs || !checkOutDayMs) return null;
99
+ return CommerceModel.fewestUnitsLeft(availability.fewLeft, checkInDayMs, checkOutDayMs);
100
+ }, [
101
+ availability,
102
+ checkInDayMs,
103
+ checkOutDayMs
104
+ ]);
94
105
  /**
95
106
  * Idempotency key for ONE reservation attempt (AGL-1697).
96
107
  *
@@ -330,6 +341,11 @@ const usd = (cents)=>`$${(cents / 100).toFixed(2)}`;
330
341
  children: usd(quote.depositCents)
331
342
  })
332
343
  ]
344
+ }) : null,
345
+ unitsLeft !== null ? /*#__PURE__*/ _jsx(Typography, {
346
+ variant: "body2",
347
+ color: "warning.main",
348
+ children: `Only ${unitsLeft} left`
333
349
  }) : null
334
350
  ]
335
351
  }) : null,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/components/reservation-widget.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\nimport * as Aglyn from '@aglyn/aglyn'\nimport {\n buildBeginCheckoutParams,\n trackEventBeforeNavigation,\n} from '@aglyn/aglyn/app-utils/analytics-events'\nimport * as CommerceModel from '../model'\nimport { mdiCalendarCheckOutline } from '@aglyn/shared-data-mdi'\nimport Alert from '@mui/material/Alert'\nimport Box from '@mui/material/Box'\nimport Button from '@mui/material/Button'\nimport Divider from '@mui/material/Divider'\nimport TextField from '@mui/material/TextField'\nimport Typography from '@mui/material/Typography'\nimport {\n forwardRef,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport { generatePresetId } from '../utils/generate-preset-id'\n\n// Component ids are persisted in screen documents; never rename.\nexport const ID: Aglyn.ComponentId = 'reservation-widget'\n\nexport interface ReservationWidgetProps {\n /** Resource id from the console Resources card. */\n resourceId?: string\n reserveLabel?: string\n}\n\ninterface Availability {\n resource: CommerceModel.HostResource\n unavailable: Array<{ fromDayMs: number; toDayMs: number }>\n}\n\nconst usd = (cents: number) => `$${(cents / 100).toFixed(2)}`\n\n/**\n * Reservation widget (AGL-310): date-range picker with a live quote\n * (nights × seasonal rates + deposit due today) and a reserve flow that\n * charges the deposit through the merchant's account. The server\n * re-quotes and re-checks availability — the widget is display-only.\n */\nconst ReservationWidget = forwardRef<HTMLDivElement, ReservationWidgetProps>(\n (props, ref) => {\n const { resourceId, reserveLabel, ...rest } = props\n // Node styles ride the renderer-merged sx; recompose (stack.ts pattern).\n const nodeSx = Array.isArray(props['sx']) ? props['sx'] : [props['sx']]\n const { hostId } = Aglyn.useSite()\n const { suppressNavigation } = useContext(Aglyn.ScreenLinkContext)\n const siteFetch = Aglyn.useSiteFetch()\n const [availability, setAvailability] = useState<Availability | null>(null)\n const [checkIn, setCheckIn] = useState('')\n const [checkOut, setCheckOut] = useState('')\n const [guestName, setGuestName] = useState('')\n const [guestEmail, setGuestEmail] = useState('')\n const [status, setStatus] = useState<'idle' | 'sending' | 'error'>('idle')\n const [message, setMessage] = useState('')\n\n useEffect(() => {\n if (!hostId || !resourceId) return\n let active = true\n void fetch(\n `/api/commerce/reservation-availability?hostId=${encodeURIComponent(hostId)}` +\n `&resourceId=${encodeURIComponent(resourceId)}`,\n )\n .then((response) => (response.ok ? response.json() : null))\n .then((payload) => {\n if (active && payload) setAvailability(payload)\n })\n .catch(() => undefined)\n return () => {\n active = false\n }\n }, [hostId, resourceId])\n\n const checkInDayMs = checkIn ? Date.parse(`${checkIn}T00:00:00Z`) : 0\n const checkOutDayMs = checkOut ? Date.parse(`${checkOut}T00:00:00Z`) : 0\n\n const quote = useMemo(() => {\n if (!availability || !checkInDayMs || !checkOutDayMs) return null\n return CommerceModel.computeReservationQuote(\n availability.resource,\n checkInDayMs,\n checkOutDayMs,\n )\n }, [availability, checkInDayMs, checkOutDayMs])\n\n const conflict = useMemo(() => {\n if (!availability || !checkInDayMs || !checkOutDayMs) return false\n return !CommerceModel.isRangeAvailable(\n availability.resource,\n availability.unavailable.map((range) => ({\n checkInDayMs: range.fromDayMs,\n checkOutDayMs: range.toDayMs,\n status: 'confirmed' as const,\n })),\n checkInDayMs,\n checkOutDayMs,\n )\n }, [availability, checkInDayMs, checkOutDayMs])\n\n /**\n * Idempotency key for ONE reservation attempt (AGL-1697).\n *\n * Minted lazily on the first Reserve click, retired whenever the stay\n * changes shape — different dates, resource or guest are a different\n * attempt. A retry of THIS attempt presents the same key, so the server\n * replays the original session URL instead of telling the guest their own\n * pending hold \"just sold out\".\n */\n const attemptKey = useRef('')\n useEffect(() => {\n attemptKey.current = ''\n }, [checkInDayMs, checkOutDayMs, resourceId, guestName, guestEmail])\n\n const handleReserve = async () => {\n if (!hostId || !resourceId || status === 'sending') return\n setStatus('sending')\n setMessage('')\n if (!attemptKey.current) {\n attemptKey.current =\n globalThis.crypto?.randomUUID?.() ??\n `${Date.now()}-${Math.random().toString(36).slice(2)}`\n }\n try {\n const response = await siteFetch('/api/commerce/reserve', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Idempotency-Key': attemptKey.current,\n },\n body: JSON.stringify({\n hostId,\n resourceId,\n checkInDayMs,\n checkOutDayMs,\n guestName,\n guestEmail,\n }),\n })\n const payload = await response.json().catch(() => ({}))\n if (response.ok && payload?.url) {\n // The stay's `begin_checkout`. This widget charges a real deposit\n // through the merchant's Stripe account and reported nothing at all,\n // so a lodging site saw traffic and then silence — GA4's ecommerce\n // reports read that as a 100% abandonment rate on every resource\n // they list, which is the same hole `purchase` closed for products.\n //\n // After the server accepted the hold and minted a session, never on\n // the click: the dates can lose a race and answer 409, and counting\n // those would report checkouts that Stripe never saw.\n //\n // `value` is what is being charged TODAY — `reserve.ts` bills\n // `depositCents || totalCents` — not the value of the stay. Reporting\n // the whole stay against a deposit payment would make the merchant's\n // checkout value disagree with the `purchase` that follows it, and\n // the gap would look like abandoned upsell rather than a deposit.\n //\n // Ex-tax, matching `buildStorefrontPurchaseParams`: the lodging tax\n // rides the session as a second line and is money held for a taxing\n // authority, not the merchant's revenue.\n const chargedCents = quote\n ? quote.depositCents || quote.totalCents\n : 0\n if (availability && chargedCents > 0) {\n await trackEventBeforeNavigation(\n 'begin_checkout',\n buildBeginCheckoutParams({\n value: chargedCents / 100,\n items: [\n {\n item_id: resourceId,\n item_name: availability.resource.name,\n price: chargedCents / 100,\n quantity: 1,\n },\n ],\n }),\n )\n }\n window.location.assign(payload.url)\n return\n }\n setMessage(String(payload?.error ?? ''))\n setStatus('error')\n } catch {\n setStatus('error')\n }\n }\n\n if (!hostId) {\n return (\n <Box\n ref={ref}\n {...rest}\n sx={[\n {\n p: 3,\n border: '1px dashed',\n borderColor: 'divider',\n borderRadius: 1,\n color: 'text.secondary',\n fontSize: 13,\n fontFamily: 'system-ui, sans-serif',\n },\n ...nodeSx,\n ]}\n >\n {'Reservation widget — dates, quote, and reserve render here'}\n </Box>\n )\n }\n if (!resourceId) {\n // The instruction is for the author, so only editing surfaces draw it;\n // a published page renders the bare element.\n if (!suppressNavigation) return <Box ref={ref} {...rest} />\n return (\n <Box ref={ref} {...rest} sx={[{ p: 2 }, ...nodeSx]}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Set a resource id on this block (console → Resources).'}\n </Typography>\n </Box>\n )\n }\n\n return (\n <Box\n ref={ref}\n {...rest}\n sx={[\n {\n display: 'flex',\n flexDirection: 'column',\n gap: 1.5,\n maxWidth: 420,\n p: 2,\n border: 1,\n borderColor: 'divider',\n borderRadius: 1,\n },\n ...nodeSx,\n ]}\n >\n {availability ? (\n <>\n <Typography variant=\"h6\">{availability.resource.name}</Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`From $${availability.resource.nightlyRateUsd}/night` +\n (availability.resource.minNights\n ? ` · ${availability.resource.minNights}-night minimum`\n : '')}\n </Typography>\n </>\n ) : null}\n <Box sx={{ display: 'flex', gap: 1 }}>\n <TextField\n label=\"Check-in\"\n type=\"date\"\n value={checkIn}\n onChange={(event) => setCheckIn(event.target.value)}\n size=\"small\"\n fullWidth\n slotProps={{ inputLabel: { shrink: true } }}\n />\n <TextField\n label=\"Check-out\"\n type=\"date\"\n value={checkOut}\n onChange={(event) => setCheckOut(event.target.value)}\n size=\"small\"\n fullWidth\n slotProps={{ inputLabel: { shrink: true } }}\n />\n </Box>\n {conflict ? (\n <Alert severity=\"warning\">{'Those dates are unavailable.'}</Alert>\n ) : quote?.problem ? (\n <Alert severity=\"info\">{quote.problem}</Alert>\n ) : quote ? (\n <>\n <Divider />\n <Box sx={{ display: 'flex', justifyContent: 'space-between' }}>\n <Typography variant=\"body2\">\n {`${quote.nights} night${quote.nights === 1 ? '' : 's'}`}\n </Typography>\n <Typography variant=\"body2\">\n {usd(quote.subtotalCents)}\n </Typography>\n </Box>\n {quote.depositCents < quote.totalCents ? (\n <Box sx={{ display: 'flex', justifyContent: 'space-between' }}>\n <Typography variant=\"body2\" sx={{ fontWeight: 600 }}>\n {'Due today (deposit)'}\n </Typography>\n <Typography variant=\"body2\" sx={{ fontWeight: 600 }}>\n {usd(quote.depositCents)}\n </Typography>\n </Box>\n ) : null}\n </>\n ) : null}\n <TextField\n label=\"Your name\"\n value={guestName}\n onChange={(event) => setGuestName(event.target.value)}\n size=\"small\"\n />\n <TextField\n label=\"Email\"\n type=\"email\"\n value={guestEmail}\n onChange={(event) => setGuestEmail(event.target.value)}\n size=\"small\"\n />\n {status === 'error' ? (\n <Alert severity=\"error\">\n {message || 'Reservation is unavailable right now.'}\n </Alert>\n ) : null}\n <Button\n variant=\"contained\"\n color=\"primary\"\n disabled={\n status === 'sending' ||\n conflict ||\n !quote ||\n Boolean(quote.problem) ||\n !guestEmail.trim()\n }\n onClick={handleReserve}\n >\n {status === 'sending'\n ? 'Redirecting…'\n : reserveLabel || 'Reserve now'}\n </Button>\n </Box>\n )\n },\n)\nReservationWidget.displayName = 'AglynReservationWidget'\n\nexport const schema: Aglyn.ComponentSchema<ReservationWidgetProps> = {\n $id: ID,\n pluginId: BUNDLE_ID,\n displayName: 'Reservation widget',\n description:\n 'A date-range booking with a live quote and a deposit taken up front.',\n category: Aglyn.ComponentCategory.COMMERCE,\n icon: { path: mdiCalendarCheckOutline.path, sx: { color: '#2e7d32' } },\n flags: { selfClosing: Aglyn.FEATURE_FLAG.ENABLED },\n attributes: [\n {\n name: 'resourceId',\n label: 'Resource id',\n description: 'From the Resources card on the Products page.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'reserveLabel',\n label: 'Button label',\n description: 'Defaults to \"Reserve now\".',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n ],\n}\n\nexport const presets: Aglyn.PresetSchema[] = [\n {\n $id: generatePresetId(ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Reservation widget',\n pluginId: BUNDLE_ID,\n description: 'Date-range stay booking with live quote + deposit',\n category: Aglyn.ComponentCategory.COMMERCE,\n icon: { path: mdiCalendarCheckOutline.path, sx: { color: '#2e7d32' } },\n data: {\n $id: null,\n componentId: ID,\n pluginId: BUNDLE_ID,\n props: {},\n },\n },\n]\n\nexport default ReservationWidget\n"],"names":["Aglyn","buildBeginCheckoutParams","trackEventBeforeNavigation","CommerceModel","mdiCalendarCheckOutline","Alert","Box","Button","Divider","TextField","Typography","forwardRef","useContext","useEffect","useMemo","useRef","useState","BUNDLE_ID","generatePresetId","ID","usd","cents","toFixed","ReservationWidget","props","ref","resourceId","reserveLabel","rest","nodeSx","Array","isArray","hostId","useSite","suppressNavigation","ScreenLinkContext","siteFetch","useSiteFetch","availability","setAvailability","checkIn","setCheckIn","checkOut","setCheckOut","guestName","setGuestName","guestEmail","setGuestEmail","status","setStatus","message","setMessage","active","fetch","encodeURIComponent","then","response","ok","json","payload","catch","undefined","checkInDayMs","Date","parse","checkOutDayMs","quote","computeReservationQuote","resource","conflict","isRangeAvailable","unavailable","map","range","fromDayMs","toDayMs","attemptKey","current","handleReserve","globalThis","crypto","randomUUID","now","Math","random","toString","slice","method","headers","body","JSON","stringify","url","chargedCents","depositCents","totalCents","value","items","item_id","item_name","name","price","quantity","window","location","assign","String","error","sx","p","border","borderColor","borderRadius","color","fontSize","fontFamily","variant","display","flexDirection","gap","maxWidth","nightlyRateUsd","minNights","label","type","onChange","event","target","size","fullWidth","slotProps","inputLabel","shrink","severity","problem","justifyContent","nights","subtotalCents","fontWeight","disabled","Boolean","trim","onClick","displayName","schema","$id","pluginId","description","category","ComponentCategory","COMMERCE","icon","path","flags","selfClosing","FEATURE_FLAG","ENABLED","attributes","component","FieldComponentType","TEXT_FIELD","presets","NodeType","PRESET","data","componentId"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/components/reservation-widget.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\nimport * as Aglyn from '@aglyn/aglyn'\nimport {\n buildBeginCheckoutParams,\n trackEventBeforeNavigation,\n} from '@aglyn/aglyn/app-utils/analytics-events'\nimport * as CommerceModel from '../model'\nimport { mdiCalendarCheckOutline } from '@aglyn/shared-data-mdi'\nimport Alert from '@mui/material/Alert'\nimport Box from '@mui/material/Box'\nimport Button from '@mui/material/Button'\nimport Divider from '@mui/material/Divider'\nimport TextField from '@mui/material/TextField'\nimport Typography from '@mui/material/Typography'\nimport {\n forwardRef,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport { generatePresetId } from '../utils/generate-preset-id'\n\n// Component ids are persisted in screen documents; never rename.\nexport const ID: Aglyn.ComponentId = 'reservation-widget'\n\nexport interface ReservationWidgetProps {\n /** Resource id from the console Resources card. */\n resourceId?: string\n reserveLabel?: string\n}\n\ninterface Availability {\n resource: CommerceModel.HostResource\n /**\n * Closed ranges. On a several-unit resource (AGL-4044) only the nights with\n * no unit left, so the one-unit overlap check below is the right one for\n * both: the endpoint sends no `units`, and the resource reads as one.\n */\n unavailable: Array<{ fromDayMs: number; toDayMs: number }>\n /** Nights with only a few units left; several-unit resources only. */\n fewLeft?: Array<{ dayMs: number; unitsLeft: number }>\n}\n\nconst usd = (cents: number) => `$${(cents / 100).toFixed(2)}`\n\n/**\n * Reservation widget (AGL-310): date-range picker with a live quote\n * (nights × seasonal rates + deposit due today) and a reserve flow that\n * charges the deposit through the merchant's account. The server\n * re-quotes and re-checks availability — the widget is display-only.\n */\nconst ReservationWidget = forwardRef<HTMLDivElement, ReservationWidgetProps>(\n (props, ref) => {\n const { resourceId, reserveLabel, ...rest } = props\n // Node styles ride the renderer-merged sx; recompose (stack.ts pattern).\n const nodeSx = Array.isArray(props['sx']) ? props['sx'] : [props['sx']]\n const { hostId } = Aglyn.useSite()\n const { suppressNavigation } = useContext(Aglyn.ScreenLinkContext)\n const siteFetch = Aglyn.useSiteFetch()\n const [availability, setAvailability] = useState<Availability | null>(null)\n const [checkIn, setCheckIn] = useState('')\n const [checkOut, setCheckOut] = useState('')\n const [guestName, setGuestName] = useState('')\n const [guestEmail, setGuestEmail] = useState('')\n const [status, setStatus] = useState<'idle' | 'sending' | 'error'>('idle')\n const [message, setMessage] = useState('')\n\n useEffect(() => {\n if (!hostId || !resourceId) return\n let active = true\n void fetch(\n `/api/commerce/reservation-availability?hostId=${encodeURIComponent(hostId)}` +\n `&resourceId=${encodeURIComponent(resourceId)}`,\n )\n .then((response) => (response.ok ? response.json() : null))\n .then((payload) => {\n if (active && payload) setAvailability(payload)\n })\n .catch(() => undefined)\n return () => {\n active = false\n }\n }, [hostId, resourceId])\n\n const checkInDayMs = checkIn ? Date.parse(`${checkIn}T00:00:00Z`) : 0\n const checkOutDayMs = checkOut ? Date.parse(`${checkOut}T00:00:00Z`) : 0\n\n const quote = useMemo(() => {\n if (!availability || !checkInDayMs || !checkOutDayMs) return null\n return CommerceModel.computeReservationQuote(\n availability.resource,\n checkInDayMs,\n checkOutDayMs,\n )\n }, [availability, checkInDayMs, checkOutDayMs])\n\n const conflict = useMemo(() => {\n if (!availability || !checkInDayMs || !checkOutDayMs) return false\n return !CommerceModel.isRangeAvailable(\n availability.resource,\n availability.unavailable.map((range) => ({\n checkInDayMs: range.fromDayMs,\n checkOutDayMs: range.toDayMs,\n status: 'confirmed' as const,\n })),\n checkInDayMs,\n checkOutDayMs,\n )\n }, [availability, checkInDayMs, checkOutDayMs])\n\n // \"Only 2 left\" (AGL-4044): the fewest units free on any night of the\n // stay, when that is a few. Never on a one-unit resource, which sends no\n // `fewLeft`.\n const unitsLeft = useMemo(() => {\n if (!availability || !checkInDayMs || !checkOutDayMs) return null\n return CommerceModel.fewestUnitsLeft(\n availability.fewLeft,\n checkInDayMs,\n checkOutDayMs,\n )\n }, [availability, checkInDayMs, checkOutDayMs])\n\n /**\n * Idempotency key for ONE reservation attempt (AGL-1697).\n *\n * Minted lazily on the first Reserve click, retired whenever the stay\n * changes shape — different dates, resource or guest are a different\n * attempt. A retry of THIS attempt presents the same key, so the server\n * replays the original session URL instead of telling the guest their own\n * pending hold \"just sold out\".\n */\n const attemptKey = useRef('')\n useEffect(() => {\n attemptKey.current = ''\n }, [checkInDayMs, checkOutDayMs, resourceId, guestName, guestEmail])\n\n const handleReserve = async () => {\n if (!hostId || !resourceId || status === 'sending') return\n setStatus('sending')\n setMessage('')\n if (!attemptKey.current) {\n attemptKey.current =\n globalThis.crypto?.randomUUID?.() ??\n `${Date.now()}-${Math.random().toString(36).slice(2)}`\n }\n try {\n const response = await siteFetch('/api/commerce/reserve', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Idempotency-Key': attemptKey.current,\n },\n body: JSON.stringify({\n hostId,\n resourceId,\n checkInDayMs,\n checkOutDayMs,\n guestName,\n guestEmail,\n }),\n })\n const payload = await response.json().catch(() => ({}))\n if (response.ok && payload?.url) {\n // The stay's `begin_checkout`. This widget charges a real deposit\n // through the merchant's Stripe account and reported nothing at all,\n // so a lodging site saw traffic and then silence — GA4's ecommerce\n // reports read that as a 100% abandonment rate on every resource\n // they list, which is the same hole `purchase` closed for products.\n //\n // After the server accepted the hold and minted a session, never on\n // the click: the dates can lose a race and answer 409, and counting\n // those would report checkouts that Stripe never saw.\n //\n // `value` is what is being charged TODAY — `reserve.ts` bills\n // `depositCents || totalCents` — not the value of the stay. Reporting\n // the whole stay against a deposit payment would make the merchant's\n // checkout value disagree with the `purchase` that follows it, and\n // the gap would look like abandoned upsell rather than a deposit.\n //\n // Ex-tax, matching `buildStorefrontPurchaseParams`: the lodging tax\n // rides the session as a second line and is money held for a taxing\n // authority, not the merchant's revenue.\n const chargedCents = quote\n ? quote.depositCents || quote.totalCents\n : 0\n if (availability && chargedCents > 0) {\n await trackEventBeforeNavigation(\n 'begin_checkout',\n buildBeginCheckoutParams({\n value: chargedCents / 100,\n items: [\n {\n item_id: resourceId,\n item_name: availability.resource.name,\n price: chargedCents / 100,\n quantity: 1,\n },\n ],\n }),\n )\n }\n window.location.assign(payload.url)\n return\n }\n setMessage(String(payload?.error ?? ''))\n setStatus('error')\n } catch {\n setStatus('error')\n }\n }\n\n if (!hostId) {\n return (\n <Box\n ref={ref}\n {...rest}\n sx={[\n {\n p: 3,\n border: '1px dashed',\n borderColor: 'divider',\n borderRadius: 1,\n color: 'text.secondary',\n fontSize: 13,\n fontFamily: 'system-ui, sans-serif',\n },\n ...nodeSx,\n ]}\n >\n {'Reservation widget — dates, quote, and reserve render here'}\n </Box>\n )\n }\n if (!resourceId) {\n // The instruction is for the author, so only editing surfaces draw it;\n // a published page renders the bare element.\n if (!suppressNavigation) return <Box ref={ref} {...rest} />\n return (\n <Box ref={ref} {...rest} sx={[{ p: 2 }, ...nodeSx]}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Set a resource id on this block (console → Resources).'}\n </Typography>\n </Box>\n )\n }\n\n return (\n <Box\n ref={ref}\n {...rest}\n sx={[\n {\n display: 'flex',\n flexDirection: 'column',\n gap: 1.5,\n maxWidth: 420,\n p: 2,\n border: 1,\n borderColor: 'divider',\n borderRadius: 1,\n },\n ...nodeSx,\n ]}\n >\n {availability ? (\n <>\n <Typography variant=\"h6\">{availability.resource.name}</Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`From $${availability.resource.nightlyRateUsd}/night` +\n (availability.resource.minNights\n ? ` · ${availability.resource.minNights}-night minimum`\n : '')}\n </Typography>\n </>\n ) : null}\n <Box sx={{ display: 'flex', gap: 1 }}>\n <TextField\n label=\"Check-in\"\n type=\"date\"\n value={checkIn}\n onChange={(event) => setCheckIn(event.target.value)}\n size=\"small\"\n fullWidth\n slotProps={{ inputLabel: { shrink: true } }}\n />\n <TextField\n label=\"Check-out\"\n type=\"date\"\n value={checkOut}\n onChange={(event) => setCheckOut(event.target.value)}\n size=\"small\"\n fullWidth\n slotProps={{ inputLabel: { shrink: true } }}\n />\n </Box>\n {conflict ? (\n <Alert severity=\"warning\">{'Those dates are unavailable.'}</Alert>\n ) : quote?.problem ? (\n <Alert severity=\"info\">{quote.problem}</Alert>\n ) : quote ? (\n <>\n <Divider />\n <Box sx={{ display: 'flex', justifyContent: 'space-between' }}>\n <Typography variant=\"body2\">\n {`${quote.nights} night${quote.nights === 1 ? '' : 's'}`}\n </Typography>\n <Typography variant=\"body2\">\n {usd(quote.subtotalCents)}\n </Typography>\n </Box>\n {quote.depositCents < quote.totalCents ? (\n <Box sx={{ display: 'flex', justifyContent: 'space-between' }}>\n <Typography variant=\"body2\" sx={{ fontWeight: 600 }}>\n {'Due today (deposit)'}\n </Typography>\n <Typography variant=\"body2\" sx={{ fontWeight: 600 }}>\n {usd(quote.depositCents)}\n </Typography>\n </Box>\n ) : null}\n {unitsLeft !== null ? (\n <Typography variant=\"body2\" color=\"warning.main\">\n {`Only ${unitsLeft} left`}\n </Typography>\n ) : null}\n </>\n ) : null}\n <TextField\n label=\"Your name\"\n value={guestName}\n onChange={(event) => setGuestName(event.target.value)}\n size=\"small\"\n />\n <TextField\n label=\"Email\"\n type=\"email\"\n value={guestEmail}\n onChange={(event) => setGuestEmail(event.target.value)}\n size=\"small\"\n />\n {status === 'error' ? (\n <Alert severity=\"error\">\n {message || 'Reservation is unavailable right now.'}\n </Alert>\n ) : null}\n <Button\n variant=\"contained\"\n color=\"primary\"\n disabled={\n status === 'sending' ||\n conflict ||\n !quote ||\n Boolean(quote.problem) ||\n !guestEmail.trim()\n }\n onClick={handleReserve}\n >\n {status === 'sending'\n ? 'Redirecting…'\n : reserveLabel || 'Reserve now'}\n </Button>\n </Box>\n )\n },\n)\nReservationWidget.displayName = 'AglynReservationWidget'\n\nexport const schema: Aglyn.ComponentSchema<ReservationWidgetProps> = {\n $id: ID,\n pluginId: BUNDLE_ID,\n displayName: 'Reservation widget',\n description:\n 'A date-range booking with a live quote and a deposit taken up front.',\n category: Aglyn.ComponentCategory.COMMERCE,\n icon: { path: mdiCalendarCheckOutline.path, sx: { color: '#2e7d32' } },\n flags: { selfClosing: Aglyn.FEATURE_FLAG.ENABLED },\n attributes: [\n {\n name: 'resourceId',\n label: 'Resource id',\n description: 'From the Resources card on the Products page.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'reserveLabel',\n label: 'Button label',\n description: 'Defaults to \"Reserve now\".',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n ],\n}\n\nexport const presets: Aglyn.PresetSchema[] = [\n {\n $id: generatePresetId(ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Reservation widget',\n pluginId: BUNDLE_ID,\n description: 'Date-range stay booking with live quote + deposit',\n category: Aglyn.ComponentCategory.COMMERCE,\n icon: { path: mdiCalendarCheckOutline.path, sx: { color: '#2e7d32' } },\n data: {\n $id: null,\n componentId: ID,\n pluginId: BUNDLE_ID,\n props: {},\n },\n },\n]\n\nexport default ReservationWidget\n"],"names":["Aglyn","buildBeginCheckoutParams","trackEventBeforeNavigation","CommerceModel","mdiCalendarCheckOutline","Alert","Box","Button","Divider","TextField","Typography","forwardRef","useContext","useEffect","useMemo","useRef","useState","BUNDLE_ID","generatePresetId","ID","usd","cents","toFixed","ReservationWidget","props","ref","resourceId","reserveLabel","rest","nodeSx","Array","isArray","hostId","useSite","suppressNavigation","ScreenLinkContext","siteFetch","useSiteFetch","availability","setAvailability","checkIn","setCheckIn","checkOut","setCheckOut","guestName","setGuestName","guestEmail","setGuestEmail","status","setStatus","message","setMessage","active","fetch","encodeURIComponent","then","response","ok","json","payload","catch","undefined","checkInDayMs","Date","parse","checkOutDayMs","quote","computeReservationQuote","resource","conflict","isRangeAvailable","unavailable","map","range","fromDayMs","toDayMs","unitsLeft","fewestUnitsLeft","fewLeft","attemptKey","current","handleReserve","globalThis","crypto","randomUUID","now","Math","random","toString","slice","method","headers","body","JSON","stringify","url","chargedCents","depositCents","totalCents","value","items","item_id","item_name","name","price","quantity","window","location","assign","String","error","sx","p","border","borderColor","borderRadius","color","fontSize","fontFamily","variant","display","flexDirection","gap","maxWidth","nightlyRateUsd","minNights","label","type","onChange","event","target","size","fullWidth","slotProps","inputLabel","shrink","severity","problem","justifyContent","nights","subtotalCents","fontWeight","disabled","Boolean","trim","onClick","displayName","schema","$id","pluginId","description","category","ComponentCategory","COMMERCE","icon","path","flags","selfClosing","FEATURE_FLAG","ENABLED","attributes","component","FieldComponentType","TEXT_FIELD","presets","NodeType","PRESET","data","componentId"],"mappings":"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"}
@@ -19,4 +19,10 @@ export declare const COMMERCE_CHARGEBACK_UNROUTABLE: OperatorAlertDefinition;
19
19
  export declare const COMMERCE_TAX_NOT_REVERSED: OperatorAlertDefinition;
20
20
  export declare const COMMERCE_SELLER_SHARE_NOT_REVERSED: OperatorAlertDefinition;
21
21
  export declare const COMMERCE_FEE_REPRICE_REFUSED: OperatorAlertDefinition;
22
+ /**
23
+ * A merchant paid for card readers in the POS hardware shop (AGL-4038). In
24
+ * phase 1 a person places the order in Stripe's Terminal hardware shop, so
25
+ * every paid order is work waiting on staff until it is marked ordered.
26
+ */
27
+ export declare const COMMERCE_POS_HARDWARE_ORDER: OperatorAlertDefinition;
22
28
  export declare const COMMERCE_OPERATOR_ALERTS: readonly OperatorAlertDefinition[];
@@ -77,11 +77,30 @@ export const COMMERCE_FEE_REPRICE_REFUSED = {
77
77
  dedupeWindowMinutes: 7 * DAY,
78
78
  defaultEnabled: true
79
79
  };
80
+ /**
81
+ * A merchant paid for card readers in the POS hardware shop (AGL-4038). In
82
+ * phase 1 a person places the order in Stripe's Terminal hardware shop, so
83
+ * every paid order is work waiting on staff until it is marked ordered.
84
+ */ export const COMMERCE_POS_HARDWARE_ORDER = {
85
+ type: 'commerce.posHardwareOrder',
86
+ pluginId: BUNDLE_ID,
87
+ label: 'New hardware order',
88
+ description: 'A workspace paid for card readers or accessories in the POS hardware shop. Place the order in the Stripe Dashboard’s Terminal hardware shop, shipping to the merchant’s address, then record the Stripe order id on Staff → Hardware orders.',
89
+ tier: 'should',
90
+ category: 'payments',
91
+ title: 'New hardware order {{orderNumber}} for {{amount}}',
92
+ body: 'Site {{site}} ordered {{items}} ({{amount}}, {{shipping}}). Place it in the Stripe Dashboard’s Terminal hardware shop, shipping to the address on the order, and record the Stripe order id on Staff → Hardware orders.',
93
+ link: '/admin/hardware-orders',
94
+ delivery: 'immediate',
95
+ dedupeWindowMinutes: 30 * DAY,
96
+ defaultEnabled: true
97
+ };
80
98
  export const COMMERCE_OPERATOR_ALERTS = [
81
99
  COMMERCE_CHARGEBACK_UNROUTABLE,
82
100
  COMMERCE_TAX_NOT_REVERSED,
83
101
  COMMERCE_SELLER_SHARE_NOT_REVERSED,
84
- COMMERCE_FEE_REPRICE_REFUSED
102
+ COMMERCE_FEE_REPRICE_REFUSED,
103
+ COMMERCE_POS_HARDWARE_ORDER
85
104
  ];
86
105
 
87
106
  //# sourceMappingURL=operator-alerts.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/constants/operator-alerts.ts"],"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\nimport type { OperatorAlertDefinition } from '@aglyn/aglyn/app-utils/operator-alerts'\nimport { BUNDLE_ID } from './bundle-common'\n\n/**\n * The storefront's operator alerts (AGL-3377): the money faults only this\n * plugin can see, declared in the registry's shape and registered from\n * `declarations.server`, so core lists none of them and an install without\n * commerce shows none of them on Staff → Operator alerts.\n *\n * Each is raised through `raiseOperatorAlert` with its definition, which\n * also registers it in a process whose declarations have not loaded yet.\n */\nconst DAY = 24 * 60\n\nexport const COMMERCE_CHARGEBACK_UNROUTABLE: OperatorAlertDefinition = {\n type: 'commerce.chargebackUnroutable',\n pluginId: BUNDLE_ID,\n label: 'Storefront chargeback could not be routed',\n description:\n 'A storefront chargeback matched no order, or more than one, so no order was flagged, no seller share reversed and no merchant told. A missing index means every storefront chargeback is being ignored.',\n tier: 'must',\n category: 'payments',\n title: 'A {{amount}} chargeback could not be routed to an order',\n body: '{{detail}}',\n delivery: 'immediate',\n dedupeWindowMinutes: DAY,\n defaultEnabled: true,\n}\n\nexport const COMMERCE_TAX_NOT_REVERSED: OperatorAlertDefinition = {\n type: 'commerce.taxNotReversed',\n pluginId: BUNDLE_ID,\n label: 'Subscription sales tax not reversed',\n description:\n 'Sales tax collected on a storefront subscription stayed with the merchant instead of coming back to the platform, which files and remits it. The platform may owe tax it does not hold.',\n tier: 'must',\n category: 'payments',\n title: 'Sales tax not reversed on invoice {{invoiceId}}',\n // `{{site}}` is the site's name and id (AGL-3432), so staff need not look\n // it up; the last sentence is the consequence the description states.\n body:\n '{{amount}} of sales tax on invoice {{invoiceId}} for site {{site}} was not pulled back from the merchant: {{reason}}. The platform files and remits that tax, so it may owe tax it does not hold.',\n delivery: 'immediate',\n dedupeWindowMinutes: 7 * DAY,\n defaultEnabled: true,\n}\n\nexport const COMMERCE_SELLER_SHARE_NOT_REVERSED: OperatorAlertDefinition = {\n type: 'commerce.sellerShareNotReversed',\n pluginId: BUNDLE_ID,\n label: 'Seller share not reversed on a lost dispute',\n description:\n 'A storefront dispute was lost and the merchant’s share could not be pulled back, so the platform is out the principal. Recover it by hand in Stripe.',\n tier: 'must',\n category: 'payments',\n title: 'Seller share not reversed on dispute {{disputeId}}',\n body:\n 'The lost dispute {{disputeId}} on order {{orderId}} from site {{site}} cost {{amount}}, and the merchant’s share was not reversed: {{reason}}. The platform is out that share until it is recovered by hand in Stripe.',\n delivery: 'immediate',\n dedupeWindowMinutes: 30 * DAY,\n defaultEnabled: true,\n}\n\nexport const COMMERCE_FEE_REPRICE_REFUSED: OperatorAlertDefinition = {\n type: 'commerce.feeRepriceRefused',\n pluginId: BUNDLE_ID,\n label: 'Platform fee correction refused',\n description:\n 'Stripe refused to change a storefront subscription’s platform fee, or to refund the part of a fee charged on the wrong base. The merchant is being charged a fee the plan does not set.',\n tier: 'must',\n category: 'payments',\n title: 'Platform fee not corrected on {{subject}}',\n body:\n 'The platform fee on {{subject}} for site {{site}} could not be corrected: {{reason}}. Until it is, the merchant is charged a fee its plan does not set.',\n delivery: 'immediate',\n dedupeWindowMinutes: 7 * DAY,\n defaultEnabled: true,\n}\n\nexport const COMMERCE_OPERATOR_ALERTS: readonly OperatorAlertDefinition[] = [\n COMMERCE_CHARGEBACK_UNROUTABLE,\n COMMERCE_TAX_NOT_REVERSED,\n COMMERCE_SELLER_SHARE_NOT_REVERSED,\n COMMERCE_FEE_REPRICE_REFUSED,\n]\n"],"names":["BUNDLE_ID","DAY","COMMERCE_CHARGEBACK_UNROUTABLE","type","pluginId","label","description","tier","category","title","body","delivery","dedupeWindowMinutes","defaultEnabled","COMMERCE_TAX_NOT_REVERSED","COMMERCE_SELLER_SHARE_NOT_REVERSED","COMMERCE_FEE_REPRICE_REFUSED","COMMERCE_OPERATOR_ALERTS"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAGD,SAASA,SAAS,QAAQ,qBAAiB;AAE3C;;;;;;;;CAQC,GACD,MAAMC,MAAM,KAAK;AAEjB,OAAO,MAAMC,iCAA0D;IACrEC,MAAM;IACNC,UAAUJ;IACVK,OAAO;IACPC,aACE;IACFC,MAAM;IACNC,UAAU;IACVC,OAAO;IACPC,MAAM;IACNC,UAAU;IACVC,qBAAqBX;IACrBY,gBAAgB;AAClB,EAAC;AAED,OAAO,MAAMC,4BAAqD;IAChEX,MAAM;IACNC,UAAUJ;IACVK,OAAO;IACPC,aACE;IACFC,MAAM;IACNC,UAAU;IACVC,OAAO;IACP,0EAA0E;IAC1E,sEAAsE;IACtEC,MACE;IACFC,UAAU;IACVC,qBAAqB,IAAIX;IACzBY,gBAAgB;AAClB,EAAC;AAED,OAAO,MAAME,qCAA8D;IACzEZ,MAAM;IACNC,UAAUJ;IACVK,OAAO;IACPC,aACE;IACFC,MAAM;IACNC,UAAU;IACVC,OAAO;IACPC,MACE;IACFC,UAAU;IACVC,qBAAqB,KAAKX;IAC1BY,gBAAgB;AAClB,EAAC;AAED,OAAO,MAAMG,+BAAwD;IACnEb,MAAM;IACNC,UAAUJ;IACVK,OAAO;IACPC,aACE;IACFC,MAAM;IACNC,UAAU;IACVC,OAAO;IACPC,MACE;IACFC,UAAU;IACVC,qBAAqB,IAAIX;IACzBY,gBAAgB;AAClB,EAAC;AAED,OAAO,MAAMI,2BAA+D;IAC1Ef;IACAY;IACAC;IACAC;CACD,CAAA"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/constants/operator-alerts.ts"],"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\nimport type { OperatorAlertDefinition } from '@aglyn/aglyn/app-utils/operator-alerts'\nimport { BUNDLE_ID } from './bundle-common'\n\n/**\n * The storefront's operator alerts (AGL-3377): the money faults only this\n * plugin can see, declared in the registry's shape and registered from\n * `declarations.server`, so core lists none of them and an install without\n * commerce shows none of them on Staff → Operator alerts.\n *\n * Each is raised through `raiseOperatorAlert` with its definition, which\n * also registers it in a process whose declarations have not loaded yet.\n */\nconst DAY = 24 * 60\n\nexport const COMMERCE_CHARGEBACK_UNROUTABLE: OperatorAlertDefinition = {\n type: 'commerce.chargebackUnroutable',\n pluginId: BUNDLE_ID,\n label: 'Storefront chargeback could not be routed',\n description:\n 'A storefront chargeback matched no order, or more than one, so no order was flagged, no seller share reversed and no merchant told. A missing index means every storefront chargeback is being ignored.',\n tier: 'must',\n category: 'payments',\n title: 'A {{amount}} chargeback could not be routed to an order',\n body: '{{detail}}',\n delivery: 'immediate',\n dedupeWindowMinutes: DAY,\n defaultEnabled: true,\n}\n\nexport const COMMERCE_TAX_NOT_REVERSED: OperatorAlertDefinition = {\n type: 'commerce.taxNotReversed',\n pluginId: BUNDLE_ID,\n label: 'Subscription sales tax not reversed',\n description:\n 'Sales tax collected on a storefront subscription stayed with the merchant instead of coming back to the platform, which files and remits it. The platform may owe tax it does not hold.',\n tier: 'must',\n category: 'payments',\n title: 'Sales tax not reversed on invoice {{invoiceId}}',\n // `{{site}}` is the site's name and id (AGL-3432), so staff need not look\n // it up; the last sentence is the consequence the description states.\n body:\n '{{amount}} of sales tax on invoice {{invoiceId}} for site {{site}} was not pulled back from the merchant: {{reason}}. The platform files and remits that tax, so it may owe tax it does not hold.',\n delivery: 'immediate',\n dedupeWindowMinutes: 7 * DAY,\n defaultEnabled: true,\n}\n\nexport const COMMERCE_SELLER_SHARE_NOT_REVERSED: OperatorAlertDefinition = {\n type: 'commerce.sellerShareNotReversed',\n pluginId: BUNDLE_ID,\n label: 'Seller share not reversed on a lost dispute',\n description:\n 'A storefront dispute was lost and the merchant’s share could not be pulled back, so the platform is out the principal. Recover it by hand in Stripe.',\n tier: 'must',\n category: 'payments',\n title: 'Seller share not reversed on dispute {{disputeId}}',\n body:\n 'The lost dispute {{disputeId}} on order {{orderId}} from site {{site}} cost {{amount}}, and the merchant’s share was not reversed: {{reason}}. The platform is out that share until it is recovered by hand in Stripe.',\n delivery: 'immediate',\n dedupeWindowMinutes: 30 * DAY,\n defaultEnabled: true,\n}\n\nexport const COMMERCE_FEE_REPRICE_REFUSED: OperatorAlertDefinition = {\n type: 'commerce.feeRepriceRefused',\n pluginId: BUNDLE_ID,\n label: 'Platform fee correction refused',\n description:\n 'Stripe refused to change a storefront subscription’s platform fee, or to refund the part of a fee charged on the wrong base. The merchant is being charged a fee the plan does not set.',\n tier: 'must',\n category: 'payments',\n title: 'Platform fee not corrected on {{subject}}',\n body:\n 'The platform fee on {{subject}} for site {{site}} could not be corrected: {{reason}}. Until it is, the merchant is charged a fee its plan does not set.',\n delivery: 'immediate',\n dedupeWindowMinutes: 7 * DAY,\n defaultEnabled: true,\n}\n\n/**\n * A merchant paid for card readers in the POS hardware shop (AGL-4038). In\n * phase 1 a person places the order in Stripe's Terminal hardware shop, so\n * every paid order is work waiting on staff until it is marked ordered.\n */\nexport const COMMERCE_POS_HARDWARE_ORDER: OperatorAlertDefinition = {\n type: 'commerce.posHardwareOrder',\n pluginId: BUNDLE_ID,\n label: 'New hardware order',\n description:\n 'A workspace paid for card readers or accessories in the POS hardware shop. Place the order in the Stripe Dashboard’s Terminal hardware shop, shipping to the merchant’s address, then record the Stripe order id on Staff → Hardware orders.',\n tier: 'should',\n category: 'payments',\n title: 'New hardware order {{orderNumber}} for {{amount}}',\n body:\n 'Site {{site}} ordered {{items}} ({{amount}}, {{shipping}}). Place it in the Stripe Dashboard’s Terminal hardware shop, shipping to the address on the order, and record the Stripe order id on Staff → Hardware orders.',\n link: '/admin/hardware-orders',\n delivery: 'immediate',\n dedupeWindowMinutes: 30 * DAY,\n defaultEnabled: true,\n}\n\nexport const COMMERCE_OPERATOR_ALERTS: readonly OperatorAlertDefinition[] = [\n COMMERCE_CHARGEBACK_UNROUTABLE,\n COMMERCE_TAX_NOT_REVERSED,\n COMMERCE_SELLER_SHARE_NOT_REVERSED,\n COMMERCE_FEE_REPRICE_REFUSED,\n COMMERCE_POS_HARDWARE_ORDER,\n]\n"],"names":["BUNDLE_ID","DAY","COMMERCE_CHARGEBACK_UNROUTABLE","type","pluginId","label","description","tier","category","title","body","delivery","dedupeWindowMinutes","defaultEnabled","COMMERCE_TAX_NOT_REVERSED","COMMERCE_SELLER_SHARE_NOT_REVERSED","COMMERCE_FEE_REPRICE_REFUSED","COMMERCE_POS_HARDWARE_ORDER","link","COMMERCE_OPERATOR_ALERTS"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAGD,SAASA,SAAS,QAAQ,qBAAiB;AAE3C;;;;;;;;CAQC,GACD,MAAMC,MAAM,KAAK;AAEjB,OAAO,MAAMC,iCAA0D;IACrEC,MAAM;IACNC,UAAUJ;IACVK,OAAO;IACPC,aACE;IACFC,MAAM;IACNC,UAAU;IACVC,OAAO;IACPC,MAAM;IACNC,UAAU;IACVC,qBAAqBX;IACrBY,gBAAgB;AAClB,EAAC;AAED,OAAO,MAAMC,4BAAqD;IAChEX,MAAM;IACNC,UAAUJ;IACVK,OAAO;IACPC,aACE;IACFC,MAAM;IACNC,UAAU;IACVC,OAAO;IACP,0EAA0E;IAC1E,sEAAsE;IACtEC,MACE;IACFC,UAAU;IACVC,qBAAqB,IAAIX;IACzBY,gBAAgB;AAClB,EAAC;AAED,OAAO,MAAME,qCAA8D;IACzEZ,MAAM;IACNC,UAAUJ;IACVK,OAAO;IACPC,aACE;IACFC,MAAM;IACNC,UAAU;IACVC,OAAO;IACPC,MACE;IACFC,UAAU;IACVC,qBAAqB,KAAKX;IAC1BY,gBAAgB;AAClB,EAAC;AAED,OAAO,MAAMG,+BAAwD;IACnEb,MAAM;IACNC,UAAUJ;IACVK,OAAO;IACPC,aACE;IACFC,MAAM;IACNC,UAAU;IACVC,OAAO;IACPC,MACE;IACFC,UAAU;IACVC,qBAAqB,IAAIX;IACzBY,gBAAgB;AAClB,EAAC;AAED;;;;CAIC,GACD,OAAO,MAAMI,8BAAuD;IAClEd,MAAM;IACNC,UAAUJ;IACVK,OAAO;IACPC,aACE;IACFC,MAAM;IACNC,UAAU;IACVC,OAAO;IACPC,MACE;IACFQ,MAAM;IACNP,UAAU;IACVC,qBAAqB,KAAKX;IAC1BY,gBAAgB;AAClB,EAAC;AAED,OAAO,MAAMM,2BAA+D;IAC1EjB;IACAY;IACAC;IACAC;IACAC;CACD,CAAA"}
@@ -17,6 +17,7 @@
17
17
  // registry, not the whole server surface.
18
18
  import { registerApiV1SiteResource } from "@aglyn/tenant-data-admin/server/api-v1-resources";
19
19
  import { registerPluginPersonEraser } from "@aglyn/aglyn/plugin-manager/plugin-person-erasure";
20
+ import { registerPluginUserEraser } from "@aglyn/aglyn/plugin-manager/plugin-user-erasure";
20
21
  import { registerPluginService } from "@aglyn/aglyn/plugin-manager/plugin-services";
21
22
  import { registerPluginSearchSources } from "@aglyn/aglyn/plugin-manager/plugin-search-sources";
22
23
  import { COMMERCE_SERVER_SEARCH_SOURCES } from "./model/commerce-server-search-sources.js";
@@ -50,6 +51,12 @@ import { registerCommerceTransferResources } from "./transfer/register-transfer-
50
51
  registerPluginPersonEraser(async (request)=>(await import("./server/person-eraser.js")).commercePersonEraser(request), {
51
52
  pluginId: BUNDLE_ID
52
53
  });
54
+ // Commerce's share of an ACCOUNT erasure (AGL-4038): every POS hardware
55
+ // order the person placed keeps its amounts and its tax region, with the
56
+ // buyer taken off it. Loaded with the first erasure.
57
+ registerPluginUserEraser(async (request)=>(await import("./server/pos-hardware-user-eraser.js")).posHardwareUserEraser(request), {
58
+ pluginId: BUNDLE_ID
59
+ });
53
60
  // A site's earlier orders, for a plugin that reads history behind the
54
61
  // order events (AGL-3614); the reader is loaded with the first page asked.
55
62
  registerPluginService(ORDER_HISTORY_CONTRACT, {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/plugins/commerce/src/lib/declarations.console-server.ts"],"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// The registry's own module, not the data layer's barrel: boot needs the\n// registry, not the whole server surface.\nimport { registerApiV1SiteResource } from '@aglyn/tenant-data-admin/server/api-v1-resources'\nimport { registerPluginPersonEraser } from '@aglyn/aglyn/plugin-manager/plugin-person-erasure'\nimport { registerPluginService } from '@aglyn/aglyn/plugin-manager/plugin-services'\nimport { registerPluginSearchSources } from '@aglyn/aglyn/plugin-manager/plugin-search-sources'\nimport { COMMERCE_SERVER_SEARCH_SOURCES } from './model/commerce-server-search-sources'\nimport { BUNDLE_ID } from './constants/bundle-common'\nimport { ORDER_HISTORY_CONTRACT } from './model/order-history'\nimport { registerCommerceTransferResources } from './transfer/register-transfer-resources'\n\n/**\n * The commerce plugin's CONSOLE-ONLY server declarations, named under\n * `consoleServerDeclarations` in `plugins.config.json` and run at the\n * console's boot. The tenant runtime registers none of it.\n *\n * It serves a site's products and orders on the customer REST API,\n * `/v1/sites/{siteId}/products/…` and `/v1/sites/{siteId}/orders/…`: the\n * console's router owns the pipeline in front — the key, the plan's API\n * access, the quota, the rate limit, the error envelope — and the site's\n * ownership, and hands every request under each resource here.\n *\n * No plan feature is named on the registrations, though both need\n * `commerce`: the handlers ask the key's scope FIRST and the plan second\n * (AGL-900, AGL-1928), so a key without the scope is told about the scope\n * whatever plan its organization is on, and the router would ask in the\n * opposite order.\n *\n * Light at boot: the handlers are imported with their first request and the\n * descriptions when the document is first built. Registering again replaces\n * this plugin's own entries.\n */\nexport function registerCommerceConsoleServerDeclarations(): void {\n // Commerce's share of a person erasure (AGL-2623, AGL-3080): every order the\n // person placed keeps its amounts, with the buyer taken off it. Required,\n // and loaded with the first erasure.\n registerPluginPersonEraser(\n async (request) => (await import('./server/person-eraser')).commercePersonEraser(request),\n { pluginId: BUNDLE_ID },\n )\n // A site's earlier orders, for a plugin that reads history behind the\n // order events (AGL-3614); the reader is loaded with the first page asked.\n registerPluginService(\n ORDER_HISTORY_CONTRACT,\n { listOrders: async (request) => (await import('./server/order-history')).listOrderHistory(request) },\n { pluginId: BUNDLE_ID },\n )\n registerApiV1SiteResource(\n 'products',\n {\n handle: async (...args) =>\n (await import('./server/api-v1/orders-and-products')).handleProducts(...args),\n describe: async () =>\n (await import('./server/api-v1/openapi')).PRODUCTS_API_V1_DESCRIPTION,\n },\n { pluginId: BUNDLE_ID },\n )\n registerApiV1SiteResource(\n 'orders',\n {\n handle: async (...args) =>\n (await import('./server/api-v1/orders-and-products')).handleOrders(...args),\n describe: async () =>\n (await import('./server/api-v1/openapi')).ORDERS_API_V1_DESCRIPTION,\n },\n { pluginId: BUNDLE_ID },\n )\n // Products, categories, orders, discounts, coupons and gift cards on the\n // import wizard and the export dialog (AGL-3531): the transfer routes are\n // the console's alone, so they register here.\n registerCommerceTransferResources()\n // What the native apps' one box finds in a store (AGL-3943): products and\n // orders, each under its own plan flag and, for orders, the site role that\n // reads them.\n registerPluginSearchSources(COMMERCE_SERVER_SEARCH_SOURCES, { pluginId: BUNDLE_ID })\n}\n"],"names":["registerApiV1SiteResource","registerPluginPersonEraser","registerPluginService","registerPluginSearchSources","COMMERCE_SERVER_SEARCH_SOURCES","BUNDLE_ID","ORDER_HISTORY_CONTRACT","registerCommerceTransferResources","registerCommerceConsoleServerDeclarations","request","commercePersonEraser","pluginId","listOrders","listOrderHistory","handle","args","handleProducts","describe","PRODUCTS_API_V1_DESCRIPTION","handleOrders","ORDERS_API_V1_DESCRIPTION"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,yEAAyE;AACzE,0CAA0C;AAC1C,SAASA,yBAAyB,QAAQ,mDAAkD;AAC5F,SAASC,0BAA0B,QAAQ,oDAAmD;AAC9F,SAASC,qBAAqB,QAAQ,8CAA6C;AACnF,SAASC,2BAA2B,QAAQ,oDAAmD;AAC/F,SAASC,8BAA8B,QAAQ,4CAAwC;AACvF,SAASC,SAAS,QAAQ,+BAA2B;AACrD,SAASC,sBAAsB,QAAQ,2BAAuB;AAC9D,SAASC,iCAAiC,QAAQ,4CAAwC;AAE1F;;;;;;;;;;;;;;;;;;;;CAoBC,GACD,OAAO,SAASC;IACd,6EAA6E;IAC7E,0EAA0E;IAC1E,qCAAqC;IACrCP,2BACE,OAAOQ,UAAY,AAAC,CAAA,MAAM,MAAM,CAAC,4BAAwB,EAAGC,oBAAoB,CAACD,UACjF;QAAEE,UAAUN;IAAU;IAExB,sEAAsE;IACtE,2EAA2E;IAC3EH,sBACEI,wBACA;QAAEM,YAAY,OAAOH,UAAY,AAAC,CAAA,MAAM,MAAM,CAAC,4BAAwB,EAAGI,gBAAgB,CAACJ;IAAS,GACpG;QAAEE,UAAUN;IAAU;IAExBL,0BACE,YACA;QACEc,QAAQ,OAAO,GAAGC,OAChB,AAAC,CAAA,MAAM,MAAM,CAAC,yCAAqC,EAAGC,cAAc,IAAID;QAC1EE,UAAU,UACR,AAAC,CAAA,MAAM,MAAM,CAAC,6BAAyB,EAAGC,2BAA2B;IACzE,GACA;QAAEP,UAAUN;IAAU;IAExBL,0BACE,UACA;QACEc,QAAQ,OAAO,GAAGC,OAChB,AAAC,CAAA,MAAM,MAAM,CAAC,yCAAqC,EAAGI,YAAY,IAAIJ;QACxEE,UAAU,UACR,AAAC,CAAA,MAAM,MAAM,CAAC,6BAAyB,EAAGG,yBAAyB;IACvE,GACA;QAAET,UAAUN;IAAU;IAExB,yEAAyE;IACzE,0EAA0E;IAC1E,8CAA8C;IAC9CE;IACA,0EAA0E;IAC1E,2EAA2E;IAC3E,cAAc;IACdJ,4BAA4BC,gCAAgC;QAAEO,UAAUN;IAAU;AACpF"}
1
+ {"version":3,"sources":["../../../../../../libs/plugins/commerce/src/lib/declarations.console-server.ts"],"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// The registry's own module, not the data layer's barrel: boot needs the\n// registry, not the whole server surface.\nimport { registerApiV1SiteResource } from '@aglyn/tenant-data-admin/server/api-v1-resources'\nimport { registerPluginPersonEraser } from '@aglyn/aglyn/plugin-manager/plugin-person-erasure'\nimport { registerPluginUserEraser } from '@aglyn/aglyn/plugin-manager/plugin-user-erasure'\nimport { registerPluginService } from '@aglyn/aglyn/plugin-manager/plugin-services'\nimport { registerPluginSearchSources } from '@aglyn/aglyn/plugin-manager/plugin-search-sources'\nimport { COMMERCE_SERVER_SEARCH_SOURCES } from './model/commerce-server-search-sources'\nimport { BUNDLE_ID } from './constants/bundle-common'\nimport { ORDER_HISTORY_CONTRACT } from './model/order-history'\nimport { registerCommerceTransferResources } from './transfer/register-transfer-resources'\n\n/**\n * The commerce plugin's CONSOLE-ONLY server declarations, named under\n * `consoleServerDeclarations` in `plugins.config.json` and run at the\n * console's boot. The tenant runtime registers none of it.\n *\n * It serves a site's products and orders on the customer REST API,\n * `/v1/sites/{siteId}/products/…` and `/v1/sites/{siteId}/orders/…`: the\n * console's router owns the pipeline in front — the key, the plan's API\n * access, the quota, the rate limit, the error envelope — and the site's\n * ownership, and hands every request under each resource here.\n *\n * No plan feature is named on the registrations, though both need\n * `commerce`: the handlers ask the key's scope FIRST and the plan second\n * (AGL-900, AGL-1928), so a key without the scope is told about the scope\n * whatever plan its organization is on, and the router would ask in the\n * opposite order.\n *\n * Light at boot: the handlers are imported with their first request and the\n * descriptions when the document is first built. Registering again replaces\n * this plugin's own entries.\n */\nexport function registerCommerceConsoleServerDeclarations(): void {\n // Commerce's share of a person erasure (AGL-2623, AGL-3080): every order the\n // person placed keeps its amounts, with the buyer taken off it. Required,\n // and loaded with the first erasure.\n registerPluginPersonEraser(\n async (request) => (await import('./server/person-eraser')).commercePersonEraser(request),\n { pluginId: BUNDLE_ID },\n )\n // Commerce's share of an ACCOUNT erasure (AGL-4038): every POS hardware\n // order the person placed keeps its amounts and its tax region, with the\n // buyer taken off it. Loaded with the first erasure.\n registerPluginUserEraser(\n async (request) => (await import('./server/pos-hardware-user-eraser')).posHardwareUserEraser(request),\n { pluginId: BUNDLE_ID },\n )\n // A site's earlier orders, for a plugin that reads history behind the\n // order events (AGL-3614); the reader is loaded with the first page asked.\n registerPluginService(\n ORDER_HISTORY_CONTRACT,\n { listOrders: async (request) => (await import('./server/order-history')).listOrderHistory(request) },\n { pluginId: BUNDLE_ID },\n )\n registerApiV1SiteResource(\n 'products',\n {\n handle: async (...args) =>\n (await import('./server/api-v1/orders-and-products')).handleProducts(...args),\n describe: async () =>\n (await import('./server/api-v1/openapi')).PRODUCTS_API_V1_DESCRIPTION,\n },\n { pluginId: BUNDLE_ID },\n )\n registerApiV1SiteResource(\n 'orders',\n {\n handle: async (...args) =>\n (await import('./server/api-v1/orders-and-products')).handleOrders(...args),\n describe: async () =>\n (await import('./server/api-v1/openapi')).ORDERS_API_V1_DESCRIPTION,\n },\n { pluginId: BUNDLE_ID },\n )\n // Products, categories, orders, discounts, coupons and gift cards on the\n // import wizard and the export dialog (AGL-3531): the transfer routes are\n // the console's alone, so they register here.\n registerCommerceTransferResources()\n // What the native apps' one box finds in a store (AGL-3943): products and\n // orders, each under its own plan flag and, for orders, the site role that\n // reads them.\n registerPluginSearchSources(COMMERCE_SERVER_SEARCH_SOURCES, { pluginId: BUNDLE_ID })\n}\n"],"names":["registerApiV1SiteResource","registerPluginPersonEraser","registerPluginUserEraser","registerPluginService","registerPluginSearchSources","COMMERCE_SERVER_SEARCH_SOURCES","BUNDLE_ID","ORDER_HISTORY_CONTRACT","registerCommerceTransferResources","registerCommerceConsoleServerDeclarations","request","commercePersonEraser","pluginId","posHardwareUserEraser","listOrders","listOrderHistory","handle","args","handleProducts","describe","PRODUCTS_API_V1_DESCRIPTION","handleOrders","ORDERS_API_V1_DESCRIPTION"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,yEAAyE;AACzE,0CAA0C;AAC1C,SAASA,yBAAyB,QAAQ,mDAAkD;AAC5F,SAASC,0BAA0B,QAAQ,oDAAmD;AAC9F,SAASC,wBAAwB,QAAQ,kDAAiD;AAC1F,SAASC,qBAAqB,QAAQ,8CAA6C;AACnF,SAASC,2BAA2B,QAAQ,oDAAmD;AAC/F,SAASC,8BAA8B,QAAQ,4CAAwC;AACvF,SAASC,SAAS,QAAQ,+BAA2B;AACrD,SAASC,sBAAsB,QAAQ,2BAAuB;AAC9D,SAASC,iCAAiC,QAAQ,4CAAwC;AAE1F;;;;;;;;;;;;;;;;;;;;CAoBC,GACD,OAAO,SAASC;IACd,6EAA6E;IAC7E,0EAA0E;IAC1E,qCAAqC;IACrCR,2BACE,OAAOS,UAAY,AAAC,CAAA,MAAM,MAAM,CAAC,4BAAwB,EAAGC,oBAAoB,CAACD,UACjF;QAAEE,UAAUN;IAAU;IAExB,wEAAwE;IACxE,yEAAyE;IACzE,qDAAqD;IACrDJ,yBACE,OAAOQ,UAAY,AAAC,CAAA,MAAM,MAAM,CAAC,uCAAmC,EAAGG,qBAAqB,CAACH,UAC7F;QAAEE,UAAUN;IAAU;IAExB,sEAAsE;IACtE,2EAA2E;IAC3EH,sBACEI,wBACA;QAAEO,YAAY,OAAOJ,UAAY,AAAC,CAAA,MAAM,MAAM,CAAC,4BAAwB,EAAGK,gBAAgB,CAACL;IAAS,GACpG;QAAEE,UAAUN;IAAU;IAExBN,0BACE,YACA;QACEgB,QAAQ,OAAO,GAAGC,OAChB,AAAC,CAAA,MAAM,MAAM,CAAC,yCAAqC,EAAGC,cAAc,IAAID;QAC1EE,UAAU,UACR,AAAC,CAAA,MAAM,MAAM,CAAC,6BAAyB,EAAGC,2BAA2B;IACzE,GACA;QAAER,UAAUN;IAAU;IAExBN,0BACE,UACA;QACEgB,QAAQ,OAAO,GAAGC,OAChB,AAAC,CAAA,MAAM,MAAM,CAAC,yCAAqC,EAAGI,YAAY,IAAIJ;QACxEE,UAAU,UACR,AAAC,CAAA,MAAM,MAAM,CAAC,6BAAyB,EAAGG,yBAAyB;IACvE,GACA;QAAEV,UAAUN;IAAU;IAExB,yEAAyE;IACzE,0EAA0E;IAC1E,8CAA8C;IAC9CE;IACA,0EAA0E;IAC1E,2EAA2E;IAC3E,cAAc;IACdJ,4BAA4BC,gCAAgC;QAAEO,UAAUN;IAAU;AACpF"}
@@ -0,0 +1,26 @@
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
+ */
17
+ /**
18
+ * The plugin's DECLARATIONS (AGL-4047): its permission keys (the point of sale key (`managePos`)), which
19
+ * the one permission catalog lists beside the core keys. Loaded eagerly by
20
+ * the console (client and server) and the tenant's server through the
21
+ * generated declarations manifest, so the role editor offers the keys, a
22
+ * custom role or a per-member override keeps them, and a route resolves
23
+ * them whether or not a request has touched the plugin yet. Light by
24
+ * construction: nothing here reaches a provider, Firestore or React.
25
+ */
26
+ export declare function registerCommerceDeclarations(): void;