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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/README.md +2 -2
  2. package/package.json +13 -13
  3. package/src/lib/components/commerce-console-page.js +29 -2
  4. package/src/lib/components/commerce-console-page.js.map +1 -1
  5. package/src/lib/components/console/commerce-analytics-card.component.js +4 -2
  6. package/src/lib/components/console/commerce-analytics-card.component.js.map +1 -1
  7. package/src/lib/components/console/host-orders-card.component.js +5 -1
  8. package/src/lib/components/console/host-orders-card.component.js.map +1 -1
  9. package/src/lib/components/console/host-quotes-card.component.d.ts +34 -0
  10. package/src/lib/components/console/host-quotes-card.component.js +247 -0
  11. package/src/lib/components/console/host-quotes-card.component.js.map +1 -0
  12. package/src/lib/components/console/pos-ops/pos-check-ins-card.component.js +33 -28
  13. package/src/lib/components/console/pos-ops/pos-check-ins-card.component.js.map +1 -1
  14. package/src/lib/components/console/quote-editor-dialog.component.d.ts +45 -0
  15. package/src/lib/components/console/quote-editor-dialog.component.js +1266 -0
  16. package/src/lib/components/console/quote-editor-dialog.component.js.map +1 -0
  17. package/src/lib/components/console/quote-pay-link.component.d.ts +38 -0
  18. package/src/lib/components/console/quote-pay-link.component.js +213 -0
  19. package/src/lib/components/console/quote-pay-link.component.js.map +1 -0
  20. package/src/lib/components/console/quote-settings-card.component.d.ts +29 -0
  21. package/src/lib/components/console/quote-settings-card.component.js +121 -0
  22. package/src/lib/components/console/quote-settings-card.component.js.map +1 -0
  23. package/src/lib/components/console/tax-settings-card.component.js +4 -1
  24. package/src/lib/components/console/tax-settings-card.component.js.map +1 -1
  25. package/src/lib/components/quote-page-body.d.ts +33 -0
  26. package/src/lib/components/quote-page-body.js +696 -0
  27. package/src/lib/components/quote-page-body.js.map +1 -0
  28. package/src/lib/components/quote-page.d.ts +29 -0
  29. package/src/lib/components/quote-page.js +96 -0
  30. package/src/lib/components/quote-page.js.map +1 -0
  31. package/src/lib/components/storefront-payment-element.d.ts +7 -1
  32. package/src/lib/components/storefront-payment-element.js +8 -3
  33. package/src/lib/components/storefront-payment-element.js.map +1 -1
  34. package/src/lib/constants/orders-list-query.js +6 -1
  35. package/src/lib/constants/orders-list-query.js.map +1 -1
  36. package/src/lib/constants/quote-page.d.ts +30 -0
  37. package/src/lib/constants/quote-page.js +35 -0
  38. package/src/lib/constants/quote-page.js.map +1 -0
  39. package/src/lib/model/commerce-orders.d.ts +25 -2
  40. package/src/lib/model/commerce-orders.js +10 -1
  41. package/src/lib/model/commerce-orders.js.map +1 -1
  42. package/src/lib/model/commerce-quote-pay.d.ts +229 -0
  43. package/src/lib/model/commerce-quote-pay.js +393 -0
  44. package/src/lib/model/commerce-quote-pay.js.map +1 -0
  45. package/src/lib/model/commerce-quotes.d.ts +279 -0
  46. package/src/lib/model/commerce-quotes.js +522 -0
  47. package/src/lib/model/commerce-quotes.js.map +1 -0
  48. package/src/lib/model/index.d.ts +2 -0
  49. package/src/lib/model/index.js +2 -0
  50. package/src/lib/model/index.js.map +1 -1
  51. package/src/lib/model/plugin-permissions.d.ts +1 -1
  52. package/src/lib/model/plugin-permissions.js +20 -0
  53. package/src/lib/model/plugin-permissions.js.map +1 -1
  54. package/src/lib/server/api-v1/orders-and-products.js +6 -2
  55. package/src/lib/server/api-v1/orders-and-products.js.map +1 -1
  56. package/src/lib/server/billing-webhook.js +127 -52
  57. package/src/lib/server/billing-webhook.js.map +1 -1
  58. package/src/lib/server/checkout-session-expiry.d.ts +36 -0
  59. package/src/lib/server/checkout-session-expiry.js +63 -0
  60. package/src/lib/server/checkout-session-expiry.js.map +1 -0
  61. package/src/lib/server/draft-order.js +16 -8
  62. package/src/lib/server/draft-order.js.map +1 -1
  63. package/src/lib/server/person-eraser.d.ts +7 -0
  64. package/src/lib/server/person-eraser.js +24 -2
  65. package/src/lib/server/person-eraser.js.map +1 -1
  66. package/src/lib/server/pos-return.js +3 -1
  67. package/src/lib/server/pos-return.js.map +1 -1
  68. package/src/lib/server/quote-link-token.d.ts +39 -0
  69. package/src/lib/server/quote-link-token.js +97 -0
  70. package/src/lib/server/quote-link-token.js.map +1 -0
  71. package/src/lib/server/quote-pay.d.ts +83 -0
  72. package/src/lib/server/quote-pay.js +606 -0
  73. package/src/lib/server/quote-pay.js.map +1 -0
  74. package/src/lib/server/quote.d.ts +49 -0
  75. package/src/lib/server/quote.js +513 -0
  76. package/src/lib/server/quote.js.map +1 -0
  77. package/src/lib/server/related.js +2 -0
  78. package/src/lib/server/related.js.map +1 -1
  79. package/src/lib/server/site-page-resolver.d.ts +2 -0
  80. package/src/lib/server/site-page-resolver.js +75 -0
  81. package/src/lib/server/site-page-resolver.js.map +1 -1
  82. package/src/lib/server.js +14 -0
  83. package/src/lib/server.js.map +1 -1
  84. package/src/lib/site.js +6 -0
  85. package/src/lib/site.js.map +1 -1
  86. package/src/lib/transfer/tracking.server.js +6 -0
  87. package/src/lib/transfer/tracking.server.js.map +1 -1
package/README.md CHANGED
@@ -54,8 +54,8 @@ All console pages and cards are code-split.
54
54
 
55
55
  `@aglyn/plugins-commerce/server` pulls in `firebase-admin` and Stripe and is kept out of the client entry point.
56
56
 
57
- - `registerCommerceApi()` (the `tenantApi` surface) registers the storefront routes under `commerce/` (catalog, product, related, reviews, cart, cart checkout, checkout, download, feed, newsletter, restock notifications, order analytics, reservations, gate, member feed, stream, subscription portal) and the account routes under `membership/` (register, login, logout, recover, reset, account, content, wishlist). It also registers a site page resolver and enricher, the commerce tax profile and a product card reader on core seams.
58
- - `registerCommerceConsoleApi()` (the `consoleApi` surface) registers the merchant routes: cancel, fulfill and refund an order, draft orders, POS orders, gift cards, member posts, a site member's password help and removal (`membership/admin-password`, `membership/admin-remove`), Stripe Connect, supplier updates, and the abandoned-checkout and restock processors. It also adds a handler on the platform billing webhook and order figure readers.
57
+ - `registerCommerceApi()` (the `tenantApi` surface) registers the storefront routes under `commerce/` (catalog, product, related, reviews, cart, cart checkout, checkout, download, feed, newsletter, restock notifications, order analytics, order status, the customer's quote page and its Pay button (`quote-view`, `quote-pay`), reservations, gate, member feed, stream, subscription portal) and the account routes under `membership/` (register, login, logout, recover, reset, account, content, wishlist). It also registers a site page resolver and enricher, the commerce tax profile and a product card reader on core seams.
58
+ - `registerCommerceConsoleApi()` (the `consoleApi` surface) registers the merchant routes: cancel, fulfill and refund an order, draft orders, quotes and their pay links, POS orders, gift cards, member posts, a site member's password help and removal (`membership/admin-password`, `membership/admin-remove`), Stripe Connect, supplier updates, and the abandoned-checkout and restock processors. It also adds a handler on the platform billing webhook and order figure readers.
59
59
  - `registerCommerceConsoleApi()` also registers what an AI build may make in a store, on core's resource-drafts and AI-capability seams: the `product` draft writer and build op, and the `store-settings` writer with the `store-shipping`, `store-pickup`, `store-delivery` and `store-tax` build ops (`server/store-setup-ai-*.ts`). Those four set shipping zones and rates, pickup, local delivery and sales tax exactly as the console's cards save them, live, using only amounts and rates the request states, and never a carrier or tax-service account. `model/store-setup-content.ts` holds the pure read-and-apply step.
60
60
  - Loading the module registers scheduled plugin jobs: `abandoned-checkout-recovery`, `back-in-stock-alerts`, `review-request-emails`, `stock-decrement-reconciliation`, `supplier-webhook-delivery`, `payment-method-domains` and `pos-reader-splash-refresh` (card reader start-up screens after a logo change). This is why `package.json` lists `./src/lib/server.*` under `sideEffects`.
61
61
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aglyn/plugins-commerce",
3
- "version": "1.0.0-beta.288",
3
+ "version": "1.0.0-beta.289",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://aglyn.com",
6
6
  "repository": {
@@ -25,18 +25,18 @@
25
25
  "./package.json": "./package.json"
26
26
  },
27
27
  "dependencies": {
28
- "@aglyn/aglyn": "1.0.0-beta.288",
29
- "@aglyn/shared-data-mdi": "1.0.0-beta.288",
30
- "@aglyn/shared-ui-jsx": "1.0.0-beta.288",
31
- "@aglyn/shared-ui-next": "1.0.0-beta.288",
32
- "@aglyn/shared-ui-snackstack": "1.0.0-beta.288",
33
- "@aglyn/shared-util-email": "1.0.0-beta.288",
34
- "@aglyn/shared-util-http": "1.0.0-beta.288",
35
- "@aglyn/shared-util-timestamp": "1.0.0-beta.288",
36
- "@aglyn/shared-util-tools": "1.0.0-beta.288",
37
- "@aglyn/tenant-data-admin": "1.0.0-beta.288",
38
- "@aglyn/tenant-feature-instance": "1.0.0-beta.288",
39
- "@aglyn/tenant-runtime": "1.0.0-beta.288",
28
+ "@aglyn/aglyn": "1.0.0-beta.289",
29
+ "@aglyn/shared-data-mdi": "1.0.0-beta.289",
30
+ "@aglyn/shared-ui-jsx": "1.0.0-beta.289",
31
+ "@aglyn/shared-ui-next": "1.0.0-beta.289",
32
+ "@aglyn/shared-ui-snackstack": "1.0.0-beta.289",
33
+ "@aglyn/shared-util-email": "1.0.0-beta.289",
34
+ "@aglyn/shared-util-http": "1.0.0-beta.289",
35
+ "@aglyn/shared-util-timestamp": "1.0.0-beta.289",
36
+ "@aglyn/shared-util-tools": "1.0.0-beta.289",
37
+ "@aglyn/tenant-data-admin": "1.0.0-beta.289",
38
+ "@aglyn/tenant-feature-instance": "1.0.0-beta.289",
39
+ "@aglyn/tenant-runtime": "1.0.0-beta.289",
40
40
  "@stripe/react-stripe-js": "^6.12.0",
41
41
  "@stripe/stripe-js": "^9.17.0",
42
42
  "@swc/helpers": "0.5.23",
@@ -28,6 +28,7 @@ import DiscountsCard from "./console/discounts-card.component.js";
28
28
  import GiftCardsCard from "./console/gift-cards-card.component.js";
29
29
  import HostCouponsCard from "./console/host-coupons-card.component.js";
30
30
  import HostOrdersCard from "./console/host-orders-card.component.js";
31
+ import HostQuotesCard from "./console/host-quotes-card.component.js";
31
32
  import LocationsCard from "./console/locations-card.component.js";
32
33
  import LocalDeliverySettingsCard from "./console/local-delivery-settings-card.component.js";
33
34
  import PickupDeliveryQueueCard from "./console/pickup-delivery-queue-card.component.js";
@@ -40,6 +41,7 @@ import PosShiftHistoryCard from "./console/pos-ops/pos-shift-history-card.compon
40
41
  import PosStaffPinsCard from "./console/pos-ops/pos-staff-pins-card.component.js";
41
42
  import PaymentMethodsCard from "./console/payment-methods-card.component.js";
42
43
  import ProductsHubCard from "./console/products-hub-card.component.js";
44
+ import QuoteSettingsCard from "./console/quote-settings-card.component.js";
43
45
  import RecoveryQueueCard from "./console/recovery-queue-card.component.js";
44
46
  import RegistersCard from "./console/registers-card.component.js";
45
47
  import RegisterHardwareCards from "./console/register-hardware-card.component.js";
@@ -126,6 +128,9 @@ import { useConsoleWidgetSlot } from "@aglyn/aglyn/app-utils/console-widget-slot
126
128
  }, /** The reservation keys this site decides for the member (AGL-4047). */ reservations = {
127
129
  canManage: true,
128
130
  canWalkIn: true
131
+ }, /** "Create and edit quotes" and "Send quotes and invoices to customers" on this site (AGL-4093, AGL-4094). */ quotes = {
132
+ canEdit: true,
133
+ canSend: true
129
134
  }) {
130
135
  switch(section){
131
136
  case 'catalog':
@@ -182,6 +187,18 @@ import { useConsoleWidgetSlot } from "@aglyn/aglyn/app-utils/console-widget-slot
182
187
  hostId: hostId
183
188
  })
184
189
  },
190
+ // Prices put to a customer before they buy (AGL-4093), beside the
191
+ // orders they become once paid.
192
+ {
193
+ size: {
194
+ xs: 12
195
+ },
196
+ children: /*#__PURE__*/ _jsx(HostQuotesCard, {
197
+ hostId: hostId,
198
+ canEdit: quotes.canEdit,
199
+ canSend: quotes.canSend
200
+ })
201
+ },
185
202
  // What to prepare, hand over and drive out (AGL-3624). Renders
186
203
  // nothing for a store with no pickup and no local delivery.
187
204
  {
@@ -329,7 +346,11 @@ import { useConsoleWidgetSlot } from "@aglyn/aglyn/app-utils/console-widget-slot
329
346
  // policy, beside the terms and guest-checkout rules above it.
330
347
  /*#__PURE__*/ _jsx(ReturnSettingsCard, {
331
348
  hostId: hostId
332
- }, "returns")
349
+ }, "returns"),
350
+ // What a new quote starts with (AGL-4093).
351
+ /*#__PURE__*/ _jsx(QuoteSettingsCard, {
352
+ hostId: hostId
353
+ }, "quotes")
333
354
  ]);
334
355
  case 'taxes':
335
356
  return settingsPage(section, hostId, [
@@ -506,6 +527,12 @@ import { useConsoleWidgetSlot } from "@aglyn/aglyn/app-utils/console-widget-slot
506
527
  canManage: (permissions == null ? void 0 : permissions['commerce.reservations']) !== false,
507
528
  canWalkIn: (permissions == null ? void 0 : permissions['commerce.walkIns']) !== false
508
529
  };
530
+ // "Create and edit quotes" (AGL-4093), read the same way; the quote route
531
+ // decides.
532
+ const quotes = {
533
+ canEdit: (permissions == null ? void 0 : permissions['commerce.quotes']) !== false,
534
+ canSend: (permissions == null ? void 0 : permissions['commerce.quotes.send']) !== false
535
+ };
509
536
  /*
510
537
  * Nothing, deliberately, while the redirect is in flight.
511
538
  *
@@ -515,7 +542,7 @@ import { useConsoleWidgetSlot } from "@aglyn/aglyn/app-utils/console-widget-slot
515
542
  */ if (!section || !(sections == null ? void 0 : sections.length) || !basePath) return null;
516
543
  return /*#__PURE__*/ _jsx(HubSections, {
517
544
  sections: sections,
518
- children: sectionBody(section, hostId, canManagePos, pos, reservations)
545
+ children: sectionBody(section, hostId, canManagePos, pos, reservations, quotes)
519
546
  });
520
547
  }
521
548
  CommerceConsolePage.displayName = 'CommerceConsolePage';
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/components/commerce-console-page.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 type { ConsolePluginPageProps } from '@aglyn/aglyn'\nimport { GridItems } from '@aglyn/shared-ui-jsx'\nimport { HubSections } from '@aglyn/shared-ui-next'\nimport { lazy, Suspense, type ReactNode } from 'react'\nimport { Stack } from '@mui/material'\nimport {\n COMMERCE_SETTINGS_PAGE_ZONES,\n type CommerceConsoleSectionId,\n type CommerceSettingsSectionId,\n} from './commerce-console-sections'\nimport CatalogOrganizationCard from './console/catalog-organization-card.component'\nimport CommerceAnalyticsCard from './console/commerce-analytics-card.component'\nimport CustomerNotificationsCard from './console/customer-notifications-card.component'\nimport DiscountsCard from './console/discounts-card.component'\nimport GiftCardsCard from './console/gift-cards-card.component'\nimport HostCouponsCard from './console/host-coupons-card.component'\nimport HostOrdersCard from './console/host-orders-card.component'\nimport LocationsCard from './console/locations-card.component'\nimport LocalDeliverySettingsCard from './console/local-delivery-settings-card.component'\nimport PickupDeliveryQueueCard from './console/pickup-delivery-queue-card.component'\nimport MemberPostsCard from './console/member-posts-card.component'\nimport OrderWebhooksCard from './console/order-webhooks-card.component'\nimport PaymentsSettingsCard from './console/payments-settings-card.component'\nimport PosCheckInsCard from './console/pos-ops/pos-check-ins-card.component'\nimport PosDayTotalsCard from './console/pos-ops/pos-day-totals-card.component'\nimport PosShiftHistoryCard from './console/pos-ops/pos-shift-history-card.component'\nimport PosStaffPinsCard from './console/pos-ops/pos-staff-pins-card.component'\nimport PaymentMethodsCard from './console/payment-methods-card.component'\nimport ProductsHubCard from './console/products-hub-card.component'\nimport RecoveryQueueCard from './console/recovery-queue-card.component'\nimport RegistersCard from './console/registers-card.component'\nimport RegisterHardwareCards from './console/register-hardware-card.component'\nimport PosDevicesCard from './console/pos-devices-card.component'\nimport PosHardwareCard from './console/pos-hardware-card.component'\n\n/**\n * Card readers the workspace bought in person, waiting to be set up\n * (AGL-4038): its own chunk, drawn only while there is one.\n */\nconst PosHardwareWaitingCard = lazy(() => import('./console/pos-hardware-waiting-card.component'))\nimport { holdsPosPermission, posPermissionRefusal } from '../model/plugin-permissions'\nimport ReservationsCard from './console/reservations-card.component'\nimport ReturnSettingsCard from './console/return-settings-card.component'\nimport ReturnsCard from './console/returns-card.component'\nimport ReviewsModerationCard from './console/reviews-moderation-card.component'\nimport ReviewSettingsCard from './console/review-settings-card.component'\nimport ShippingSettingsCard from './console/shipping-settings-card.component'\nimport ShipStationCard from './console/shipstation-card.component'\nimport ShippingEasyCard from './console/shippingeasy-card.component'\nimport StockMovementsCard from './console/stock-movements-card.component'\nimport StoreSettingsCard from './console/store-settings-card.component'\nimport StorefrontTaxSummaryCard from './console/storefront-tax-summary-card.component'\nimport SuppliersCard from './console/suppliers-card.component'\nimport TaxSettingsCard from './console/tax-settings-card.component'\nimport { COMMERCE_PROMOTIONS_ZONE } from './console/store-zones'\nimport { useConsoleWidgetSlot } from '@aglyn/aglyn/app-utils/console-widget-slot-context'\n\n/**\n * A settings page's zone (AGL-3612, AGL-3713): other plugins' settings for\n * the store — label settings, carrier accounts, a tax service — under the\n * page's own cards. One zone per page, from `COMMERCE_SETTINGS_PAGE_ZONES`;\n * Integrations draws `commerceSettings`, where a widget naming no page lands.\n */\nfunction CommerceSettingsZone(props: {\n hostId: string\n page: CommerceSettingsSectionId\n}) {\n const WidgetSlot = useConsoleWidgetSlot()\n return WidgetSlot ? (\n <WidgetSlot\n slot={COMMERCE_SETTINGS_PAGE_ZONES[props.page]}\n hostId={props.hostId}\n orgId={undefined}\n />\n ) : null\n}\n\n/** One settings page: its cards in order, then its zone (AGL-3713). */\nfunction settingsPage(\n page: CommerceSettingsSectionId,\n hostId: string,\n cards: readonly ReactNode[],\n): ReactNode {\n return (\n <GridItems\n spacing={3}\n items={[\n ...cards.map((children) => ({ size: { xs: 12 }, children })),\n {\n size: { xs: 12 },\n children: <CommerceSettingsZone hostId={hostId} page={page} />,\n },\n ]}\n />\n )\n}\n\n/**\n * The `commercePromotions` zone (AGL-3640): other plugins' programs beside\n * the store's own money-off surfaces — a rewards program first.\n */\nfunction CommercePromotionsZone(props: { hostId: string }) {\n const WidgetSlot = useConsoleWidgetSlot()\n return WidgetSlot ? (\n <WidgetSlot slot={COMMERCE_PROMOTIONS_ZONE.id} hostId={props.hostId} orgId={undefined} />\n ) : null\n}\n\n/**\n * The body of one commerce section, built only when that section is the one\n * being read (AGL-2501).\n *\n * A function rather than a map of nodes on purpose: a `Record<id, ReactNode>`\n * would CONSTRUCT every section every render, and each card opens its Firestore\n * listens on mount — which is the entire cost this page exists to stop paying.\n * Only the returned branch is ever built.\n */\nfunction sectionBody(\n section: CommerceConsoleSectionId,\n hostId: string,\n canManagePos: boolean,\n /** The register's finer keys this page's cards ask (AGL-4047). */\n pos: { canSetUpDevices: boolean; canBuyHardware: boolean; canCashDrawer: boolean } = {\n canSetUpDevices: canManagePos,\n canBuyHardware: canManagePos,\n canCashDrawer: canManagePos,\n },\n /** The reservation keys this site decides for the member (AGL-4047). */\n reservations: { canManage: boolean; canWalkIn: boolean } = { canManage: true, canWalkIn: true },\n): ReactNode {\n switch (section) {\n case 'catalog':\n return (\n <GridItems\n spacing={3}\n items={[\n { size: { xs: 12 }, children: <ProductsHubCard hostId={hostId} /> },\n {\n size: { xs: 12 },\n children: <CatalogOrganizationCard hostId={hostId} />,\n },\n // The ledger every stock writer fills and nothing displayed\n // (AGL-2341). Directly under the catalog, because the question\n // it answers — \"why does this count disagree with the shelf\" —\n // is asked while looking at the count.\n {\n size: { xs: 12 },\n children: <StockMovementsCard hostId={hostId} />,\n },\n { size: { xs: 12 }, children: <MemberPostsCard hostId={hostId} /> },\n ]}\n />\n )\n case 'orders':\n return (\n <GridItems\n spacing={3}\n items={[\n { size: { xs: 12 }, children: <HostOrdersCard hostId={hostId} /> },\n // What to prepare, hand over and drive out (AGL-3624). Renders\n // nothing for a store with no pickup and no local delivery.\n {\n size: { xs: 12 },\n children: <PickupDeliveryQueueCard hostId={hostId} />,\n },\n // The two queues that feed orders rather than record them\n // (AGL-2227): checkouts that stalled and shoppers waiting on\n // stock. Beneath the orders list because both are pre-order.\n {\n size: { xs: 12 },\n children: <RecoveryQueueCard hostId={hostId} />,\n },\n // One day's or one event's totals across every register, shift\n // and cashier (AGL-4037), above the shifts it adds up.\n {\n size: { xs: 12 },\n children: <PosDayTotalsCard hostId={hostId} canManagePos={canManagePos} />,\n },\n // Who is due at the window and who checked in (AGL-4043), beside\n // the day's totals.\n {\n size: { xs: 12 },\n children: <PosCheckInsCard hostId={hostId} canManagePos={canManagePos} />,\n },\n {\n size: { xs: 12 },\n children: <PosShiftHistoryCard hostId={hostId} />,\n },\n ]}\n />\n )\n case 'returns':\n // Returns (AGL-3611); `?return={id}` opens one on arrival.\n return <ReturnsCard hostId={hostId} />\n case 'promotions':\n return (\n <GridItems\n spacing={3}\n items={[\n { size: { xs: 12 }, children: <DiscountsCard hostId={hostId} /> },\n { size: { xs: 12 }, children: <HostCouponsCard hostId={hostId} /> },\n // Store credit lives with the other money-off surfaces\n // (AGL-2226) — it had no console anywhere before this.\n { size: { xs: 12 }, children: <GiftCardsCard hostId={hostId} /> },\n {\n size: { xs: 12 },\n children: <ReviewsModerationCard hostId={hostId} />,\n },\n // Who may review, and the review-request email (AGL-3736),\n // beside the queue those reviews land in.\n {\n size: { xs: 12 },\n children: <ReviewSettingsCard hostId={hostId} />,\n },\n {\n size: { xs: 12 },\n children: <CommercePromotionsZone hostId={hostId} />,\n },\n ]}\n />\n )\n case 'reservations':\n return (\n <ReservationsCard\n hostId={hostId}\n canManage={reservations.canManage}\n canWalkIn={reservations.canWalkIn}\n />\n )\n /*\n * The store's settings, a page each (AGL-3713). They were one `settings`\n * section — every card below in one column — and its old address opens\n * Payments (`COMMERCE_RETIRED_SECTIONS`). Each page is its cards, then\n * the zone where other plugins' cards for that page land.\n */\n case 'payments':\n return settingsPage(section, hostId, [\n <PaymentsSettingsCard key=\"payments\" hostId={hostId} />,\n // What shoppers may pay with (AGL-3629), beside the account the\n // money lands in.\n <PaymentMethodsCard key=\"methods\" hostId={hostId} />,\n ])\n case 'checkout':\n return settingsPage(section, hostId, [\n <StoreSettingsCard key=\"store\" hostId={hostId} />,\n // Whether buyers may ask for a return online (AGL-3611): the store's\n // policy, beside the terms and guest-checkout rules above it.\n <ReturnSettingsCard key=\"returns\" hostId={hostId} />,\n ])\n case 'taxes':\n return settingsPage(section, hostId, [\n <TaxSettingsCard key=\"tax\" hostId={hostId} />,\n ])\n case 'shipping':\n return settingsPage(section, hostId, [\n <ShippingSettingsCard key=\"shipping\" hostId={hostId} />,\n // Where parcels ship from, and pickup, per location.\n <LocationsCard key=\"locations\" hostId={hostId} />,\n // The store's own delivery (AGL-3624); pickup is per location, on\n // the Inventory locations card above.\n <LocalDeliverySettingsCard key=\"local\" hostId={hostId} />,\n <SuppliersCard key=\"suppliers\" hostId={hostId} />,\n <ShipStationCard key=\"shipstation\" hostId={hostId} />,\n // ShippingEasy, with the merchant's own keys (AGL-3633).\n <ShippingEasyCard key=\"shippingeasy\" hostId={hostId} />,\n ])\n case 'notifications':\n return settingsPage(section, hostId, [\n <CustomerNotificationsCard key=\"customers\" hostId={hostId} />,\n // The store's own endpoints for order events (AGL-3611). Renders\n // nothing for a person who may not manage them.\n <OrderWebhooksCard key=\"webhooks\" hostId={hostId} />,\n ])\n case 'pos':\n return settingsPage(section, hostId, [\n // A reader bought in person waits at the top until it is set up\n // (AGL-4038), in the registers' own grid cell so an empty one\n // leaves no gap.\n <Stack key=\"registers\" spacing={3}>\n <Suspense fallback={null}>\n <PosHardwareWaitingCard\n hostId={hostId}\n {...(pos.canSetUpDevices ? {} : { devicesBlockedReason: posPermissionRefusal('posDevices') })}\n />\n </Suspense>\n <RegistersCard hostId={hostId} />\n </Stack>,\n <PosStaffPinsCard key=\"pins\" hostId={hostId} />,\n // A register's printers, cash drawer and status (AGL-3619), one card\n // per register, after the list that names them.\n <RegisterHardwareCards\n key=\"hardware\"\n hostId={hostId}\n blocked={{\n ...(pos.canSetUpDevices ? {} : { devices: posPermissionRefusal('posDevices') }),\n ...(pos.canCashDrawer ? {} : { drawer: posPermissionRefusal('posCashDrawer') }),\n }}\n />,\n <PosDevicesCard\n key=\"devices\"\n hostId={hostId}\n canManagePos={canManagePos}\n canSetUpDevices={pos.canSetUpDevices}\n />,\n // Card readers bought from the platform (AGL-4038), after the card\n // that registers them. Draws nothing until the deployment opens the\n // shop.\n <PosHardwareCard\n key=\"hardware-shop\"\n hostId={hostId}\n canManagePos={canManagePos}\n canBuyHardware={pos.canBuyHardware}\n />,\n ])\n case 'channels':\n case 'integrations':\n // Other plugins' cards only; the rail leaves either page out while\n // its zone has nothing to draw (`onlyWithZones`).\n return settingsPage(section, hostId, [])\n case 'analytics':\n return (\n // Two cards since AGL-2440. The tax summary lives here rather\n // than on the Taxes settings page because that page CONFIGURES tax and\n // this REPORTS what was collected — a merchant reading a figure\n // they may file from should not be one field away from changing\n // the rate that produced it.\n <GridItems\n spacing={3}\n items={[\n {\n size: { xs: 12 },\n children: <CommerceAnalyticsCard hostId={hostId} />,\n },\n {\n size: { xs: 12 },\n children: <StorefrontTaxSummaryCard hostId={hostId} />,\n },\n ]}\n />\n )\n default:\n return null\n }\n}\n\n/**\n * Commerce console page (AGL-395): the Products management surface, owned by\n * the commerce plugin and rendered by the shell's generic plugin route. The\n * product editor's media browser is supplied by the shell's\n * ConsoleMediaPickerProvider (the media library is org/session coupled and\n * stays in the app).\n *\n * ## Sections are ROUTES (AGL-2501)\n *\n * The sections hold thirty-two Firestore listens between them, and a tab\n * strip mounts every panel: opening Catalog also subscribed the orders,\n * promotions, reservations, settings and analytics cards. The `limit()` on\n * those queries is the bill — several ask for 500, and one load reached a\n * ceiling of ~4,460 documents to render one tab.\n *\n * `HubTabs lazy` bought that back as a flag somebody has to remember. A URL\n * per section makes it structural instead: the page builds one section's body\n * and the others do not exist to subscribe. Three things come with it that the\n * tab version faked — a section is linkable, the back button walks sections,\n * and which one is open is a fact about the URL rather than state kept in sync\n * with it. `commerce-console-read-cost.spec.tsx` meters it at the Firestore\n * boundary and refuses a section that listens before it is opened.\n *\n * The shell resolves the section, gates it and hands back the rail's hrefs;\n * this page only has to say what each section contains.\n */\nexport function CommerceConsolePage(props: ConsolePluginPageProps) {\n const { hostId, section, sections, basePath, permissions } = props\n /*\n * The register's own gate, for the devices card's Pair display (AGL-3712):\n * `managePos` — what the server's `mayWorkRegister` asks. The key decides,\n * not the site role (AGL-4047). The server stays the enforcement point.\n */\n const canManagePos = permissions?.managePos === true\n // The register's finer keys (AGL-4047), each following `managePos` unless a\n // role or the member sets it — what the devices and hardware routes ask.\n // Buying hardware stands on its own: the Hardware card is this page's for a\n // member who holds it without the register.\n const pos = {\n canSetUpDevices: canManagePos && holdsPosPermission(permissions, 'posDevices'),\n canBuyHardware: holdsPosPermission(permissions, 'posBuyHardware'),\n canCashDrawer: canManagePos && holdsPosPermission(permissions, 'posCashDrawer'),\n }\n // The reservation keys (AGL-4047), whose preset is the member's role on\n // this site; the shell decides them for the site in view. A map that does\n // not carry one (a shell that predates them) leaves the card as it was:\n // the rules and the routes decide.\n const reservations = {\n canManage: permissions?.['commerce.reservations'] !== false,\n canWalkIn: permissions?.['commerce.walkIns'] !== false,\n }\n\n /*\n * Nothing, deliberately, while the redirect is in flight.\n *\n * Rendering the default section here instead would issue that section's\n * listens on a URL that is about to be replaced — paying for Catalog on\n * every arrival at `/products`, which is every click of the nav tab.\n */\n if (!section || !sections?.length || !basePath) return null\n\n return (\n <HubSections sections={sections}>\n {sectionBody(section as CommerceConsoleSectionId, hostId, canManagePos, pos, reservations)}\n </HubSections>\n )\n}\nCommerceConsolePage.displayName = 'CommerceConsolePage'\n\nexport default CommerceConsolePage\n"],"names":["GridItems","HubSections","lazy","Suspense","Stack","COMMERCE_SETTINGS_PAGE_ZONES","CatalogOrganizationCard","CommerceAnalyticsCard","CustomerNotificationsCard","DiscountsCard","GiftCardsCard","HostCouponsCard","HostOrdersCard","LocationsCard","LocalDeliverySettingsCard","PickupDeliveryQueueCard","MemberPostsCard","OrderWebhooksCard","PaymentsSettingsCard","PosCheckInsCard","PosDayTotalsCard","PosShiftHistoryCard","PosStaffPinsCard","PaymentMethodsCard","ProductsHubCard","RecoveryQueueCard","RegistersCard","RegisterHardwareCards","PosDevicesCard","PosHardwareCard","PosHardwareWaitingCard","holdsPosPermission","posPermissionRefusal","ReservationsCard","ReturnSettingsCard","ReturnsCard","ReviewsModerationCard","ReviewSettingsCard","ShippingSettingsCard","ShipStationCard","ShippingEasyCard","StockMovementsCard","StoreSettingsCard","StorefrontTaxSummaryCard","SuppliersCard","TaxSettingsCard","COMMERCE_PROMOTIONS_ZONE","useConsoleWidgetSlot","CommerceSettingsZone","props","WidgetSlot","slot","page","hostId","orgId","undefined","settingsPage","cards","spacing","items","map","children","size","xs","CommercePromotionsZone","id","sectionBody","section","canManagePos","pos","canSetUpDevices","canBuyHardware","canCashDrawer","reservations","canManage","canWalkIn","fallback","devicesBlockedReason","blocked","devices","drawer","CommerceConsolePage","sections","basePath","permissions","managePos","length","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/components/commerce-console-page.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 type { ConsolePluginPageProps } from '@aglyn/aglyn'\nimport { GridItems } from '@aglyn/shared-ui-jsx'\nimport { HubSections } from '@aglyn/shared-ui-next'\nimport { lazy, Suspense, type ReactNode } from 'react'\nimport { Stack } from '@mui/material'\nimport {\n COMMERCE_SETTINGS_PAGE_ZONES,\n type CommerceConsoleSectionId,\n type CommerceSettingsSectionId,\n} from './commerce-console-sections'\nimport CatalogOrganizationCard from './console/catalog-organization-card.component'\nimport CommerceAnalyticsCard from './console/commerce-analytics-card.component'\nimport CustomerNotificationsCard from './console/customer-notifications-card.component'\nimport DiscountsCard from './console/discounts-card.component'\nimport GiftCardsCard from './console/gift-cards-card.component'\nimport HostCouponsCard from './console/host-coupons-card.component'\nimport HostOrdersCard from './console/host-orders-card.component'\nimport HostQuotesCard from './console/host-quotes-card.component'\nimport LocationsCard from './console/locations-card.component'\nimport LocalDeliverySettingsCard from './console/local-delivery-settings-card.component'\nimport PickupDeliveryQueueCard from './console/pickup-delivery-queue-card.component'\nimport MemberPostsCard from './console/member-posts-card.component'\nimport OrderWebhooksCard from './console/order-webhooks-card.component'\nimport PaymentsSettingsCard from './console/payments-settings-card.component'\nimport PosCheckInsCard from './console/pos-ops/pos-check-ins-card.component'\nimport PosDayTotalsCard from './console/pos-ops/pos-day-totals-card.component'\nimport PosShiftHistoryCard from './console/pos-ops/pos-shift-history-card.component'\nimport PosStaffPinsCard from './console/pos-ops/pos-staff-pins-card.component'\nimport PaymentMethodsCard from './console/payment-methods-card.component'\nimport ProductsHubCard from './console/products-hub-card.component'\nimport QuoteSettingsCard from './console/quote-settings-card.component'\nimport RecoveryQueueCard from './console/recovery-queue-card.component'\nimport RegistersCard from './console/registers-card.component'\nimport RegisterHardwareCards from './console/register-hardware-card.component'\nimport PosDevicesCard from './console/pos-devices-card.component'\nimport PosHardwareCard from './console/pos-hardware-card.component'\n\n/**\n * Card readers the workspace bought in person, waiting to be set up\n * (AGL-4038): its own chunk, drawn only while there is one.\n */\nconst PosHardwareWaitingCard = lazy(() => import('./console/pos-hardware-waiting-card.component'))\nimport { holdsPosPermission, posPermissionRefusal } from '../model/plugin-permissions'\nimport ReservationsCard from './console/reservations-card.component'\nimport ReturnSettingsCard from './console/return-settings-card.component'\nimport ReturnsCard from './console/returns-card.component'\nimport ReviewsModerationCard from './console/reviews-moderation-card.component'\nimport ReviewSettingsCard from './console/review-settings-card.component'\nimport ShippingSettingsCard from './console/shipping-settings-card.component'\nimport ShipStationCard from './console/shipstation-card.component'\nimport ShippingEasyCard from './console/shippingeasy-card.component'\nimport StockMovementsCard from './console/stock-movements-card.component'\nimport StoreSettingsCard from './console/store-settings-card.component'\nimport StorefrontTaxSummaryCard from './console/storefront-tax-summary-card.component'\nimport SuppliersCard from './console/suppliers-card.component'\nimport TaxSettingsCard from './console/tax-settings-card.component'\nimport { COMMERCE_PROMOTIONS_ZONE } from './console/store-zones'\nimport { useConsoleWidgetSlot } from '@aglyn/aglyn/app-utils/console-widget-slot-context'\n\n/**\n * A settings page's zone (AGL-3612, AGL-3713): other plugins' settings for\n * the store — label settings, carrier accounts, a tax service — under the\n * page's own cards. One zone per page, from `COMMERCE_SETTINGS_PAGE_ZONES`;\n * Integrations draws `commerceSettings`, where a widget naming no page lands.\n */\nfunction CommerceSettingsZone(props: {\n hostId: string\n page: CommerceSettingsSectionId\n}) {\n const WidgetSlot = useConsoleWidgetSlot()\n return WidgetSlot ? (\n <WidgetSlot\n slot={COMMERCE_SETTINGS_PAGE_ZONES[props.page]}\n hostId={props.hostId}\n orgId={undefined}\n />\n ) : null\n}\n\n/** One settings page: its cards in order, then its zone (AGL-3713). */\nfunction settingsPage(\n page: CommerceSettingsSectionId,\n hostId: string,\n cards: readonly ReactNode[],\n): ReactNode {\n return (\n <GridItems\n spacing={3}\n items={[\n ...cards.map((children) => ({ size: { xs: 12 }, children })),\n {\n size: { xs: 12 },\n children: <CommerceSettingsZone hostId={hostId} page={page} />,\n },\n ]}\n />\n )\n}\n\n/**\n * The `commercePromotions` zone (AGL-3640): other plugins' programs beside\n * the store's own money-off surfaces — a rewards program first.\n */\nfunction CommercePromotionsZone(props: { hostId: string }) {\n const WidgetSlot = useConsoleWidgetSlot()\n return WidgetSlot ? (\n <WidgetSlot slot={COMMERCE_PROMOTIONS_ZONE.id} hostId={props.hostId} orgId={undefined} />\n ) : null\n}\n\n/**\n * The body of one commerce section, built only when that section is the one\n * being read (AGL-2501).\n *\n * A function rather than a map of nodes on purpose: a `Record<id, ReactNode>`\n * would CONSTRUCT every section every render, and each card opens its Firestore\n * listens on mount — which is the entire cost this page exists to stop paying.\n * Only the returned branch is ever built.\n */\nfunction sectionBody(\n section: CommerceConsoleSectionId,\n hostId: string,\n canManagePos: boolean,\n /** The register's finer keys this page's cards ask (AGL-4047). */\n pos: { canSetUpDevices: boolean; canBuyHardware: boolean; canCashDrawer: boolean } = {\n canSetUpDevices: canManagePos,\n canBuyHardware: canManagePos,\n canCashDrawer: canManagePos,\n },\n /** The reservation keys this site decides for the member (AGL-4047). */\n reservations: { canManage: boolean; canWalkIn: boolean } = { canManage: true, canWalkIn: true },\n /** \"Create and edit quotes\" and \"Send quotes and invoices to customers\" on this site (AGL-4093, AGL-4094). */\n quotes: { canEdit: boolean; canSend: boolean } = { canEdit: true, canSend: true },\n): ReactNode {\n switch (section) {\n case 'catalog':\n return (\n <GridItems\n spacing={3}\n items={[\n { size: { xs: 12 }, children: <ProductsHubCard hostId={hostId} /> },\n {\n size: { xs: 12 },\n children: <CatalogOrganizationCard hostId={hostId} />,\n },\n // The ledger every stock writer fills and nothing displayed\n // (AGL-2341). Directly under the catalog, because the question\n // it answers — \"why does this count disagree with the shelf\" —\n // is asked while looking at the count.\n {\n size: { xs: 12 },\n children: <StockMovementsCard hostId={hostId} />,\n },\n { size: { xs: 12 }, children: <MemberPostsCard hostId={hostId} /> },\n ]}\n />\n )\n case 'orders':\n return (\n <GridItems\n spacing={3}\n items={[\n { size: { xs: 12 }, children: <HostOrdersCard hostId={hostId} /> },\n // Prices put to a customer before they buy (AGL-4093), beside the\n // orders they become once paid.\n {\n size: { xs: 12 },\n children: <HostQuotesCard hostId={hostId} canEdit={quotes.canEdit} canSend={quotes.canSend} />,\n },\n // What to prepare, hand over and drive out (AGL-3624). Renders\n // nothing for a store with no pickup and no local delivery.\n {\n size: { xs: 12 },\n children: <PickupDeliveryQueueCard hostId={hostId} />,\n },\n // The two queues that feed orders rather than record them\n // (AGL-2227): checkouts that stalled and shoppers waiting on\n // stock. Beneath the orders list because both are pre-order.\n {\n size: { xs: 12 },\n children: <RecoveryQueueCard hostId={hostId} />,\n },\n // One day's or one event's totals across every register, shift\n // and cashier (AGL-4037), above the shifts it adds up.\n {\n size: { xs: 12 },\n children: <PosDayTotalsCard hostId={hostId} canManagePos={canManagePos} />,\n },\n // Who is due at the window and who checked in (AGL-4043), beside\n // the day's totals.\n {\n size: { xs: 12 },\n children: <PosCheckInsCard hostId={hostId} canManagePos={canManagePos} />,\n },\n {\n size: { xs: 12 },\n children: <PosShiftHistoryCard hostId={hostId} />,\n },\n ]}\n />\n )\n case 'returns':\n // Returns (AGL-3611); `?return={id}` opens one on arrival.\n return <ReturnsCard hostId={hostId} />\n case 'promotions':\n return (\n <GridItems\n spacing={3}\n items={[\n { size: { xs: 12 }, children: <DiscountsCard hostId={hostId} /> },\n { size: { xs: 12 }, children: <HostCouponsCard hostId={hostId} /> },\n // Store credit lives with the other money-off surfaces\n // (AGL-2226) — it had no console anywhere before this.\n { size: { xs: 12 }, children: <GiftCardsCard hostId={hostId} /> },\n {\n size: { xs: 12 },\n children: <ReviewsModerationCard hostId={hostId} />,\n },\n // Who may review, and the review-request email (AGL-3736),\n // beside the queue those reviews land in.\n {\n size: { xs: 12 },\n children: <ReviewSettingsCard hostId={hostId} />,\n },\n {\n size: { xs: 12 },\n children: <CommercePromotionsZone hostId={hostId} />,\n },\n ]}\n />\n )\n case 'reservations':\n return (\n <ReservationsCard\n hostId={hostId}\n canManage={reservations.canManage}\n canWalkIn={reservations.canWalkIn}\n />\n )\n /*\n * The store's settings, a page each (AGL-3713). They were one `settings`\n * section — every card below in one column — and its old address opens\n * Payments (`COMMERCE_RETIRED_SECTIONS`). Each page is its cards, then\n * the zone where other plugins' cards for that page land.\n */\n case 'payments':\n return settingsPage(section, hostId, [\n <PaymentsSettingsCard key=\"payments\" hostId={hostId} />,\n // What shoppers may pay with (AGL-3629), beside the account the\n // money lands in.\n <PaymentMethodsCard key=\"methods\" hostId={hostId} />,\n ])\n case 'checkout':\n return settingsPage(section, hostId, [\n <StoreSettingsCard key=\"store\" hostId={hostId} />,\n // Whether buyers may ask for a return online (AGL-3611): the store's\n // policy, beside the terms and guest-checkout rules above it.\n <ReturnSettingsCard key=\"returns\" hostId={hostId} />,\n // What a new quote starts with (AGL-4093).\n <QuoteSettingsCard key=\"quotes\" hostId={hostId} />,\n ])\n case 'taxes':\n return settingsPage(section, hostId, [\n <TaxSettingsCard key=\"tax\" hostId={hostId} />,\n ])\n case 'shipping':\n return settingsPage(section, hostId, [\n <ShippingSettingsCard key=\"shipping\" hostId={hostId} />,\n // Where parcels ship from, and pickup, per location.\n <LocationsCard key=\"locations\" hostId={hostId} />,\n // The store's own delivery (AGL-3624); pickup is per location, on\n // the Inventory locations card above.\n <LocalDeliverySettingsCard key=\"local\" hostId={hostId} />,\n <SuppliersCard key=\"suppliers\" hostId={hostId} />,\n <ShipStationCard key=\"shipstation\" hostId={hostId} />,\n // ShippingEasy, with the merchant's own keys (AGL-3633).\n <ShippingEasyCard key=\"shippingeasy\" hostId={hostId} />,\n ])\n case 'notifications':\n return settingsPage(section, hostId, [\n <CustomerNotificationsCard key=\"customers\" hostId={hostId} />,\n // The store's own endpoints for order events (AGL-3611). Renders\n // nothing for a person who may not manage them.\n <OrderWebhooksCard key=\"webhooks\" hostId={hostId} />,\n ])\n case 'pos':\n return settingsPage(section, hostId, [\n // A reader bought in person waits at the top until it is set up\n // (AGL-4038), in the registers' own grid cell so an empty one\n // leaves no gap.\n <Stack key=\"registers\" spacing={3}>\n <Suspense fallback={null}>\n <PosHardwareWaitingCard\n hostId={hostId}\n {...(pos.canSetUpDevices ? {} : { devicesBlockedReason: posPermissionRefusal('posDevices') })}\n />\n </Suspense>\n <RegistersCard hostId={hostId} />\n </Stack>,\n <PosStaffPinsCard key=\"pins\" hostId={hostId} />,\n // A register's printers, cash drawer and status (AGL-3619), one card\n // per register, after the list that names them.\n <RegisterHardwareCards\n key=\"hardware\"\n hostId={hostId}\n blocked={{\n ...(pos.canSetUpDevices ? {} : { devices: posPermissionRefusal('posDevices') }),\n ...(pos.canCashDrawer ? {} : { drawer: posPermissionRefusal('posCashDrawer') }),\n }}\n />,\n <PosDevicesCard\n key=\"devices\"\n hostId={hostId}\n canManagePos={canManagePos}\n canSetUpDevices={pos.canSetUpDevices}\n />,\n // Card readers bought from the platform (AGL-4038), after the card\n // that registers them. Draws nothing until the deployment opens the\n // shop.\n <PosHardwareCard\n key=\"hardware-shop\"\n hostId={hostId}\n canManagePos={canManagePos}\n canBuyHardware={pos.canBuyHardware}\n />,\n ])\n case 'channels':\n case 'integrations':\n // Other plugins' cards only; the rail leaves either page out while\n // its zone has nothing to draw (`onlyWithZones`).\n return settingsPage(section, hostId, [])\n case 'analytics':\n return (\n // Two cards since AGL-2440. The tax summary lives here rather\n // than on the Taxes settings page because that page CONFIGURES tax and\n // this REPORTS what was collected — a merchant reading a figure\n // they may file from should not be one field away from changing\n // the rate that produced it.\n <GridItems\n spacing={3}\n items={[\n {\n size: { xs: 12 },\n children: <CommerceAnalyticsCard hostId={hostId} />,\n },\n {\n size: { xs: 12 },\n children: <StorefrontTaxSummaryCard hostId={hostId} />,\n },\n ]}\n />\n )\n default:\n return null\n }\n}\n\n/**\n * Commerce console page (AGL-395): the Products management surface, owned by\n * the commerce plugin and rendered by the shell's generic plugin route. The\n * product editor's media browser is supplied by the shell's\n * ConsoleMediaPickerProvider (the media library is org/session coupled and\n * stays in the app).\n *\n * ## Sections are ROUTES (AGL-2501)\n *\n * The sections hold thirty-two Firestore listens between them, and a tab\n * strip mounts every panel: opening Catalog also subscribed the orders,\n * promotions, reservations, settings and analytics cards. The `limit()` on\n * those queries is the bill — several ask for 500, and one load reached a\n * ceiling of ~4,460 documents to render one tab.\n *\n * `HubTabs lazy` bought that back as a flag somebody has to remember. A URL\n * per section makes it structural instead: the page builds one section's body\n * and the others do not exist to subscribe. Three things come with it that the\n * tab version faked — a section is linkable, the back button walks sections,\n * and which one is open is a fact about the URL rather than state kept in sync\n * with it. `commerce-console-read-cost.spec.tsx` meters it at the Firestore\n * boundary and refuses a section that listens before it is opened.\n *\n * The shell resolves the section, gates it and hands back the rail's hrefs;\n * this page only has to say what each section contains.\n */\nexport function CommerceConsolePage(props: ConsolePluginPageProps) {\n const { hostId, section, sections, basePath, permissions } = props\n /*\n * The register's own gate, for the devices card's Pair display (AGL-3712):\n * `managePos` — what the server's `mayWorkRegister` asks. The key decides,\n * not the site role (AGL-4047). The server stays the enforcement point.\n */\n const canManagePos = permissions?.managePos === true\n // The register's finer keys (AGL-4047), each following `managePos` unless a\n // role or the member sets it — what the devices and hardware routes ask.\n // Buying hardware stands on its own: the Hardware card is this page's for a\n // member who holds it without the register.\n const pos = {\n canSetUpDevices: canManagePos && holdsPosPermission(permissions, 'posDevices'),\n canBuyHardware: holdsPosPermission(permissions, 'posBuyHardware'),\n canCashDrawer: canManagePos && holdsPosPermission(permissions, 'posCashDrawer'),\n }\n // The reservation keys (AGL-4047), whose preset is the member's role on\n // this site; the shell decides them for the site in view. A map that does\n // not carry one (a shell that predates them) leaves the card as it was:\n // the rules and the routes decide.\n const reservations = {\n canManage: permissions?.['commerce.reservations'] !== false,\n canWalkIn: permissions?.['commerce.walkIns'] !== false,\n }\n // \"Create and edit quotes\" (AGL-4093), read the same way; the quote route\n // decides.\n const quotes = {\n canEdit: permissions?.['commerce.quotes'] !== false,\n canSend: permissions?.['commerce.quotes.send'] !== false,\n }\n\n /*\n * Nothing, deliberately, while the redirect is in flight.\n *\n * Rendering the default section here instead would issue that section's\n * listens on a URL that is about to be replaced — paying for Catalog on\n * every arrival at `/products`, which is every click of the nav tab.\n */\n if (!section || !sections?.length || !basePath) return null\n\n return (\n <HubSections sections={sections}>\n {sectionBody(section as CommerceConsoleSectionId, hostId, canManagePos, pos, reservations, quotes)}\n </HubSections>\n )\n}\nCommerceConsolePage.displayName = 'CommerceConsolePage'\n\nexport default CommerceConsolePage\n"],"names":["GridItems","HubSections","lazy","Suspense","Stack","COMMERCE_SETTINGS_PAGE_ZONES","CatalogOrganizationCard","CommerceAnalyticsCard","CustomerNotificationsCard","DiscountsCard","GiftCardsCard","HostCouponsCard","HostOrdersCard","HostQuotesCard","LocationsCard","LocalDeliverySettingsCard","PickupDeliveryQueueCard","MemberPostsCard","OrderWebhooksCard","PaymentsSettingsCard","PosCheckInsCard","PosDayTotalsCard","PosShiftHistoryCard","PosStaffPinsCard","PaymentMethodsCard","ProductsHubCard","QuoteSettingsCard","RecoveryQueueCard","RegistersCard","RegisterHardwareCards","PosDevicesCard","PosHardwareCard","PosHardwareWaitingCard","holdsPosPermission","posPermissionRefusal","ReservationsCard","ReturnSettingsCard","ReturnsCard","ReviewsModerationCard","ReviewSettingsCard","ShippingSettingsCard","ShipStationCard","ShippingEasyCard","StockMovementsCard","StoreSettingsCard","StorefrontTaxSummaryCard","SuppliersCard","TaxSettingsCard","COMMERCE_PROMOTIONS_ZONE","useConsoleWidgetSlot","CommerceSettingsZone","props","WidgetSlot","slot","page","hostId","orgId","undefined","settingsPage","cards","spacing","items","map","children","size","xs","CommercePromotionsZone","id","sectionBody","section","canManagePos","pos","canSetUpDevices","canBuyHardware","canCashDrawer","reservations","canManage","canWalkIn","quotes","canEdit","canSend","fallback","devicesBlockedReason","blocked","devices","drawer","CommerceConsolePage","sections","basePath","permissions","managePos","length","displayName"],"mappings":"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"}
@@ -122,12 +122,14 @@ const usd = (cents)=>`$${(cents / 100).toFixed(2)}`;
122
122
  var _order_lineItems;
123
123
  for (const line of (_order_lineItems = order.lineItems) != null ? _order_lineItems : []){
124
124
  var _productCents_get;
125
- const entry = (_productCents_get = productCents.get(line.productId)) != null ? _productCents_get : {
125
+ // A line typed on a quote has no product; it ranks under its own name.
126
+ const key = CommerceModel.orderLineProductKey(line);
127
+ const entry = (_productCents_get = productCents.get(key)) != null ? _productCents_get : {
126
128
  name: line.name,
127
129
  cents: 0
128
130
  };
129
131
  entry.cents += line.unitAmountCents * line.quantity;
130
- productCents.set(line.productId, entry);
132
+ productCents.set(key, entry);
131
133
  }
132
134
  }
133
135
  const topProducts = [
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/commerce-analytics-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 CommerceModel from '../../model'\nimport { checkEntitlement, pluginDocsHelp } from '@aglyn/aglyn'\nimport { AppLink, CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { Alert, Box, Stack, Typography } from '@mui/material'\nimport { collection, limit, orderBy, query, where } from 'firebase/firestore'\nimport { useMemo } from 'react'\nimport {\n ceilingedWindow,\n useConsoleHostRoute,\n useFirestore,\n useOrgPlan,\n} from '@aglyn/tenant-feature-instance'\nimport { useFirestoreCollection } from '@aglyn/tenant-feature-instance'\n\nexport interface CommerceAnalyticsCardProps {\n hostId: string\n}\n\nconst DAY_MS = 24 * 60 * 60 * 1000\n\n/**\n * The widest span any figure on this card is computed over. The trend is\n * fourteen days and the totals are thirty, so thirty is what the query asks\n * for — the card reads the period it reports rather than a fixed slab of\n * all-time orders that may or may not overlap it.\n */\nconst ANALYTICS_WINDOW_MS = 30 * DAY_MS\n\n/**\n * The safety bound on that window, for a store selling more in a month than\n * any of these figures can usefully be read from.\n *\n * Every tile here is an aggregate, so a truncated read understates rather than\n * shortens — a sum over part of a period is not a smaller list, it is a wrong\n * number. That is why the probe drives a rendered disclosure instead of a\n * silent slice.\n */\nconst ANALYTICS_ORDER_CEILING = 500\nconst usd = (cents: number) => `$${(cents / 100).toFixed(2)}`\n\n/**\n * Commerce analytics (AGL-327): revenue trend (14 days), order count,\n * AOV, channel split, and top products computed from the loaded order\n * window; storefront GA4 events mirror the funnel into the site's own\n * Google Analytics.\n *\n * Gated on `commerceAnalytics` since AGL-2056. AGL-1938 made that flag\n * non-inert by gating `CommerceGlanceCard` — the dashboard WIDGET — and\n * stopped there, leaving this, the actual Analytics tab, open to every\n * plan. `commerce` is true from Starter; `commerceAnalytics` is false\n * until Pro, so a Starter org read the widget's upgrade prompt and then\n * found the whole paid surface one tab over.\n *\n * The gate is client-side because it has to be: the numbers are derived\n * from `hosts/{hostId}/orders`, which this org may legitimately read, so\n * there is no server boundary to put it behind. That makes it a packaging\n * claim rather than a security one — which is exactly why it has to be\n * applied at EVERY surface or the claim is simply untrue.\n */\nexport function CommerceAnalyticsCard(props: CommerceAnalyticsCardProps) {\n const { hostId } = props\n // Console routes are /[orgSlug]/hosts/[subdomain]/… (AGL-673); this\n // component only has a host doc id.\n const consoleRoute = useConsoleHostRoute(hostId)\n const { org, ready: orgReady } = useOrgPlan(hostId)\n const entitled = checkEntitlement(org as never, 'commerceAnalytics')\n const firestore = useFirestore()\n /*\n * Anchored once per mount so the query identity is stable; recomputing it\n * per render would rebuild the listener and re-read the window each pass.\n */\n const since = useMemo(() => Date.now() - ANALYTICS_WINDOW_MS, [])\n /**\n * `createdAtMs` is the field every order writer in the plugin stamps, and\n * the one `reconcile-stock` already walks the collection by. `createdAt` is\n * not interchangeable: the orders collection group is indexed on\n * `createdAtMs` alone, and a range on a field a document lacks drops that\n * document rather than mis-placing it.\n *\n * Range and order are the same field, so no composite index is needed beyond\n * the `createdAtMs` override already declared.\n */\n const { data: orderDocs } = useFirestoreCollection<any>(\n () =>\n query(\n collection(firestore, 'hosts', hostId, 'orders'),\n where('createdAtMs', '>=', since),\n orderBy('createdAtMs', 'desc'),\n limit(ANALYTICS_ORDER_CEILING + 1),\n ),\n [firestore, hostId, since],\n { idField: '$id' },\n )\n const orderWindow = useMemo(\n () => ceilingedWindow<any>(orderDocs ?? undefined, ANALYTICS_ORDER_CEILING),\n [orderDocs],\n )\n\n const stats = useMemo(() => {\n const now = Date.now()\n // The range predicate admits only documents that carry `createdAtMs`, so\n // the figures below read it directly.\n // What counts as a sale, and what one brought in, are the model's rules\n // (`order-figures.ts`), which the insight readers sum by too: a pending or\n // canceled order is not money, a rehearsal checkout is not revenue, and a\n // refund comes off.\n const orders = orderWindow.rows\n .filter((order: any) => CommerceModel.orderCountsAsSale(order))\n .map((order: any) => ({\n ...CommerceModel.liftLegacyOrder(order),\n createdAtMs: order.createdAtMs,\n }))\n const paidCents = (order: any) => CommerceModel.orderPaidCents(order)\n const window30 = orders.filter(\n (order: any) => now - order.createdAtMs < 30 * DAY_MS,\n )\n const revenue30 = window30.reduce(\n (sum: number, order: any) => sum + paidCents(order),\n 0,\n )\n const days = Array.from({ length: 14 }, (_item, index) => {\n const dayStart = now - (13 - index) * DAY_MS\n const cents = orders\n .filter(\n (order: any) =>\n order.createdAtMs >= dayStart - (dayStart % DAY_MS) &&\n order.createdAtMs < dayStart - (dayStart % DAY_MS) + DAY_MS,\n )\n .reduce((sum: number, order: any) => sum + paidCents(order), 0)\n return cents\n })\n const channels: Record<string, number> = {}\n for (const order of window30) {\n const channel = order.channel ?? 'online'\n channels[channel] = (channels[channel] ?? 0) + paidCents(order)\n }\n const productCents = new Map<string, { name: string; cents: number }>()\n for (const order of window30) {\n for (const line of order.lineItems ?? []) {\n const entry = productCents.get(line.productId) ?? {\n name: line.name,\n cents: 0,\n }\n entry.cents += line.unitAmountCents * line.quantity\n productCents.set(line.productId, entry)\n }\n }\n const topProducts = [...productCents.values()]\n .sort((a, b) => b.cents - a.cents)\n .slice(0, 5)\n return {\n revenue30,\n orders30: window30.length,\n aov: window30.length ? Math.round(revenue30 / window30.length) : 0,\n days,\n channels,\n topProducts,\n }\n }, [orderWindow])\n\n const maxDay = Math.max(1, ...stats.days)\n\n if (!orgReady) {\n // `checkEntitlement(undefined)` resolves the FREE tier rather than\n // \"unknown\" (see `useOrgPlan`), and this card's \"no\" is an upsell —\n // never accuse a paying org of being unentitled while the plan doc is\n // still in flight. Same three-state shape as `CommerceGlanceCard`.\n return (\n <CardDisplay\n header={'Commerce analytics (30 days)'}\n help={pluginDocsHelp('commerce', { anchor: '#commerce-analytics' })}\n contentGutterX\n contentGutterY\n >\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Checking your plan…'}\n </Typography>\n </CardDisplay>\n )\n }\n\n if (!entitled) {\n return (\n <CardDisplay\n header={'Commerce analytics (30 days)'}\n help={pluginDocsHelp('commerce', { anchor: '#commerce-analytics' })}\n contentGutterX\n contentGutterY\n >\n <Alert\n severity=\"info\"\n action={\n consoleRoute.orgSlug ? (\n <AppLink\n componentVariant=\"button\"\n color=\"inherit\"\n size=\"small\"\n href={`/${consoleRoute.orgSlug}/billing`}\n >\n {'Upgrade'}\n </AppLink>\n ) : undefined\n }\n >\n {'Commerce analytics — revenue trend, orders, average order ' +\n 'value, channel split and top products — is a Pro feature. ' +\n 'Every order is still being recorded, so the numbers are ' +\n 'waiting the moment you upgrade.'}\n </Alert>\n </CardDisplay>\n )\n }\n\n return (\n <CardDisplay\n header={'Commerce analytics (30 days)'}\n help={pluginDocsHelp('commerce', { anchor: '#commerce-analytics' })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={2}>\n <Stack direction=\"row\" spacing={3}>\n {[\n ['Revenue', usd(stats.revenue30)],\n ['Orders', String(stats.orders30)],\n ['Avg order', usd(stats.aov)],\n ].map(([label, value]) => (\n <Box key={label}>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {label}\n </Typography>\n <Typography variant=\"h6\">{value}</Typography>\n </Box>\n ))}\n </Stack>\n {orderWindow.truncated ? (\n <Alert severity=\"info\">\n {`This store took more than ${ANALYTICS_ORDER_CEILING} orders in the last 30 days. Every figure here is computed from the ${ANALYTICS_ORDER_CEILING} most recent of them, so each one reads low.`}\n </Alert>\n ) : null}\n <Box>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Last 14 days'}\n </Typography>\n <Box sx={{ display: 'flex', gap: 0.5, alignItems: 'flex-end', height: 56 }}>\n {stats.days.map((cents, index) => (\n <Box\n key={index}\n title={usd(cents)}\n sx={{\n flex: 1,\n bgcolor: 'primary.main',\n opacity: cents ? 0.9 : 0.25,\n borderRadius: 0.5,\n height: `${Math.max(6, Math.round((cents / maxDay) * 100))}%`,\n }}\n />\n ))}\n </Box>\n </Box>\n <Stack direction=\"row\" spacing={2}>\n {Object.entries(stats.channels).map(([channel, cents]) => (\n <Typography key={channel} variant=\"body2\" color=\"text.secondary\">\n {`${channel}: ${usd(cents)}`}\n </Typography>\n ))}\n </Stack>\n {stats.topProducts.length ? (\n <Box>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Top products'}\n </Typography>\n {stats.topProducts.map((product) => (\n <Stack key={product.name} direction=\"row\" spacing={1}>\n <Typography variant=\"body2\" sx={{ flex: 1 }} noWrap>\n {product.name}\n </Typography>\n <Typography variant=\"body2\">{usd(product.cents)}</Typography>\n </Stack>\n ))}\n </Box>\n ) : null}\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Storefront funnel events (view_item, add_to_cart, ' +\n 'begin_checkout, purchase) also mirror into your Google ' +\n 'Analytics when connected.'}\n </Typography>\n </Stack>\n </CardDisplay>\n )\n}\nCommerceAnalyticsCard.displayName = 'CommerceAnalyticsCard'\n\nexport default CommerceAnalyticsCard\n"],"names":["CommerceModel","checkEntitlement","pluginDocsHelp","AppLink","CardDisplay","Alert","Box","Stack","Typography","collection","limit","orderBy","query","where","useMemo","ceilingedWindow","useConsoleHostRoute","useFirestore","useOrgPlan","useFirestoreCollection","DAY_MS","ANALYTICS_WINDOW_MS","ANALYTICS_ORDER_CEILING","usd","cents","toFixed","CommerceAnalyticsCard","props","hostId","consoleRoute","org","ready","orgReady","entitled","firestore","since","Date","now","data","orderDocs","idField","orderWindow","undefined","stats","orders","rows","filter","order","orderCountsAsSale","map","liftLegacyOrder","createdAtMs","paidCents","orderPaidCents","window30","revenue30","reduce","sum","days","Array","from","length","_item","index","dayStart","channels","channel","productCents","Map","line","lineItems","entry","get","productId","name","unitAmountCents","quantity","set","topProducts","values","sort","a","b","slice","orders30","aov","Math","round","maxDay","max","header","help","anchor","contentGutterX","contentGutterY","variant","color","severity","action","orgSlug","componentVariant","size","href","spacing","direction","String","label","value","truncated","sx","display","gap","alignItems","height","title","flex","bgcolor","opacity","borderRadius","Object","entries","product","noWrap","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/commerce-analytics-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 CommerceModel from '../../model'\nimport { checkEntitlement, pluginDocsHelp } from '@aglyn/aglyn'\nimport { AppLink, CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { Alert, Box, Stack, Typography } from '@mui/material'\nimport { collection, limit, orderBy, query, where } from 'firebase/firestore'\nimport { useMemo } from 'react'\nimport {\n ceilingedWindow,\n useConsoleHostRoute,\n useFirestore,\n useOrgPlan,\n} from '@aglyn/tenant-feature-instance'\nimport { useFirestoreCollection } from '@aglyn/tenant-feature-instance'\n\nexport interface CommerceAnalyticsCardProps {\n hostId: string\n}\n\nconst DAY_MS = 24 * 60 * 60 * 1000\n\n/**\n * The widest span any figure on this card is computed over. The trend is\n * fourteen days and the totals are thirty, so thirty is what the query asks\n * for — the card reads the period it reports rather than a fixed slab of\n * all-time orders that may or may not overlap it.\n */\nconst ANALYTICS_WINDOW_MS = 30 * DAY_MS\n\n/**\n * The safety bound on that window, for a store selling more in a month than\n * any of these figures can usefully be read from.\n *\n * Every tile here is an aggregate, so a truncated read understates rather than\n * shortens — a sum over part of a period is not a smaller list, it is a wrong\n * number. That is why the probe drives a rendered disclosure instead of a\n * silent slice.\n */\nconst ANALYTICS_ORDER_CEILING = 500\nconst usd = (cents: number) => `$${(cents / 100).toFixed(2)}`\n\n/**\n * Commerce analytics (AGL-327): revenue trend (14 days), order count,\n * AOV, channel split, and top products computed from the loaded order\n * window; storefront GA4 events mirror the funnel into the site's own\n * Google Analytics.\n *\n * Gated on `commerceAnalytics` since AGL-2056. AGL-1938 made that flag\n * non-inert by gating `CommerceGlanceCard` — the dashboard WIDGET — and\n * stopped there, leaving this, the actual Analytics tab, open to every\n * plan. `commerce` is true from Starter; `commerceAnalytics` is false\n * until Pro, so a Starter org read the widget's upgrade prompt and then\n * found the whole paid surface one tab over.\n *\n * The gate is client-side because it has to be: the numbers are derived\n * from `hosts/{hostId}/orders`, which this org may legitimately read, so\n * there is no server boundary to put it behind. That makes it a packaging\n * claim rather than a security one — which is exactly why it has to be\n * applied at EVERY surface or the claim is simply untrue.\n */\nexport function CommerceAnalyticsCard(props: CommerceAnalyticsCardProps) {\n const { hostId } = props\n // Console routes are /[orgSlug]/hosts/[subdomain]/… (AGL-673); this\n // component only has a host doc id.\n const consoleRoute = useConsoleHostRoute(hostId)\n const { org, ready: orgReady } = useOrgPlan(hostId)\n const entitled = checkEntitlement(org as never, 'commerceAnalytics')\n const firestore = useFirestore()\n /*\n * Anchored once per mount so the query identity is stable; recomputing it\n * per render would rebuild the listener and re-read the window each pass.\n */\n const since = useMemo(() => Date.now() - ANALYTICS_WINDOW_MS, [])\n /**\n * `createdAtMs` is the field every order writer in the plugin stamps, and\n * the one `reconcile-stock` already walks the collection by. `createdAt` is\n * not interchangeable: the orders collection group is indexed on\n * `createdAtMs` alone, and a range on a field a document lacks drops that\n * document rather than mis-placing it.\n *\n * Range and order are the same field, so no composite index is needed beyond\n * the `createdAtMs` override already declared.\n */\n const { data: orderDocs } = useFirestoreCollection<any>(\n () =>\n query(\n collection(firestore, 'hosts', hostId, 'orders'),\n where('createdAtMs', '>=', since),\n orderBy('createdAtMs', 'desc'),\n limit(ANALYTICS_ORDER_CEILING + 1),\n ),\n [firestore, hostId, since],\n { idField: '$id' },\n )\n const orderWindow = useMemo(\n () => ceilingedWindow<any>(orderDocs ?? undefined, ANALYTICS_ORDER_CEILING),\n [orderDocs],\n )\n\n const stats = useMemo(() => {\n const now = Date.now()\n // The range predicate admits only documents that carry `createdAtMs`, so\n // the figures below read it directly.\n // What counts as a sale, and what one brought in, are the model's rules\n // (`order-figures.ts`), which the insight readers sum by too: a pending or\n // canceled order is not money, a rehearsal checkout is not revenue, and a\n // refund comes off.\n const orders = orderWindow.rows\n .filter((order: any) => CommerceModel.orderCountsAsSale(order))\n .map((order: any) => ({\n ...CommerceModel.liftLegacyOrder(order),\n createdAtMs: order.createdAtMs,\n }))\n const paidCents = (order: any) => CommerceModel.orderPaidCents(order)\n const window30 = orders.filter(\n (order: any) => now - order.createdAtMs < 30 * DAY_MS,\n )\n const revenue30 = window30.reduce(\n (sum: number, order: any) => sum + paidCents(order),\n 0,\n )\n const days = Array.from({ length: 14 }, (_item, index) => {\n const dayStart = now - (13 - index) * DAY_MS\n const cents = orders\n .filter(\n (order: any) =>\n order.createdAtMs >= dayStart - (dayStart % DAY_MS) &&\n order.createdAtMs < dayStart - (dayStart % DAY_MS) + DAY_MS,\n )\n .reduce((sum: number, order: any) => sum + paidCents(order), 0)\n return cents\n })\n const channels: Record<string, number> = {}\n for (const order of window30) {\n const channel = order.channel ?? 'online'\n channels[channel] = (channels[channel] ?? 0) + paidCents(order)\n }\n const productCents = new Map<string, { name: string; cents: number }>()\n for (const order of window30) {\n for (const line of order.lineItems ?? []) {\n // A line typed on a quote has no product; it ranks under its own name.\n const key = CommerceModel.orderLineProductKey(line)\n const entry = productCents.get(key) ?? {\n name: line.name,\n cents: 0,\n }\n entry.cents += line.unitAmountCents * line.quantity\n productCents.set(key, entry)\n }\n }\n const topProducts = [...productCents.values()]\n .sort((a, b) => b.cents - a.cents)\n .slice(0, 5)\n return {\n revenue30,\n orders30: window30.length,\n aov: window30.length ? Math.round(revenue30 / window30.length) : 0,\n days,\n channels,\n topProducts,\n }\n }, [orderWindow])\n\n const maxDay = Math.max(1, ...stats.days)\n\n if (!orgReady) {\n // `checkEntitlement(undefined)` resolves the FREE tier rather than\n // \"unknown\" (see `useOrgPlan`), and this card's \"no\" is an upsell —\n // never accuse a paying org of being unentitled while the plan doc is\n // still in flight. Same three-state shape as `CommerceGlanceCard`.\n return (\n <CardDisplay\n header={'Commerce analytics (30 days)'}\n help={pluginDocsHelp('commerce', { anchor: '#commerce-analytics' })}\n contentGutterX\n contentGutterY\n >\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Checking your plan…'}\n </Typography>\n </CardDisplay>\n )\n }\n\n if (!entitled) {\n return (\n <CardDisplay\n header={'Commerce analytics (30 days)'}\n help={pluginDocsHelp('commerce', { anchor: '#commerce-analytics' })}\n contentGutterX\n contentGutterY\n >\n <Alert\n severity=\"info\"\n action={\n consoleRoute.orgSlug ? (\n <AppLink\n componentVariant=\"button\"\n color=\"inherit\"\n size=\"small\"\n href={`/${consoleRoute.orgSlug}/billing`}\n >\n {'Upgrade'}\n </AppLink>\n ) : undefined\n }\n >\n {'Commerce analytics — revenue trend, orders, average order ' +\n 'value, channel split and top products — is a Pro feature. ' +\n 'Every order is still being recorded, so the numbers are ' +\n 'waiting the moment you upgrade.'}\n </Alert>\n </CardDisplay>\n )\n }\n\n return (\n <CardDisplay\n header={'Commerce analytics (30 days)'}\n help={pluginDocsHelp('commerce', { anchor: '#commerce-analytics' })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={2}>\n <Stack direction=\"row\" spacing={3}>\n {[\n ['Revenue', usd(stats.revenue30)],\n ['Orders', String(stats.orders30)],\n ['Avg order', usd(stats.aov)],\n ].map(([label, value]) => (\n <Box key={label}>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {label}\n </Typography>\n <Typography variant=\"h6\">{value}</Typography>\n </Box>\n ))}\n </Stack>\n {orderWindow.truncated ? (\n <Alert severity=\"info\">\n {`This store took more than ${ANALYTICS_ORDER_CEILING} orders in the last 30 days. Every figure here is computed from the ${ANALYTICS_ORDER_CEILING} most recent of them, so each one reads low.`}\n </Alert>\n ) : null}\n <Box>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Last 14 days'}\n </Typography>\n <Box sx={{ display: 'flex', gap: 0.5, alignItems: 'flex-end', height: 56 }}>\n {stats.days.map((cents, index) => (\n <Box\n key={index}\n title={usd(cents)}\n sx={{\n flex: 1,\n bgcolor: 'primary.main',\n opacity: cents ? 0.9 : 0.25,\n borderRadius: 0.5,\n height: `${Math.max(6, Math.round((cents / maxDay) * 100))}%`,\n }}\n />\n ))}\n </Box>\n </Box>\n <Stack direction=\"row\" spacing={2}>\n {Object.entries(stats.channels).map(([channel, cents]) => (\n <Typography key={channel} variant=\"body2\" color=\"text.secondary\">\n {`${channel}: ${usd(cents)}`}\n </Typography>\n ))}\n </Stack>\n {stats.topProducts.length ? (\n <Box>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Top products'}\n </Typography>\n {stats.topProducts.map((product) => (\n <Stack key={product.name} direction=\"row\" spacing={1}>\n <Typography variant=\"body2\" sx={{ flex: 1 }} noWrap>\n {product.name}\n </Typography>\n <Typography variant=\"body2\">{usd(product.cents)}</Typography>\n </Stack>\n ))}\n </Box>\n ) : null}\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Storefront funnel events (view_item, add_to_cart, ' +\n 'begin_checkout, purchase) also mirror into your Google ' +\n 'Analytics when connected.'}\n </Typography>\n </Stack>\n </CardDisplay>\n )\n}\nCommerceAnalyticsCard.displayName = 'CommerceAnalyticsCard'\n\nexport default CommerceAnalyticsCard\n"],"names":["CommerceModel","checkEntitlement","pluginDocsHelp","AppLink","CardDisplay","Alert","Box","Stack","Typography","collection","limit","orderBy","query","where","useMemo","ceilingedWindow","useConsoleHostRoute","useFirestore","useOrgPlan","useFirestoreCollection","DAY_MS","ANALYTICS_WINDOW_MS","ANALYTICS_ORDER_CEILING","usd","cents","toFixed","CommerceAnalyticsCard","props","hostId","consoleRoute","org","ready","orgReady","entitled","firestore","since","Date","now","data","orderDocs","idField","orderWindow","undefined","stats","orders","rows","filter","order","orderCountsAsSale","map","liftLegacyOrder","createdAtMs","paidCents","orderPaidCents","window30","revenue30","reduce","sum","days","Array","from","length","_item","index","dayStart","channels","channel","productCents","Map","line","lineItems","key","orderLineProductKey","entry","get","name","unitAmountCents","quantity","set","topProducts","values","sort","a","b","slice","orders30","aov","Math","round","maxDay","max","header","help","anchor","contentGutterX","contentGutterY","variant","color","severity","action","orgSlug","componentVariant","size","href","spacing","direction","String","label","value","truncated","sx","display","gap","alignItems","height","title","flex","bgcolor","opacity","borderRadius","Object","entries","product","noWrap","displayName"],"mappings":"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"}
@@ -254,7 +254,11 @@ const ORDER_PAGE_SORT_HEADERS = {
254
254
  * a status and channel on a row that predates them, and the item name
255
255
  * reads the line items first (AGL-1747): the flat `productId` is written
256
256
  * only by the two buy-now Stripe paths.
257
- */ const orderRows = useMemo(()=>orders.map((order)=>{
257
+ */ const orderRows = useMemo(()=>orders// An unpaid quote is not an order (AGL-4093). It carries no
258
+ // `createdAtMs`, so the default order and every filter leave it out;
259
+ // the unfiltered Order, Channel and Status header sorts are the only
260
+ // queries that can reach one, and this keeps it off those pages too.
261
+ .filter((order)=>!CommerceModel.isUnpaidQuote(order)).map((order)=>{
258
262
  var _ref, _ref1, _ref2, _lifted_channel;
259
263
  var _lifted_lineItems_, _lifted_lineItems;
260
264
  const lifted = CommerceModel.liftLegacyOrder(order);