@aglyn/plugins-commerce 1.0.0-beta.244 → 1.0.0-beta.247

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 (61) hide show
  1. package/README.md +4 -1
  2. package/package.json +13 -13
  3. package/src/lib/components/commerce-console-page.js +10 -0
  4. package/src/lib/components/commerce-console-page.js.map +1 -1
  5. package/src/lib/components/console/local-delivery-settings-card.component.js +10 -13
  6. package/src/lib/components/console/local-delivery-settings-card.component.js.map +1 -1
  7. package/src/lib/components/console/locations-card.component.js +5 -9
  8. package/src/lib/components/console/locations-card.component.js.map +1 -1
  9. package/src/lib/components/console/product-zones.d.ts +7 -1
  10. package/src/lib/components/console/product-zones.js.map +1 -1
  11. package/src/lib/components/console/products-hub-zone.component.d.ts +3 -2
  12. package/src/lib/components/console/products-hub-zone.component.js +9 -1
  13. package/src/lib/components/console/products-hub-zone.component.js.map +1 -1
  14. package/src/lib/components/console/review-settings-card.component.d.ts +14 -0
  15. package/src/lib/components/console/review-settings-card.component.js +168 -0
  16. package/src/lib/components/console/review-settings-card.component.js.map +1 -0
  17. package/src/lib/components/console/reviews-moderation-card.component.d.ts +14 -0
  18. package/src/lib/components/console/reviews-moderation-card.component.js +85 -11
  19. package/src/lib/components/console/reviews-moderation-card.component.js.map +1 -1
  20. package/src/lib/components/product-reviews.d.ts +29 -1
  21. package/src/lib/components/product-reviews.js +97 -12
  22. package/src/lib/components/product-reviews.js.map +1 -1
  23. package/src/lib/model/commerce.d.ts +11 -2
  24. package/src/lib/model/commerce.js +14 -2
  25. package/src/lib/model/commerce.js.map +1 -1
  26. package/src/lib/model/index.d.ts +1 -0
  27. package/src/lib/model/index.js +1 -0
  28. package/src/lib/model/index.js.map +1 -1
  29. package/src/lib/model/product-reviews.d.ts +124 -0
  30. package/src/lib/model/product-reviews.js +133 -0
  31. package/src/lib/model/product-reviews.js.map +1 -0
  32. package/src/lib/model/store-setup-content.d.ts +145 -0
  33. package/src/lib/model/store-setup-content.js +813 -0
  34. package/src/lib/model/store-setup-content.js.map +1 -0
  35. package/src/lib/server/membership-reset.js +8 -2
  36. package/src/lib/server/membership-reset.js.map +1 -1
  37. package/src/lib/server/order-event-triggers.d.ts +2 -0
  38. package/src/lib/server/order-event-triggers.js +18 -1
  39. package/src/lib/server/order-event-triggers.js.map +1 -1
  40. package/src/lib/server/review-requests.d.ts +102 -0
  41. package/src/lib/server/review-requests.js +346 -0
  42. package/src/lib/server/review-requests.js.map +1 -0
  43. package/src/lib/server/review-tokens.d.ts +82 -0
  44. package/src/lib/server/review-tokens.js +119 -0
  45. package/src/lib/server/review-tokens.js.map +1 -0
  46. package/src/lib/server/reviews.d.ts +36 -3
  47. package/src/lib/server/reviews.js +310 -52
  48. package/src/lib/server/reviews.js.map +1 -1
  49. package/src/lib/server/store-setup-ai-capability.d.ts +61 -0
  50. package/src/lib/server/store-setup-ai-capability.js +343 -0
  51. package/src/lib/server/store-setup-ai-capability.js.map +1 -0
  52. package/src/lib/server/store-setup-ai-writer.console.d.ts +27 -0
  53. package/src/lib/server/store-setup-ai-writer.console.js +44 -0
  54. package/src/lib/server/store-setup-ai-writer.console.js.map +1 -0
  55. package/src/lib/server/store-setup-ai-writer.d.ts +37 -0
  56. package/src/lib/server/store-setup-ai-writer.js +174 -0
  57. package/src/lib/server/store-setup-ai-writer.js.map +1 -0
  58. package/src/lib/server.js +27 -0
  59. package/src/lib/server.js.map +1 -1
  60. package/src/lib/tenant-emails.js +58 -0
  61. package/src/lib/tenant-emails.js.map +1 -1
package/README.md CHANGED
@@ -56,7 +56,10 @@ All console pages and cards are code-split.
56
56
 
57
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
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.
59
- - Loading the module registers scheduled plugin jobs: `abandoned-checkout-recovery`, `back-in-stock-alerts`, `stock-decrement-reconciliation` and `supplier-webhook-delivery`. This is why `package.json` lists `./src/lib/server.*` under `sideEffects`.
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
+ - Loading the module registers scheduled plugin jobs: `abandoned-checkout-recovery`, `back-in-stock-alerts`, `review-request-emails`, `stock-decrement-reconciliation` and `supplier-webhook-delivery`. This is why `package.json` lists `./src/lib/server.*` under `sideEffects`.
61
+
62
+ - Product reviews are verified-buyer by default. `commerce/reviews` (POST) marks a review verified only for a signed-in site member whose proven email has an order that still entitles the product, or for a single-use link from the review-request email (`server/review-tokens.ts`: hashed, 60 days, bound to order, product and email). A typed email never verifies. Verified reviews publish at once; under the store's `anyone` policy (`settings/store.reviews.policy`) the rest wait as `pending`, and under `verified` they get 403. `GET commerce/reviews?mode=eligibility` tells the storefront block which form to draw. The POST is rate limited per address and per member. The `order.fulfilled`, `order.delivered` and `order.paid` subscriber stamps an order for its review request, and the hourly `review-request-emails` job sends it (`server/review-requests.ts`, catalog key `review-request`). Merchants hide a review only for a content reason (`REVIEW_MODERATION_REASONS`), which the Firestore rules enforce.
60
63
 
61
64
  Routes are served by the host app's API dispatcher under `/api/`, for example `/api/commerce/catalog`.
62
65
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aglyn/plugins-commerce",
3
- "version": "1.0.0-beta.244",
3
+ "version": "1.0.0-beta.247",
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.244",
29
- "@aglyn/shared-data-mdi": "1.0.0-beta.244",
30
- "@aglyn/shared-ui-jsx": "1.0.0-beta.244",
31
- "@aglyn/shared-ui-next": "1.0.0-beta.244",
32
- "@aglyn/shared-ui-snackstack": "1.0.0-beta.244",
33
- "@aglyn/shared-util-email": "1.0.0-beta.244",
34
- "@aglyn/shared-util-http": "1.0.0-beta.244",
35
- "@aglyn/shared-util-timestamp": "1.0.0-beta.244",
36
- "@aglyn/shared-util-tools": "1.0.0-beta.244",
37
- "@aglyn/tenant-data-admin": "1.0.0-beta.244",
38
- "@aglyn/tenant-feature-instance": "1.0.0-beta.244",
39
- "@aglyn/tenant-runtime": "1.0.0-beta.244",
28
+ "@aglyn/aglyn": "1.0.0-beta.247",
29
+ "@aglyn/shared-data-mdi": "1.0.0-beta.247",
30
+ "@aglyn/shared-ui-jsx": "1.0.0-beta.247",
31
+ "@aglyn/shared-ui-next": "1.0.0-beta.247",
32
+ "@aglyn/shared-ui-snackstack": "1.0.0-beta.247",
33
+ "@aglyn/shared-util-email": "1.0.0-beta.247",
34
+ "@aglyn/shared-util-http": "1.0.0-beta.247",
35
+ "@aglyn/shared-util-timestamp": "1.0.0-beta.247",
36
+ "@aglyn/shared-util-tools": "1.0.0-beta.247",
37
+ "@aglyn/tenant-data-admin": "1.0.0-beta.247",
38
+ "@aglyn/tenant-feature-instance": "1.0.0-beta.247",
39
+ "@aglyn/tenant-runtime": "1.0.0-beta.247",
40
40
  "@stripe/react-stripe-js": "^6.12.0",
41
41
  "@stripe/stripe-js": "^9.17.0",
42
42
  "@swc/helpers": "0.5.23",
@@ -42,6 +42,7 @@ import ReservationsCard from "./console/reservations-card.component.js";
42
42
  import ReturnSettingsCard from "./console/return-settings-card.component.js";
43
43
  import ReturnsCard from "./console/returns-card.component.js";
44
44
  import ReviewsModerationCard from "./console/reviews-moderation-card.component.js";
45
+ import ReviewSettingsCard from "./console/review-settings-card.component.js";
45
46
  import ShippingSettingsCard from "./console/shipping-settings-card.component.js";
46
47
  import ShipStationCard from "./console/shipstation-card.component.js";
47
48
  import ShippingEasyCard from "./console/shippingeasy-card.component.js";
@@ -272,6 +273,15 @@ import { useConsoleWidgetSlot } from "@aglyn/aglyn/app-utils/console-widget-slot
272
273
  hostId: hostId
273
274
  })
274
275
  },
276
+ // Who may review, and the review-request email (AGL-3736).
277
+ {
278
+ size: {
279
+ xs: 12
280
+ },
281
+ children: /*#__PURE__*/ _jsx(ReviewSettingsCard, {
282
+ hostId: hostId
283
+ })
284
+ },
275
285
  {
276
286
  size: {
277
287
  xs: 12
@@ -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 type { ReactNode } from 'react'\nimport type { CommerceConsoleSectionId } 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 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 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 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, COMMERCE_SETTINGS_ZONE } from './console/store-zones'\nimport { useConsoleWidgetSlot } from '@aglyn/aglyn/app-utils/console-widget-slot-context'\n\n/**\n * The `commerceSettings` zone (AGL-3612): other plugins' settings for the\n * store — label settings and carrier accounts — under the store's own.\n */\nfunction CommerceSettingsZone(props: { hostId: string }) {\n const WidgetSlot = useConsoleWidgetSlot()\n return WidgetSlot ? (\n <WidgetSlot slot={COMMERCE_SETTINGS_ZONE.id} hostId={props.hostId} orgId={undefined} />\n ) : null\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 all six 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): 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 {\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 {\n size: { xs: 12 },\n children: <CommercePromotionsZone hostId={hostId} />,\n },\n ]}\n />\n )\n case 'reservations':\n return <ReservationsCard hostId={hostId} />\n case 'settings':\n return (\n <GridItems\n spacing={3}\n items={[\n {\n size: { xs: 12 },\n children: <PaymentsSettingsCard hostId={hostId} />,\n },\n // What shoppers may pay with (AGL-3629), beside the account the\n // money lands in.\n {\n size: { xs: 12 },\n children: <PaymentMethodsCard hostId={hostId} />,\n },\n {\n size: { xs: 12 },\n children: <StoreSettingsCard hostId={hostId} />,\n },\n {\n size: { xs: 12 },\n children: <ReturnSettingsCard hostId={hostId} />,\n },\n {\n size: { xs: 12 },\n children: <CustomerNotificationsCard hostId={hostId} />,\n },\n { size: { xs: 12 }, children: <TaxSettingsCard hostId={hostId} /> },\n { size: { xs: 12 }, children: <LocationsCard hostId={hostId} /> },\n { size: { xs: 12 }, children: <RegistersCard hostId={hostId} /> },\n { size: { xs: 12 }, children: <PosStaffPinsCard hostId={hostId} /> },\n // A register's printers, cash drawer and status (AGL-3619), one\n // card per register, after the list that names them.\n {\n size: { xs: 12 },\n children: <RegisterHardwareCards hostId={hostId} />,\n },\n { size: { xs: 12 }, children: <PosDevicesCard hostId={hostId} /> },\n {\n size: { xs: 12 },\n children: <ShippingSettingsCard hostId={hostId} />,\n },\n // The store's own delivery (AGL-3624); pickup is per location,\n // on the Inventory locations card above.\n {\n size: { xs: 12 },\n children: <LocalDeliverySettingsCard hostId={hostId} />,\n },\n { size: { xs: 12 }, children: <SuppliersCard hostId={hostId} /> },\n { size: { xs: 12 }, children: <ShipStationCard hostId={hostId} /> },\n // ShippingEasy, with the merchant's own keys (AGL-3633).\n { size: { xs: 12 }, children: <ShippingEasyCard 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 {\n size: { xs: 12 },\n children: <OrderWebhooksCard hostId={hostId} />,\n },\n {\n size: { xs: 12 },\n children: <CommerceSettingsZone hostId={hostId} />,\n },\n ]}\n />\n )\n case 'analytics':\n return (\n // Two cards since AGL-2440. The tax summary lives here rather\n // than under Settings → Tax because that tab 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 six 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 } = props\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)}\n </HubSections>\n )\n}\nCommerceConsolePage.displayName = 'CommerceConsolePage'\n\nexport default CommerceConsolePage\n"],"names":["GridItems","HubSections","CatalogOrganizationCard","CommerceAnalyticsCard","CustomerNotificationsCard","DiscountsCard","GiftCardsCard","HostCouponsCard","HostOrdersCard","LocationsCard","LocalDeliverySettingsCard","PickupDeliveryQueueCard","MemberPostsCard","OrderWebhooksCard","PaymentsSettingsCard","PosShiftHistoryCard","PosStaffPinsCard","PaymentMethodsCard","ProductsHubCard","RecoveryQueueCard","RegistersCard","RegisterHardwareCards","PosDevicesCard","ReservationsCard","ReturnSettingsCard","ReturnsCard","ReviewsModerationCard","ShippingSettingsCard","ShipStationCard","ShippingEasyCard","StockMovementsCard","StoreSettingsCard","StorefrontTaxSummaryCard","SuppliersCard","TaxSettingsCard","COMMERCE_PROMOTIONS_ZONE","COMMERCE_SETTINGS_ZONE","useConsoleWidgetSlot","CommerceSettingsZone","props","WidgetSlot","slot","id","hostId","orgId","undefined","CommercePromotionsZone","sectionBody","section","spacing","items","size","xs","children","CommerceConsolePage","sections","basePath","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 type { ReactNode } from 'react'\nimport type { CommerceConsoleSectionId } 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 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 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, COMMERCE_SETTINGS_ZONE } from './console/store-zones'\nimport { useConsoleWidgetSlot } from '@aglyn/aglyn/app-utils/console-widget-slot-context'\n\n/**\n * The `commerceSettings` zone (AGL-3612): other plugins' settings for the\n * store — label settings and carrier accounts — under the store's own.\n */\nfunction CommerceSettingsZone(props: { hostId: string }) {\n const WidgetSlot = useConsoleWidgetSlot()\n return WidgetSlot ? (\n <WidgetSlot slot={COMMERCE_SETTINGS_ZONE.id} hostId={props.hostId} orgId={undefined} />\n ) : null\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 all six 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): 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 {\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 {\n size: { xs: 12 },\n children: <CommercePromotionsZone hostId={hostId} />,\n },\n ]}\n />\n )\n case 'reservations':\n return <ReservationsCard hostId={hostId} />\n case 'settings':\n return (\n <GridItems\n spacing={3}\n items={[\n {\n size: { xs: 12 },\n children: <PaymentsSettingsCard hostId={hostId} />,\n },\n // What shoppers may pay with (AGL-3629), beside the account the\n // money lands in.\n {\n size: { xs: 12 },\n children: <PaymentMethodsCard hostId={hostId} />,\n },\n {\n size: { xs: 12 },\n children: <StoreSettingsCard hostId={hostId} />,\n },\n {\n size: { xs: 12 },\n children: <ReturnSettingsCard hostId={hostId} />,\n },\n {\n size: { xs: 12 },\n children: <CustomerNotificationsCard hostId={hostId} />,\n },\n // Who may review, and the review-request email (AGL-3736).\n {\n size: { xs: 12 },\n children: <ReviewSettingsCard hostId={hostId} />,\n },\n { size: { xs: 12 }, children: <TaxSettingsCard hostId={hostId} /> },\n { size: { xs: 12 }, children: <LocationsCard hostId={hostId} /> },\n { size: { xs: 12 }, children: <RegistersCard hostId={hostId} /> },\n { size: { xs: 12 }, children: <PosStaffPinsCard hostId={hostId} /> },\n // A register's printers, cash drawer and status (AGL-3619), one\n // card per register, after the list that names them.\n {\n size: { xs: 12 },\n children: <RegisterHardwareCards hostId={hostId} />,\n },\n { size: { xs: 12 }, children: <PosDevicesCard hostId={hostId} /> },\n {\n size: { xs: 12 },\n children: <ShippingSettingsCard hostId={hostId} />,\n },\n // The store's own delivery (AGL-3624); pickup is per location,\n // on the Inventory locations card above.\n {\n size: { xs: 12 },\n children: <LocalDeliverySettingsCard hostId={hostId} />,\n },\n { size: { xs: 12 }, children: <SuppliersCard hostId={hostId} /> },\n { size: { xs: 12 }, children: <ShipStationCard hostId={hostId} /> },\n // ShippingEasy, with the merchant's own keys (AGL-3633).\n { size: { xs: 12 }, children: <ShippingEasyCard 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 {\n size: { xs: 12 },\n children: <OrderWebhooksCard hostId={hostId} />,\n },\n {\n size: { xs: 12 },\n children: <CommerceSettingsZone hostId={hostId} />,\n },\n ]}\n />\n )\n case 'analytics':\n return (\n // Two cards since AGL-2440. The tax summary lives here rather\n // than under Settings → Tax because that tab 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 six 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 } = props\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)}\n </HubSections>\n )\n}\nCommerceConsolePage.displayName = 'CommerceConsolePage'\n\nexport default CommerceConsolePage\n"],"names":["GridItems","HubSections","CatalogOrganizationCard","CommerceAnalyticsCard","CustomerNotificationsCard","DiscountsCard","GiftCardsCard","HostCouponsCard","HostOrdersCard","LocationsCard","LocalDeliverySettingsCard","PickupDeliveryQueueCard","MemberPostsCard","OrderWebhooksCard","PaymentsSettingsCard","PosShiftHistoryCard","PosStaffPinsCard","PaymentMethodsCard","ProductsHubCard","RecoveryQueueCard","RegistersCard","RegisterHardwareCards","PosDevicesCard","ReservationsCard","ReturnSettingsCard","ReturnsCard","ReviewsModerationCard","ReviewSettingsCard","ShippingSettingsCard","ShipStationCard","ShippingEasyCard","StockMovementsCard","StoreSettingsCard","StorefrontTaxSummaryCard","SuppliersCard","TaxSettingsCard","COMMERCE_PROMOTIONS_ZONE","COMMERCE_SETTINGS_ZONE","useConsoleWidgetSlot","CommerceSettingsZone","props","WidgetSlot","slot","id","hostId","orgId","undefined","CommercePromotionsZone","sectionBody","section","spacing","items","size","xs","children","CommerceConsolePage","sections","basePath","length","displayName"],"mappings":"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"}
@@ -20,6 +20,7 @@ import * as Aglyn from "@aglyn/aglyn";
20
20
  import { invalidOpeningHoursLines } from "@aglyn/aglyn/app-utils/local-business";
21
21
  import * as CommerceModel from "../../model/index.js";
22
22
  import { CardDisplay } from "@aglyn/shared-ui-jsx";
23
+ import { OpeningHoursPicker } from "@aglyn/shared-ui-jsx/components/weekly-hours-picker.component";
23
24
  import { useSnackbar } from "@aglyn/shared-ui-snackstack";
24
25
  import { Alert, Button, Divider, FormControlLabel, MenuItem, Stack, Switch, TextField, Typography } from "@mui/material";
25
26
  import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
@@ -461,22 +462,18 @@ const LEAD_TIMES = [
461
462
  ]
462
463
  }) : null,
463
464
  /*#__PURE__*/ _jsx(Divider, {}),
464
- /*#__PURE__*/ _jsx(Typography, {
465
- variant: "subtitle2",
466
- children: 'Delivery times'
467
- }),
468
- /*#__PURE__*/ _jsx(TextField, {
465
+ /*#__PURE__*/ _jsx(OpeningHoursPicker, {
469
466
  label: "Delivery windows",
470
467
  value: (_current_windows1 = current.windows) != null ? _current_windows1 : '',
471
- onChange: (event)=>update({
472
- windows: event.target.value
468
+ onChange: (windows)=>update({
469
+ windows
473
470
  }),
474
- multiline: true,
475
- minRows: 2,
476
- size: "small",
477
- placeholder: 'Mo-Fr 09:00-12:00\nMo-Fr 13:00-17:00',
478
- error: badWindowLines.length > 0,
479
- helperText: badWindowLines.length ? `Line ${badWindowLines.join(', ')} does not read as days and times, like Mo-Fr 09:00-12:00.` : 'One window per line. Each line is offered on each of its days.'
471
+ intervalNoun: "window",
472
+ defaultInterval: {
473
+ start: 9 * 60,
474
+ end: 12 * 60
475
+ },
476
+ helperText: "Switch on each day you deliver, then pick its windows. Each window is offered on its day."
480
477
  }),
481
478
  /*#__PURE__*/ _jsxs(Stack, {
482
479
  direction: {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/local-delivery-settings-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use client'\n\nimport * as Aglyn from '@aglyn/aglyn'\nimport { invalidOpeningHoursLines } from '@aglyn/aglyn/app-utils/local-business'\nimport * as CommerceModel from '../../model'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Alert,\n Button,\n Divider,\n FormControlLabel,\n MenuItem,\n Stack,\n Switch,\n TextField,\n Typography,\n} from '@mui/material'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { collection, doc, limit, query, setDoc } from 'firebase/firestore'\nimport { useCallback, useEffect, useState } from 'react'\nimport {\n useFirestore,\n useFirestoreCollection,\n useFirestoreDoc,\n useUser,\n writeGuardedBySeed,\n} from '@aglyn/tenant-feature-instance'\nimport { pluginDocsHelp } from '@aglyn/aglyn'\n\nexport interface LocalDeliverySettingsCardProps {\n hostId: string\n}\n\nconst LEAD_TIMES: ReadonlyArray<[number, string]> = [\n [0, 'No notice'],\n [60, '1 hour'],\n [120, '2 hours'],\n [240, '4 hours'],\n [1440, '1 day'],\n [2880, '2 days'],\n]\n\n/**\n * Local delivery (AGL-3624): the store's OWN delivery — zones by postal code\n * or by distance, each with a fee, a minimum order and a free-over\n * threshold, and the delivery windows a buyer books at the cart. Stored on\n * `settings/store.localDelivery`, written whole so a cleared field is\n * cleared. Marketplace couriers (DoorDash and the rest) are the Delivery apps\n * plugin's, and nothing here touches them.\n *\n * DISTANCE ZONES are offered only where the store's shipping address check\n * can place an address on a map (`GET /api/commerce/local-fulfillment`), and\n * need the store placed first (\"Place on map\"). Postal-code zones need\n * nothing.\n */\nexport function LocalDeliverySettingsCard(props: LocalDeliverySettingsCardProps) {\n const { hostId } = props\n const firestore = useFirestore()\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const { data: store, status: storeStatus, fromCache } = useFirestoreDoc<any>(\n () => doc(firestore, 'hosts', hostId, 'settings', 'store'),\n [firestore, hostId],\n )\n const { data: locationDocs } = useFirestoreCollection<any>(\n () => query(collection(firestore, 'hosts', hostId, 'locations'), limit(25)),\n [firestore, hostId],\n { idField: '$id' },\n )\n const [draft, setDraft] = useState<CommerceModel.LocalDeliverySettings | null>(null)\n const current: CommerceModel.LocalDeliverySettings = draft ?? (store?.localDelivery as CommerceModel.LocalDeliverySettings) ?? {}\n const update = (patch: Partial<CommerceModel.LocalDeliverySettings>) => setDraft({ ...current, ...patch })\n const [radiusAvailable, setRadiusAvailable] = useState(false)\n useEffect(() => {\n let live = true\n authorizedFetch(user, `/api/commerce/local-fulfillment?hostId=${encodeURIComponent(hostId)}`)\n .then((response) => (response.ok ? response.json() : null))\n .then((answer) => {\n if (live) setRadiusAvailable(Boolean(answer?.radius))\n })\n .catch(() => undefined)\n return () => {\n live = false\n }\n }, [hostId, user])\n\n const zones = current.zones ?? []\n const updateZone = (index: number, patch: Partial<CommerceModel.LocalDeliveryZone> | null) => {\n const next = [...zones]\n if (patch === null) next.splice(index, 1)\n else next[index] = { ...next[index], ...patch }\n update({ zones: next })\n }\n const addZone = () =>\n update({\n zones: [\n ...zones,\n { id: Aglyn.createResourceUid(), name: 'Local delivery', kind: 'postcode', postcodes: [], feeCents: 0 },\n ],\n })\n\n const [locating, setLocating] = useState(false)\n const handleLocate = useCallback(async () => {\n if (!current.locationId) {\n return void enqueueSnackbar('Choose the location deliveries leave from first', { variant: 'info', persist: false })\n }\n setLocating(true)\n try {\n const response = await authorizedFetch(user, '/api/commerce/local-fulfillment', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ hostId, action: 'locate', locationId: current.locationId }),\n })\n const payload: any = await response.json().catch(() => ({}))\n if (!response.ok || !payload?.origin) {\n enqueueSnackbar(payload?.error ?? 'Could not place the store on a map', { variant: 'warning', persist: false })\n return\n }\n setDraft({ ...current, origin: payload.origin })\n } finally {\n setLocating(false)\n }\n }, [current, enqueueSnackbar, hostId, user])\n\n const badWindowLines = invalidOpeningHoursLines(current.windows ?? '')\n const problems = CommerceModel.localDeliveryProblems(CommerceModel.normalizeLocalDeliverySettings(current))\n\n const handleSave = useCallback(async () => {\n const value = CommerceModel.normalizeLocalDeliverySettings(current)\n const verdict = await writeGuardedBySeed(\n { subject: 'local delivery settings', unreadable: storeStatus === 'error', fromCache },\n async () => {\n // `mergeFields` replaces `localDelivery` whole and leaves every other\n // map on the settings document alone.\n await setDoc(doc(firestore, 'hosts', hostId, 'settings', 'store'), { localDelivery: value }, {\n mergeFields: ['localDelivery'],\n })\n },\n )\n if (!verdict.ok) {\n return void enqueueSnackbar(verdict.message, { variant: 'warning', persist: false })\n }\n setDraft(null)\n enqueueSnackbar('Local delivery saved', { variant: 'success', persist: false })\n }, [current, enqueueSnackbar, firestore, fromCache, hostId, storeStatus])\n\n const dollars = (cents: number | undefined) => (cents ? String(cents / 100) : '')\n const cents = (raw: string) => (raw.trim() === '' ? 0 : Math.max(0, Math.round(Number(raw) * 100) || 0))\n const locations = [...(locationDocs ?? [])].sort((a: any, b: any) =>\n String(a.name ?? '').localeCompare(String(b.name ?? '')),\n )\n\n return (\n <CardDisplay\n header={'Local delivery'}\n help={pluginDocsHelp('pickupAndDelivery', {\n anchor: '#set-up-local-delivery',\n })}\n HeaderProps={{\n action: (\n <Button variant=\"contained\" size=\"small\" disabled={!draft || badWindowLines.length > 0} onClick={handleSave}>\n {'Save'}\n </Button>\n ),\n }}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1.5}>\n <FormControlLabel\n control={\n <Switch\n size=\"small\"\n checked={Boolean(current.enabled)}\n onChange={(event) => update({ enabled: event.target.checked })}\n />\n }\n label=\"Deliver orders yourself\"\n />\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Buyers choose local delivery at the cart, enter their postal code and book a delivery time. ' +\n 'You mark each order out for delivery and delivered, and the buyer is told at each step.'}\n </Typography>\n {problems.length ? (\n <Alert severity=\"warning\">{`Checkout won’t offer delivery yet: ${problems.join(' ')}`}</Alert>\n ) : null}\n <Stack direction={{ xs: 'column', sm: 'row' }} spacing={1}>\n <TextField\n label=\"Country\"\n value={current.country ?? ''}\n onChange={(event) => update({ country: event.target.value.toUpperCase().slice(0, 2) })}\n size=\"small\"\n placeholder=\"US\"\n helperText=\"Two letters\"\n sx={{ width: { sm: 120 } }}\n />\n <TextField\n select\n label=\"Leaves from\"\n value={current.locationId ?? ''}\n onChange={(event) => update({ locationId: event.target.value || undefined })}\n size=\"small\"\n sx={{ flex: 1 }}\n helperText=\"Its stock is reserved and sold for delivery orders\"\n >\n <MenuItem value=\"\">{'No particular location'}</MenuItem>\n {locations.map((location: any) => (\n <MenuItem key={location.$id} value={location.$id}>\n {location.name}\n </MenuItem>\n ))}\n </TextField>\n </Stack>\n\n <Divider />\n <Typography variant=\"subtitle2\">{'Zones'}</Typography>\n {zones.map((zone, index) => (\n <Stack key={zone.id} spacing={1}>\n <Stack direction={{ xs: 'column', sm: 'row' }} spacing={1}>\n <TextField\n label=\"Zone name\"\n value={zone.name}\n onChange={(event) => updateZone(index, { name: event.target.value })}\n size=\"small\"\n sx={{ flex: 1 }}\n />\n {radiusAvailable || zone.kind === 'radius' ? (\n <TextField\n select\n label=\"Matched by\"\n value={zone.kind}\n onChange={(event) =>\n updateZone(index, { kind: event.target.value as CommerceModel.LocalDeliveryZone['kind'] })\n }\n size=\"small\"\n sx={{ width: { sm: 160 } }}\n >\n <MenuItem value=\"postcode\">{'Postal codes'}</MenuItem>\n <MenuItem value=\"radius\">{'Distance'}</MenuItem>\n </TextField>\n ) : null}\n <Button size=\"small\" color=\"error\" onClick={() => updateZone(index, null)}>\n {'Remove'}\n </Button>\n </Stack>\n {zone.kind === 'radius' ? (\n <TextField\n label=\"Within (km)\"\n type=\"number\"\n value={zone.radiusKm ?? ''}\n onChange={(event) => updateZone(index, { radiusKm: Number(event.target.value) || 0 })}\n size=\"small\"\n sx={{ width: { sm: 160 } }}\n />\n ) : (\n <TextField\n label=\"Postal codes\"\n value={(zone.postcodes ?? []).join(', ')}\n onChange={(event) =>\n updateZone(index, {\n postcodes: event.target.value.split(',').map((code) => code.trim()).filter(Boolean),\n })\n }\n size=\"small\"\n placeholder=\"10001, 100*, 10010-10020\"\n helperText=\"Exact codes, a prefix ending in *, or a range\"\n />\n )}\n <Stack direction={{ xs: 'column', sm: 'row' }} spacing={1}>\n <TextField\n label=\"Fee ($)\"\n value={dollars(zone.feeCents)}\n onChange={(event) => updateZone(index, { feeCents: cents(event.target.value) })}\n size=\"small\"\n slotProps={{ htmlInput: { inputMode: 'decimal' } }}\n />\n <TextField\n label=\"Minimum order ($)\"\n value={dollars(zone.minimumCents)}\n onChange={(event) => updateZone(index, { minimumCents: cents(event.target.value) || undefined })}\n size=\"small\"\n slotProps={{ htmlInput: { inputMode: 'decimal' } }}\n />\n <TextField\n label=\"Free over ($)\"\n value={dollars(zone.freeOverCents)}\n onChange={(event) => updateZone(index, { freeOverCents: cents(event.target.value) || undefined })}\n size=\"small\"\n slotProps={{ htmlInput: { inputMode: 'decimal' } }}\n />\n </Stack>\n <Divider flexItem />\n </Stack>\n ))}\n <Button size=\"small\" sx={{ alignSelf: 'flex-start' }} onClick={addZone}>\n {'Add zone'}\n </Button>\n {zones.some((zone) => zone.kind === 'radius') ? (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography variant=\"body2\" sx={{ flex: 1 }}>\n {current.origin\n ? `Distances measure from ${current.origin.lat.toFixed(4)}, ${current.origin.lng.toFixed(4)}.`\n : 'Distance zones measure from the location deliveries leave from, once it is placed on a map.'}\n </Typography>\n <Button size=\"small\" disabled={locating} onClick={handleLocate}>\n {current.origin ? 'Place again' : 'Place on map'}\n </Button>\n </Stack>\n ) : null}\n\n <Divider />\n <Typography variant=\"subtitle2\">{'Delivery times'}</Typography>\n <TextField\n label=\"Delivery windows\"\n value={current.windows ?? ''}\n onChange={(event) => update({ windows: event.target.value })}\n multiline\n minRows={2}\n size=\"small\"\n placeholder={'Mo-Fr 09:00-12:00\\nMo-Fr 13:00-17:00'}\n error={badWindowLines.length > 0}\n helperText={\n badWindowLines.length\n ? `Line ${badWindowLines.join(', ')} does not read as days and times, like Mo-Fr 09:00-12:00.`\n : 'One window per line. Each line is offered on each of its days.'\n }\n />\n <Stack direction={{ xs: 'column', sm: 'row' }} spacing={1}>\n <TextField\n select\n label=\"Order at least\"\n value={String(current.leadTimeMinutes ?? CommerceModel.LOCAL_DELIVERY_DEFAULT_LEAD_MINUTES)}\n onChange={(event) => update({ leadTimeMinutes: Number(event.target.value) })}\n size=\"small\"\n helperText=\"Before a window starts\"\n sx={{ flex: 1 }}\n >\n {LEAD_TIMES.map(([minutes, label]) => (\n <MenuItem key={minutes} value={String(minutes)}>\n {label}\n </MenuItem>\n ))}\n </TextField>\n <TextField\n label=\"Book up to (days ahead)\"\n type=\"number\"\n value={current.daysAhead ?? CommerceModel.LOCAL_DELIVERY_DEFAULT_DAYS_AHEAD}\n onChange={(event) =>\n update({\n daysAhead: Math.min(\n CommerceModel.LOCAL_DELIVERY_MAX_DAYS_AHEAD,\n Math.max(1, Math.round(Number(event.target.value)) || 1),\n ),\n })\n }\n size=\"small\"\n sx={{ flex: 1 }}\n />\n </Stack>\n <TextField\n label=\"What buyers should know\"\n value={current.instructions ?? ''}\n onChange={(event) => update({ instructions: event.target.value })}\n multiline\n minRows={2}\n size=\"small\"\n placeholder=\"We text when we’re 10 minutes away.\"\n slotProps={{ htmlInput: { maxLength: CommerceModel.PICKUP_INSTRUCTIONS_MAX } }}\n />\n </Stack>\n </CardDisplay>\n )\n}\nLocalDeliverySettingsCard.displayName = 'LocalDeliverySettingsCard'\n\nexport default LocalDeliverySettingsCard\n"],"names":["Aglyn","invalidOpeningHoursLines","CommerceModel","CardDisplay","useSnackbar","Alert","Button","Divider","FormControlLabel","MenuItem","Stack","Switch","TextField","Typography","authorizedFetch","collection","doc","limit","query","setDoc","useCallback","useEffect","useState","useFirestore","useFirestoreCollection","useFirestoreDoc","useUser","writeGuardedBySeed","pluginDocsHelp","LEAD_TIMES","LocalDeliverySettingsCard","props","draft","current","hostId","firestore","data","user","enqueueSnackbar","store","status","storeStatus","fromCache","locationDocs","idField","setDraft","localDelivery","update","patch","radiusAvailable","setRadiusAvailable","live","encodeURIComponent","then","response","ok","json","answer","Boolean","radius","catch","undefined","zones","updateZone","index","next","splice","addZone","id","createResourceUid","name","kind","postcodes","feeCents","locating","setLocating","handleLocate","locationId","variant","persist","method","headers","body","JSON","stringify","action","payload","origin","error","badWindowLines","windows","problems","localDeliveryProblems","normalizeLocalDeliverySettings","handleSave","value","verdict","subject","unreadable","mergeFields","message","dollars","cents","String","raw","trim","Math","max","round","Number","locations","sort","a","b","localeCompare","header","help","anchor","HeaderProps","size","disabled","length","onClick","contentGutterX","contentGutterY","spacing","control","checked","enabled","onChange","event","target","label","color","severity","join","direction","xs","sm","country","toUpperCase","slice","placeholder","helperText","sx","width","select","flex","map","location","$id","zone","type","radiusKm","split","code","filter","slotProps","htmlInput","inputMode","minimumCents","freeOverCents","flexItem","alignSelf","some","alignItems","lat","toFixed","lng","multiline","minRows","leadTimeMinutes","LOCAL_DELIVERY_DEFAULT_LEAD_MINUTES","minutes","daysAhead","LOCAL_DELIVERY_DEFAULT_DAYS_AHEAD","min","LOCAL_DELIVERY_MAX_DAYS_AHEAD","instructions","maxLength","PICKUP_INSTRUCTIONS_MAX","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/local-delivery-settings-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use client'\n\nimport * as Aglyn from '@aglyn/aglyn'\nimport { invalidOpeningHoursLines } from '@aglyn/aglyn/app-utils/local-business'\nimport * as CommerceModel from '../../model'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { OpeningHoursPicker } from '@aglyn/shared-ui-jsx/components/weekly-hours-picker.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Alert,\n Button,\n Divider,\n FormControlLabel,\n MenuItem,\n Stack,\n Switch,\n TextField,\n Typography,\n} from '@mui/material'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { collection, doc, limit, query, setDoc } from 'firebase/firestore'\nimport { useCallback, useEffect, useState } from 'react'\nimport {\n useFirestore,\n useFirestoreCollection,\n useFirestoreDoc,\n useUser,\n writeGuardedBySeed,\n} from '@aglyn/tenant-feature-instance'\nimport { pluginDocsHelp } from '@aglyn/aglyn'\n\nexport interface LocalDeliverySettingsCardProps {\n hostId: string\n}\n\nconst LEAD_TIMES: ReadonlyArray<[number, string]> = [\n [0, 'No notice'],\n [60, '1 hour'],\n [120, '2 hours'],\n [240, '4 hours'],\n [1440, '1 day'],\n [2880, '2 days'],\n]\n\n/**\n * Local delivery (AGL-3624): the store's OWN delivery — zones by postal code\n * or by distance, each with a fee, a minimum order and a free-over\n * threshold, and the delivery windows a buyer books at the cart. Stored on\n * `settings/store.localDelivery`, written whole so a cleared field is\n * cleared. Marketplace couriers (DoorDash and the rest) are the Delivery apps\n * plugin's, and nothing here touches them.\n *\n * DISTANCE ZONES are offered only where the store's shipping address check\n * can place an address on a map (`GET /api/commerce/local-fulfillment`), and\n * need the store placed first (\"Place on map\"). Postal-code zones need\n * nothing.\n */\nexport function LocalDeliverySettingsCard(props: LocalDeliverySettingsCardProps) {\n const { hostId } = props\n const firestore = useFirestore()\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const { data: store, status: storeStatus, fromCache } = useFirestoreDoc<any>(\n () => doc(firestore, 'hosts', hostId, 'settings', 'store'),\n [firestore, hostId],\n )\n const { data: locationDocs } = useFirestoreCollection<any>(\n () => query(collection(firestore, 'hosts', hostId, 'locations'), limit(25)),\n [firestore, hostId],\n { idField: '$id' },\n )\n const [draft, setDraft] = useState<CommerceModel.LocalDeliverySettings | null>(null)\n const current: CommerceModel.LocalDeliverySettings = draft ?? (store?.localDelivery as CommerceModel.LocalDeliverySettings) ?? {}\n const update = (patch: Partial<CommerceModel.LocalDeliverySettings>) => setDraft({ ...current, ...patch })\n const [radiusAvailable, setRadiusAvailable] = useState(false)\n useEffect(() => {\n let live = true\n authorizedFetch(user, `/api/commerce/local-fulfillment?hostId=${encodeURIComponent(hostId)}`)\n .then((response) => (response.ok ? response.json() : null))\n .then((answer) => {\n if (live) setRadiusAvailable(Boolean(answer?.radius))\n })\n .catch(() => undefined)\n return () => {\n live = false\n }\n }, [hostId, user])\n\n const zones = current.zones ?? []\n const updateZone = (index: number, patch: Partial<CommerceModel.LocalDeliveryZone> | null) => {\n const next = [...zones]\n if (patch === null) next.splice(index, 1)\n else next[index] = { ...next[index], ...patch }\n update({ zones: next })\n }\n const addZone = () =>\n update({\n zones: [\n ...zones,\n { id: Aglyn.createResourceUid(), name: 'Local delivery', kind: 'postcode', postcodes: [], feeCents: 0 },\n ],\n })\n\n const [locating, setLocating] = useState(false)\n const handleLocate = useCallback(async () => {\n if (!current.locationId) {\n return void enqueueSnackbar('Choose the location deliveries leave from first', { variant: 'info', persist: false })\n }\n setLocating(true)\n try {\n const response = await authorizedFetch(user, '/api/commerce/local-fulfillment', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ hostId, action: 'locate', locationId: current.locationId }),\n })\n const payload: any = await response.json().catch(() => ({}))\n if (!response.ok || !payload?.origin) {\n enqueueSnackbar(payload?.error ?? 'Could not place the store on a map', { variant: 'warning', persist: false })\n return\n }\n setDraft({ ...current, origin: payload.origin })\n } finally {\n setLocating(false)\n }\n }, [current, enqueueSnackbar, hostId, user])\n\n const badWindowLines = invalidOpeningHoursLines(current.windows ?? '')\n const problems = CommerceModel.localDeliveryProblems(CommerceModel.normalizeLocalDeliverySettings(current))\n\n const handleSave = useCallback(async () => {\n const value = CommerceModel.normalizeLocalDeliverySettings(current)\n const verdict = await writeGuardedBySeed(\n { subject: 'local delivery settings', unreadable: storeStatus === 'error', fromCache },\n async () => {\n // `mergeFields` replaces `localDelivery` whole and leaves every other\n // map on the settings document alone.\n await setDoc(doc(firestore, 'hosts', hostId, 'settings', 'store'), { localDelivery: value }, {\n mergeFields: ['localDelivery'],\n })\n },\n )\n if (!verdict.ok) {\n return void enqueueSnackbar(verdict.message, { variant: 'warning', persist: false })\n }\n setDraft(null)\n enqueueSnackbar('Local delivery saved', { variant: 'success', persist: false })\n }, [current, enqueueSnackbar, firestore, fromCache, hostId, storeStatus])\n\n const dollars = (cents: number | undefined) => (cents ? String(cents / 100) : '')\n const cents = (raw: string) => (raw.trim() === '' ? 0 : Math.max(0, Math.round(Number(raw) * 100) || 0))\n const locations = [...(locationDocs ?? [])].sort((a: any, b: any) =>\n String(a.name ?? '').localeCompare(String(b.name ?? '')),\n )\n\n return (\n <CardDisplay\n header={'Local delivery'}\n help={pluginDocsHelp('pickupAndDelivery', {\n anchor: '#set-up-local-delivery',\n })}\n HeaderProps={{\n action: (\n <Button variant=\"contained\" size=\"small\" disabled={!draft || badWindowLines.length > 0} onClick={handleSave}>\n {'Save'}\n </Button>\n ),\n }}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1.5}>\n <FormControlLabel\n control={\n <Switch\n size=\"small\"\n checked={Boolean(current.enabled)}\n onChange={(event) => update({ enabled: event.target.checked })}\n />\n }\n label=\"Deliver orders yourself\"\n />\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Buyers choose local delivery at the cart, enter their postal code and book a delivery time. ' +\n 'You mark each order out for delivery and delivered, and the buyer is told at each step.'}\n </Typography>\n {problems.length ? (\n <Alert severity=\"warning\">{`Checkout won’t offer delivery yet: ${problems.join(' ')}`}</Alert>\n ) : null}\n <Stack direction={{ xs: 'column', sm: 'row' }} spacing={1}>\n <TextField\n label=\"Country\"\n value={current.country ?? ''}\n onChange={(event) => update({ country: event.target.value.toUpperCase().slice(0, 2) })}\n size=\"small\"\n placeholder=\"US\"\n helperText=\"Two letters\"\n sx={{ width: { sm: 120 } }}\n />\n <TextField\n select\n label=\"Leaves from\"\n value={current.locationId ?? ''}\n onChange={(event) => update({ locationId: event.target.value || undefined })}\n size=\"small\"\n sx={{ flex: 1 }}\n helperText=\"Its stock is reserved and sold for delivery orders\"\n >\n <MenuItem value=\"\">{'No particular location'}</MenuItem>\n {locations.map((location: any) => (\n <MenuItem key={location.$id} value={location.$id}>\n {location.name}\n </MenuItem>\n ))}\n </TextField>\n </Stack>\n\n <Divider />\n <Typography variant=\"subtitle2\">{'Zones'}</Typography>\n {zones.map((zone, index) => (\n <Stack key={zone.id} spacing={1}>\n <Stack direction={{ xs: 'column', sm: 'row' }} spacing={1}>\n <TextField\n label=\"Zone name\"\n value={zone.name}\n onChange={(event) => updateZone(index, { name: event.target.value })}\n size=\"small\"\n sx={{ flex: 1 }}\n />\n {radiusAvailable || zone.kind === 'radius' ? (\n <TextField\n select\n label=\"Matched by\"\n value={zone.kind}\n onChange={(event) =>\n updateZone(index, { kind: event.target.value as CommerceModel.LocalDeliveryZone['kind'] })\n }\n size=\"small\"\n sx={{ width: { sm: 160 } }}\n >\n <MenuItem value=\"postcode\">{'Postal codes'}</MenuItem>\n <MenuItem value=\"radius\">{'Distance'}</MenuItem>\n </TextField>\n ) : null}\n <Button size=\"small\" color=\"error\" onClick={() => updateZone(index, null)}>\n {'Remove'}\n </Button>\n </Stack>\n {zone.kind === 'radius' ? (\n <TextField\n label=\"Within (km)\"\n type=\"number\"\n value={zone.radiusKm ?? ''}\n onChange={(event) => updateZone(index, { radiusKm: Number(event.target.value) || 0 })}\n size=\"small\"\n sx={{ width: { sm: 160 } }}\n />\n ) : (\n <TextField\n label=\"Postal codes\"\n value={(zone.postcodes ?? []).join(', ')}\n onChange={(event) =>\n updateZone(index, {\n postcodes: event.target.value.split(',').map((code) => code.trim()).filter(Boolean),\n })\n }\n size=\"small\"\n placeholder=\"10001, 100*, 10010-10020\"\n helperText=\"Exact codes, a prefix ending in *, or a range\"\n />\n )}\n <Stack direction={{ xs: 'column', sm: 'row' }} spacing={1}>\n <TextField\n label=\"Fee ($)\"\n value={dollars(zone.feeCents)}\n onChange={(event) => updateZone(index, { feeCents: cents(event.target.value) })}\n size=\"small\"\n slotProps={{ htmlInput: { inputMode: 'decimal' } }}\n />\n <TextField\n label=\"Minimum order ($)\"\n value={dollars(zone.minimumCents)}\n onChange={(event) => updateZone(index, { minimumCents: cents(event.target.value) || undefined })}\n size=\"small\"\n slotProps={{ htmlInput: { inputMode: 'decimal' } }}\n />\n <TextField\n label=\"Free over ($)\"\n value={dollars(zone.freeOverCents)}\n onChange={(event) => updateZone(index, { freeOverCents: cents(event.target.value) || undefined })}\n size=\"small\"\n slotProps={{ htmlInput: { inputMode: 'decimal' } }}\n />\n </Stack>\n <Divider flexItem />\n </Stack>\n ))}\n <Button size=\"small\" sx={{ alignSelf: 'flex-start' }} onClick={addZone}>\n {'Add zone'}\n </Button>\n {zones.some((zone) => zone.kind === 'radius') ? (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography variant=\"body2\" sx={{ flex: 1 }}>\n {current.origin\n ? `Distances measure from ${current.origin.lat.toFixed(4)}, ${current.origin.lng.toFixed(4)}.`\n : 'Distance zones measure from the location deliveries leave from, once it is placed on a map.'}\n </Typography>\n <Button size=\"small\" disabled={locating} onClick={handleLocate}>\n {current.origin ? 'Place again' : 'Place on map'}\n </Button>\n </Stack>\n ) : null}\n\n <Divider />\n <OpeningHoursPicker\n label=\"Delivery windows\"\n value={current.windows ?? ''}\n onChange={(windows) => update({ windows })}\n intervalNoun=\"window\"\n defaultInterval={{ start: 9 * 60, end: 12 * 60 }}\n helperText=\"Switch on each day you deliver, then pick its windows. Each window is offered on its day.\"\n />\n <Stack direction={{ xs: 'column', sm: 'row' }} spacing={1}>\n <TextField\n select\n label=\"Order at least\"\n value={String(current.leadTimeMinutes ?? CommerceModel.LOCAL_DELIVERY_DEFAULT_LEAD_MINUTES)}\n onChange={(event) => update({ leadTimeMinutes: Number(event.target.value) })}\n size=\"small\"\n helperText=\"Before a window starts\"\n sx={{ flex: 1 }}\n >\n {LEAD_TIMES.map(([minutes, label]) => (\n <MenuItem key={minutes} value={String(minutes)}>\n {label}\n </MenuItem>\n ))}\n </TextField>\n <TextField\n label=\"Book up to (days ahead)\"\n type=\"number\"\n value={current.daysAhead ?? CommerceModel.LOCAL_DELIVERY_DEFAULT_DAYS_AHEAD}\n onChange={(event) =>\n update({\n daysAhead: Math.min(\n CommerceModel.LOCAL_DELIVERY_MAX_DAYS_AHEAD,\n Math.max(1, Math.round(Number(event.target.value)) || 1),\n ),\n })\n }\n size=\"small\"\n sx={{ flex: 1 }}\n />\n </Stack>\n <TextField\n label=\"What buyers should know\"\n value={current.instructions ?? ''}\n onChange={(event) => update({ instructions: event.target.value })}\n multiline\n minRows={2}\n size=\"small\"\n placeholder=\"We text when we’re 10 minutes away.\"\n slotProps={{ htmlInput: { maxLength: CommerceModel.PICKUP_INSTRUCTIONS_MAX } }}\n />\n </Stack>\n </CardDisplay>\n )\n}\nLocalDeliverySettingsCard.displayName = 'LocalDeliverySettingsCard'\n\nexport default LocalDeliverySettingsCard\n"],"names":["Aglyn","invalidOpeningHoursLines","CommerceModel","CardDisplay","OpeningHoursPicker","useSnackbar","Alert","Button","Divider","FormControlLabel","MenuItem","Stack","Switch","TextField","Typography","authorizedFetch","collection","doc","limit","query","setDoc","useCallback","useEffect","useState","useFirestore","useFirestoreCollection","useFirestoreDoc","useUser","writeGuardedBySeed","pluginDocsHelp","LEAD_TIMES","LocalDeliverySettingsCard","props","draft","current","hostId","firestore","data","user","enqueueSnackbar","store","status","storeStatus","fromCache","locationDocs","idField","setDraft","localDelivery","update","patch","radiusAvailable","setRadiusAvailable","live","encodeURIComponent","then","response","ok","json","answer","Boolean","radius","catch","undefined","zones","updateZone","index","next","splice","addZone","id","createResourceUid","name","kind","postcodes","feeCents","locating","setLocating","handleLocate","locationId","variant","persist","method","headers","body","JSON","stringify","action","payload","origin","error","badWindowLines","windows","problems","localDeliveryProblems","normalizeLocalDeliverySettings","handleSave","value","verdict","subject","unreadable","mergeFields","message","dollars","cents","String","raw","trim","Math","max","round","Number","locations","sort","a","b","localeCompare","header","help","anchor","HeaderProps","size","disabled","length","onClick","contentGutterX","contentGutterY","spacing","control","checked","enabled","onChange","event","target","label","color","severity","join","direction","xs","sm","country","toUpperCase","slice","placeholder","helperText","sx","width","select","flex","map","location","$id","zone","type","radiusKm","split","code","filter","slotProps","htmlInput","inputMode","minimumCents","freeOverCents","flexItem","alignSelf","some","alignItems","lat","toFixed","lng","intervalNoun","defaultInterval","start","end","leadTimeMinutes","LOCAL_DELIVERY_DEFAULT_LEAD_MINUTES","minutes","daysAhead","LOCAL_DELIVERY_DEFAULT_DAYS_AHEAD","min","LOCAL_DELIVERY_MAX_DAYS_AHEAD","instructions","multiline","minRows","maxLength","PICKUP_INSTRUCTIONS_MAX","displayName"],"mappings":"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"}
@@ -20,6 +20,7 @@ import * as Aglyn from "@aglyn/aglyn";
20
20
  import * as CommerceModel from "../../model/index.js";
21
21
  import { CardDisplay, useConfirmationContext } from "@aglyn/shared-ui-jsx";
22
22
  import QuotaReadoutComponent from "@aglyn/shared-ui-jsx/components/quota-readout.component";
23
+ import { OpeningHoursPicker } from "@aglyn/shared-ui-jsx/components/weekly-hours-picker.component";
23
24
  import { useSnackbar } from "@aglyn/shared-ui-snackstack";
24
25
  import { Button, Chip, Dialog, DialogActions, DialogContent, DialogTitle, FormControlLabel, MenuItem, Stack, Switch, TextField, Typography } from "@mui/material";
25
26
  import { invalidOpeningHoursLines } from "@aglyn/aglyn/app-utils/local-business";
@@ -431,18 +432,13 @@ import { pluginDocsHelp } from "@aglyn/aglyn";
431
432
  color: "warning.main",
432
433
  children: 'Add this location’s address so buyers know where to go.'
433
434
  }) : null,
434
- /*#__PURE__*/ _jsx(TextField, {
435
+ /*#__PURE__*/ _jsx(OpeningHoursPicker, {
435
436
  label: "Pickup hours",
436
437
  value: (_pickupDraft_hours1 = pickupDraft.hours) != null ? _pickupDraft_hours1 : '',
437
- onChange: (event)=>setPickupDraft((prior)=>_extends({}, prior, {
438
- hours: event.target.value
438
+ onChange: (hours)=>setPickupDraft((prior)=>_extends({}, prior, {
439
+ hours
439
440
  })),
440
- multiline: true,
441
- minRows: 2,
442
- size: "small",
443
- placeholder: 'Mo-Fr 09:00-17:00\nSa 10:00-14:00',
444
- error: badHourLines.length > 0,
445
- helperText: badHourLines.length ? `Line ${badHourLines.join(', ')} does not read as days and times, like Mo-Fr 09:00-17:00.` : 'One line per set of days, like Mo-Fr 09:00-17:00.'
441
+ helperText: "Switch on each day buyers can collect, then pick the times."
446
442
  }),
447
443
  /*#__PURE__*/ _jsx(TextField, {
448
444
  label: "Arrival instructions",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/locations-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 QuotaReadoutComponent from '@aglyn/shared-ui-jsx/components/quota-readout.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Button,\n Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n FormControlLabel,\n MenuItem,\n Stack,\n Switch,\n TextField,\n Typography,\n} from '@mui/material'\nimport { invalidOpeningHoursLines } from '@aglyn/aglyn/app-utils/local-business'\nimport {\n collection,\n deleteDoc,\n doc,\n getCountFromServer,\n limit,\n query,\n setDoc,\n updateDoc,\n} from 'firebase/firestore'\nimport { useCallback, useEffect, useState } from 'react'\nimport { useFirestore } from '@aglyn/tenant-feature-instance'\nimport { useFirestoreCollection } from '@aglyn/tenant-feature-instance'\nimport { useHostResourceApi } from '@aglyn/tenant-feature-instance'\nimport { useOrgPlan } from '@aglyn/tenant-feature-instance'\nimport { pluginDocsHelp } from '@aglyn/aglyn'\n\nexport interface LocationsCardProps {\n hostId: string\n}\n\n/**\n * Inventory locations (AGL-286): named stock buckets under\n * `hosts/{hostId}/locations`, capped by the plan's `inventoryLocations`\n * quota (AGL-278). Variant stock splits per location in the products\n * hub; POS registers sell from their location (AGL-312).\n */\nexport function LocationsCard(props: LocationsCardProps) {\n const { hostId } = props\n const firestore = useFirestore()\n const { enqueueSnackbar } = useSnackbar()\n const { confirm } = useConfirmationContext()\n const createHostResource = useHostResourceApi()\n const { org, ready: planReady } = useOrgPlan(hostId)\n const { data: locationDocs } = useFirestoreCollection<any>(\n () => query(collection(firestore, 'hosts', hostId, 'locations'), limit(25)),\n [firestore, hostId],\n { idField: '$id' },\n )\n const locations = [...(locationDocs ?? [])].sort((a: any, b: any) =>\n String(a.name ?? '').localeCompare(String(b.name ?? '')),\n )\n /**\n * The location HEAD-COUNT is a server aggregate, not the length of the\n * capped listener (AGL-1716, the AGL-1706 shape).\n *\n * The smallest margin of the eight, and real for exactly one plan: the\n * listener is `limit(25)` and `inventoryLocations` runs 1 / 1 / 2 / 4 / 6 /\n * 10 / 50, so only Agency's 50 sits above the window. An agency with more\n * than 25 stock buckets read its cap as satisfiable, and\n * `api/hosts/resources` — which counts the collection — refused the add.\n *\n * Re-read after an add AND a delete: locations are hard-deleted here, so\n * unlike the soft-deleting cards a removal really does free a slot on both\n * sides, and a one-shot that missed it would keep refusing.\n *\n * `locations.length === 0` below still decides the FIRST location's\n * `isDefault` flag off the loaded rows, which is the right question for it\n * — that one is about what this card can see, not about the site's total.\n */\n const [locationCountEpoch, setLocationCountEpoch] = useState(0)\n const [serverLocationCount, setServerLocationCount] = useState<number | null>(\n null,\n )\n useEffect(() => {\n let active = true\n void getCountFromServer(collection(firestore, 'hosts', hostId, 'locations'))\n .then((snapshot) => {\n if (active) setServerLocationCount(snapshot.data().count)\n })\n .catch(() => {\n // Falls back to the loaded rows — a LOWER bound and the prior\n // behaviour, never 0, which reads as \"no locations used\" on a site\n // that is over its band.\n })\n return () => {\n active = false\n }\n }, [firestore, hostId, locationCountEpoch])\n const locationCount = serverLocationCount ?? locations.length\n const quota = Aglyn.checkQuota(org, 'inventoryLocations', locationCount)\n const [name, setName] = useState('')\n\n const handleAdd = useCallback(async () => {\n if (!name.trim()) return\n // Held until the org doc lands (AGL-1064): an absent org resolves to\n // the free tier's `inventoryLocations: 1`, so a site that already has\n // its default location would be refused with an upgrade prompt it does\n // not need. The button is disabled too; this guards the race.\n if (!planReady) return\n if (!quota.allowed) {\n return void enqueueSnackbar(\n `Your plan includes ${quota.limit} locations — upgrade for more`,\n { variant: 'info', persist: false },\n )\n }\n try {\n // Creation rides the quota-enforcing resources API (AGL-473).\n await createHostResource({\n hostId,\n resource: 'location',\n data: {\n name: name.trim().slice(0, 80),\n ...(locations.length === 0 ? { isDefault: true } : {}),\n } satisfies CommerceModel.InventoryLocation,\n })\n setName('')\n setLocationCountEpoch((epoch) => epoch + 1)\n } catch (error: any) {\n enqueueSnackbar(error?.message ?? 'Could not add location', {\n variant: 'warning',\n persist: false,\n })\n }\n }, [\n name,\n planReady,\n quota,\n locations.length,\n hostId,\n createHostResource,\n enqueueSnackbar,\n ])\n\n const handleDelete = useCallback(\n (location: any) => async () => {\n const confirmed = await confirm({\n title: 'Remove this location?',\n description:\n `Stock bucketed under \"${location.name}\" folds back into the ` +\n 'flat totals; adjust counts afterward if needed.',\n confirmationText: 'Remove',\n confirmationButtonProps: { color: 'error' },\n })\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n await deleteDoc(doc(firestore, 'hosts', hostId, 'locations', location.$id))\n // A HARD delete, so the slot really is freed on both sides — the\n // one-shot aggregate has to be told (AGL-1716).\n setLocationCountEpoch((epoch) => epoch + 1)\n },\n [confirm, firestore, hostId],\n )\n\n const handleMakeDefault = useCallback(\n (location: any) => async () => {\n for (const other of locations) {\n if (other.isDefault && other.$id !== location.$id) {\n await setDoc(\n doc(firestore, 'hosts', hostId, 'locations', other.$id),\n { isDefault: false },\n { merge: true },\n )\n }\n }\n await setDoc(\n doc(firestore, 'hosts', hostId, 'locations', location.$id),\n { isDefault: true },\n { merge: true },\n )\n },\n [locations, firestore, hostId],\n )\n\n /*\n * A location's postal address (AGL-3612): where parcels can leave from,\n * for anything that ships. Optional, written beside the free-text name.\n */\n const [addressFor, setAddressFor] = useState<any | null>(null)\n const [addressDraft, setAddressDraft] = useState<CommerceModel.PostalAddress>({})\n const openAddress = useCallback(\n (location: any) => () => {\n setAddressFor(location)\n setAddressDraft(CommerceModel.normalizePostalAddress(location.postalAddress) ?? {})\n },\n [],\n )\n const handleSaveAddress = useCallback(async () => {\n if (!addressFor) return\n try {\n await setDoc(\n doc(firestore, 'hosts', hostId, 'locations', addressFor.$id),\n { postalAddress: CommerceModel.normalizePostalAddress(addressDraft) ?? {} },\n { merge: true },\n )\n setAddressFor(null)\n } catch (error: any) {\n enqueueSnackbar(error?.message ?? 'Could not save the address', {\n variant: 'warning',\n persist: false,\n })\n }\n }, [addressDraft, addressFor, enqueueSnackbar, firestore, hostId])\n const addressField = (field: keyof CommerceModel.PostalAddress, label: string) => (\n <TextField\n label={label}\n value={addressDraft[field] ?? ''}\n onChange={(event) =>\n setAddressDraft((prior) => ({\n ...prior,\n [field]: field === 'country' ? event.target.value.toUpperCase().slice(0, 2) : event.target.value,\n }))\n }\n size=\"small\"\n />\n )\n\n /*\n * Pickup at a location (AGL-3624): whether buyers may collect orders here,\n * its hours and what to do on arrival. Checkout offers every location that\n * has it on, and routes the order and its stock there.\n */\n const [pickupFor, setPickupFor] = useState<any | null>(null)\n const [pickupDraft, setPickupDraft] = useState<CommerceModel.PickupLocationSettings>({})\n const openPickup = useCallback(\n (location: any) => () => {\n setPickupFor(location)\n setPickupDraft(CommerceModel.normalizePickupSettings(location.pickup))\n },\n [],\n )\n const badHourLines = invalidOpeningHoursLines(pickupDraft.hours ?? '')\n const handleSavePickup = useCallback(async () => {\n if (!pickupFor) return\n try {\n // `updateDoc` replaces the map whole, so a cleared field is cleared;\n // a merge-set would deep-merge it back.\n await updateDoc(doc(firestore, 'hosts', hostId, 'locations', pickupFor.$id), {\n pickup: CommerceModel.normalizePickupSettings(pickupDraft),\n })\n setPickupFor(null)\n } catch (error: any) {\n enqueueSnackbar(error?.message ?? 'Could not save pickup', {\n variant: 'warning',\n persist: false,\n })\n }\n }, [enqueueSnackbar, firestore, hostId, pickupDraft, pickupFor])\n\n return (\n <CardDisplay\n header={'Inventory locations'}\n help={pluginDocsHelp('commerce', { anchor: '#inventory-locations' })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1}>\n {locations.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Track stock per warehouse or storefront. Without locations, ' +\n 'every variant has a single stock count.'}\n </Typography>\n ) : (\n locations.map((location: any) => (\n <Stack\n key={location.$id}\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center' }}\n >\n <Typography variant=\"body2\" sx={{ flex: 1 }} noWrap>\n {location.name}\n </Typography>\n {CommerceModel.normalizePickupSettings(location.pickup).enabled ? (\n <Chip label=\"Pickup\" size=\"small\" color=\"primary\" variant=\"outlined\" />\n ) : null}\n <Button size=\"small\" onClick={openPickup(location)}>\n {'Pickup'}\n </Button>\n <Button size=\"small\" onClick={openAddress(location)}>\n {location.postalAddress?.line1 ? 'Edit address' : 'Add address'}\n </Button>\n {location.isDefault ? (\n <Chip label=\"Default\" size=\"small\" variant=\"outlined\" />\n ) : (\n <Button size=\"small\" onClick={handleMakeDefault(location)}>\n {'Make default'}\n </Button>\n )}\n <Button size=\"small\" color=\"error\" onClick={handleDelete(location)}>\n {'Remove'}\n </Button>\n </Stack>\n ))\n )}\n <Stack direction=\"row\" spacing={1}>\n <TextField\n label=\"New location\"\n value={name}\n onChange={(event) => setName(event.target.value)}\n size=\"small\"\n sx={{ flex: 1 }}\n placeholder=\"Main warehouse\"\n />\n <Button\n size=\"small\"\n disabled={!name.trim() || !planReady}\n onClick={handleAdd}\n >\n {'Add'}\n </Button>\n </Stack>\n {/* The site's locations, not this card's rows (AGL-1716) — the\n readout and the gate must agree, and the gate now counts.\n The wording moved to the shared component in AGL-2113 so the five\n sibling cards that were missing it render the identical string\n rather than five near-misses; the output here is unchanged. */}\n <QuotaReadoutComponent\n ready={planReady}\n used={locationCount}\n limit={quota.limit}\n noun=\"location\"\n />\n </Stack>\n <Dialog open={Boolean(addressFor)} onClose={() => setAddressFor(null)} fullWidth maxWidth=\"sm\">\n <DialogTitle>{`Address of ${addressFor?.name ?? 'this location'}`}</DialogTitle>\n <DialogContent>\n <Stack spacing={1.5} sx={{ pt: 1 }}>\n {addressField('line1', 'Street address')}\n {addressField('line2', 'Apartment, suite (optional)')}\n <Stack direction={{ xs: 'column', sm: 'row' }} spacing={1.5}>\n {addressField('city', 'City')}\n {addressField('state', 'State or region')}\n {addressField('postalCode', 'Postal code')}\n {addressField('country', 'Country (two letters)')}\n </Stack>\n {addressField('phone', 'Phone')}\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setAddressFor(null)}>{'Cancel'}</Button>\n <Button variant=\"contained\" onClick={handleSaveAddress}>\n {'Save'}\n </Button>\n </DialogActions>\n </Dialog>\n <Dialog open={Boolean(pickupFor)} onClose={() => setPickupFor(null)} fullWidth maxWidth=\"sm\">\n <DialogTitle>{`Pickup at ${pickupFor?.name ?? 'this location'}`}</DialogTitle>\n <DialogContent>\n <Stack spacing={1.5} sx={{ pt: 1 }}>\n <FormControlLabel\n control={\n <Switch\n checked={Boolean(pickupDraft.enabled)}\n onChange={(event) =>\n setPickupDraft((prior) => ({ ...prior, enabled: event.target.checked }))\n }\n />\n }\n label=\"Buyers can pick up orders here\"\n />\n {pickupDraft.enabled && !pickupFor?.postalAddress?.line1 && !pickupFor?.address ? (\n <Typography variant=\"caption\" color=\"warning.main\">\n {'Add this location’s address so buyers know where to go.'}\n </Typography>\n ) : null}\n <TextField\n label=\"Pickup hours\"\n value={pickupDraft.hours ?? ''}\n onChange={(event) => setPickupDraft((prior) => ({ ...prior, hours: event.target.value }))}\n multiline\n minRows={2}\n size=\"small\"\n placeholder={'Mo-Fr 09:00-17:00\\nSa 10:00-14:00'}\n error={badHourLines.length > 0}\n helperText={\n badHourLines.length\n ? `Line ${badHourLines.join(', ')} does not read as days and times, like Mo-Fr 09:00-17:00.`\n : 'One line per set of days, like Mo-Fr 09:00-17:00.'\n }\n />\n <TextField\n label=\"Arrival instructions\"\n value={pickupDraft.instructions ?? ''}\n onChange={(event) =>\n setPickupDraft((prior) => ({ ...prior, instructions: event.target.value }))\n }\n multiline\n minRows={2}\n size=\"small\"\n placeholder=\"Ring the bell at the side door and give your order number.\"\n slotProps={{ htmlInput: { maxLength: CommerceModel.PICKUP_INSTRUCTIONS_MAX } }}\n />\n <TextField\n select\n label=\"Usually ready in\"\n value={String(pickupDraft.readyWithinMinutes ?? '')}\n onChange={(event) =>\n setPickupDraft((prior) => ({\n ...prior,\n readyWithinMinutes: event.target.value ? Number(event.target.value) : undefined,\n }))\n }\n size=\"small\"\n >\n <MenuItem value=\"\">{'Don’t say'}</MenuItem>\n {[\n [30, '30 minutes'],\n [60, '1 hour'],\n [120, '2 hours'],\n [240, '4 hours'],\n [1440, '1 day'],\n [2880, '2 days'],\n ].map(([minutes, label]) => (\n <MenuItem key={minutes} value={String(minutes)}>\n {label}\n </MenuItem>\n ))}\n </TextField>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Buyers choose this location at checkout. The order’s stock comes off this location, ' +\n 'and the buyer is emailed when you mark the order ready.'}\n </Typography>\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setPickupFor(null)}>{'Cancel'}</Button>\n <Button variant=\"contained\" disabled={badHourLines.length > 0} onClick={handleSavePickup}>\n {'Save'}\n </Button>\n </DialogActions>\n </Dialog>\n </CardDisplay>\n )\n}\nLocationsCard.displayName = 'LocationsCard'\n\nexport default LocationsCard\n"],"names":["Aglyn","CommerceModel","CardDisplay","useConfirmationContext","QuotaReadoutComponent","useSnackbar","Button","Chip","Dialog","DialogActions","DialogContent","DialogTitle","FormControlLabel","MenuItem","Stack","Switch","TextField","Typography","invalidOpeningHoursLines","collection","deleteDoc","doc","getCountFromServer","limit","query","setDoc","updateDoc","useCallback","useEffect","useState","useFirestore","useFirestoreCollection","useHostResourceApi","useOrgPlan","pluginDocsHelp","LocationsCard","props","pickupDraft","pickupFor","hostId","firestore","enqueueSnackbar","confirm","createHostResource","org","ready","planReady","data","locationDocs","idField","locations","sort","a","b","String","name","localeCompare","locationCountEpoch","setLocationCountEpoch","serverLocationCount","setServerLocationCount","active","then","snapshot","count","catch","locationCount","length","quota","checkQuota","setName","handleAdd","trim","allowed","variant","persist","resource","slice","isDefault","epoch","error","message","handleDelete","location","confirmed","title","description","confirmationText","confirmationButtonProps","color","$id","handleMakeDefault","other","merge","addressFor","setAddressFor","addressDraft","setAddressDraft","openAddress","normalizePostalAddress","postalAddress","handleSaveAddress","addressField","field","label","value","onChange","event","prior","target","toUpperCase","size","setPickupFor","setPickupDraft","openPickup","normalizePickupSettings","pickup","badHourLines","hours","handleSavePickup","header","help","anchor","contentGutterX","contentGutterY","spacing","map","direction","sx","alignItems","flex","noWrap","enabled","onClick","line1","placeholder","disabled","used","noun","open","Boolean","onClose","fullWidth","maxWidth","pt","xs","sm","control","checked","address","multiline","minRows","helperText","join","instructions","slotProps","htmlInput","maxLength","PICKUP_INSTRUCTIONS_MAX","select","readyWithinMinutes","Number","undefined","minutes","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/locations-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 QuotaReadoutComponent from '@aglyn/shared-ui-jsx/components/quota-readout.component'\nimport { OpeningHoursPicker } from '@aglyn/shared-ui-jsx/components/weekly-hours-picker.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Button,\n Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n FormControlLabel,\n MenuItem,\n Stack,\n Switch,\n TextField,\n Typography,\n} from '@mui/material'\nimport { invalidOpeningHoursLines } from '@aglyn/aglyn/app-utils/local-business'\nimport {\n collection,\n deleteDoc,\n doc,\n getCountFromServer,\n limit,\n query,\n setDoc,\n updateDoc,\n} from 'firebase/firestore'\nimport { useCallback, useEffect, useState } from 'react'\nimport { useFirestore } from '@aglyn/tenant-feature-instance'\nimport { useFirestoreCollection } from '@aglyn/tenant-feature-instance'\nimport { useHostResourceApi } from '@aglyn/tenant-feature-instance'\nimport { useOrgPlan } from '@aglyn/tenant-feature-instance'\nimport { pluginDocsHelp } from '@aglyn/aglyn'\n\nexport interface LocationsCardProps {\n hostId: string\n}\n\n/**\n * Inventory locations (AGL-286): named stock buckets under\n * `hosts/{hostId}/locations`, capped by the plan's `inventoryLocations`\n * quota (AGL-278). Variant stock splits per location in the products\n * hub; POS registers sell from their location (AGL-312).\n */\nexport function LocationsCard(props: LocationsCardProps) {\n const { hostId } = props\n const firestore = useFirestore()\n const { enqueueSnackbar } = useSnackbar()\n const { confirm } = useConfirmationContext()\n const createHostResource = useHostResourceApi()\n const { org, ready: planReady } = useOrgPlan(hostId)\n const { data: locationDocs } = useFirestoreCollection<any>(\n () => query(collection(firestore, 'hosts', hostId, 'locations'), limit(25)),\n [firestore, hostId],\n { idField: '$id' },\n )\n const locations = [...(locationDocs ?? [])].sort((a: any, b: any) =>\n String(a.name ?? '').localeCompare(String(b.name ?? '')),\n )\n /**\n * The location HEAD-COUNT is a server aggregate, not the length of the\n * capped listener (AGL-1716, the AGL-1706 shape).\n *\n * The smallest margin of the eight, and real for exactly one plan: the\n * listener is `limit(25)` and `inventoryLocations` runs 1 / 1 / 2 / 4 / 6 /\n * 10 / 50, so only Agency's 50 sits above the window. An agency with more\n * than 25 stock buckets read its cap as satisfiable, and\n * `api/hosts/resources` — which counts the collection — refused the add.\n *\n * Re-read after an add AND a delete: locations are hard-deleted here, so\n * unlike the soft-deleting cards a removal really does free a slot on both\n * sides, and a one-shot that missed it would keep refusing.\n *\n * `locations.length === 0` below still decides the FIRST location's\n * `isDefault` flag off the loaded rows, which is the right question for it\n * — that one is about what this card can see, not about the site's total.\n */\n const [locationCountEpoch, setLocationCountEpoch] = useState(0)\n const [serverLocationCount, setServerLocationCount] = useState<number | null>(\n null,\n )\n useEffect(() => {\n let active = true\n void getCountFromServer(collection(firestore, 'hosts', hostId, 'locations'))\n .then((snapshot) => {\n if (active) setServerLocationCount(snapshot.data().count)\n })\n .catch(() => {\n // Falls back to the loaded rows — a LOWER bound and the prior\n // behaviour, never 0, which reads as \"no locations used\" on a site\n // that is over its band.\n })\n return () => {\n active = false\n }\n }, [firestore, hostId, locationCountEpoch])\n const locationCount = serverLocationCount ?? locations.length\n const quota = Aglyn.checkQuota(org, 'inventoryLocations', locationCount)\n const [name, setName] = useState('')\n\n const handleAdd = useCallback(async () => {\n if (!name.trim()) return\n // Held until the org doc lands (AGL-1064): an absent org resolves to\n // the free tier's `inventoryLocations: 1`, so a site that already has\n // its default location would be refused with an upgrade prompt it does\n // not need. The button is disabled too; this guards the race.\n if (!planReady) return\n if (!quota.allowed) {\n return void enqueueSnackbar(\n `Your plan includes ${quota.limit} locations — upgrade for more`,\n { variant: 'info', persist: false },\n )\n }\n try {\n // Creation rides the quota-enforcing resources API (AGL-473).\n await createHostResource({\n hostId,\n resource: 'location',\n data: {\n name: name.trim().slice(0, 80),\n ...(locations.length === 0 ? { isDefault: true } : {}),\n } satisfies CommerceModel.InventoryLocation,\n })\n setName('')\n setLocationCountEpoch((epoch) => epoch + 1)\n } catch (error: any) {\n enqueueSnackbar(error?.message ?? 'Could not add location', {\n variant: 'warning',\n persist: false,\n })\n }\n }, [\n name,\n planReady,\n quota,\n locations.length,\n hostId,\n createHostResource,\n enqueueSnackbar,\n ])\n\n const handleDelete = useCallback(\n (location: any) => async () => {\n const confirmed = await confirm({\n title: 'Remove this location?',\n description:\n `Stock bucketed under \"${location.name}\" folds back into the ` +\n 'flat totals; adjust counts afterward if needed.',\n confirmationText: 'Remove',\n confirmationButtonProps: { color: 'error' },\n })\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n await deleteDoc(doc(firestore, 'hosts', hostId, 'locations', location.$id))\n // A HARD delete, so the slot really is freed on both sides — the\n // one-shot aggregate has to be told (AGL-1716).\n setLocationCountEpoch((epoch) => epoch + 1)\n },\n [confirm, firestore, hostId],\n )\n\n const handleMakeDefault = useCallback(\n (location: any) => async () => {\n for (const other of locations) {\n if (other.isDefault && other.$id !== location.$id) {\n await setDoc(\n doc(firestore, 'hosts', hostId, 'locations', other.$id),\n { isDefault: false },\n { merge: true },\n )\n }\n }\n await setDoc(\n doc(firestore, 'hosts', hostId, 'locations', location.$id),\n { isDefault: true },\n { merge: true },\n )\n },\n [locations, firestore, hostId],\n )\n\n /*\n * A location's postal address (AGL-3612): where parcels can leave from,\n * for anything that ships. Optional, written beside the free-text name.\n */\n const [addressFor, setAddressFor] = useState<any | null>(null)\n const [addressDraft, setAddressDraft] = useState<CommerceModel.PostalAddress>({})\n const openAddress = useCallback(\n (location: any) => () => {\n setAddressFor(location)\n setAddressDraft(CommerceModel.normalizePostalAddress(location.postalAddress) ?? {})\n },\n [],\n )\n const handleSaveAddress = useCallback(async () => {\n if (!addressFor) return\n try {\n await setDoc(\n doc(firestore, 'hosts', hostId, 'locations', addressFor.$id),\n { postalAddress: CommerceModel.normalizePostalAddress(addressDraft) ?? {} },\n { merge: true },\n )\n setAddressFor(null)\n } catch (error: any) {\n enqueueSnackbar(error?.message ?? 'Could not save the address', {\n variant: 'warning',\n persist: false,\n })\n }\n }, [addressDraft, addressFor, enqueueSnackbar, firestore, hostId])\n const addressField = (field: keyof CommerceModel.PostalAddress, label: string) => (\n <TextField\n label={label}\n value={addressDraft[field] ?? ''}\n onChange={(event) =>\n setAddressDraft((prior) => ({\n ...prior,\n [field]: field === 'country' ? event.target.value.toUpperCase().slice(0, 2) : event.target.value,\n }))\n }\n size=\"small\"\n />\n )\n\n /*\n * Pickup at a location (AGL-3624): whether buyers may collect orders here,\n * its hours and what to do on arrival. Checkout offers every location that\n * has it on, and routes the order and its stock there.\n */\n const [pickupFor, setPickupFor] = useState<any | null>(null)\n const [pickupDraft, setPickupDraft] = useState<CommerceModel.PickupLocationSettings>({})\n const openPickup = useCallback(\n (location: any) => () => {\n setPickupFor(location)\n setPickupDraft(CommerceModel.normalizePickupSettings(location.pickup))\n },\n [],\n )\n const badHourLines = invalidOpeningHoursLines(pickupDraft.hours ?? '')\n const handleSavePickup = useCallback(async () => {\n if (!pickupFor) return\n try {\n // `updateDoc` replaces the map whole, so a cleared field is cleared;\n // a merge-set would deep-merge it back.\n await updateDoc(doc(firestore, 'hosts', hostId, 'locations', pickupFor.$id), {\n pickup: CommerceModel.normalizePickupSettings(pickupDraft),\n })\n setPickupFor(null)\n } catch (error: any) {\n enqueueSnackbar(error?.message ?? 'Could not save pickup', {\n variant: 'warning',\n persist: false,\n })\n }\n }, [enqueueSnackbar, firestore, hostId, pickupDraft, pickupFor])\n\n return (\n <CardDisplay\n header={'Inventory locations'}\n help={pluginDocsHelp('commerce', { anchor: '#inventory-locations' })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1}>\n {locations.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Track stock per warehouse or storefront. Without locations, ' +\n 'every variant has a single stock count.'}\n </Typography>\n ) : (\n locations.map((location: any) => (\n <Stack\n key={location.$id}\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center' }}\n >\n <Typography variant=\"body2\" sx={{ flex: 1 }} noWrap>\n {location.name}\n </Typography>\n {CommerceModel.normalizePickupSettings(location.pickup).enabled ? (\n <Chip label=\"Pickup\" size=\"small\" color=\"primary\" variant=\"outlined\" />\n ) : null}\n <Button size=\"small\" onClick={openPickup(location)}>\n {'Pickup'}\n </Button>\n <Button size=\"small\" onClick={openAddress(location)}>\n {location.postalAddress?.line1 ? 'Edit address' : 'Add address'}\n </Button>\n {location.isDefault ? (\n <Chip label=\"Default\" size=\"small\" variant=\"outlined\" />\n ) : (\n <Button size=\"small\" onClick={handleMakeDefault(location)}>\n {'Make default'}\n </Button>\n )}\n <Button size=\"small\" color=\"error\" onClick={handleDelete(location)}>\n {'Remove'}\n </Button>\n </Stack>\n ))\n )}\n <Stack direction=\"row\" spacing={1}>\n <TextField\n label=\"New location\"\n value={name}\n onChange={(event) => setName(event.target.value)}\n size=\"small\"\n sx={{ flex: 1 }}\n placeholder=\"Main warehouse\"\n />\n <Button\n size=\"small\"\n disabled={!name.trim() || !planReady}\n onClick={handleAdd}\n >\n {'Add'}\n </Button>\n </Stack>\n {/* The site's locations, not this card's rows (AGL-1716) — the\n readout and the gate must agree, and the gate now counts.\n The wording moved to the shared component in AGL-2113 so the five\n sibling cards that were missing it render the identical string\n rather than five near-misses; the output here is unchanged. */}\n <QuotaReadoutComponent\n ready={planReady}\n used={locationCount}\n limit={quota.limit}\n noun=\"location\"\n />\n </Stack>\n <Dialog open={Boolean(addressFor)} onClose={() => setAddressFor(null)} fullWidth maxWidth=\"sm\">\n <DialogTitle>{`Address of ${addressFor?.name ?? 'this location'}`}</DialogTitle>\n <DialogContent>\n <Stack spacing={1.5} sx={{ pt: 1 }}>\n {addressField('line1', 'Street address')}\n {addressField('line2', 'Apartment, suite (optional)')}\n <Stack direction={{ xs: 'column', sm: 'row' }} spacing={1.5}>\n {addressField('city', 'City')}\n {addressField('state', 'State or region')}\n {addressField('postalCode', 'Postal code')}\n {addressField('country', 'Country (two letters)')}\n </Stack>\n {addressField('phone', 'Phone')}\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setAddressFor(null)}>{'Cancel'}</Button>\n <Button variant=\"contained\" onClick={handleSaveAddress}>\n {'Save'}\n </Button>\n </DialogActions>\n </Dialog>\n <Dialog open={Boolean(pickupFor)} onClose={() => setPickupFor(null)} fullWidth maxWidth=\"sm\">\n <DialogTitle>{`Pickup at ${pickupFor?.name ?? 'this location'}`}</DialogTitle>\n <DialogContent>\n <Stack spacing={1.5} sx={{ pt: 1 }}>\n <FormControlLabel\n control={\n <Switch\n checked={Boolean(pickupDraft.enabled)}\n onChange={(event) =>\n setPickupDraft((prior) => ({ ...prior, enabled: event.target.checked }))\n }\n />\n }\n label=\"Buyers can pick up orders here\"\n />\n {pickupDraft.enabled && !pickupFor?.postalAddress?.line1 && !pickupFor?.address ? (\n <Typography variant=\"caption\" color=\"warning.main\">\n {'Add this location’s address so buyers know where to go.'}\n </Typography>\n ) : null}\n <OpeningHoursPicker\n label=\"Pickup hours\"\n value={pickupDraft.hours ?? ''}\n onChange={(hours) => setPickupDraft((prior) => ({ ...prior, hours }))}\n helperText=\"Switch on each day buyers can collect, then pick the times.\"\n />\n <TextField\n label=\"Arrival instructions\"\n value={pickupDraft.instructions ?? ''}\n onChange={(event) =>\n setPickupDraft((prior) => ({ ...prior, instructions: event.target.value }))\n }\n multiline\n minRows={2}\n size=\"small\"\n placeholder=\"Ring the bell at the side door and give your order number.\"\n slotProps={{ htmlInput: { maxLength: CommerceModel.PICKUP_INSTRUCTIONS_MAX } }}\n />\n <TextField\n select\n label=\"Usually ready in\"\n value={String(pickupDraft.readyWithinMinutes ?? '')}\n onChange={(event) =>\n setPickupDraft((prior) => ({\n ...prior,\n readyWithinMinutes: event.target.value ? Number(event.target.value) : undefined,\n }))\n }\n size=\"small\"\n >\n <MenuItem value=\"\">{'Don’t say'}</MenuItem>\n {[\n [30, '30 minutes'],\n [60, '1 hour'],\n [120, '2 hours'],\n [240, '4 hours'],\n [1440, '1 day'],\n [2880, '2 days'],\n ].map(([minutes, label]) => (\n <MenuItem key={minutes} value={String(minutes)}>\n {label}\n </MenuItem>\n ))}\n </TextField>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Buyers choose this location at checkout. The order’s stock comes off this location, ' +\n 'and the buyer is emailed when you mark the order ready.'}\n </Typography>\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setPickupFor(null)}>{'Cancel'}</Button>\n <Button variant=\"contained\" disabled={badHourLines.length > 0} onClick={handleSavePickup}>\n {'Save'}\n </Button>\n </DialogActions>\n </Dialog>\n </CardDisplay>\n )\n}\nLocationsCard.displayName = 'LocationsCard'\n\nexport default LocationsCard\n"],"names":["Aglyn","CommerceModel","CardDisplay","useConfirmationContext","QuotaReadoutComponent","OpeningHoursPicker","useSnackbar","Button","Chip","Dialog","DialogActions","DialogContent","DialogTitle","FormControlLabel","MenuItem","Stack","Switch","TextField","Typography","invalidOpeningHoursLines","collection","deleteDoc","doc","getCountFromServer","limit","query","setDoc","updateDoc","useCallback","useEffect","useState","useFirestore","useFirestoreCollection","useHostResourceApi","useOrgPlan","pluginDocsHelp","LocationsCard","props","pickupDraft","pickupFor","hostId","firestore","enqueueSnackbar","confirm","createHostResource","org","ready","planReady","data","locationDocs","idField","locations","sort","a","b","String","name","localeCompare","locationCountEpoch","setLocationCountEpoch","serverLocationCount","setServerLocationCount","active","then","snapshot","count","catch","locationCount","length","quota","checkQuota","setName","handleAdd","trim","allowed","variant","persist","resource","slice","isDefault","epoch","error","message","handleDelete","location","confirmed","title","description","confirmationText","confirmationButtonProps","color","$id","handleMakeDefault","other","merge","addressFor","setAddressFor","addressDraft","setAddressDraft","openAddress","normalizePostalAddress","postalAddress","handleSaveAddress","addressField","field","label","value","onChange","event","prior","target","toUpperCase","size","setPickupFor","setPickupDraft","openPickup","normalizePickupSettings","pickup","badHourLines","hours","handleSavePickup","header","help","anchor","contentGutterX","contentGutterY","spacing","map","direction","sx","alignItems","flex","noWrap","enabled","onClick","line1","placeholder","disabled","used","noun","open","Boolean","onClose","fullWidth","maxWidth","pt","xs","sm","control","checked","address","helperText","instructions","multiline","minRows","slotProps","htmlInput","maxLength","PICKUP_INSTRUCTIONS_MAX","select","readyWithinMinutes","Number","undefined","minutes","displayName"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;;AAEA,YAAYA,WAAW,eAAc;AACrC,YAAYC,mBAAmB,uBAAa;AAC5C,SAASC,WAAW,EAAEC,sBAAsB,QAAQ,uBAAsB;AAC1E,OAAOC,2BAA2B,0DAAyD;AAC3F,SAASC,kBAAkB,QAAQ,gEAA+D;AAClG,SAASC,WAAW,QAAQ,8BAA6B;AACzD,SACEC,MAAM,EACNC,IAAI,EACJC,MAAM,EACNC,aAAa,EACbC,aAAa,EACbC,WAAW,EACXC,gBAAgB,EAChBC,QAAQ,EACRC,KAAK,EACLC,MAAM,EACNC,SAAS,EACTC,UAAU,QACL,gBAAe;AACtB,SAASC,wBAAwB,QAAQ,wCAAuC;AAChF,SACEC,UAAU,EACVC,SAAS,EACTC,GAAG,EACHC,kBAAkB,EAClBC,KAAK,EACLC,KAAK,EACLC,MAAM,EACNC,SAAS,QACJ,qBAAoB;AAC3B,SAASC,WAAW,EAAEC,SAAS,EAAEC,QAAQ,QAAQ,QAAO;AACxD,SAASC,YAAY,QAAQ,iCAAgC;AAC7D,SAASC,sBAAsB,QAAQ,iCAAgC;AACvE,SAASC,kBAAkB,QAAQ,iCAAgC;AACnE,SAASC,UAAU,QAAQ,iCAAgC;AAC3D,SAASC,cAAc,QAAQ,eAAc;AAM7C;;;;;CAKC,GACD,OAAO,SAASC,cAAcC,KAAyB;QAmMPC,iCAyI3BA,qBAMAA,2BAaOA;QA1BSC;IApUnC,MAAM,EAAEC,MAAM,EAAE,GAAGH;IACnB,MAAMI,YAAYV;IAClB,MAAM,EAAEW,eAAe,EAAE,GAAGpC;IAC5B,MAAM,EAAEqC,OAAO,EAAE,GAAGxC;IACpB,MAAMyC,qBAAqBX;IAC3B,MAAM,EAAEY,GAAG,EAAEC,OAAOC,SAAS,EAAE,GAAGb,WAAWM;IAC7C,MAAM,EAAEQ,MAAMC,YAAY,EAAE,GAAGjB,uBAC7B,IAAMP,MAAML,WAAWqB,WAAW,SAASD,QAAQ,cAAchB,MAAM,MACvE;QAACiB;QAAWD;KAAO,EACnB;QAAEU,SAAS;IAAM;IAEnB,MAAMC,YAAY;WAAKF,uBAAAA,eAAgB,EAAE;KAAE,CAACG,IAAI,CAAC,CAACC,GAAQC;YACjDD,SAAmCC;eAA1CC,QAAOF,UAAAA,EAAEG,IAAI,YAANH,UAAU,IAAII,aAAa,CAACF,QAAOD,UAAAA,EAAEE,IAAI,YAANF,UAAU;;IAEtD;;;;;;;;;;;;;;;;;GAiBC,GACD,MAAM,CAACI,oBAAoBC,sBAAsB,GAAG7B,SAAS;IAC7D,MAAM,CAAC8B,qBAAqBC,uBAAuB,GAAG/B,SACpD;IAEFD,UAAU;QACR,IAAIiC,SAAS;QACb,KAAKvC,mBAAmBH,WAAWqB,WAAW,SAASD,QAAQ,cAC5DuB,IAAI,CAAC,CAACC;YACL,IAAIF,QAAQD,uBAAuBG,SAAShB,IAAI,GAAGiB,KAAK;QAC1D,GACCC,KAAK,CAAC;QACL,8DAA8D;QAC9D,mEAAmE;QACnE,yBAAyB;QAC3B;QACF,OAAO;YACLJ,SAAS;QACX;IACF,GAAG;QAACrB;QAAWD;QAAQkB;KAAmB;IAC1C,MAAMS,gBAAgBP,8BAAAA,sBAAuBT,UAAUiB,MAAM;IAC7D,MAAMC,QAAQrE,MAAMsE,UAAU,CAACzB,KAAK,sBAAsBsB;IAC1D,MAAM,CAACX,MAAMe,QAAQ,GAAGzC,SAAS;IAEjC,MAAM0C,YAAY5C,YAAY;QAC5B,IAAI,CAAC4B,KAAKiB,IAAI,IAAI;QAClB,qEAAqE;QACrE,sEAAsE;QACtE,uEAAuE;QACvE,8DAA8D;QAC9D,IAAI,CAAC1B,WAAW;QAChB,IAAI,CAACsB,MAAMK,OAAO,EAAE;YAClB,OAAO,KAAKhC,gBACV,CAAC,mBAAmB,EAAE2B,MAAM7C,KAAK,CAAC,6BAA6B,CAAC,EAChE;gBAAEmD,SAAS;gBAAQC,SAAS;YAAM;QAEtC;QACA,IAAI;YACF,8DAA8D;YAC9D,MAAMhC,mBAAmB;gBACvBJ;gBACAqC,UAAU;gBACV7B,MAAM;oBACJQ,MAAMA,KAAKiB,IAAI,GAAGK,KAAK,CAAC,GAAG;mBACvB3B,UAAUiB,MAAM,KAAK,IAAI;oBAAEW,WAAW;gBAAK,IAAI,CAAC;YAExD;YACAR,QAAQ;YACRZ,sBAAsB,CAACqB,QAAUA,QAAQ;QAC3C,EAAE,OAAOC,OAAY;;YACnBvC,wBAAgBuC,yBAAAA,MAAOC,OAAO,mBAAI,0BAA0B;gBAC1DP,SAAS;gBACTC,SAAS;YACX;QACF;IACF,GAAG;QACDpB;QACAT;QACAsB;QACAlB,UAAUiB,MAAM;QAChB5B;QACAI;QACAF;KACD;IAED,MAAMyC,eAAevD,YACnB,CAACwD,WAAkB;YACjB,MAAMC,YAAY,MAAM1C,QAAQ;gBAC9B2C,OAAO;gBACPC,aACE,CAAC,sBAAsB,EAAEH,SAAS5B,IAAI,CAAC,sBAAsB,CAAC,GAC9D;gBACFgC,kBAAkB;gBAClBC,yBAAyB;oBAAEC,OAAO;gBAAQ;YAC5C,GACG3B,IAAI,CAAC,IAAM,MACXG,KAAK,CAAC,IAAM;YACf,IAAI,CAACmB,WAAW;YAChB,MAAMhE,UAAUC,IAAImB,WAAW,SAASD,QAAQ,aAAa4C,SAASO,GAAG;YACzE,iEAAiE;YACjE,gDAAgD;YAChDhC,sBAAsB,CAACqB,QAAUA,QAAQ;QAC3C,GACA;QAACrC;QAASF;QAAWD;KAAO;IAG9B,MAAMoD,oBAAoBhE,YACxB,CAACwD,WAAkB;YACjB,KAAK,MAAMS,SAAS1C,UAAW;gBAC7B,IAAI0C,MAAMd,SAAS,IAAIc,MAAMF,GAAG,KAAKP,SAASO,GAAG,EAAE;oBACjD,MAAMjE,OACJJ,IAAImB,WAAW,SAASD,QAAQ,aAAaqD,MAAMF,GAAG,GACtD;wBAAEZ,WAAW;oBAAM,GACnB;wBAAEe,OAAO;oBAAK;gBAElB;YACF;YACA,MAAMpE,OACJJ,IAAImB,WAAW,SAASD,QAAQ,aAAa4C,SAASO,GAAG,GACzD;gBAAEZ,WAAW;YAAK,GAClB;gBAAEe,OAAO;YAAK;QAElB,GACA;QAAC3C;QAAWV;QAAWD;KAAO;IAGhC;;;GAGC,GACD,MAAM,CAACuD,YAAYC,cAAc,GAAGlE,SAAqB;IACzD,MAAM,CAACmE,cAAcC,gBAAgB,GAAGpE,SAAsC,CAAC;IAC/E,MAAMqE,cAAcvE,YAClB,CAACwD,WAAkB;gBAEDnF;YADhB+F,cAAcZ;YACdc,iBAAgBjG,wCAAAA,cAAcmG,sBAAsB,CAAChB,SAASiB,aAAa,aAA3DpG,wCAAgE,CAAC;QACnF,GACA,EAAE;IAEJ,MAAMqG,oBAAoB1E,YAAY;QACpC,IAAI,CAACmE,YAAY;QACjB,IAAI;gBAGiB9F;YAFnB,MAAMyB,OACJJ,IAAImB,WAAW,SAASD,QAAQ,aAAauD,WAAWJ,GAAG,GAC3D;gBAAEU,aAAa,GAAEpG,wCAAAA,cAAcmG,sBAAsB,CAACH,yBAArChG,wCAAsD,CAAC;YAAE,GAC1E;gBAAE6F,OAAO;YAAK;YAEhBE,cAAc;QAChB,EAAE,OAAOf,OAAY;;YACnBvC,wBAAgBuC,yBAAAA,MAAOC,OAAO,mBAAI,8BAA8B;gBAC9DP,SAAS;gBACTC,SAAS;YACX;QACF;IACF,GAAG;QAACqB;QAAcF;QAAYrD;QAAiBD;QAAWD;KAAO;IACjE,MAAM+D,eAAe,CAACC,OAA0CC;YAGrDR;6BAFT,KAAChF;YACCwF,OAAOA;YACPC,KAAK,GAAET,sBAAAA,YAAY,CAACO,MAAM,YAAnBP,sBAAuB;YAC9BU,UAAU,CAACC,QACTV,gBAAgB,CAACW,QAAW,aACvBA;wBACH,CAACL,MAAM,EAAEA,UAAU,YAAYI,MAAME,MAAM,CAACJ,KAAK,CAACK,WAAW,GAAGjC,KAAK,CAAC,GAAG,KAAK8B,MAAME,MAAM,CAACJ,KAAK;;YAGpGM,MAAK;;;IAIT;;;;GAIC,GACD,MAAM,CAACzE,WAAW0E,aAAa,GAAGnF,SAAqB;IACvD,MAAM,CAACQ,aAAa4E,eAAe,GAAGpF,SAA+C,CAAC;IACtF,MAAMqF,aAAavF,YACjB,CAACwD,WAAkB;YACjB6B,aAAa7B;YACb8B,eAAejH,cAAcmH,uBAAuB,CAAChC,SAASiC,MAAM;QACtE,GACA,EAAE;IAEJ,MAAMC,eAAenG,0BAAyBmB,qBAAAA,YAAYiF,KAAK,YAAjBjF,qBAAqB;IACnE,MAAMkF,mBAAmB5F,YAAY;QACnC,IAAI,CAACW,WAAW;QAChB,IAAI;YACF,qEAAqE;YACrE,wCAAwC;YACxC,MAAMZ,UAAUL,IAAImB,WAAW,SAASD,QAAQ,aAAaD,UAAUoD,GAAG,GAAG;gBAC3E0B,QAAQpH,cAAcmH,uBAAuB,CAAC9E;YAChD;YACA2E,aAAa;QACf,EAAE,OAAOhC,OAAY;;YACnBvC,wBAAgBuC,yBAAAA,MAAOC,OAAO,mBAAI,yBAAyB;gBACzDP,SAAS;gBACTC,SAAS;YACX;QACF;IACF,GAAG;QAAClC;QAAiBD;QAAWD;QAAQF;QAAaC;KAAU;IAE/D,qBACE,MAACrC;QACCuH,QAAQ;QACRC,MAAMvF,eAAe,YAAY;YAAEwF,QAAQ;QAAuB;QAClEC,cAAc;QACdC,cAAc;;0BAEd,MAAC9G;gBAAM+G,SAAS;;oBACb3E,UAAUiB,MAAM,KAAK,kBACpB,KAAClD;wBAAWyD,SAAQ;wBAAQe,OAAM;kCAC/B,iEACC;yBAGJvC,UAAU4E,GAAG,CAAC,CAAC3C;4BAiBRA;6CAhBL,MAACrE;4BAECiH,WAAU;4BACVF,SAAS;4BACTG,IAAI;gCAAEC,YAAY;4BAAS;;8CAE3B,KAAChH;oCAAWyD,SAAQ;oCAAQsD,IAAI;wCAAEE,MAAM;oCAAE;oCAAGC,MAAM;8CAChDhD,SAAS5B,IAAI;;gCAEfvD,cAAcmH,uBAAuB,CAAChC,SAASiC,MAAM,EAAEgB,OAAO,iBAC7D,KAAC7H;oCAAKiG,OAAM;oCAASO,MAAK;oCAAQtB,OAAM;oCAAUf,SAAQ;qCACxD;8CACJ,KAACpE;oCAAOyG,MAAK;oCAAQsB,SAASnB,WAAW/B;8CACtC;;8CAEH,KAAC7E;oCAAOyG,MAAK;oCAAQsB,SAASnC,YAAYf;8CACvCA,EAAAA,0BAAAA,SAASiB,aAAa,qBAAtBjB,wBAAwBmD,KAAK,IAAG,iBAAiB;;gCAEnDnD,SAASL,SAAS,iBACjB,KAACvE;oCAAKiG,OAAM;oCAAUO,MAAK;oCAAQrC,SAAQ;mDAE3C,KAACpE;oCAAOyG,MAAK;oCAAQsB,SAAS1C,kBAAkBR;8CAC7C;;8CAGL,KAAC7E;oCAAOyG,MAAK;oCAAQtB,OAAM;oCAAQ4C,SAASnD,aAAaC;8CACtD;;;2BAzBEA,SAASO,GAAG;;kCA8BvB,MAAC5E;wBAAMiH,WAAU;wBAAMF,SAAS;;0CAC9B,KAAC7G;gCACCwF,OAAM;gCACNC,OAAOlD;gCACPmD,UAAU,CAACC,QAAUrC,QAAQqC,MAAME,MAAM,CAACJ,KAAK;gCAC/CM,MAAK;gCACLiB,IAAI;oCAAEE,MAAM;gCAAE;gCACdK,aAAY;;0CAEd,KAACjI;gCACCyG,MAAK;gCACLyB,UAAU,CAACjF,KAAKiB,IAAI,MAAM,CAAC1B;gCAC3BuF,SAAS9D;0CAER;;;;kCAQL,KAACpE;wBACC0C,OAAOC;wBACP2F,MAAMvE;wBACN3C,OAAO6C,MAAM7C,KAAK;wBAClBmH,MAAK;;;;0BAGT,MAAClI;gBAAOmI,MAAMC,QAAQ9C;gBAAa+C,SAAS,IAAM9C,cAAc;gBAAO+C,SAAS;gBAACC,UAAS;;kCACxF,KAACpI;kCAAa,CAAC,WAAW,UAAEmF,8BAAAA,WAAYvC,IAAI,mBAAI,iBAAiB;;kCACjE,KAAC7C;kCACC,cAAA,MAACI;4BAAM+G,SAAS;4BAAKG,IAAI;gCAAEgB,IAAI;4BAAE;;gCAC9B1C,aAAa,SAAS;gCACtBA,aAAa,SAAS;8CACvB,MAACxF;oCAAMiH,WAAW;wCAAEkB,IAAI;wCAAUC,IAAI;oCAAM;oCAAGrB,SAAS;;wCACrDvB,aAAa,QAAQ;wCACrBA,aAAa,SAAS;wCACtBA,aAAa,cAAc;wCAC3BA,aAAa,WAAW;;;gCAE1BA,aAAa,SAAS;;;;kCAG3B,MAAC7F;;0CACC,KAACH;gCAAO+H,SAAS,IAAMtC,cAAc;0CAAQ;;0CAC7C,KAACzF;gCAAOoE,SAAQ;gCAAY2D,SAAShC;0CAClC;;;;;;0BAIP,MAAC7F;gBAAOmI,MAAMC,QAAQtG;gBAAYuG,SAAS,IAAM7B,aAAa;gBAAO8B,SAAS;gBAACC,UAAS;;kCACtF,KAACpI;kCAAa,CAAC,UAAU,WAAE2B,6BAAAA,UAAWiB,IAAI,oBAAI,iBAAiB;;kCAC/D,KAAC7C;kCACC,cAAA,MAACI;4BAAM+G,SAAS;4BAAKG,IAAI;gCAAEgB,IAAI;4BAAE;;8CAC/B,KAACpI;oCACCuI,uBACE,KAACpI;wCACCqI,SAASR,QAAQvG,YAAY+F,OAAO;wCACpC1B,UAAU,CAACC,QACTM,eAAe,CAACL,QAAW,aAAKA;oDAAOwB,SAASzB,MAAME,MAAM,CAACuC,OAAO;;;oCAI1E5C,OAAM;;gCAEPnE,YAAY+F,OAAO,IAAI,EAAC9F,8BAAAA,2BAAAA,UAAW8D,aAAa,qBAAxB9D,yBAA0BgG,KAAK,KAAI,EAAChG,6BAAAA,UAAW+G,OAAO,kBAC7E,KAACpI;oCAAWyD,SAAQ;oCAAUe,OAAM;8CACjC;qCAED;8CACJ,KAACrF;oCACCoG,OAAM;oCACNC,KAAK,GAAEpE,sBAAAA,YAAYiF,KAAK,YAAjBjF,sBAAqB;oCAC5BqE,UAAU,CAACY,QAAUL,eAAe,CAACL,QAAW,aAAKA;gDAAOU;;oCAC5DgC,YAAW;;8CAEb,KAACtI;oCACCwF,OAAM;oCACNC,KAAK,GAAEpE,4BAAAA,YAAYkH,YAAY,YAAxBlH,4BAA4B;oCACnCqE,UAAU,CAACC,QACTM,eAAe,CAACL,QAAW,aAAKA;gDAAO2C,cAAc5C,MAAME,MAAM,CAACJ,KAAK;;oCAEzE+C,SAAS;oCACTC,SAAS;oCACT1C,MAAK;oCACLwB,aAAY;oCACZmB,WAAW;wCAAEC,WAAW;4CAAEC,WAAW5J,cAAc6J,uBAAuB;wCAAC;oCAAE;;8CAE/E,MAAC7I;oCACC8I,MAAM;oCACNtD,OAAM;oCACNC,OAAOnD,QAAOjB,kCAAAA,YAAY0H,kBAAkB,YAA9B1H,kCAAkC;oCAChDqE,UAAU,CAACC,QACTM,eAAe,CAACL,QAAW,aACtBA;gDACHmD,oBAAoBpD,MAAME,MAAM,CAACJ,KAAK,GAAGuD,OAAOrD,MAAME,MAAM,CAACJ,KAAK,IAAIwD;;oCAG1ElD,MAAK;;sDAEL,KAAClG;4CAAS4F,OAAM;sDAAI;;wCACnB;4CACC;gDAAC;gDAAI;6CAAa;4CAClB;gDAAC;gDAAI;6CAAS;4CACd;gDAAC;gDAAK;6CAAU;4CAChB;gDAAC;gDAAK;6CAAU;4CAChB;gDAAC;gDAAM;6CAAQ;4CACf;gDAAC;gDAAM;6CAAS;yCACjB,CAACqB,GAAG,CAAC,CAAC,CAACoC,SAAS1D,MAAM,iBACrB,KAAC3F;gDAAuB4F,OAAOnD,OAAO4G;0DACnC1D;+CADY0D;;;8CAKnB,KAACjJ;oCAAWyD,SAAQ;oCAAUe,OAAM;8CACjC,yFACC;;;;;kCAIR,MAAChF;;0CACC,KAACH;gCAAO+H,SAAS,IAAMrB,aAAa;0CAAQ;;0CAC5C,KAAC1G;gCAAOoE,SAAQ;gCAAY8D,UAAUnB,aAAalD,MAAM,GAAG;gCAAGkE,SAASd;0CACrE;;;;;;;;AAMb;AACApF,cAAcgI,WAAW,GAAG;AAE5B,eAAehI,cAAa"}
@@ -137,7 +137,11 @@ export interface ConsoleProductSummary {
137
137
  /** Whether the product has a photo. */
138
138
  hasPhoto: boolean;
139
139
  }
140
- /** A new product a widget asks the hub to create, always as a draft with no price. */
140
+ /**
141
+ * A new product a widget asks the hub to create, always as a draft, at its
142
+ * suggested price when it carries one in range, else the default price
143
+ * (`proposedProductPriceUsd`, Zach 2026-10-12).
144
+ */
141
145
  export interface ConsoleProposedProduct {
142
146
  name: string;
143
147
  type: ConsoleProductType;
@@ -146,6 +150,8 @@ export interface ConsoleProposedProduct {
146
150
  options: ConsoleProductOption[];
147
151
  seoTitle: string;
148
152
  seoDescription: string;
153
+ /** The suggested price for every variant, in dollars; absent or `null` is the default price. */
154
+ priceUsd?: number | null;
149
155
  }
150
156
  /** A discount a widget asks the hub to create, always switched off. */
151
157
  export interface ConsoleProposedDiscount {