@aglyn/plugins-commerce 1.0.0-beta.277 → 1.0.0-beta.282
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +17 -14
- package/src/lib/components/commerce-console-page.js +33 -1
- package/src/lib/components/commerce-console-page.js.map +1 -1
- package/src/lib/components/console/payments-settings-card.component.js +1 -1
- package/src/lib/components/console/payments-settings-card.component.js.map +1 -1
- package/src/lib/components/console/pos/pos-hardware-api.d.ts +122 -0
- package/src/lib/components/console/pos/pos-hardware-api.js +90 -0
- package/src/lib/components/console/pos/pos-hardware-api.js.map +1 -0
- package/src/lib/components/console/pos/pos-reader-splash-api.d.ts +58 -0
- package/src/lib/components/console/pos/pos-reader-splash-api.js +101 -0
- package/src/lib/components/console/pos/pos-reader-splash-api.js.map +1 -0
- package/src/lib/components/console/pos-devices-card.component.js +5 -0
- package/src/lib/components/console/pos-devices-card.component.js.map +1 -1
- package/src/lib/components/console/pos-hardware-card.component.d.ts +26 -0
- package/src/lib/components/console/pos-hardware-card.component.js +456 -0
- package/src/lib/components/console/pos-hardware-card.component.js.map +1 -0
- package/src/lib/components/console/pos-hardware-orders-staff-page.component.d.ts +14 -0
- package/src/lib/components/console/pos-hardware-orders-staff-page.component.js +519 -0
- package/src/lib/components/console/pos-hardware-orders-staff-page.component.js.map +1 -0
- package/src/lib/components/console/pos-ops/pos-check-in-dialog.component.d.ts +27 -0
- package/src/lib/components/console/pos-ops/pos-check-in-dialog.component.js +444 -0
- package/src/lib/components/console/pos-ops/pos-check-in-dialog.component.js.map +1 -0
- package/src/lib/components/console/pos-ops/pos-check-in-zone.component.d.ts +9 -0
- package/src/lib/components/console/pos-ops/pos-check-in-zone.component.js +37 -0
- package/src/lib/components/console/pos-ops/pos-check-in-zone.component.js.map +1 -0
- package/src/lib/components/console/pos-ops/pos-check-ins-card.component.d.ts +24 -0
- package/src/lib/components/console/pos-ops/pos-check-ins-card.component.js +346 -0
- package/src/lib/components/console/pos-ops/pos-check-ins-card.component.js.map +1 -0
- package/src/lib/components/console/pos-ops/pos-customer-lookup.component.d.ts +5 -1
- package/src/lib/components/console/pos-ops/pos-customer-lookup.component.js +83 -4
- package/src/lib/components/console/pos-ops/pos-customer-lookup.component.js.map +1 -1
- package/src/lib/components/console/pos-ops/pos-day-totals-card.component.d.ts +35 -0
- package/src/lib/components/console/pos-ops/pos-day-totals-card.component.js +546 -0
- package/src/lib/components/console/pos-ops/pos-day-totals-card.component.js.map +1 -0
- package/src/lib/components/console/pos-ops/pos-operations-bar.component.d.ts +2 -1
- package/src/lib/components/console/pos-ops/pos-operations-bar.component.js +20 -1
- package/src/lib/components/console/pos-ops/pos-operations-bar.component.js.map +1 -1
- package/src/lib/components/console/pos-ops/pos-ops-api.d.ts +1 -1
- package/src/lib/components/console/pos-ops/pos-ops-api.js.map +1 -1
- package/src/lib/components/console/pos-page.component.js +6 -3
- package/src/lib/components/console/pos-page.component.js.map +1 -1
- package/src/lib/components/console/pos-reader-splash-setting.component.d.ts +22 -0
- package/src/lib/components/console/pos-reader-splash-setting.component.js +275 -0
- package/src/lib/components/console/pos-reader-splash-setting.component.js.map +1 -0
- package/src/lib/components/console/pos-reader-splash-staff-page.component.d.ts +14 -0
- package/src/lib/components/console/pos-reader-splash-staff-page.component.js +301 -0
- package/src/lib/components/console/pos-reader-splash-staff-page.component.js.map +1 -0
- package/src/lib/components/console/pos-zones.d.ts +37 -3
- package/src/lib/components/console/pos-zones.js +11 -1
- package/src/lib/components/console/pos-zones.js.map +1 -1
- package/src/lib/components/console/product-check-in-fields.component.d.ts +20 -0
- package/src/lib/components/console/product-check-in-fields.component.js +172 -0
- package/src/lib/components/console/product-check-in-fields.component.js.map +1 -0
- package/src/lib/components/console/product-editor-dialog.component.js +48 -9
- package/src/lib/components/console/product-editor-dialog.component.js.map +1 -1
- package/src/lib/components/console/product-register-fields.component.d.ts +12 -0
- package/src/lib/components/console/product-register-fields.component.js +39 -1
- package/src/lib/components/console/product-register-fields.component.js.map +1 -1
- package/src/lib/components/console/products-hub-card.component.js +21 -6
- package/src/lib/components/console/products-hub-card.component.js.map +1 -1
- package/src/lib/components/console/reservations-card.component.js +239 -70
- package/src/lib/components/console/reservations-card.component.js.map +1 -1
- package/src/lib/components/reservation-widget.js +16 -0
- package/src/lib/components/reservation-widget.js.map +1 -1
- package/src/lib/constants/operator-alerts.d.ts +6 -0
- package/src/lib/constants/operator-alerts.js +20 -1
- package/src/lib/constants/operator-alerts.js.map +1 -1
- package/src/lib/declarations.console-server.js +7 -0
- package/src/lib/declarations.console-server.js.map +1 -1
- package/src/lib/declarations.d.ts +26 -0
- package/src/lib/declarations.js +34 -0
- package/src/lib/declarations.js.map +1 -0
- package/src/lib/model/commerce-check-in-product.d.ts +37 -0
- package/src/lib/model/commerce-check-in-product.js +49 -0
- package/src/lib/model/commerce-check-in-product.js.map +1 -0
- package/src/lib/model/commerce-check-in.d.ts +187 -0
- package/src/lib/model/commerce-check-in.js +468 -0
- package/src/lib/model/commerce-check-in.js.map +1 -0
- package/src/lib/model/commerce-pos-day-report.d.ts +209 -0
- package/src/lib/model/commerce-pos-day-report.js +572 -0
- package/src/lib/model/commerce-pos-day-report.js.map +1 -0
- package/src/lib/model/commerce-pos-offline.d.ts +65 -3
- package/src/lib/model/commerce-pos-offline.js +104 -7
- package/src/lib/model/commerce-pos-offline.js.map +1 -1
- package/src/lib/model/commerce-pos-ops.d.ts +3 -0
- package/src/lib/model/commerce-pos-ops.js +1 -0
- package/src/lib/model/commerce-pos-ops.js.map +1 -1
- package/src/lib/model/commerce-pos.d.ts +43 -3
- package/src/lib/model/commerce-pos.js +73 -5
- package/src/lib/model/commerce-pos.js.map +1 -1
- package/src/lib/model/commerce-reservations.d.ts +73 -7
- package/src/lib/model/commerce-reservations.js +90 -10
- package/src/lib/model/commerce-reservations.js.map +1 -1
- package/src/lib/model/commerce.d.ts +40 -0
- package/src/lib/model/commerce.js +22 -4
- package/src/lib/model/commerce.js.map +1 -1
- package/src/lib/model/index.d.ts +1 -0
- package/src/lib/model/index.js +1 -0
- package/src/lib/model/index.js.map +1 -1
- package/src/lib/model/plugin-permissions.d.ts +1 -1
- package/src/lib/model/plugin-permissions.js +2 -1
- package/src/lib/model/plugin-permissions.js.map +1 -1
- package/src/lib/model/pos-hardware.d.ts +288 -0
- package/src/lib/model/pos-hardware.js +499 -0
- package/src/lib/model/pos-hardware.js.map +1 -0
- package/src/lib/model/pos-offline-card-limits.d.ts +20 -0
- package/src/lib/model/pos-offline-card-limits.js +28 -0
- package/src/lib/model/pos-offline-card-limits.js.map +1 -0
- package/src/lib/model/pos-reader-splash.d.ts +114 -0
- package/src/lib/model/pos-reader-splash.js +141 -0
- package/src/lib/model/pos-reader-splash.js.map +1 -0
- package/src/lib/model/product-write.d.ts +6 -0
- package/src/lib/plugin-config.d.ts +10 -0
- package/src/lib/plugin-config.js +40 -0
- package/src/lib/plugin-config.js.map +1 -1
- package/src/lib/plugin.js +28 -0
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/server/cart-checkout.js +3 -1
- package/src/lib/server/cart-checkout.js.map +1 -1
- package/src/lib/server/cart.js +12 -1
- package/src/lib/server/cart.js.map +1 -1
- package/src/lib/server/catalog.js +49 -12
- package/src/lib/server/catalog.js.map +1 -1
- package/src/lib/server/checkout-extras.js +1 -1
- package/src/lib/server/checkout-extras.js.map +1 -1
- package/src/lib/server/checkout.js +7 -0
- package/src/lib/server/checkout.js.map +1 -1
- package/src/lib/server/person-eraser.d.ts +8 -1
- package/src/lib/server/person-eraser.js +23 -1
- package/src/lib/server/person-eraser.js.map +1 -1
- package/src/lib/server/pos-check-in.d.ts +49 -0
- package/src/lib/server/pos-check-in.js +652 -0
- package/src/lib/server/pos-check-in.js.map +1 -0
- package/src/lib/server/pos-day-report.d.ts +46 -0
- package/src/lib/server/pos-day-report.js +299 -0
- package/src/lib/server/pos-day-report.js.map +1 -0
- package/src/lib/server/pos-hardware-admin.d.ts +50 -0
- package/src/lib/server/pos-hardware-admin.js +366 -0
- package/src/lib/server/pos-hardware-admin.js.map +1 -0
- package/src/lib/server/pos-hardware-notify.d.ts +27 -0
- package/src/lib/server/pos-hardware-notify.js +142 -0
- package/src/lib/server/pos-hardware-notify.js.map +1 -0
- package/src/lib/server/pos-hardware-user-eraser.d.ts +26 -0
- package/src/lib/server/pos-hardware-user-eraser.js +67 -0
- package/src/lib/server/pos-hardware-user-eraser.js.map +1 -0
- package/src/lib/server/pos-hardware-webhook.d.ts +31 -0
- package/src/lib/server/pos-hardware-webhook.js +176 -0
- package/src/lib/server/pos-hardware-webhook.js.map +1 -0
- package/src/lib/server/pos-hardware.d.ts +58 -0
- package/src/lib/server/pos-hardware.js +234 -0
- package/src/lib/server/pos-hardware.js.map +1 -0
- package/src/lib/server/pos-offline-card-reconcile.d.ts +38 -0
- package/src/lib/server/pos-offline-card-reconcile.js +58 -0
- package/src/lib/server/pos-offline-card-reconcile.js.map +1 -0
- package/src/lib/server/pos-offline-sync.d.ts +32 -0
- package/src/lib/server/pos-offline-sync.js +191 -15
- package/src/lib/server/pos-offline-sync.js.map +1 -1
- package/src/lib/server/pos-ops-routes.d.ts +4 -2
- package/src/lib/server/pos-ops-routes.js +14 -2
- package/src/lib/server/pos-ops-routes.js.map +1 -1
- package/src/lib/server/pos-order.js +39 -51
- package/src/lib/server/pos-order.js.map +1 -1
- package/src/lib/server/pos-payment.js +8 -4
- package/src/lib/server/pos-payment.js.map +1 -1
- package/src/lib/server/pos-reader-splash-admin.d.ts +36 -0
- package/src/lib/server/pos-reader-splash-admin.js +295 -0
- package/src/lib/server/pos-reader-splash-admin.js.map +1 -0
- package/src/lib/server/pos-reader-splash-render.d.ts +40 -0
- package/src/lib/server/pos-reader-splash-render.js +138 -0
- package/src/lib/server/pos-reader-splash-render.js.map +1 -0
- package/src/lib/server/pos-reader-splash-server.d.ts +32 -0
- package/src/lib/server/pos-reader-splash-server.js +32 -0
- package/src/lib/server/pos-reader-splash-server.js.map +1 -0
- package/src/lib/server/pos-reader-splash.d.ts +105 -0
- package/src/lib/server/pos-reader-splash.js +548 -0
- package/src/lib/server/pos-reader-splash.js.map +1 -0
- package/src/lib/server/pos-sale.d.ts +2 -1
- package/src/lib/server/pos-sale.js +3 -1
- package/src/lib/server/pos-sale.js.map +1 -1
- package/src/lib/server/pos-terminal.d.ts +67 -4
- package/src/lib/server/pos-terminal.js +239 -32
- package/src/lib/server/pos-terminal.js.map +1 -1
- package/src/lib/server/process-restock.js +4 -0
- package/src/lib/server/process-restock.js.map +1 -1
- package/src/lib/server/product-card.js +2 -2
- package/src/lib/server/product-card.js.map +1 -1
- package/src/lib/server/product-catalog.d.ts +2 -0
- package/src/lib/server/product-catalog.js +11 -4
- package/src/lib/server/product-catalog.js.map +1 -1
- package/src/lib/server/product.js +3 -1
- package/src/lib/server/product.js.map +1 -1
- package/src/lib/server/related.js +8 -1
- package/src/lib/server/related.js.map +1 -1
- package/src/lib/server/reservation-availability-query.d.ts +35 -0
- package/src/lib/server/reservation-availability-query.js +34 -0
- package/src/lib/server/reservation-availability-query.js.map +1 -0
- package/src/lib/server/reservation-availability.d.ts +21 -0
- package/src/lib/server/reservation-availability.js +64 -10
- package/src/lib/server/reservation-availability.js.map +1 -1
- package/src/lib/server/reservation-walk-in.d.ts +28 -0
- package/src/lib/server/reservation-walk-in.js +166 -0
- package/src/lib/server/reservation-walk-in.js.map +1 -0
- package/src/lib/server/reserve.js +13 -10
- package/src/lib/server/reserve.js.map +1 -1
- package/src/lib/server/reviews.js +3 -1
- package/src/lib/server/reviews.js.map +1 -1
- package/src/lib/server/site-page-resolver.js +5 -2
- package/src/lib/server/site-page-resolver.js.map +1 -1
- package/src/lib/server.js +26 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/testing/fake-firestore.js +72 -0
- package/src/lib/testing/fake-firestore.js.map +1 -1
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
18
18
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
19
19
|
import { Box, Button, FormControlLabel, IconButton, Stack, Switch, TextField, Typography } from "@mui/material";
|
|
20
|
-
import {
|
|
20
|
+
import { PLATFORM_BRAND_NAME } from "@aglyn/aglyn/app-utils/platform-brand";
|
|
21
|
+
import { memo, useCallback, useId } from "react";
|
|
21
22
|
import * as CommerceModel from "../../model/index.js";
|
|
22
23
|
function newId(prefix) {
|
|
23
24
|
return `${prefix}${Math.random().toString(36).slice(2, 10)}`;
|
|
@@ -26,6 +27,39 @@ function newId(prefix) {
|
|
|
26
27
|
const number = Math.round(Number(String(value).replace(/[^0-9.]/g, '')) * 100);
|
|
27
28
|
return Number.isFinite(number) && number > 0 ? number : 0;
|
|
28
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* The product's "Sell online" switch (AGL-4060). On (the default, and what
|
|
32
|
+
* every product stored before it reads as) the product sells on the site and
|
|
33
|
+
* its channels as it always has; off, it sells in person only.
|
|
34
|
+
*/ export const ProductSellOnlineField = /*#__PURE__*/ memo(function ProductSellOnlineField(props) {
|
|
35
|
+
const { sellOnline, update } = props;
|
|
36
|
+
const helpId = useId();
|
|
37
|
+
return /*#__PURE__*/ _jsxs(Stack, {
|
|
38
|
+
spacing: 0.5,
|
|
39
|
+
children: [
|
|
40
|
+
/*#__PURE__*/ _jsx(FormControlLabel, {
|
|
41
|
+
control: /*#__PURE__*/ _jsx(Switch, {
|
|
42
|
+
checked: sellOnline,
|
|
43
|
+
onChange: (event)=>update({
|
|
44
|
+
sellOnline: event.target.checked
|
|
45
|
+
}),
|
|
46
|
+
slotProps: {
|
|
47
|
+
input: {
|
|
48
|
+
'aria-describedby': helpId
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}),
|
|
52
|
+
label: "Sell online"
|
|
53
|
+
}),
|
|
54
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
55
|
+
id: helpId,
|
|
56
|
+
variant: "body2",
|
|
57
|
+
color: "text.secondary",
|
|
58
|
+
children: sellOnline ? 'Sold on your site — its product page, product grids, search, the cart and checkout — and in any shopping feeds and marketplaces you connect, as well as in person.' : `In person only: sold at the register, the kiosk, the ${PLATFORM_BRAND_NAME} POS apps and on draft orders. It has no page on your site and is left out of product grids, search, the sitemap, the cart, checkout and every shopping feed.`
|
|
59
|
+
})
|
|
60
|
+
]
|
|
61
|
+
});
|
|
62
|
+
});
|
|
29
63
|
/**
|
|
30
64
|
* The product's register settings (AGL-3607): whether it is a quick key on
|
|
31
65
|
* the register's first screen, and its modifier groups — choices such as
|
|
@@ -67,6 +101,10 @@ function newId(prefix) {
|
|
|
67
101
|
}),
|
|
68
102
|
label: "Quick key at the register"
|
|
69
103
|
}),
|
|
104
|
+
/*#__PURE__*/ _jsx(ProductSellOnlineField, {
|
|
105
|
+
sellOnline: props.sellOnline,
|
|
106
|
+
update: update
|
|
107
|
+
}),
|
|
70
108
|
/*#__PURE__*/ _jsx(Typography, {
|
|
71
109
|
variant: "body2",
|
|
72
110
|
color: "text.secondary",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/product-register-fields.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 {\n Box,\n Button,\n FormControlLabel,\n IconButton,\n Stack,\n Switch,\n TextField,\n Typography,\n} from '@mui/material'\nimport { memo, useCallback } from 'react'\nimport * as CommerceModel from '../../model'\n\ntype Patch = Partial<CommerceModel.HostProduct>\n\nexport interface ProductRegisterFieldsProps {\n posQuickKey: boolean\n modifierGroups: readonly CommerceModel.ProductModifierGroup[]\n update: (patch: Patch) => void\n}\n\nfunction newId(prefix: string): string {\n return `${prefix}${Math.random().toString(36).slice(2, 10)}`\n}\n\n/** Dollars as typed, to whole cents; blank is free. */\nfunction centsFromDollars(value: string): number {\n const number = Math.round(Number(String(value).replace(/[^0-9.]/g, '')) * 100)\n return Number.isFinite(number) && number > 0 ? number : 0\n}\n\n/**\n * The product's register settings (AGL-3607): whether it is a quick key on\n * the register's first screen, and its modifier groups — choices such as\n * \"Milk\" or \"Add-ons\" the cashier picks as the item is rung up, each choice\n * free or with a price added to the item. A group with a minimum of 1 or\n * more must be answered before the item can be added.\n *\n * Memoized on its own props like every section of this editor (AGL-3423):\n * a keystroke elsewhere in the dialog does not redraw it.\n */\nexport const ProductRegisterFields = memo(function ProductRegisterFields(\n props: ProductRegisterFieldsProps,\n) {\n const { modifierGroups, update } = props\n const setGroups = useCallback(\n (groups: CommerceModel.ProductModifierGroup[]) => update({ modifierGroups: groups }),\n [update],\n )\n const editGroup = (index: number, patch: Partial<CommerceModel.ProductModifierGroup>) =>\n setGroups(modifierGroups.map((group, at) => (at === index ? { ...group, ...patch } : group)))\n const editOption = (\n groupIndex: number,\n optionIndex: number,\n patch: Partial<CommerceModel.ProductModifierOption>,\n ) => {\n const group = modifierGroups[groupIndex]\n editGroup(groupIndex, {\n options: group.options.map((option, at) => (at === optionIndex ? { ...option, ...patch } : option)),\n })\n }\n const problem = CommerceModel.modifierGroupsProblem(modifierGroups)\n\n return (\n <Stack spacing={2}>\n <Typography variant=\"subtitle1\" component=\"h3\">\n {'At the register'}\n </Typography>\n <FormControlLabel\n control={\n <Switch\n checked={props.posQuickKey}\n onChange={(event) => update({ posQuickKey: event.target.checked })}\n />\n }\n label=\"Quick key at the register\"\n />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Modifiers are choices added as the item is rung up, such as a milk or an extra shot. ' +\n 'Each can be free or add to the price.'}\n </Typography>\n {modifierGroups.map((group, groupIndex) => (\n <Box\n key={group.id}\n role=\"group\"\n aria-label={group.name || 'Modifier group'}\n sx={{ border: 1, borderColor: 'divider', borderRadius: 1, p: 2 }}\n >\n <Stack spacing={1.5}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'flex-start' }}>\n <TextField\n label=\"Group name\"\n size=\"small\"\n value={group.name}\n onChange={(event) => editGroup(groupIndex, { name: event.target.value })}\n sx={{ flex: 1 }}\n />\n <TextField\n label=\"At least\"\n size=\"small\"\n value={group.min}\n onChange={(event) =>\n editGroup(groupIndex, { min: Math.max(0, Math.round(Number(event.target.value) || 0)) })\n }\n helperText={group.min > 0 ? 'Required' : 'Optional'}\n sx={{ width: 96 }}\n slotProps={{ htmlInput: { inputMode: 'numeric' } }}\n />\n <TextField\n label=\"At most\"\n size=\"small\"\n value={group.max}\n onChange={(event) =>\n editGroup(groupIndex, { max: Math.max(1, Math.round(Number(event.target.value) || 1)) })\n }\n sx={{ width: 96 }}\n slotProps={{ htmlInput: { inputMode: 'numeric' } }}\n />\n <Button\n color=\"error\"\n onClick={() => setGroups(modifierGroups.filter((_group, at) => at !== groupIndex))}\n >\n {'Remove group'}\n </Button>\n </Stack>\n {group.options.map((option, optionIndex) => (\n <Stack key={option.id} direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <TextField\n label=\"Choice\"\n size=\"small\"\n value={option.name}\n onChange={(event) => editOption(groupIndex, optionIndex, { name: event.target.value })}\n sx={{ flex: 1 }}\n />\n <TextField\n label=\"Adds ($)\"\n size=\"small\"\n defaultValue={option.priceCents ? (option.priceCents / 100).toFixed(2) : ''}\n placeholder=\"0.00\"\n onBlur={(event) =>\n editOption(groupIndex, optionIndex, { priceCents: centsFromDollars(event.target.value) })\n }\n sx={{ width: 120 }}\n slotProps={{ htmlInput: { inputMode: 'decimal' } }}\n />\n <IconButton\n aria-label={`Remove ${option.name || 'choice'}`}\n onClick={() =>\n editGroup(groupIndex, {\n options: group.options.filter((_option, at) => at !== optionIndex),\n max: Math.max(1, Math.min(group.max, group.options.length - 1)),\n })\n }\n >\n {'✕'}\n </IconButton>\n </Stack>\n ))}\n <Box>\n <Button\n onClick={() =>\n editGroup(groupIndex, {\n options: [...group.options, { id: newId('o'), name: '', priceCents: 0 }],\n })\n }\n disabled={group.options.length >= CommerceModel.POS_MAX_MODIFIER_OPTIONS}\n >\n {'Add choice'}\n </Button>\n </Box>\n </Stack>\n </Box>\n ))}\n <Box>\n <Button\n variant=\"outlined\"\n onClick={() =>\n setGroups([\n ...modifierGroups,\n {\n id: newId('g'),\n name: '',\n min: 0,\n max: 1,\n options: [{ id: newId('o'), name: '', priceCents: 0 }],\n },\n ])\n }\n disabled={modifierGroups.length >= CommerceModel.POS_MAX_MODIFIER_GROUPS}\n >\n {'Add modifier group'}\n </Button>\n </Box>\n {problem && modifierGroups.length ? (\n <Typography variant=\"body2\" color=\"warning.main\">\n {problem}\n </Typography>\n ) : null}\n </Stack>\n )\n})\n"],"names":["Box","Button","FormControlLabel","IconButton","Stack","Switch","TextField","Typography","memo","useCallback","CommerceModel","newId","prefix","Math","random","toString","slice","centsFromDollars","value","number","round","Number","String","replace","isFinite","ProductRegisterFields","props","modifierGroups","update","setGroups","groups","editGroup","index","patch","map","group","at","editOption","groupIndex","optionIndex","options","option","problem","modifierGroupsProblem","spacing","variant","component","control","checked","posQuickKey","onChange","event","target","label","color","role","aria-label","name","sx","border","borderColor","borderRadius","p","direction","alignItems","size","flex","min","max","helperText","width","slotProps","htmlInput","inputMode","onClick","filter","_group","defaultValue","priceCents","toFixed","placeholder","onBlur","_option","length","id","disabled","POS_MAX_MODIFIER_OPTIONS","POS_MAX_MODIFIER_GROUPS"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/product-register-fields.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 {\n Box,\n Button,\n FormControlLabel,\n IconButton,\n Stack,\n Switch,\n TextField,\n Typography,\n} from '@mui/material'\nimport { PLATFORM_BRAND_NAME } from '@aglyn/aglyn/app-utils/platform-brand'\nimport { memo, useCallback, useId } from 'react'\nimport * as CommerceModel from '../../model'\n\ntype Patch = Partial<CommerceModel.HostProduct>\n\nexport interface ProductRegisterFieldsProps {\n posQuickKey: boolean\n /** `productSellsOnline` of the product: absent reads as on. */\n sellOnline: boolean\n modifierGroups: readonly CommerceModel.ProductModifierGroup[]\n update: (patch: Patch) => void\n}\n\nfunction newId(prefix: string): string {\n return `${prefix}${Math.random().toString(36).slice(2, 10)}`\n}\n\n/** Dollars as typed, to whole cents; blank is free. */\nfunction centsFromDollars(value: string): number {\n const number = Math.round(Number(String(value).replace(/[^0-9.]/g, '')) * 100)\n return Number.isFinite(number) && number > 0 ? number : 0\n}\n\nexport interface ProductSellOnlineFieldProps {\n sellOnline: boolean\n update: (patch: Patch) => void\n}\n\n/**\n * The product's \"Sell online\" switch (AGL-4060). On (the default, and what\n * every product stored before it reads as) the product sells on the site and\n * its channels as it always has; off, it sells in person only.\n */\nexport const ProductSellOnlineField = memo(function ProductSellOnlineField(\n props: ProductSellOnlineFieldProps,\n) {\n const { sellOnline, update } = props\n const helpId = useId()\n return (\n <Stack spacing={0.5}>\n <FormControlLabel\n control={\n <Switch\n checked={sellOnline}\n onChange={(event) => update({ sellOnline: event.target.checked })}\n slotProps={{ input: { 'aria-describedby': helpId } }}\n />\n }\n label=\"Sell online\"\n />\n <Typography id={helpId} variant=\"body2\" color=\"text.secondary\">\n {sellOnline\n ? 'Sold on your site — its product page, product grids, search, the cart and checkout — and in any shopping feeds and marketplaces you connect, as well as in person.'\n : `In person only: sold at the register, the kiosk, the ${PLATFORM_BRAND_NAME} POS apps and on draft orders. It has no page on your site and is left out of product grids, search, the sitemap, the cart, checkout and every shopping feed.`}\n </Typography>\n </Stack>\n )\n})\n\n/**\n * The product's register settings (AGL-3607): whether it is a quick key on\n * the register's first screen, and its modifier groups — choices such as\n * \"Milk\" or \"Add-ons\" the cashier picks as the item is rung up, each choice\n * free or with a price added to the item. A group with a minimum of 1 or\n * more must be answered before the item can be added.\n *\n * Memoized on its own props like every section of this editor (AGL-3423):\n * a keystroke elsewhere in the dialog does not redraw it.\n */\nexport const ProductRegisterFields = memo(function ProductRegisterFields(\n props: ProductRegisterFieldsProps,\n) {\n const { modifierGroups, update } = props\n const setGroups = useCallback(\n (groups: CommerceModel.ProductModifierGroup[]) => update({ modifierGroups: groups }),\n [update],\n )\n const editGroup = (index: number, patch: Partial<CommerceModel.ProductModifierGroup>) =>\n setGroups(modifierGroups.map((group, at) => (at === index ? { ...group, ...patch } : group)))\n const editOption = (\n groupIndex: number,\n optionIndex: number,\n patch: Partial<CommerceModel.ProductModifierOption>,\n ) => {\n const group = modifierGroups[groupIndex]\n editGroup(groupIndex, {\n options: group.options.map((option, at) => (at === optionIndex ? { ...option, ...patch } : option)),\n })\n }\n const problem = CommerceModel.modifierGroupsProblem(modifierGroups)\n\n return (\n <Stack spacing={2}>\n <Typography variant=\"subtitle1\" component=\"h3\">\n {'At the register'}\n </Typography>\n <FormControlLabel\n control={\n <Switch\n checked={props.posQuickKey}\n onChange={(event) => update({ posQuickKey: event.target.checked })}\n />\n }\n label=\"Quick key at the register\"\n />\n <ProductSellOnlineField sellOnline={props.sellOnline} update={update} />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Modifiers are choices added as the item is rung up, such as a milk or an extra shot. ' +\n 'Each can be free or add to the price.'}\n </Typography>\n {modifierGroups.map((group, groupIndex) => (\n <Box\n key={group.id}\n role=\"group\"\n aria-label={group.name || 'Modifier group'}\n sx={{ border: 1, borderColor: 'divider', borderRadius: 1, p: 2 }}\n >\n <Stack spacing={1.5}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'flex-start' }}>\n <TextField\n label=\"Group name\"\n size=\"small\"\n value={group.name}\n onChange={(event) => editGroup(groupIndex, { name: event.target.value })}\n sx={{ flex: 1 }}\n />\n <TextField\n label=\"At least\"\n size=\"small\"\n value={group.min}\n onChange={(event) =>\n editGroup(groupIndex, { min: Math.max(0, Math.round(Number(event.target.value) || 0)) })\n }\n helperText={group.min > 0 ? 'Required' : 'Optional'}\n sx={{ width: 96 }}\n slotProps={{ htmlInput: { inputMode: 'numeric' } }}\n />\n <TextField\n label=\"At most\"\n size=\"small\"\n value={group.max}\n onChange={(event) =>\n editGroup(groupIndex, { max: Math.max(1, Math.round(Number(event.target.value) || 1)) })\n }\n sx={{ width: 96 }}\n slotProps={{ htmlInput: { inputMode: 'numeric' } }}\n />\n <Button\n color=\"error\"\n onClick={() => setGroups(modifierGroups.filter((_group, at) => at !== groupIndex))}\n >\n {'Remove group'}\n </Button>\n </Stack>\n {group.options.map((option, optionIndex) => (\n <Stack key={option.id} direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <TextField\n label=\"Choice\"\n size=\"small\"\n value={option.name}\n onChange={(event) => editOption(groupIndex, optionIndex, { name: event.target.value })}\n sx={{ flex: 1 }}\n />\n <TextField\n label=\"Adds ($)\"\n size=\"small\"\n defaultValue={option.priceCents ? (option.priceCents / 100).toFixed(2) : ''}\n placeholder=\"0.00\"\n onBlur={(event) =>\n editOption(groupIndex, optionIndex, { priceCents: centsFromDollars(event.target.value) })\n }\n sx={{ width: 120 }}\n slotProps={{ htmlInput: { inputMode: 'decimal' } }}\n />\n <IconButton\n aria-label={`Remove ${option.name || 'choice'}`}\n onClick={() =>\n editGroup(groupIndex, {\n options: group.options.filter((_option, at) => at !== optionIndex),\n max: Math.max(1, Math.min(group.max, group.options.length - 1)),\n })\n }\n >\n {'✕'}\n </IconButton>\n </Stack>\n ))}\n <Box>\n <Button\n onClick={() =>\n editGroup(groupIndex, {\n options: [...group.options, { id: newId('o'), name: '', priceCents: 0 }],\n })\n }\n disabled={group.options.length >= CommerceModel.POS_MAX_MODIFIER_OPTIONS}\n >\n {'Add choice'}\n </Button>\n </Box>\n </Stack>\n </Box>\n ))}\n <Box>\n <Button\n variant=\"outlined\"\n onClick={() =>\n setGroups([\n ...modifierGroups,\n {\n id: newId('g'),\n name: '',\n min: 0,\n max: 1,\n options: [{ id: newId('o'), name: '', priceCents: 0 }],\n },\n ])\n }\n disabled={modifierGroups.length >= CommerceModel.POS_MAX_MODIFIER_GROUPS}\n >\n {'Add modifier group'}\n </Button>\n </Box>\n {problem && modifierGroups.length ? (\n <Typography variant=\"body2\" color=\"warning.main\">\n {problem}\n </Typography>\n ) : null}\n </Stack>\n )\n})\n"],"names":["Box","Button","FormControlLabel","IconButton","Stack","Switch","TextField","Typography","PLATFORM_BRAND_NAME","memo","useCallback","useId","CommerceModel","newId","prefix","Math","random","toString","slice","centsFromDollars","value","number","round","Number","String","replace","isFinite","ProductSellOnlineField","props","sellOnline","update","helpId","spacing","control","checked","onChange","event","target","slotProps","input","label","id","variant","color","ProductRegisterFields","modifierGroups","setGroups","groups","editGroup","index","patch","map","group","at","editOption","groupIndex","optionIndex","options","option","problem","modifierGroupsProblem","component","posQuickKey","role","aria-label","name","sx","border","borderColor","borderRadius","p","direction","alignItems","size","flex","min","max","helperText","width","htmlInput","inputMode","onClick","filter","_group","defaultValue","priceCents","toFixed","placeholder","onBlur","_option","length","disabled","POS_MAX_MODIFIER_OPTIONS","POS_MAX_MODIFIER_GROUPS"],"mappings":"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"}
|
|
@@ -597,14 +597,29 @@ const STATUS_COLOR = {
|
|
|
597
597
|
{
|
|
598
598
|
field: 'status',
|
|
599
599
|
headerName: 'Status',
|
|
600
|
-
width:
|
|
600
|
+
width: 230,
|
|
601
601
|
renderCell: ({ row })=>{
|
|
602
602
|
var _STATUS_COLOR_row_status;
|
|
603
|
-
return /*#__PURE__*/
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
603
|
+
return /*#__PURE__*/ _jsxs(Stack, {
|
|
604
|
+
direction: "row",
|
|
605
|
+
spacing: 0.5,
|
|
606
|
+
sx: {
|
|
607
|
+
alignItems: 'center',
|
|
608
|
+
height: '100%'
|
|
609
|
+
},
|
|
610
|
+
children: [
|
|
611
|
+
/*#__PURE__*/ _jsx(Chip, {
|
|
612
|
+
label: row.status,
|
|
613
|
+
size: "small",
|
|
614
|
+
color: (_STATUS_COLOR_row_status = STATUS_COLOR[row.status]) != null ? _STATUS_COLOR_row_status : 'default',
|
|
615
|
+
variant: "outlined"
|
|
616
|
+
}),
|
|
617
|
+
CommerceModel.productSellsOnline(row) ? null : /*#__PURE__*/ _jsx(Chip, {
|
|
618
|
+
label: "In person only",
|
|
619
|
+
size: "small",
|
|
620
|
+
variant: "outlined"
|
|
621
|
+
})
|
|
622
|
+
]
|
|
608
623
|
});
|
|
609
624
|
}
|
|
610
625
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/products-hub-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 { TransferResumeImport } from '@aglyn/aglyn/app-utils/transfer-resume-import'\nimport { useConsoleWidgetSlot } from '@aglyn/aglyn/app-utils/console-widget-slot-context'\nimport * as Aglyn from '@aglyn/aglyn'\nimport * as CommerceModel from '../../model'\nimport {\n PRODUCT_LIST_BASE,\n PRODUCT_LIST_COLUMN_SORTS,\n PRODUCT_LIST_FIELDS,\n PRODUCT_LIST_HEADERS,\n PRODUCT_LIST_OPTIONS,\n PRODUCT_LIST_QUERY,\n PRODUCT_LIST_SELECT_FIELDS,\n productListRequestClauses,\n} from '../../constants/product-list-query'\nimport { CardDisplay, useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport ListFilterChips from '@aglyn/shared-ui-jsx/components/list-filter-chips.component'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport {\n ListQueryNotices,\n listQueryRefusals,\n} from '@aglyn/shared-ui-jsx/components/list-query-notices.component'\nimport {\n ListTable,\n listActionsColumn,\n} from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport QuotaReadoutComponent from '@aglyn/shared-ui-jsx/components/quota-readout.component'\nimport { hiddenFilterVisibility } from '@aglyn/shared-ui-jsx/const/list-filter'\nimport { listFilterGridColumns } from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport type { ListQuerySort } from '@aglyn/shared-ui-jsx/const/list-query-plan'\nimport { useListColumnSort } from '@aglyn/shared-ui-jsx/hooks/use-list-column-sort'\nimport { useListGridFilter } from '@aglyn/shared-ui-jsx/hooks/use-list-grid-filter'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { Timestamp } from '@aglyn/shared-util-timestamp'\nimport {\n Alert,\n Box,\n Button,\n Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n MenuItem,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport {\n addDoc,\n collection,\n doc,\n documentId,\n limit,\n orderBy,\n query,\n updateDoc,\n where,\n} from 'firebase/firestore'\nimport { getLiveCountFromServer } from '@aglyn/aglyn/app-utils/live-document-count.client'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport { useFirestore, useUser } from '@aglyn/tenant-feature-instance'\nimport { writeSiteWideChange } from '@aglyn/tenant-feature-instance/hooks/helpers/site-wide-change'\nimport {\n useFirestoreCollection,\n writeGuardedBySeed,\n} from '@aglyn/tenant-feature-instance'\nimport { useListQuery } from '@aglyn/tenant-feature-instance/hooks/use-list-query'\nimport { useHostResourceApi } from '@aglyn/tenant-feature-instance'\nimport { useOrgPlan } from '@aglyn/tenant-feature-instance'\nimport { ProductLabelsDialog } from './product-labels-dialog.component'\nimport ProductEditorDialog from './product-editor-dialog.component'\nimport { productSlugLedger } from './product-slugs'\nimport { productCollectionFields } from './smart-collections'\nimport ProductsHubZone from './products-hub-zone.component'\nimport { pluginDocsHelp } from '@aglyn/aglyn'\nimport { PRODUCTS_CREATE_ZONE, type ConsoleProductsHubZoneProps } from './product-zones'\nimport { commerceListFilter } from '../../transfer/list-filter'\nimport { COMMERCE_PRODUCTS_TRANSFER } from '../../transfer/transfer-keys'\n\n/**\n * How many of ONE product's license keys the dialog reads.\n *\n * Per product, not per store: the query carries the `productId` equality, so\n * this bounds a single pool rather than the site's whole key collection.\n */\nconst KEY_POOL_CEILING = 500\n\nexport interface ProductsHubCardProps {\n hostId: string\n}\n\ntype ProductRow = CommerceModel.HostProduct & { $id: string }\n\n/**\n * Stock and Variants are read from inside `variants`, which no query can\n * order by, so they sort the page on screen (AGL-3680); Product, Status, Type\n * and Price are the query's order (`PRODUCT_LIST_COLUMN_SORTS`).\n */\nconst PRODUCT_PAGE_SORTS = {\n stock: (row: ProductRow) => CommerceModel.productInventory(row),\n variants: (row: ProductRow) => row.variants.length,\n}\nconst PRODUCT_PAGE_SORT_HEADERS = { stock: 'Stock', variants: 'Variants' }\n\nconst STATUS_COLOR: Record<string, 'default' | 'success' | 'warning'> = {\n active: 'success',\n draft: 'warning',\n archived: 'default',\n}\n\n/** The grid's own columns; every other declared field is a hidden filter column. */\nconst PRODUCT_VISIBLE_COLUMNS = ['name', 'status', 'type', 'priceUsd', 'stock', 'variants']\n\n/**\n * Products hub v1 (AGL-279): the catalog manager replacing the Commerce\n * Starter card — a table over `hosts/{hostId}/products` whose search and\n * filters are its Firestore query (AGL-3321), full editor dialog, duplicate, archive/activate, soft delete (past\n * order rows keep resolving). Product cap (`productsPerHost`, AGL-278)\n * gated here on create/duplicate/import (AGL-471); server-side\n * enforcement of the client-write path rides AGL-473.\n */\nexport function ProductsHubCard(props: ProductsHubCardProps) {\n const { hostId } = props\n /** The shell's zone renderer, for the `productsCreate` zone (AGL-3596). */\n const CreateZone = useConsoleWidgetSlot()\n const firestore = useFirestore()\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n // Product cap (AGL-471): per-plan `productsPerHost`, same pattern as\n // locations. Console-side gate; server enforcement rides AGL-473.\n const createHostResource = useHostResourceApi()\n const { org, ready: planReady } = useOrgPlan(hostId)\n const { confirm } = useConfirmationContext()\n /*\n * The grid's Filters panel and quick search, bound to the clauses the\n * query below serves (AGL-3321). Every clause and the search word go on\n * that query; see `constants/product-list-query.ts` for what is offered.\n */\n const gridFilter = useListGridFilter({\n selectFields: PRODUCT_LIST_SELECT_FIELDS,\n })\n const filtering =\n gridFilter.clauses.length > 0 ||\n gridFilter.searchWords.some((word) => word.trim())\n // Every header sorts (AGL-3680); A to Z until one is clicked.\n const [askedSort, setAskedSort] = useState<ListQuerySort | null>(\n PRODUCT_LIST_COLUMN_SORTS[0],\n )\n const [editing, setEditing] = useState<ProductRow | null>(null)\n const [creating, setCreating] = useState(false)\n const [adjusting, setAdjusting] = useState<{\n product: ProductRow\n variantId: string\n delta: string\n reason: CommerceModel.InventoryAdjustmentReason\n locationId: string\n } | null>(null)\n const [keysFor, setKeysFor] = useState<ProductRow | null>(null)\n // Product labels for a label printer (AGL-3619).\n const [labelsFor, setLabelsFor] = useState<ProductRow | null>(null)\n const [keysText, setKeysText] = useState('')\n /**\n * What the last import created, with the options its After import step\n * set (AGL-2916, AGL-3531), which the hub's zone hands to its widgets.\n */\n const [lastImport, setLastImport] =\n useState<ConsoleProductsHubZoneProps['lastImport']>(null)\n // The console's import wizard and export dialog; none outside the console.\n const transfer = Aglyn.useTransferLauncher()\n\n const {\n rows: productDocs,\n data: productWindow,\n hasMore,\n page,\n setPage,\n pageSize,\n setPageSize,\n plan,\n status: productsStatus,\n /**\n * The product rows the stock dialog is seeded from are unconfirmed by\n * the server (AGL-1358). A stock adjustment does not write the delta —\n * it recomputes the WHOLE `variants` array from the seeded product and\n * replaces it, so a cached seed silently reverts every sale, return and\n * adjustment the server has recorded since that snapshot, on every\n * variant and every location.\n */\n fromCache: productsFromCache,\n } = useListQuery<any>({\n /*\n * PAGED BY THE QUERY, and every filter ON it (AGL-3321).\n *\n * The table read a five-hundred-product window and put ONE predicate on\n * it — the search or the Status clause — then matched Status beside a\n * search, dropped soft-deleted products and sorted by name over the rows\n * it held, and paged that. Past five hundred products, a match past the\n * window answered \"no products match\", and a page came back short by\n * however many deleted products it had held.\n *\n * Now the plan puts the search word and every clause on one query under\n * the `deletedAt == null` scope, ordered by `nameLower`, and each page is\n * a page of that query — one page plus a probe row, widened as the reader\n * pages forward. Nothing is matched over the rows afterwards; a clause\n * the query cannot hold is refused by name (`ListQueryNotices` below).\n */\n collection: collection(firestore, 'hosts', hostId, 'products'),\n declaration: PRODUCT_LIST_QUERY,\n request: {\n clauses: productListRequestClauses(gridFilter.clauses),\n search: gridFilter.searchWords,\n base: PRODUCT_LIST_BASE,\n sort: askedSort,\n },\n deps: [firestore, hostId],\n idField: '$id',\n /*\n * The scope and the filters read MUTABLE fields — a delete sets\n * `deletedAt`, Archive moves `status`, a rename moves the tokens — so a\n * product can leave this query mid-session, and the SDK can cache that\n * departure as a tombstone at the product's own path (AGL-1196). A\n * disappearance is confirmed against the server before it is believed.\n */\n confirmDisappearances: true,\n })\n /*\n * License key pool (AGL-308) for the open dialog's product — asked FOR that\n * product, and ordered (AGL-2501).\n *\n * This read `limit(500)` over the site's whole `licenseKeys` collection with\n * no `orderBy`, then filtered by `productId` in the browser. Firestore\n * answers an unordered limit in DOCUMENT-ID order, so on a store past five\n * hundred keys the window was an arbitrary five hundred taken across every\n * product — and the \"N available\" line below counted what happened to be in\n * it. A product whose keys all hashed high showed a pool of zero while the\n * storefront went on delivering them.\n *\n * The equality moves into the query, so the window is this product's keys\n * rather than the store's. `orderBy(documentId())` orders on the document\n * NAME, which cannot be absent — every candidate FIELD here (`createdAtMs`,\n * `assignedAtMs`, `revokedAtMs`) is either optional by design or absent on\n * exactly the keys the counts are about. An equality plus an ordering on\n * `__name__` is served by the automatic single-field index, so this adds no\n * composite for anyone to deploy.\n */\n const { data: keyDocs } = useFirestoreCollection<any>(\n () =>\n keysFor\n ? query(\n collection(firestore, 'hosts', hostId, 'licenseKeys'),\n where('productId', '==', keysFor.$id),\n orderBy(documentId()),\n // One past the ceiling, so a pool larger than the window is a\n // fact rather than a guess from `length === 500`.\n limit(KEY_POOL_CEILING + 1),\n )\n : null,\n [firestore, hostId, keysFor?.$id],\n { idField: '$id' },\n )\n /** The key pool is larger than the window, so the counts below are partial. */\n const keyPoolTruncated = (keyDocs?.length ?? 0) > KEY_POOL_CEILING\n // Locations (AGL-286): the stock dialog buckets deltas when they exist.\n const { data: locationDocs } = useFirestoreCollection<any>(\n () => query(collection(firestore, 'hosts', hostId, 'locations'), limit(25)),\n [firestore, hostId],\n { idField: '$id' },\n )\n /*\n * The PAGE on screen, lifted from legacy shapes. It is what the table\n * draws, the reserved-stock clock watches and the zone hands its widgets,\n * and nothing narrows it: the query already answered every filter, and\n * soft-deleted products are outside its scope.\n */\n const products = useMemo<ProductRow[]>(\n () =>\n (productDocs ?? []).map((product: any) => ({\n ...CommerceModel.liftLegacyProduct(product),\n $id: product.$id,\n })),\n [productDocs],\n )\n // Listed but not yet priced (AGL-3676), among the products on screen.\n const comingSoon = useMemo(\n () =>\n products.filter(\n (product) => product.status === 'active' && CommerceModel.productPriceMissing(product),\n ),\n [products],\n )\n const columnSort = useListColumnSort<ProductRow>({\n sorts: PRODUCT_LIST_COLUMN_SORTS,\n defaultSort: PRODUCT_LIST_COLUMN_SORTS[0],\n sort: askedSort,\n onSortChange: setAskedSort,\n orderBy: plan.orderBy,\n rows: products,\n pageSorts: PRODUCT_PAGE_SORTS,\n headers: PRODUCT_PAGE_SORT_HEADERS,\n })\n\n /**\n * A CLOCK, because a hold lapses without anybody writing anything\n * (AGL-2356).\n *\n * The Firestore listener re-renders on document changes, and a reservation\n * expiring is not one — `expiresAtMs` simply passes. Without a tick the\n * \"reserved\" caption below would keep naming a hold that lapsed twenty\n * minutes ago, which is worse than not showing it at all: the merchant would\n * be told stock is spoken for while the storefront happily sells it.\n *\n * Only runs while something is actually held, so an ordinary catalog costs\n * no timer. A NEW hold arrives as a document change, which re-renders, which\n * re-arms this. It watches the page on screen, which is every row whose\n * caption it keeps true.\n */\n const [nowMs, setNowMs] = useState(() => Date.now())\n const anyHeld = useMemo(\n () =>\n products.some(\n (product) => CommerceModel.heldProductUnits(product, nowMs) > 0,\n ),\n [products, nowMs],\n )\n useEffect(() => {\n if (!anyHeld) return undefined\n const timer = setInterval(() => setNowMs(Date.now()), 60_000)\n return () => clearInterval(timer)\n }, [anyHeld])\n\n /**\n * The catalog HEAD-COUNT is a server aggregate, not the length of the\n * capped listener (AGL-1716, the AGL-1706 shape).\n *\n * The listener is `limit(500)` — correctly; a 25,000-product catalog does\n * not belong in a table. What it must not do is answer \"how many products\n * does this site have\", and it did: the length saturated at 500 and was\n * handed to `checkQuota(org, 'productsPerHost', …)` and to the batch check\n * the CSV importer runs. The bands are 2,500 / 10,000 / 25,000 above the\n * window, so on Pro and up the check compared 500 against thousands and\n * could never refuse — the card offered headroom and `api/hosts/resources`\n * then refused the create, which is the AGL-1716 shape exactly.\n *\n * It counts LIVE products, as `api/hosts/resources` and every product\n * writer count them (AGL-3918): a deleted product frees its slot, an\n * archived or draft one keeps it. Every document less the tombstones\n * (`getLiveCountFromServer`), so the card asks the enforcing route's\n * question in the enforcing route's terms.\n *\n * The table pages its own query (AGL-3321) and answers a different\n * question — which products match — so it never stands in for this. A\n * one-shot goes stale where a listener refreshed for free, so the count is\n * re-read after any mutation that moves it.\n *\n * No counting RULE moves: `checkQuota` is untouched and `report-usage`\n * meters contacts, storage and API requests — never the catalog.\n */\n const [productCountEpoch, setProductCountEpoch] = useState(0)\n const [serverProductCount, setServerProductCount] = useState<number | null>(\n null,\n )\n useEffect(() => {\n let active = true\n void getLiveCountFromServer(collection(firestore, 'hosts', hostId, 'products'))\n .then((count) => {\n if (active) setServerProductCount(count)\n })\n .catch(() => {\n // Falls back to the live-row count below — a LOWER bound, and this\n // card's prior behaviour. Deliberately not 0: `checkQuota` answers\n // from whatever it is handed, and 0 used is a confident wrong\n // number in the flattering direction.\n })\n return () => {\n active = false\n }\n }, [firestore, hostId, productCountEpoch])\n // The fallback, pending or denied: the live products the table's query has\n // read, probe row included. Each is a product the site has, so it can only\n // UNDERSTATE, never overstate, and nothing it gates fires on a count larger\n // than the truth (AGL-471).\n const loadedProductCount = productWindow?.length ?? 0\n const productCount = serverProductCount ?? loadedProductCount\n // Gate only once the org doc has loaded: an unresolved org reads as the\n // free tier's 0-product cap, which swallowed every Add/Duplicate click.\n // The resources API (AGL-473) stays the authoritative cap on create.\n //\n // `planReady` rather than `org` truthiness (AGL-1064): a host with no\n // owning org never produces one, and waiting on the value alone would\n // hold this open forever. The old fallback allowed UNLIMITED during the\n // window — safe only because the API re-checks; the controls now disable\n // instead, which does not lean on that backstop.\n // NULL means \"not known yet\", which a boolean cannot say — the old\n // fallback object claimed UNLIMITED and every consumer believed it.\n const productQuota = useMemo(\n () =>\n planReady\n ? Aglyn.checkQuota(org, 'productsPerHost', productCount)\n : null,\n [org, planReady, productCount],\n )\n\n const handleDuplicate = useCallback(\n (product: ProductRow) => async () => {\n // Plan unknown — the control is disabled; this guards the race.\n if (!productQuota) return\n if (!productQuota.allowed) {\n return void enqueueSnackbar(\n `Your plan includes ${productQuota.limit} products — upgrade for more`,\n { variant: 'info', persist: false },\n )\n }\n try {\n /*\n * The copy's slug is asked of the store, so a second Duplicate of\n * the same product does not mint a second `-copy` (AGL-3321), and\n * its search keys are the COPY's: spreading the source's would sort\n * and find it by the source's name.\n */\n const slug = await productSlugLedger(firestore, hostId).claim(\n CommerceModel.productDuplicateSlugBase(product.slug, product.name),\n )\n // Duplicate is a create — rides the quota-enforcing API (AGL-473).\n // The source's SKU and barcode keys are not copied: the copy's own\n // are derived (`productDuplicateFields`), and a copy of a product\n // with none must have none.\n await createHostResource({\n hostId,\n resource: 'product',\n data: {\n ...CommerceModel.productDuplicateFields(product, slug, Date.now()),\n // The COPY's smart collections: its name differs from the source's.\n ...(await productCollectionFields(firestore, hostId, {\n ...product,\n name: CommerceModel.productDuplicateName(product.name),\n })),\n },\n })\n // A create moves the count and the aggregate is a one-shot — the\n // listener refreshes the ROWS for free, the count has to be asked\n // again or the cap drifts stale for the rest of the session.\n setProductCountEpoch((epoch) => epoch + 1)\n enqueueSnackbar('Product duplicated as draft', {\n variant: 'success',\n persist: false,\n })\n } catch (error: any) {\n enqueueSnackbar(error?.message ?? 'Could not duplicate product', {\n variant: 'warning',\n persist: false,\n })\n }\n },\n [firestore, hostId, createHostResource, enqueueSnackbar, productQuota],\n )\n\n const handleStatus = useCallback(\n (product: ProductRow, status: CommerceModel.ProductStatus) => async () => {\n // A product with a variant nobody has priced may go live (AGL-3676):\n // the storefront says \"Price coming soon\" and sells it nowhere until\n // the editor has a price for each. The owner is told, not stopped.\n if (status === 'active' && CommerceModel.productPriceMissing(product)) {\n enqueueSnackbar(\n `${product.name} has no price yet: visitors will see “Price coming soon” and can’t buy it.`,\n { variant: 'info', persist: false },\n )\n }\n await writeSiteWideChange({\n firestore,\n user,\n hostId,\n write: (batch) =>\n batch.update(doc(firestore, 'hosts', hostId, 'products', product.$id), {\n status,\n updatedAtMs: Date.now(),\n updatedAt: Timestamp.now(),\n }),\n })\n },\n [firestore, hostId, user, enqueueSnackbar],\n )\n\n const handleDelete = useCallback(\n (product: ProductRow) => async () => {\n const confirmed = await confirm({\n title: 'Delete this product?',\n description:\n `\"${product.name}\" stops being purchasable; blocks referencing ` +\n 'it show a checkout error until repointed.',\n confirmationText: 'Delete',\n confirmationButtonProps: { color: 'error' },\n })\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n await writeSiteWideChange({\n firestore,\n user,\n hostId,\n write: (batch) =>\n batch.update(doc(firestore, 'hosts', hostId, 'products', product.$id), {\n deletedAt: Timestamp.now(),\n }),\n })\n // Deleting frees the slot (AGL-3918), so the head-count is re-read.\n setProductCountEpoch((epoch) => epoch + 1)\n enqueueSnackbar('Product deleted', { variant: 'success', persist: false })\n },\n [confirm, firestore, hostId, user, enqueueSnackbar],\n )\n\n /*\n * Import and export (AGL-3531) run on the console's transfer wizard and\n * dialog, on the `commerce.products` resource: a product matched by\n * handle, SKU or ID is updated, every step is a choice, and the export\n * writes what the table's query matches — its filters and search — on the\n * server, a row per variant, with a Shopify preset.\n *\n * When the wizard closes on its results, the products the import created\n * reach the hub's zone with the options its After import step set.\n */\n const openImport = useCallback(() => {\n transfer?.openImport({\n resource: COMMERCE_PRODUCTS_TRANSFER,\n scope: 'host',\n hostId,\n onFinished: () => {\n setProductCountEpoch((epoch) => epoch + 1)\n void (async () => {\n const { takeProductImport, createdProductIds } = await import(\n '../../transfer/product-import-results'\n )\n const finished = takeProductImport(hostId)\n if (!finished) return\n const productIds = await createdProductIds(user, finished)\n setLastImport({\n key: `${finished.jobId}-${productIds.length}`,\n productIds,\n options: finished.options,\n })\n })()\n },\n })\n }, [transfer, hostId, user])\n const openExport = useCallback(() => {\n transfer?.openExport({\n resource: COMMERCE_PRODUCTS_TRANSFER,\n scope: 'host',\n hostId,\n ...(filtering\n ? {\n filter: {\n label: 'the products the list’s filters and search find',\n value: commerceListFilter(plan),\n },\n }\n : {}),\n })\n }, [transfer, hostId, filtering, plan])\n\n const handleAdjustSave = useCallback(async () => {\n if (!adjusting) return\n // The update and the ledger row, as the native stock route writes them (AGL-3652).\n const write = CommerceModel.stockAdjustmentWrite(\n adjusting.product,\n adjusting.product.$id,\n {\n variantId: adjusting.variantId,\n delta: Number(adjusting.delta),\n reason: adjusting.reason,\n locationId: adjusting.locationId || undefined,\n },\n Date.now(),\n )\n if (!write) return\n /**\n * Refuse the adjustment when the seed is unconfirmed (AGL-1358).\n *\n * `adjustVariantInventory` reads counts off the seeded product and\n * returns a whole new `variants` array, which is then written over the\n * stored one — so this is a full replace of live stock, not a delta, and\n * `merge` could not help. The refusal also has to cover the ADJUSTMENT\n * LOG, which is why the guard wraps both writes: a logged adjustment\n * whose stock write never happened is worse than neither, because the\n * history then disagrees with the count it is supposed to explain.\n */\n const verdict = await writeGuardedBySeed(\n {\n subject: 'stock',\n unreadable: productsStatus === 'error',\n fromCache: productsFromCache,\n },\n async () => {\n await writeSiteWideChange({\n firestore,\n user,\n hostId,\n write: (batch) =>\n batch.update(\n doc(firestore, 'hosts', hostId, 'products', adjusting.product.$id),\n // The total and the In stock verdict move with the count (AGL-3321).\n write.update,\n ),\n })\n // Adjustment history (AGL-281): the same log the sale webhook writes.\n await addDoc(\n collection(firestore, 'hosts', hostId, 'inventoryAdjustments'),\n write.ledger,\n )\n },\n )\n // Keep the dialog open with the typed delta rather than failing silently.\n if (!verdict.ok) {\n return void enqueueSnackbar(verdict.message, {\n variant: 'warning',\n persist: false,\n })\n }\n setAdjusting(null)\n enqueueSnackbar('Stock adjusted', { variant: 'success', persist: false })\n }, [\n user,\n adjusting,\n firestore,\n hostId,\n enqueueSnackbar,\n productsFromCache,\n productsStatus,\n ])\n\n /** The hub's allowance check for a batch of `count` new products, for its zone. */\n const roomFor = useCallback(\n (count: number) =>\n planReady\n ? Aglyn.checkQuota(org, 'productsPerHost', productCount + count - 1)\n : null,\n [org, planReady, productCount],\n )\n const onCatalogCreated = useCallback(\n () => setProductCountEpoch((epoch) => epoch + 1),\n [],\n )\n\n const formatPrice = (product: ProductRow) => {\n const [min, max] = CommerceModel.productPriceRange(product)\n return min === max ? `$${min}` : `$${min}–$${max}`\n }\n const formatStock = (product: ProductRow) => {\n const total = CommerceModel.productInventory(product)\n if (total == null) return '—'\n return total > 0 ? String(total) : 'Sold out'\n }\n /**\n * RESERVED UNITS, NAMED (AGL-2356).\n *\n * Checkout now takes a hold on the units a live session is about to buy, and\n * that hold deliberately does NOT move `inventory` — the shelf count means\n * units on the shelf, and half the product reads it that way. The\n * consequence a merchant meets is that the storefront can refuse a sale of\n * the third unit while this column says `3`, and without this caption the\n * number is right and the behaviour looks broken.\n *\n * The house style of `stock-movements-card.component.tsx`: a trailing\n * `caption` in `text.secondary`, rendered ONLY when there is something to\n * say. Nothing is held on the overwhelming majority of products, so the\n * column reads exactly as it does today.\n */\n const formatHeld = (product: ProductRow) => {\n const held = CommerceModel.heldProductUnits(product, nowMs)\n return held > 0 ? ` (${held} reserved)` : ''\n }\n\n const productColumns = listFilterGridColumns(\n [\n {\n field: 'name',\n headerName: 'Product',\n flex: 1,\n minWidth: 200,\n renderCell: ({ row }: { row: ProductRow }) => (\n <Stack sx={{ minWidth: 0 }}>\n <Typography variant=\"body2\" noWrap>\n {row.name}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {`/${row.slug} · id: ${row.$id}`}\n </Typography>\n </Stack>\n ),\n },\n {\n field: 'status',\n headerName: 'Status',\n width: 120,\n renderCell: ({ row }: { row: ProductRow }) => (\n <Chip\n label={row.status}\n size=\"small\"\n color={STATUS_COLOR[row.status] ?? 'default'}\n variant=\"outlined\"\n />\n ),\n },\n { field: 'type', headerName: 'Type', width: 110 },\n {\n field: 'priceUsd',\n headerName: 'Price',\n width: 130,\n renderCell: ({ row }: { row: ProductRow }) =>\n CommerceModel.productPriceMissing(row) ? (\n <Chip label=\"Set a price\" size=\"small\" color=\"warning\" variant=\"outlined\" />\n ) : (\n formatPrice(row)\n ),\n },\n {\n field: 'stock',\n headerName: 'Stock',\n width: 150,\n renderCell: ({ row }: { row: ProductRow }) => (\n <span>\n {formatStock(row)}\n {formatHeld(row) ? (\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"span\">\n {formatHeld(row)}\n </Typography>\n ) : null}\n </span>\n ),\n },\n {\n field: 'variants',\n headerName: 'Variants',\n width: 100,\n valueGetter: (_value: unknown, row: ProductRow) => row.variants.length,\n },\n listActionsColumn(\n (product: ProductRow) => (\n <Stack\n direction=\"row\"\n spacing={0.5}\n sx={{ justifyContent: 'flex-end', whiteSpace: 'nowrap' }}\n >\n <Button size=\"small\" onClick={() => setEditing(product)}>\n {'Edit'}\n </Button>\n <Button size=\"small\" onClick={handleDuplicate(product)}>\n {'Duplicate'}\n </Button>\n {product.type === 'physical' ? (\n <Button size=\"small\" onClick={() => setLabelsFor(product)}>\n {'Labels'}\n </Button>\n ) : null}\n {product.type === 'digital' ? (\n <Button size=\"small\" onClick={() => setKeysFor(product)}>\n {'Keys'}\n </Button>\n ) : null}\n {CommerceModel.productInventory(product) != null ? (\n <Button\n size=\"small\"\n onClick={() =>\n setAdjusting({\n product,\n variantId:\n product.variants.find(\n (variant) => variant.inventory != null,\n )?.id ?? product.variants[0].id,\n delta: '',\n reason: 'restock',\n locationId:\n (locationDocs ?? []).find(\n (location: any) => location.isDefault,\n )?.$id ??\n (locationDocs ?? [])[0]?.$id ??\n '',\n })\n }\n >\n {'Stock'}\n </Button>\n ) : null}\n <Button\n size=\"small\"\n onClick={handleStatus(\n product,\n product.status === 'archived' ? 'active' : 'archived',\n )}\n >\n {product.status === 'archived' ? 'Activate' : 'Archive'}\n </Button>\n <Button size=\"small\" color=\"error\" onClick={handleDelete(product)}>\n {'Delete'}\n </Button>\n </Stack>\n ),\n { width: 470 },\n ),\n ] as GridColDef[],\n PRODUCT_LIST_FIELDS,\n PRODUCT_LIST_OPTIONS,\n PRODUCT_LIST_HEADERS,\n )\n\n /** Add product: the quota first, then the create drawer. */\n const startAddProduct = () => {\n if (!productQuota) return\n if (!productQuota.allowed) {\n return void enqueueSnackbar(\n `Your plan includes ${productQuota.limit} products — ` + 'upgrade for more',\n { variant: 'info', persist: false },\n )\n }\n setCreating(true)\n }\n\n /*\n * Other ways to start a product (AGL-3596): the `productsCreate` zone this\n * plugin declares, drawn through the shell's gated slot beside Add product\n * and again in the empty catalog. `null` outside the console shell. The\n * org is not this page's to know, so a widget reads it from the site.\n */\n const createZone = CreateZone ? (\n <CreateZone slot={PRODUCTS_CREATE_ZONE.id} hostId={hostId} orgId={undefined} />\n ) : null\n\n return (\n <CardDisplay\n /*\n * The header count is the SITE'S catalog, not the rows in hand.\n *\n * It was `products.length`, which is the filtered, ceilinged, now-paged\n * view — so it read 500 on a 25,000-product catalog and would read 10\n * once the table paged. `productCount` is the same server aggregate the\n * quota gate uses. Under a filter there is no honest number to put here:\n * the aggregate counts the whole catalog and the view counts matches, so\n * neither describes what the reader is looking at, and the footer's own\n * count line answers it instead.\n */\n header={\n filtering\n ? 'Products'\n : `Products${productCount ? ` (${productCount})` : ''}`\n }\n help={pluginDocsHelp('commerce', { anchor: '#products-hub' })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={2}>\n <Stack direction={{ xs: 'column', sm: 'row' }} spacing={1}>\n <Box sx={{ flex: 1 }} />\n {createZone}\n <Button\n variant=\"contained\"\n color=\"primary\"\n size=\"small\"\n disabled={!productQuota}\n onClick={startAddProduct}\n >\n {'Add product'}\n </Button>\n {/* Each only for whom the route takes it (AGL-3554); an import\n left unfinished, reopened where it stopped (AGL-3549). */}\n <TransferResumeImport\n target={{ resource: COMMERCE_PRODUCTS_TRANSFER, scope: 'host', hostId }}\n onFinished={() => setProductCountEpoch((epoch) => epoch + 1)}\n />\n {transfer?.can('import', { resource: COMMERCE_PRODUCTS_TRANSFER, scope: 'host', hostId }) ? (\n <Button size=\"small\" disabled={!planReady} onClick={openImport}>\n {'Import'}\n </Button>\n ) : null}\n {transfer?.can('export', { resource: COMMERCE_PRODUCTS_TRANSFER, scope: 'host', hostId }) ? (\n <Button\n size=\"small\"\n // Nothing matches: there is nothing for the export to write.\n disabled={products.length === 0 && page === 0}\n onClick={openExport}\n >\n {'Export'}\n </Button>\n ) : null}\n </Stack>\n {/* The cap, standing rather than only on refusal (AGL-2113). The\n count is `productCount` — the site's products, the same number\n the gate above counts — not `products.length`, which is one page\n of the table's query (AGL-1716). */}\n {/* Products listed before they have a price (AGL-3676) — a guided\n start's — show \"Price coming soon\" on the store and sell nowhere\n until each is priced: the one step left before the store sells. */}\n {comingSoon.length ? (\n <Alert severity=\"info\">\n {`Set prices to start selling: ${comingSoon.length === 1 ? `${comingSoon[0].name} shows` : `${comingSoon.length} products show`} “Price coming soon” on your store until you set ${comingSoon.length === 1 ? 'its price' : 'their prices'}. Open each one and give every variant a price.`}\n </Alert>\n ) : null}\n <QuotaReadoutComponent\n ready={productQuota !== null}\n used={productCount}\n limit={productQuota?.limit ?? 0}\n noun=\"product\"\n />\n {/* The zone's widgets are handed the PAGE on screen — the products\n the reader has found and is looking at. Every write the zone makes\n asks the store for what it must not duplicate, never these rows. */}\n <ProductsHubZone\n hostId={hostId}\n products={products}\n roomFor={roomFor}\n lastImport={lastImport}\n onCreated={onCatalogCreated}\n />\n <ListFilterChips\n fields={PRODUCT_LIST_FIELDS}\n headers={PRODUCT_LIST_HEADERS}\n clauses={gridFilter.clauses}\n onChange={gridFilter.setClauses}\n options={PRODUCT_LIST_OPTIONS}\n />\n <ListQueryNotices\n refused={listQueryRefusals(plan.refused, {\n fields: PRODUCT_LIST_FIELDS,\n headers: PRODUCT_LIST_HEADERS,\n options: PRODUCT_LIST_OPTIONS,\n })}\n notices={[...plan.notices, ...columnSort.notices]}\n />\n <ListTable\n aria-label=\"Products\"\n rows={columnSort.rows}\n columns={productColumns}\n loading={productsStatus === 'loading'}\n // One page of the query, turned by the footer below: the grid\n // neither slices, filters nor sorts it.\n hideFooter\n filterMode=\"server\"\n filterModel={gridFilter.filterModel}\n onFilterModelChange={gridFilter.onFilterModelChange}\n quickFilter\n // Every header order is the query's, or the page's (`columnSort`).\n columnSort={columnSort}\n initialState={{\n columns: {\n columnVisibilityModel: hiddenFilterVisibility(\n PRODUCT_LIST_FIELDS,\n PRODUCT_VISIBLE_COLUMNS,\n ),\n },\n }}\n noRowsLabel={\n filtering ? 'No products match these filters' : 'No products yet'\n }\n noRowsDescription={\n filtering\n ? undefined\n : 'Build your catalog: add a product, then drop commerce ' +\n 'blocks on any page in Besigner.'\n }\n noRowsAction={\n filtering ? undefined : (\n <Stack direction=\"row\" spacing={1}>\n {createZone}\n <Button variant=\"contained\" disabled={!productQuota} onClick={startAddProduct}>\n {'Add your first product'}\n </Button>\n </Stack>\n )\n }\n />\n {products.length === 0 && page === 0 && productsStatus !== 'loading' ? null : (\n /* The count line is the PAGE's. `hasMore` is a fact from the probe\n row; the site's total is `productCount` above, which counts the\n whole catalog rather than the matches, so it is not this. */\n <ListPagination\n page={page}\n pageSize={pageSize}\n rowCount={products.length}\n hasMore={hasMore}\n onPageChange={setPage}\n onPageSizeChange={setPageSize}\n />\n )}\n </Stack>\n <ProductLabelsDialog product={labelsFor} onClose={() => setLabelsFor(null)} />\n <Dialog\n open={Boolean(keysFor)}\n onClose={() => setKeysFor(null)}\n maxWidth=\"sm\"\n fullWidth\n >\n <DialogTitle>{`License keys — ${keysFor?.name ?? ''}`}</DialogTitle>\n <DialogContent sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>\n {(() => {\n // The query already asked for this product's keys, so no second\n // filter here: narrowing a window that was already narrowed is how\n // a reader comes to believe the count covers the pool.\n const productKeys = (keyDocs ?? []).slice(0, KEY_POOL_CEILING)\n const available = productKeys.filter(\n (key: any) => !key.assignedAtMs,\n )\n // RETIRED KEYS ARE COUNTED SEPARATELY (AGL-2454). A refund retires\n // the key rather than returning it to the pool — the buyer already\n // holds the string, so reissuing it would give two people one\n // working key — and without this line the merchant simply watches\n // \"available\" fall with nothing to explain where the key went.\n const retired = productKeys.filter((key: any) => key.revokedAtMs)\n const assigned =\n productKeys.length - available.length - retired.length\n return (\n <>\n <Typography variant=\"body2\" color=\"text.secondary\" sx={{ mt: 1 }}>\n {`${available.length} available · ${assigned} assigned` +\n (retired.length\n ? ` · ${retired.length} retired (refunded or revoked — not reissued)`\n : '') +\n (keyPoolTruncated\n ? `, of the ${KEY_POOL_CEILING} keys read`\n : '') +\n '. Keys deliver automatically on purchase (receipt + account).'}\n </Typography>\n {available.slice(0, 8).map((key: any) => (\n <Stack\n key={key.$id}\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center' }}\n >\n <Typography variant=\"caption\" sx={{ flex: 1, fontFamily: 'monospace' }} noWrap>\n {key.key}\n </Typography>\n <Button\n size=\"small\"\n color=\"error\"\n onClick={() =>\n updateDoc(\n doc(firestore, 'hosts', hostId, 'licenseKeys', key.$id),\n { revokedAtMs: Date.now(), assignedAtMs: Date.now() },\n )\n }\n >\n {'Revoke'}\n </Button>\n </Stack>\n ))}\n <TextField\n label=\"Add keys (one per line)\"\n value={keysText}\n onChange={(event) => setKeysText(event.target.value)}\n size=\"small\"\n multiline\n minRows={3}\n />\n </>\n )\n })()}\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setKeysFor(null)}>{'Close'}</Button>\n <Button\n variant=\"contained\"\n color=\"primary\"\n disabled={!keysText.trim()}\n onClick={async () => {\n const keys = keysText\n .split('\\n')\n .map((key) => key.trim())\n .filter(Boolean)\n .slice(0, 200)\n for (const key of keys) {\n await addDoc(\n collection(firestore, 'hosts', hostId, 'licenseKeys'),\n {\n productId: keysFor!.$id,\n key,\n assignedAtMs: null,\n createdAtMs: Date.now(),\n },\n )\n }\n setKeysText('')\n enqueueSnackbar(`Added ${keys.length} keys`, {\n variant: 'success',\n persist: false,\n })\n }}\n >\n {'Add keys'}\n </Button>\n </DialogActions>\n </Dialog>\n <Dialog\n open={Boolean(adjusting)}\n onClose={() => setAdjusting(null)}\n maxWidth=\"xs\"\n fullWidth\n >\n <DialogTitle>{`Adjust stock — ${adjusting?.product.name ?? ''}`}</DialogTitle>\n <DialogContent sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>\n <TextField\n label=\"Variant\"\n value={adjusting?.variantId ?? ''}\n onChange={(event) =>\n setAdjusting((prev) =>\n prev ? { ...prev, variantId: event.target.value } : prev,\n )\n }\n size=\"small\"\n select\n sx={{ mt: 1 }}\n >\n {(adjusting?.product.variants ?? [])\n .filter((variant) => variant.inventory != null)\n .map((variant) => (\n <MenuItem key={variant.id} value={variant.id}>\n {`${Object.values(variant.options ?? {}).join(' / ') || 'Default'} — ${variant.inventory} in stock${\n CommerceModel.heldVariantUnits(\n adjusting?.product,\n variant.id,\n nowMs,\n ) > 0\n ? `, ${CommerceModel.heldVariantUnits(\n adjusting?.product,\n variant.id,\n nowMs,\n )} reserved in checkout`\n : ''\n }`}\n </MenuItem>\n ))}\n </TextField>\n {(locationDocs?.length ?? 0) > 1 ? (\n <TextField\n label=\"Location\"\n value={adjusting?.locationId ?? ''}\n onChange={(event) =>\n setAdjusting((prev) =>\n prev ? { ...prev, locationId: event.target.value } : prev,\n )\n }\n size=\"small\"\n select\n >\n {(locationDocs ?? []).map((location: any) => (\n <MenuItem key={location.$id} value={location.$id}>\n {location.name}\n </MenuItem>\n ))}\n </TextField>\n ) : null}\n <TextField\n label=\"Change\"\n placeholder=\"+10 or -3\"\n value={adjusting?.delta ?? ''}\n onChange={(event) =>\n setAdjusting((prev) =>\n prev\n ? {\n ...prev,\n delta: event.target.value.replace(/[^0-9+-]/g, ''),\n }\n : prev,\n )\n }\n size=\"small\"\n />\n <TextField\n label=\"Reason\"\n value={adjusting?.reason ?? 'restock'}\n onChange={(event) =>\n setAdjusting((prev) =>\n prev\n ? {\n ...prev,\n reason: event.target\n .value as CommerceModel.InventoryAdjustmentReason,\n }\n : prev,\n )\n }\n size=\"small\"\n select\n >\n <MenuItem value=\"restock\">{'Restock'}</MenuItem>\n <MenuItem value=\"correction\">{'Correction'}</MenuItem>\n <MenuItem value=\"damage\">{'Damaged'}</MenuItem>\n <MenuItem value=\"refund\">{'Refund return'}</MenuItem>\n </TextField>\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setAdjusting(null)}>{'Cancel'}</Button>\n <Button\n variant=\"contained\"\n color=\"primary\"\n disabled={!Math.round(Number(adjusting?.delta))}\n onClick={handleAdjustSave}\n >\n {'Apply'}\n </Button>\n </DialogActions>\n </Dialog>\n <ProductEditorDialog\n key={editing?.$id ?? (creating ? 'new' : 'closed')}\n hostId={hostId}\n product={editing}\n // The editor is seeded from a row of THIS listener and replaces the\n // whole document, so the freshness verdict belongs here (AGL-1358) —\n // the dialog has no listener of its own to ask.\n seedFromCache={productsFromCache}\n seedUnreadable={productsStatus === 'error'}\n open={creating || editing !== null}\n onClose={() => {\n setEditing(null)\n setCreating(false)\n // The dialog reports no verdict, so a create and a cancel look\n // the same from here. Re-reading on both is one aggregate and\n // keeps the cap off the dialog's shoulders (AGL-1716).\n setProductCountEpoch((epoch) => epoch + 1)\n }}\n />\n </CardDisplay>\n )\n}\nProductsHubCard.displayName = 'ProductsHubCard'\n\nexport default ProductsHubCard\n"],"names":["TransferResumeImport","useConsoleWidgetSlot","Aglyn","CommerceModel","PRODUCT_LIST_BASE","PRODUCT_LIST_COLUMN_SORTS","PRODUCT_LIST_FIELDS","PRODUCT_LIST_HEADERS","PRODUCT_LIST_OPTIONS","PRODUCT_LIST_QUERY","PRODUCT_LIST_SELECT_FIELDS","productListRequestClauses","CardDisplay","useConfirmationContext","ListFilterChips","ListPagination","ListQueryNotices","listQueryRefusals","ListTable","listActionsColumn","QuotaReadoutComponent","hiddenFilterVisibility","listFilterGridColumns","useListColumnSort","useListGridFilter","useSnackbar","Timestamp","Alert","Box","Button","Chip","Dialog","DialogActions","DialogContent","DialogTitle","MenuItem","Stack","TextField","Typography","addDoc","collection","doc","documentId","limit","orderBy","query","updateDoc","where","getLiveCountFromServer","useCallback","useEffect","useMemo","useState","useFirestore","useUser","writeSiteWideChange","useFirestoreCollection","writeGuardedBySeed","useListQuery","useHostResourceApi","useOrgPlan","ProductLabelsDialog","ProductEditorDialog","productSlugLedger","productCollectionFields","ProductsHubZone","pluginDocsHelp","PRODUCTS_CREATE_ZONE","commerceListFilter","COMMERCE_PRODUCTS_TRANSFER","KEY_POOL_CEILING","PRODUCT_PAGE_SORTS","stock","row","productInventory","variants","length","PRODUCT_PAGE_SORT_HEADERS","STATUS_COLOR","active","draft","archived","PRODUCT_VISIBLE_COLUMNS","ProductsHubCard","props","hostId","CreateZone","firestore","data","user","enqueueSnackbar","createHostResource","org","ready","planReady","confirm","gridFilter","selectFields","filtering","clauses","searchWords","some","word","trim","askedSort","setAskedSort","editing","setEditing","creating","setCreating","adjusting","setAdjusting","keysFor","setKeysFor","labelsFor","setLabelsFor","keysText","setKeysText","lastImport","setLastImport","transfer","useTransferLauncher","rows","productDocs","productWindow","hasMore","page","setPage","pageSize","setPageSize","plan","status","productsStatus","fromCache","productsFromCache","declaration","request","search","base","sort","deps","idField","confirmDisappearances","keyDocs","$id","keyPoolTruncated","locationDocs","products","map","product","liftLegacyProduct","comingSoon","filter","productPriceMissing","columnSort","sorts","defaultSort","onSortChange","pageSorts","headers","nowMs","setNowMs","Date","now","anyHeld","heldProductUnits","undefined","timer","setInterval","clearInterval","productCountEpoch","setProductCountEpoch","serverProductCount","setServerProductCount","then","count","catch","loadedProductCount","productCount","productQuota","checkQuota","handleDuplicate","allowed","variant","persist","slug","claim","productDuplicateSlugBase","name","resource","productDuplicateFields","productDuplicateName","epoch","error","message","handleStatus","write","batch","update","updatedAtMs","updatedAt","handleDelete","confirmed","title","description","confirmationText","confirmationButtonProps","color","deletedAt","openImport","scope","onFinished","takeProductImport","createdProductIds","finished","productIds","key","jobId","options","openExport","label","value","handleAdjustSave","stockAdjustmentWrite","variantId","delta","Number","reason","locationId","verdict","subject","unreadable","ledger","ok","roomFor","onCatalogCreated","formatPrice","min","max","productPriceRange","formatStock","total","String","formatHeld","held","productColumns","field","headerName","flex","minWidth","renderCell","sx","noWrap","width","size","span","component","valueGetter","_value","direction","spacing","justifyContent","whiteSpace","onClick","type","find","inventory","id","location","isDefault","startAddProduct","createZone","slot","orgId","header","help","anchor","contentGutterX","contentGutterY","xs","sm","disabled","target","can","severity","used","noun","onCreated","fields","onChange","setClauses","refused","notices","aria-label","columns","loading","hideFooter","filterMode","filterModel","onFilterModelChange","quickFilter","initialState","columnVisibilityModel","noRowsLabel","noRowsDescription","noRowsAction","rowCount","onPageChange","onPageSizeChange","onClose","open","Boolean","maxWidth","fullWidth","display","flexDirection","gap","productKeys","slice","available","assignedAtMs","retired","revokedAtMs","assigned","mt","alignItems","fontFamily","event","multiline","minRows","keys","split","productId","createdAtMs","prev","select","Object","values","join","heldVariantUnits","placeholder","replace","Math","round","seedFromCache","seedUnreadable","displayName"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/products-hub-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 { TransferResumeImport } from '@aglyn/aglyn/app-utils/transfer-resume-import'\nimport { useConsoleWidgetSlot } from '@aglyn/aglyn/app-utils/console-widget-slot-context'\nimport * as Aglyn from '@aglyn/aglyn'\nimport * as CommerceModel from '../../model'\nimport {\n PRODUCT_LIST_BASE,\n PRODUCT_LIST_COLUMN_SORTS,\n PRODUCT_LIST_FIELDS,\n PRODUCT_LIST_HEADERS,\n PRODUCT_LIST_OPTIONS,\n PRODUCT_LIST_QUERY,\n PRODUCT_LIST_SELECT_FIELDS,\n productListRequestClauses,\n} from '../../constants/product-list-query'\nimport { CardDisplay, useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport ListFilterChips from '@aglyn/shared-ui-jsx/components/list-filter-chips.component'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport {\n ListQueryNotices,\n listQueryRefusals,\n} from '@aglyn/shared-ui-jsx/components/list-query-notices.component'\nimport {\n ListTable,\n listActionsColumn,\n} from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport QuotaReadoutComponent from '@aglyn/shared-ui-jsx/components/quota-readout.component'\nimport { hiddenFilterVisibility } from '@aglyn/shared-ui-jsx/const/list-filter'\nimport { listFilterGridColumns } from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport type { ListQuerySort } from '@aglyn/shared-ui-jsx/const/list-query-plan'\nimport { useListColumnSort } from '@aglyn/shared-ui-jsx/hooks/use-list-column-sort'\nimport { useListGridFilter } from '@aglyn/shared-ui-jsx/hooks/use-list-grid-filter'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { Timestamp } from '@aglyn/shared-util-timestamp'\nimport {\n Alert,\n Box,\n Button,\n Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n MenuItem,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport {\n addDoc,\n collection,\n doc,\n documentId,\n limit,\n orderBy,\n query,\n updateDoc,\n where,\n} from 'firebase/firestore'\nimport { getLiveCountFromServer } from '@aglyn/aglyn/app-utils/live-document-count.client'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport { useFirestore, useUser } from '@aglyn/tenant-feature-instance'\nimport { writeSiteWideChange } from '@aglyn/tenant-feature-instance/hooks/helpers/site-wide-change'\nimport {\n useFirestoreCollection,\n writeGuardedBySeed,\n} from '@aglyn/tenant-feature-instance'\nimport { useListQuery } from '@aglyn/tenant-feature-instance/hooks/use-list-query'\nimport { useHostResourceApi } from '@aglyn/tenant-feature-instance'\nimport { useOrgPlan } from '@aglyn/tenant-feature-instance'\nimport { ProductLabelsDialog } from './product-labels-dialog.component'\nimport ProductEditorDialog from './product-editor-dialog.component'\nimport { productSlugLedger } from './product-slugs'\nimport { productCollectionFields } from './smart-collections'\nimport ProductsHubZone from './products-hub-zone.component'\nimport { pluginDocsHelp } from '@aglyn/aglyn'\nimport { PRODUCTS_CREATE_ZONE, type ConsoleProductsHubZoneProps } from './product-zones'\nimport { commerceListFilter } from '../../transfer/list-filter'\nimport { COMMERCE_PRODUCTS_TRANSFER } from '../../transfer/transfer-keys'\n\n/**\n * How many of ONE product's license keys the dialog reads.\n *\n * Per product, not per store: the query carries the `productId` equality, so\n * this bounds a single pool rather than the site's whole key collection.\n */\nconst KEY_POOL_CEILING = 500\n\nexport interface ProductsHubCardProps {\n hostId: string\n}\n\ntype ProductRow = CommerceModel.HostProduct & { $id: string }\n\n/**\n * Stock and Variants are read from inside `variants`, which no query can\n * order by, so they sort the page on screen (AGL-3680); Product, Status, Type\n * and Price are the query's order (`PRODUCT_LIST_COLUMN_SORTS`).\n */\nconst PRODUCT_PAGE_SORTS = {\n stock: (row: ProductRow) => CommerceModel.productInventory(row),\n variants: (row: ProductRow) => row.variants.length,\n}\nconst PRODUCT_PAGE_SORT_HEADERS = { stock: 'Stock', variants: 'Variants' }\n\nconst STATUS_COLOR: Record<string, 'default' | 'success' | 'warning'> = {\n active: 'success',\n draft: 'warning',\n archived: 'default',\n}\n\n/** The grid's own columns; every other declared field is a hidden filter column. */\nconst PRODUCT_VISIBLE_COLUMNS = ['name', 'status', 'type', 'priceUsd', 'stock', 'variants']\n\n/**\n * Products hub v1 (AGL-279): the catalog manager replacing the Commerce\n * Starter card — a table over `hosts/{hostId}/products` whose search and\n * filters are its Firestore query (AGL-3321), full editor dialog, duplicate, archive/activate, soft delete (past\n * order rows keep resolving). Product cap (`productsPerHost`, AGL-278)\n * gated here on create/duplicate/import (AGL-471); server-side\n * enforcement of the client-write path rides AGL-473.\n */\nexport function ProductsHubCard(props: ProductsHubCardProps) {\n const { hostId } = props\n /** The shell's zone renderer, for the `productsCreate` zone (AGL-3596). */\n const CreateZone = useConsoleWidgetSlot()\n const firestore = useFirestore()\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n // Product cap (AGL-471): per-plan `productsPerHost`, same pattern as\n // locations. Console-side gate; server enforcement rides AGL-473.\n const createHostResource = useHostResourceApi()\n const { org, ready: planReady } = useOrgPlan(hostId)\n const { confirm } = useConfirmationContext()\n /*\n * The grid's Filters panel and quick search, bound to the clauses the\n * query below serves (AGL-3321). Every clause and the search word go on\n * that query; see `constants/product-list-query.ts` for what is offered.\n */\n const gridFilter = useListGridFilter({\n selectFields: PRODUCT_LIST_SELECT_FIELDS,\n })\n const filtering =\n gridFilter.clauses.length > 0 ||\n gridFilter.searchWords.some((word) => word.trim())\n // Every header sorts (AGL-3680); A to Z until one is clicked.\n const [askedSort, setAskedSort] = useState<ListQuerySort | null>(\n PRODUCT_LIST_COLUMN_SORTS[0],\n )\n const [editing, setEditing] = useState<ProductRow | null>(null)\n const [creating, setCreating] = useState(false)\n const [adjusting, setAdjusting] = useState<{\n product: ProductRow\n variantId: string\n delta: string\n reason: CommerceModel.InventoryAdjustmentReason\n locationId: string\n } | null>(null)\n const [keysFor, setKeysFor] = useState<ProductRow | null>(null)\n // Product labels for a label printer (AGL-3619).\n const [labelsFor, setLabelsFor] = useState<ProductRow | null>(null)\n const [keysText, setKeysText] = useState('')\n /**\n * What the last import created, with the options its After import step\n * set (AGL-2916, AGL-3531), which the hub's zone hands to its widgets.\n */\n const [lastImport, setLastImport] =\n useState<ConsoleProductsHubZoneProps['lastImport']>(null)\n // The console's import wizard and export dialog; none outside the console.\n const transfer = Aglyn.useTransferLauncher()\n\n const {\n rows: productDocs,\n data: productWindow,\n hasMore,\n page,\n setPage,\n pageSize,\n setPageSize,\n plan,\n status: productsStatus,\n /**\n * The product rows the stock dialog is seeded from are unconfirmed by\n * the server (AGL-1358). A stock adjustment does not write the delta —\n * it recomputes the WHOLE `variants` array from the seeded product and\n * replaces it, so a cached seed silently reverts every sale, return and\n * adjustment the server has recorded since that snapshot, on every\n * variant and every location.\n */\n fromCache: productsFromCache,\n } = useListQuery<any>({\n /*\n * PAGED BY THE QUERY, and every filter ON it (AGL-3321).\n *\n * The table read a five-hundred-product window and put ONE predicate on\n * it — the search or the Status clause — then matched Status beside a\n * search, dropped soft-deleted products and sorted by name over the rows\n * it held, and paged that. Past five hundred products, a match past the\n * window answered \"no products match\", and a page came back short by\n * however many deleted products it had held.\n *\n * Now the plan puts the search word and every clause on one query under\n * the `deletedAt == null` scope, ordered by `nameLower`, and each page is\n * a page of that query — one page plus a probe row, widened as the reader\n * pages forward. Nothing is matched over the rows afterwards; a clause\n * the query cannot hold is refused by name (`ListQueryNotices` below).\n */\n collection: collection(firestore, 'hosts', hostId, 'products'),\n declaration: PRODUCT_LIST_QUERY,\n request: {\n clauses: productListRequestClauses(gridFilter.clauses),\n search: gridFilter.searchWords,\n base: PRODUCT_LIST_BASE,\n sort: askedSort,\n },\n deps: [firestore, hostId],\n idField: '$id',\n /*\n * The scope and the filters read MUTABLE fields — a delete sets\n * `deletedAt`, Archive moves `status`, a rename moves the tokens — so a\n * product can leave this query mid-session, and the SDK can cache that\n * departure as a tombstone at the product's own path (AGL-1196). A\n * disappearance is confirmed against the server before it is believed.\n */\n confirmDisappearances: true,\n })\n /*\n * License key pool (AGL-308) for the open dialog's product — asked FOR that\n * product, and ordered (AGL-2501).\n *\n * This read `limit(500)` over the site's whole `licenseKeys` collection with\n * no `orderBy`, then filtered by `productId` in the browser. Firestore\n * answers an unordered limit in DOCUMENT-ID order, so on a store past five\n * hundred keys the window was an arbitrary five hundred taken across every\n * product — and the \"N available\" line below counted what happened to be in\n * it. A product whose keys all hashed high showed a pool of zero while the\n * storefront went on delivering them.\n *\n * The equality moves into the query, so the window is this product's keys\n * rather than the store's. `orderBy(documentId())` orders on the document\n * NAME, which cannot be absent — every candidate FIELD here (`createdAtMs`,\n * `assignedAtMs`, `revokedAtMs`) is either optional by design or absent on\n * exactly the keys the counts are about. An equality plus an ordering on\n * `__name__` is served by the automatic single-field index, so this adds no\n * composite for anyone to deploy.\n */\n const { data: keyDocs } = useFirestoreCollection<any>(\n () =>\n keysFor\n ? query(\n collection(firestore, 'hosts', hostId, 'licenseKeys'),\n where('productId', '==', keysFor.$id),\n orderBy(documentId()),\n // One past the ceiling, so a pool larger than the window is a\n // fact rather than a guess from `length === 500`.\n limit(KEY_POOL_CEILING + 1),\n )\n : null,\n [firestore, hostId, keysFor?.$id],\n { idField: '$id' },\n )\n /** The key pool is larger than the window, so the counts below are partial. */\n const keyPoolTruncated = (keyDocs?.length ?? 0) > KEY_POOL_CEILING\n // Locations (AGL-286): the stock dialog buckets deltas when they exist.\n const { data: locationDocs } = useFirestoreCollection<any>(\n () => query(collection(firestore, 'hosts', hostId, 'locations'), limit(25)),\n [firestore, hostId],\n { idField: '$id' },\n )\n /*\n * The PAGE on screen, lifted from legacy shapes. It is what the table\n * draws, the reserved-stock clock watches and the zone hands its widgets,\n * and nothing narrows it: the query already answered every filter, and\n * soft-deleted products are outside its scope.\n */\n const products = useMemo<ProductRow[]>(\n () =>\n (productDocs ?? []).map((product: any) => ({\n ...CommerceModel.liftLegacyProduct(product),\n $id: product.$id,\n })),\n [productDocs],\n )\n // Listed but not yet priced (AGL-3676), among the products on screen.\n const comingSoon = useMemo(\n () =>\n products.filter(\n (product) => product.status === 'active' && CommerceModel.productPriceMissing(product),\n ),\n [products],\n )\n const columnSort = useListColumnSort<ProductRow>({\n sorts: PRODUCT_LIST_COLUMN_SORTS,\n defaultSort: PRODUCT_LIST_COLUMN_SORTS[0],\n sort: askedSort,\n onSortChange: setAskedSort,\n orderBy: plan.orderBy,\n rows: products,\n pageSorts: PRODUCT_PAGE_SORTS,\n headers: PRODUCT_PAGE_SORT_HEADERS,\n })\n\n /**\n * A CLOCK, because a hold lapses without anybody writing anything\n * (AGL-2356).\n *\n * The Firestore listener re-renders on document changes, and a reservation\n * expiring is not one — `expiresAtMs` simply passes. Without a tick the\n * \"reserved\" caption below would keep naming a hold that lapsed twenty\n * minutes ago, which is worse than not showing it at all: the merchant would\n * be told stock is spoken for while the storefront happily sells it.\n *\n * Only runs while something is actually held, so an ordinary catalog costs\n * no timer. A NEW hold arrives as a document change, which re-renders, which\n * re-arms this. It watches the page on screen, which is every row whose\n * caption it keeps true.\n */\n const [nowMs, setNowMs] = useState(() => Date.now())\n const anyHeld = useMemo(\n () =>\n products.some(\n (product) => CommerceModel.heldProductUnits(product, nowMs) > 0,\n ),\n [products, nowMs],\n )\n useEffect(() => {\n if (!anyHeld) return undefined\n const timer = setInterval(() => setNowMs(Date.now()), 60_000)\n return () => clearInterval(timer)\n }, [anyHeld])\n\n /**\n * The catalog HEAD-COUNT is a server aggregate, not the length of the\n * capped listener (AGL-1716, the AGL-1706 shape).\n *\n * The listener is `limit(500)` — correctly; a 25,000-product catalog does\n * not belong in a table. What it must not do is answer \"how many products\n * does this site have\", and it did: the length saturated at 500 and was\n * handed to `checkQuota(org, 'productsPerHost', …)` and to the batch check\n * the CSV importer runs. The bands are 2,500 / 10,000 / 25,000 above the\n * window, so on Pro and up the check compared 500 against thousands and\n * could never refuse — the card offered headroom and `api/hosts/resources`\n * then refused the create, which is the AGL-1716 shape exactly.\n *\n * It counts LIVE products, as `api/hosts/resources` and every product\n * writer count them (AGL-3918): a deleted product frees its slot, an\n * archived or draft one keeps it. Every document less the tombstones\n * (`getLiveCountFromServer`), so the card asks the enforcing route's\n * question in the enforcing route's terms.\n *\n * The table pages its own query (AGL-3321) and answers a different\n * question — which products match — so it never stands in for this. A\n * one-shot goes stale where a listener refreshed for free, so the count is\n * re-read after any mutation that moves it.\n *\n * No counting RULE moves: `checkQuota` is untouched and `report-usage`\n * meters contacts, storage and API requests — never the catalog.\n */\n const [productCountEpoch, setProductCountEpoch] = useState(0)\n const [serverProductCount, setServerProductCount] = useState<number | null>(\n null,\n )\n useEffect(() => {\n let active = true\n void getLiveCountFromServer(collection(firestore, 'hosts', hostId, 'products'))\n .then((count) => {\n if (active) setServerProductCount(count)\n })\n .catch(() => {\n // Falls back to the live-row count below — a LOWER bound, and this\n // card's prior behaviour. Deliberately not 0: `checkQuota` answers\n // from whatever it is handed, and 0 used is a confident wrong\n // number in the flattering direction.\n })\n return () => {\n active = false\n }\n }, [firestore, hostId, productCountEpoch])\n // The fallback, pending or denied: the live products the table's query has\n // read, probe row included. Each is a product the site has, so it can only\n // UNDERSTATE, never overstate, and nothing it gates fires on a count larger\n // than the truth (AGL-471).\n const loadedProductCount = productWindow?.length ?? 0\n const productCount = serverProductCount ?? loadedProductCount\n // Gate only once the org doc has loaded: an unresolved org reads as the\n // free tier's 0-product cap, which swallowed every Add/Duplicate click.\n // The resources API (AGL-473) stays the authoritative cap on create.\n //\n // `planReady` rather than `org` truthiness (AGL-1064): a host with no\n // owning org never produces one, and waiting on the value alone would\n // hold this open forever. The old fallback allowed UNLIMITED during the\n // window — safe only because the API re-checks; the controls now disable\n // instead, which does not lean on that backstop.\n // NULL means \"not known yet\", which a boolean cannot say — the old\n // fallback object claimed UNLIMITED and every consumer believed it.\n const productQuota = useMemo(\n () =>\n planReady\n ? Aglyn.checkQuota(org, 'productsPerHost', productCount)\n : null,\n [org, planReady, productCount],\n )\n\n const handleDuplicate = useCallback(\n (product: ProductRow) => async () => {\n // Plan unknown — the control is disabled; this guards the race.\n if (!productQuota) return\n if (!productQuota.allowed) {\n return void enqueueSnackbar(\n `Your plan includes ${productQuota.limit} products — upgrade for more`,\n { variant: 'info', persist: false },\n )\n }\n try {\n /*\n * The copy's slug is asked of the store, so a second Duplicate of\n * the same product does not mint a second `-copy` (AGL-3321), and\n * its search keys are the COPY's: spreading the source's would sort\n * and find it by the source's name.\n */\n const slug = await productSlugLedger(firestore, hostId).claim(\n CommerceModel.productDuplicateSlugBase(product.slug, product.name),\n )\n // Duplicate is a create — rides the quota-enforcing API (AGL-473).\n // The source's SKU and barcode keys are not copied: the copy's own\n // are derived (`productDuplicateFields`), and a copy of a product\n // with none must have none.\n await createHostResource({\n hostId,\n resource: 'product',\n data: {\n ...CommerceModel.productDuplicateFields(product, slug, Date.now()),\n // The COPY's smart collections: its name differs from the source's.\n ...(await productCollectionFields(firestore, hostId, {\n ...product,\n name: CommerceModel.productDuplicateName(product.name),\n })),\n },\n })\n // A create moves the count and the aggregate is a one-shot — the\n // listener refreshes the ROWS for free, the count has to be asked\n // again or the cap drifts stale for the rest of the session.\n setProductCountEpoch((epoch) => epoch + 1)\n enqueueSnackbar('Product duplicated as draft', {\n variant: 'success',\n persist: false,\n })\n } catch (error: any) {\n enqueueSnackbar(error?.message ?? 'Could not duplicate product', {\n variant: 'warning',\n persist: false,\n })\n }\n },\n [firestore, hostId, createHostResource, enqueueSnackbar, productQuota],\n )\n\n const handleStatus = useCallback(\n (product: ProductRow, status: CommerceModel.ProductStatus) => async () => {\n // A product with a variant nobody has priced may go live (AGL-3676):\n // the storefront says \"Price coming soon\" and sells it nowhere until\n // the editor has a price for each. The owner is told, not stopped.\n if (status === 'active' && CommerceModel.productPriceMissing(product)) {\n enqueueSnackbar(\n `${product.name} has no price yet: visitors will see “Price coming soon” and can’t buy it.`,\n { variant: 'info', persist: false },\n )\n }\n await writeSiteWideChange({\n firestore,\n user,\n hostId,\n write: (batch) =>\n batch.update(doc(firestore, 'hosts', hostId, 'products', product.$id), {\n status,\n updatedAtMs: Date.now(),\n updatedAt: Timestamp.now(),\n }),\n })\n },\n [firestore, hostId, user, enqueueSnackbar],\n )\n\n const handleDelete = useCallback(\n (product: ProductRow) => async () => {\n const confirmed = await confirm({\n title: 'Delete this product?',\n description:\n `\"${product.name}\" stops being purchasable; blocks referencing ` +\n 'it show a checkout error until repointed.',\n confirmationText: 'Delete',\n confirmationButtonProps: { color: 'error' },\n })\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n await writeSiteWideChange({\n firestore,\n user,\n hostId,\n write: (batch) =>\n batch.update(doc(firestore, 'hosts', hostId, 'products', product.$id), {\n deletedAt: Timestamp.now(),\n }),\n })\n // Deleting frees the slot (AGL-3918), so the head-count is re-read.\n setProductCountEpoch((epoch) => epoch + 1)\n enqueueSnackbar('Product deleted', { variant: 'success', persist: false })\n },\n [confirm, firestore, hostId, user, enqueueSnackbar],\n )\n\n /*\n * Import and export (AGL-3531) run on the console's transfer wizard and\n * dialog, on the `commerce.products` resource: a product matched by\n * handle, SKU or ID is updated, every step is a choice, and the export\n * writes what the table's query matches — its filters and search — on the\n * server, a row per variant, with a Shopify preset.\n *\n * When the wizard closes on its results, the products the import created\n * reach the hub's zone with the options its After import step set.\n */\n const openImport = useCallback(() => {\n transfer?.openImport({\n resource: COMMERCE_PRODUCTS_TRANSFER,\n scope: 'host',\n hostId,\n onFinished: () => {\n setProductCountEpoch((epoch) => epoch + 1)\n void (async () => {\n const { takeProductImport, createdProductIds } = await import(\n '../../transfer/product-import-results'\n )\n const finished = takeProductImport(hostId)\n if (!finished) return\n const productIds = await createdProductIds(user, finished)\n setLastImport({\n key: `${finished.jobId}-${productIds.length}`,\n productIds,\n options: finished.options,\n })\n })()\n },\n })\n }, [transfer, hostId, user])\n const openExport = useCallback(() => {\n transfer?.openExport({\n resource: COMMERCE_PRODUCTS_TRANSFER,\n scope: 'host',\n hostId,\n ...(filtering\n ? {\n filter: {\n label: 'the products the list’s filters and search find',\n value: commerceListFilter(plan),\n },\n }\n : {}),\n })\n }, [transfer, hostId, filtering, plan])\n\n const handleAdjustSave = useCallback(async () => {\n if (!adjusting) return\n // The update and the ledger row, as the native stock route writes them (AGL-3652).\n const write = CommerceModel.stockAdjustmentWrite(\n adjusting.product,\n adjusting.product.$id,\n {\n variantId: adjusting.variantId,\n delta: Number(adjusting.delta),\n reason: adjusting.reason,\n locationId: adjusting.locationId || undefined,\n },\n Date.now(),\n )\n if (!write) return\n /**\n * Refuse the adjustment when the seed is unconfirmed (AGL-1358).\n *\n * `adjustVariantInventory` reads counts off the seeded product and\n * returns a whole new `variants` array, which is then written over the\n * stored one — so this is a full replace of live stock, not a delta, and\n * `merge` could not help. The refusal also has to cover the ADJUSTMENT\n * LOG, which is why the guard wraps both writes: a logged adjustment\n * whose stock write never happened is worse than neither, because the\n * history then disagrees with the count it is supposed to explain.\n */\n const verdict = await writeGuardedBySeed(\n {\n subject: 'stock',\n unreadable: productsStatus === 'error',\n fromCache: productsFromCache,\n },\n async () => {\n await writeSiteWideChange({\n firestore,\n user,\n hostId,\n write: (batch) =>\n batch.update(\n doc(firestore, 'hosts', hostId, 'products', adjusting.product.$id),\n // The total and the In stock verdict move with the count (AGL-3321).\n write.update,\n ),\n })\n // Adjustment history (AGL-281): the same log the sale webhook writes.\n await addDoc(\n collection(firestore, 'hosts', hostId, 'inventoryAdjustments'),\n write.ledger,\n )\n },\n )\n // Keep the dialog open with the typed delta rather than failing silently.\n if (!verdict.ok) {\n return void enqueueSnackbar(verdict.message, {\n variant: 'warning',\n persist: false,\n })\n }\n setAdjusting(null)\n enqueueSnackbar('Stock adjusted', { variant: 'success', persist: false })\n }, [\n user,\n adjusting,\n firestore,\n hostId,\n enqueueSnackbar,\n productsFromCache,\n productsStatus,\n ])\n\n /** The hub's allowance check for a batch of `count` new products, for its zone. */\n const roomFor = useCallback(\n (count: number) =>\n planReady\n ? Aglyn.checkQuota(org, 'productsPerHost', productCount + count - 1)\n : null,\n [org, planReady, productCount],\n )\n const onCatalogCreated = useCallback(\n () => setProductCountEpoch((epoch) => epoch + 1),\n [],\n )\n\n const formatPrice = (product: ProductRow) => {\n const [min, max] = CommerceModel.productPriceRange(product)\n return min === max ? `$${min}` : `$${min}–$${max}`\n }\n const formatStock = (product: ProductRow) => {\n const total = CommerceModel.productInventory(product)\n if (total == null) return '—'\n return total > 0 ? String(total) : 'Sold out'\n }\n /**\n * RESERVED UNITS, NAMED (AGL-2356).\n *\n * Checkout now takes a hold on the units a live session is about to buy, and\n * that hold deliberately does NOT move `inventory` — the shelf count means\n * units on the shelf, and half the product reads it that way. The\n * consequence a merchant meets is that the storefront can refuse a sale of\n * the third unit while this column says `3`, and without this caption the\n * number is right and the behaviour looks broken.\n *\n * The house style of `stock-movements-card.component.tsx`: a trailing\n * `caption` in `text.secondary`, rendered ONLY when there is something to\n * say. Nothing is held on the overwhelming majority of products, so the\n * column reads exactly as it does today.\n */\n const formatHeld = (product: ProductRow) => {\n const held = CommerceModel.heldProductUnits(product, nowMs)\n return held > 0 ? ` (${held} reserved)` : ''\n }\n\n const productColumns = listFilterGridColumns(\n [\n {\n field: 'name',\n headerName: 'Product',\n flex: 1,\n minWidth: 200,\n renderCell: ({ row }: { row: ProductRow }) => (\n <Stack sx={{ minWidth: 0 }}>\n <Typography variant=\"body2\" noWrap>\n {row.name}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {`/${row.slug} · id: ${row.$id}`}\n </Typography>\n </Stack>\n ),\n },\n {\n field: 'status',\n headerName: 'Status',\n width: 230,\n renderCell: ({ row }: { row: ProductRow }) => (\n <Stack direction=\"row\" spacing={0.5} sx={{ alignItems: 'center', height: '100%' }}>\n <Chip\n label={row.status}\n size=\"small\"\n color={STATUS_COLOR[row.status] ?? 'default'}\n variant=\"outlined\"\n />\n {/* Sold at the register, kiosk and POS apps, never online (AGL-4060). */}\n {CommerceModel.productSellsOnline(row) ? null : (\n <Chip label=\"In person only\" size=\"small\" variant=\"outlined\" />\n )}\n </Stack>\n ),\n },\n { field: 'type', headerName: 'Type', width: 110 },\n {\n field: 'priceUsd',\n headerName: 'Price',\n width: 130,\n renderCell: ({ row }: { row: ProductRow }) =>\n CommerceModel.productPriceMissing(row) ? (\n <Chip label=\"Set a price\" size=\"small\" color=\"warning\" variant=\"outlined\" />\n ) : (\n formatPrice(row)\n ),\n },\n {\n field: 'stock',\n headerName: 'Stock',\n width: 150,\n renderCell: ({ row }: { row: ProductRow }) => (\n <span>\n {formatStock(row)}\n {formatHeld(row) ? (\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"span\">\n {formatHeld(row)}\n </Typography>\n ) : null}\n </span>\n ),\n },\n {\n field: 'variants',\n headerName: 'Variants',\n width: 100,\n valueGetter: (_value: unknown, row: ProductRow) => row.variants.length,\n },\n listActionsColumn(\n (product: ProductRow) => (\n <Stack\n direction=\"row\"\n spacing={0.5}\n sx={{ justifyContent: 'flex-end', whiteSpace: 'nowrap' }}\n >\n <Button size=\"small\" onClick={() => setEditing(product)}>\n {'Edit'}\n </Button>\n <Button size=\"small\" onClick={handleDuplicate(product)}>\n {'Duplicate'}\n </Button>\n {product.type === 'physical' ? (\n <Button size=\"small\" onClick={() => setLabelsFor(product)}>\n {'Labels'}\n </Button>\n ) : null}\n {product.type === 'digital' ? (\n <Button size=\"small\" onClick={() => setKeysFor(product)}>\n {'Keys'}\n </Button>\n ) : null}\n {CommerceModel.productInventory(product) != null ? (\n <Button\n size=\"small\"\n onClick={() =>\n setAdjusting({\n product,\n variantId:\n product.variants.find(\n (variant) => variant.inventory != null,\n )?.id ?? product.variants[0].id,\n delta: '',\n reason: 'restock',\n locationId:\n (locationDocs ?? []).find(\n (location: any) => location.isDefault,\n )?.$id ??\n (locationDocs ?? [])[0]?.$id ??\n '',\n })\n }\n >\n {'Stock'}\n </Button>\n ) : null}\n <Button\n size=\"small\"\n onClick={handleStatus(\n product,\n product.status === 'archived' ? 'active' : 'archived',\n )}\n >\n {product.status === 'archived' ? 'Activate' : 'Archive'}\n </Button>\n <Button size=\"small\" color=\"error\" onClick={handleDelete(product)}>\n {'Delete'}\n </Button>\n </Stack>\n ),\n { width: 470 },\n ),\n ] as GridColDef[],\n PRODUCT_LIST_FIELDS,\n PRODUCT_LIST_OPTIONS,\n PRODUCT_LIST_HEADERS,\n )\n\n /** Add product: the quota first, then the create drawer. */\n const startAddProduct = () => {\n if (!productQuota) return\n if (!productQuota.allowed) {\n return void enqueueSnackbar(\n `Your plan includes ${productQuota.limit} products — ` + 'upgrade for more',\n { variant: 'info', persist: false },\n )\n }\n setCreating(true)\n }\n\n /*\n * Other ways to start a product (AGL-3596): the `productsCreate` zone this\n * plugin declares, drawn through the shell's gated slot beside Add product\n * and again in the empty catalog. `null` outside the console shell. The\n * org is not this page's to know, so a widget reads it from the site.\n */\n const createZone = CreateZone ? (\n <CreateZone slot={PRODUCTS_CREATE_ZONE.id} hostId={hostId} orgId={undefined} />\n ) : null\n\n return (\n <CardDisplay\n /*\n * The header count is the SITE'S catalog, not the rows in hand.\n *\n * It was `products.length`, which is the filtered, ceilinged, now-paged\n * view — so it read 500 on a 25,000-product catalog and would read 10\n * once the table paged. `productCount` is the same server aggregate the\n * quota gate uses. Under a filter there is no honest number to put here:\n * the aggregate counts the whole catalog and the view counts matches, so\n * neither describes what the reader is looking at, and the footer's own\n * count line answers it instead.\n */\n header={\n filtering\n ? 'Products'\n : `Products${productCount ? ` (${productCount})` : ''}`\n }\n help={pluginDocsHelp('commerce', { anchor: '#products-hub' })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={2}>\n <Stack direction={{ xs: 'column', sm: 'row' }} spacing={1}>\n <Box sx={{ flex: 1 }} />\n {createZone}\n <Button\n variant=\"contained\"\n color=\"primary\"\n size=\"small\"\n disabled={!productQuota}\n onClick={startAddProduct}\n >\n {'Add product'}\n </Button>\n {/* Each only for whom the route takes it (AGL-3554); an import\n left unfinished, reopened where it stopped (AGL-3549). */}\n <TransferResumeImport\n target={{ resource: COMMERCE_PRODUCTS_TRANSFER, scope: 'host', hostId }}\n onFinished={() => setProductCountEpoch((epoch) => epoch + 1)}\n />\n {transfer?.can('import', { resource: COMMERCE_PRODUCTS_TRANSFER, scope: 'host', hostId }) ? (\n <Button size=\"small\" disabled={!planReady} onClick={openImport}>\n {'Import'}\n </Button>\n ) : null}\n {transfer?.can('export', { resource: COMMERCE_PRODUCTS_TRANSFER, scope: 'host', hostId }) ? (\n <Button\n size=\"small\"\n // Nothing matches: there is nothing for the export to write.\n disabled={products.length === 0 && page === 0}\n onClick={openExport}\n >\n {'Export'}\n </Button>\n ) : null}\n </Stack>\n {/* The cap, standing rather than only on refusal (AGL-2113). The\n count is `productCount` — the site's products, the same number\n the gate above counts — not `products.length`, which is one page\n of the table's query (AGL-1716). */}\n {/* Products listed before they have a price (AGL-3676) — a guided\n start's — show \"Price coming soon\" on the store and sell nowhere\n until each is priced: the one step left before the store sells. */}\n {comingSoon.length ? (\n <Alert severity=\"info\">\n {`Set prices to start selling: ${comingSoon.length === 1 ? `${comingSoon[0].name} shows` : `${comingSoon.length} products show`} “Price coming soon” on your store until you set ${comingSoon.length === 1 ? 'its price' : 'their prices'}. Open each one and give every variant a price.`}\n </Alert>\n ) : null}\n <QuotaReadoutComponent\n ready={productQuota !== null}\n used={productCount}\n limit={productQuota?.limit ?? 0}\n noun=\"product\"\n />\n {/* The zone's widgets are handed the PAGE on screen — the products\n the reader has found and is looking at. Every write the zone makes\n asks the store for what it must not duplicate, never these rows. */}\n <ProductsHubZone\n hostId={hostId}\n products={products}\n roomFor={roomFor}\n lastImport={lastImport}\n onCreated={onCatalogCreated}\n />\n <ListFilterChips\n fields={PRODUCT_LIST_FIELDS}\n headers={PRODUCT_LIST_HEADERS}\n clauses={gridFilter.clauses}\n onChange={gridFilter.setClauses}\n options={PRODUCT_LIST_OPTIONS}\n />\n <ListQueryNotices\n refused={listQueryRefusals(plan.refused, {\n fields: PRODUCT_LIST_FIELDS,\n headers: PRODUCT_LIST_HEADERS,\n options: PRODUCT_LIST_OPTIONS,\n })}\n notices={[...plan.notices, ...columnSort.notices]}\n />\n <ListTable\n aria-label=\"Products\"\n rows={columnSort.rows}\n columns={productColumns}\n loading={productsStatus === 'loading'}\n // One page of the query, turned by the footer below: the grid\n // neither slices, filters nor sorts it.\n hideFooter\n filterMode=\"server\"\n filterModel={gridFilter.filterModel}\n onFilterModelChange={gridFilter.onFilterModelChange}\n quickFilter\n // Every header order is the query's, or the page's (`columnSort`).\n columnSort={columnSort}\n initialState={{\n columns: {\n columnVisibilityModel: hiddenFilterVisibility(\n PRODUCT_LIST_FIELDS,\n PRODUCT_VISIBLE_COLUMNS,\n ),\n },\n }}\n noRowsLabel={\n filtering ? 'No products match these filters' : 'No products yet'\n }\n noRowsDescription={\n filtering\n ? undefined\n : 'Build your catalog: add a product, then drop commerce ' +\n 'blocks on any page in Besigner.'\n }\n noRowsAction={\n filtering ? undefined : (\n <Stack direction=\"row\" spacing={1}>\n {createZone}\n <Button variant=\"contained\" disabled={!productQuota} onClick={startAddProduct}>\n {'Add your first product'}\n </Button>\n </Stack>\n )\n }\n />\n {products.length === 0 && page === 0 && productsStatus !== 'loading' ? null : (\n /* The count line is the PAGE's. `hasMore` is a fact from the probe\n row; the site's total is `productCount` above, which counts the\n whole catalog rather than the matches, so it is not this. */\n <ListPagination\n page={page}\n pageSize={pageSize}\n rowCount={products.length}\n hasMore={hasMore}\n onPageChange={setPage}\n onPageSizeChange={setPageSize}\n />\n )}\n </Stack>\n <ProductLabelsDialog product={labelsFor} onClose={() => setLabelsFor(null)} />\n <Dialog\n open={Boolean(keysFor)}\n onClose={() => setKeysFor(null)}\n maxWidth=\"sm\"\n fullWidth\n >\n <DialogTitle>{`License keys — ${keysFor?.name ?? ''}`}</DialogTitle>\n <DialogContent sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>\n {(() => {\n // The query already asked for this product's keys, so no second\n // filter here: narrowing a window that was already narrowed is how\n // a reader comes to believe the count covers the pool.\n const productKeys = (keyDocs ?? []).slice(0, KEY_POOL_CEILING)\n const available = productKeys.filter(\n (key: any) => !key.assignedAtMs,\n )\n // RETIRED KEYS ARE COUNTED SEPARATELY (AGL-2454). A refund retires\n // the key rather than returning it to the pool — the buyer already\n // holds the string, so reissuing it would give two people one\n // working key — and without this line the merchant simply watches\n // \"available\" fall with nothing to explain where the key went.\n const retired = productKeys.filter((key: any) => key.revokedAtMs)\n const assigned =\n productKeys.length - available.length - retired.length\n return (\n <>\n <Typography variant=\"body2\" color=\"text.secondary\" sx={{ mt: 1 }}>\n {`${available.length} available · ${assigned} assigned` +\n (retired.length\n ? ` · ${retired.length} retired (refunded or revoked — not reissued)`\n : '') +\n (keyPoolTruncated\n ? `, of the ${KEY_POOL_CEILING} keys read`\n : '') +\n '. Keys deliver automatically on purchase (receipt + account).'}\n </Typography>\n {available.slice(0, 8).map((key: any) => (\n <Stack\n key={key.$id}\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center' }}\n >\n <Typography variant=\"caption\" sx={{ flex: 1, fontFamily: 'monospace' }} noWrap>\n {key.key}\n </Typography>\n <Button\n size=\"small\"\n color=\"error\"\n onClick={() =>\n updateDoc(\n doc(firestore, 'hosts', hostId, 'licenseKeys', key.$id),\n { revokedAtMs: Date.now(), assignedAtMs: Date.now() },\n )\n }\n >\n {'Revoke'}\n </Button>\n </Stack>\n ))}\n <TextField\n label=\"Add keys (one per line)\"\n value={keysText}\n onChange={(event) => setKeysText(event.target.value)}\n size=\"small\"\n multiline\n minRows={3}\n />\n </>\n )\n })()}\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setKeysFor(null)}>{'Close'}</Button>\n <Button\n variant=\"contained\"\n color=\"primary\"\n disabled={!keysText.trim()}\n onClick={async () => {\n const keys = keysText\n .split('\\n')\n .map((key) => key.trim())\n .filter(Boolean)\n .slice(0, 200)\n for (const key of keys) {\n await addDoc(\n collection(firestore, 'hosts', hostId, 'licenseKeys'),\n {\n productId: keysFor!.$id,\n key,\n assignedAtMs: null,\n createdAtMs: Date.now(),\n },\n )\n }\n setKeysText('')\n enqueueSnackbar(`Added ${keys.length} keys`, {\n variant: 'success',\n persist: false,\n })\n }}\n >\n {'Add keys'}\n </Button>\n </DialogActions>\n </Dialog>\n <Dialog\n open={Boolean(adjusting)}\n onClose={() => setAdjusting(null)}\n maxWidth=\"xs\"\n fullWidth\n >\n <DialogTitle>{`Adjust stock — ${adjusting?.product.name ?? ''}`}</DialogTitle>\n <DialogContent sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>\n <TextField\n label=\"Variant\"\n value={adjusting?.variantId ?? ''}\n onChange={(event) =>\n setAdjusting((prev) =>\n prev ? { ...prev, variantId: event.target.value } : prev,\n )\n }\n size=\"small\"\n select\n sx={{ mt: 1 }}\n >\n {(adjusting?.product.variants ?? [])\n .filter((variant) => variant.inventory != null)\n .map((variant) => (\n <MenuItem key={variant.id} value={variant.id}>\n {`${Object.values(variant.options ?? {}).join(' / ') || 'Default'} — ${variant.inventory} in stock${\n CommerceModel.heldVariantUnits(\n adjusting?.product,\n variant.id,\n nowMs,\n ) > 0\n ? `, ${CommerceModel.heldVariantUnits(\n adjusting?.product,\n variant.id,\n nowMs,\n )} reserved in checkout`\n : ''\n }`}\n </MenuItem>\n ))}\n </TextField>\n {(locationDocs?.length ?? 0) > 1 ? (\n <TextField\n label=\"Location\"\n value={adjusting?.locationId ?? ''}\n onChange={(event) =>\n setAdjusting((prev) =>\n prev ? { ...prev, locationId: event.target.value } : prev,\n )\n }\n size=\"small\"\n select\n >\n {(locationDocs ?? []).map((location: any) => (\n <MenuItem key={location.$id} value={location.$id}>\n {location.name}\n </MenuItem>\n ))}\n </TextField>\n ) : null}\n <TextField\n label=\"Change\"\n placeholder=\"+10 or -3\"\n value={adjusting?.delta ?? ''}\n onChange={(event) =>\n setAdjusting((prev) =>\n prev\n ? {\n ...prev,\n delta: event.target.value.replace(/[^0-9+-]/g, ''),\n }\n : prev,\n )\n }\n size=\"small\"\n />\n <TextField\n label=\"Reason\"\n value={adjusting?.reason ?? 'restock'}\n onChange={(event) =>\n setAdjusting((prev) =>\n prev\n ? {\n ...prev,\n reason: event.target\n .value as CommerceModel.InventoryAdjustmentReason,\n }\n : prev,\n )\n }\n size=\"small\"\n select\n >\n <MenuItem value=\"restock\">{'Restock'}</MenuItem>\n <MenuItem value=\"correction\">{'Correction'}</MenuItem>\n <MenuItem value=\"damage\">{'Damaged'}</MenuItem>\n <MenuItem value=\"refund\">{'Refund return'}</MenuItem>\n </TextField>\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setAdjusting(null)}>{'Cancel'}</Button>\n <Button\n variant=\"contained\"\n color=\"primary\"\n disabled={!Math.round(Number(adjusting?.delta))}\n onClick={handleAdjustSave}\n >\n {'Apply'}\n </Button>\n </DialogActions>\n </Dialog>\n <ProductEditorDialog\n key={editing?.$id ?? (creating ? 'new' : 'closed')}\n hostId={hostId}\n product={editing}\n // The editor is seeded from a row of THIS listener and replaces the\n // whole document, so the freshness verdict belongs here (AGL-1358) —\n // the dialog has no listener of its own to ask.\n seedFromCache={productsFromCache}\n seedUnreadable={productsStatus === 'error'}\n open={creating || editing !== null}\n onClose={() => {\n setEditing(null)\n setCreating(false)\n // The dialog reports no verdict, so a create and a cancel look\n // the same from here. Re-reading on both is one aggregate and\n // keeps the cap off the dialog's shoulders (AGL-1716).\n setProductCountEpoch((epoch) => epoch + 1)\n }}\n />\n </CardDisplay>\n )\n}\nProductsHubCard.displayName = 'ProductsHubCard'\n\nexport default ProductsHubCard\n"],"names":["TransferResumeImport","useConsoleWidgetSlot","Aglyn","CommerceModel","PRODUCT_LIST_BASE","PRODUCT_LIST_COLUMN_SORTS","PRODUCT_LIST_FIELDS","PRODUCT_LIST_HEADERS","PRODUCT_LIST_OPTIONS","PRODUCT_LIST_QUERY","PRODUCT_LIST_SELECT_FIELDS","productListRequestClauses","CardDisplay","useConfirmationContext","ListFilterChips","ListPagination","ListQueryNotices","listQueryRefusals","ListTable","listActionsColumn","QuotaReadoutComponent","hiddenFilterVisibility","listFilterGridColumns","useListColumnSort","useListGridFilter","useSnackbar","Timestamp","Alert","Box","Button","Chip","Dialog","DialogActions","DialogContent","DialogTitle","MenuItem","Stack","TextField","Typography","addDoc","collection","doc","documentId","limit","orderBy","query","updateDoc","where","getLiveCountFromServer","useCallback","useEffect","useMemo","useState","useFirestore","useUser","writeSiteWideChange","useFirestoreCollection","writeGuardedBySeed","useListQuery","useHostResourceApi","useOrgPlan","ProductLabelsDialog","ProductEditorDialog","productSlugLedger","productCollectionFields","ProductsHubZone","pluginDocsHelp","PRODUCTS_CREATE_ZONE","commerceListFilter","COMMERCE_PRODUCTS_TRANSFER","KEY_POOL_CEILING","PRODUCT_PAGE_SORTS","stock","row","productInventory","variants","length","PRODUCT_PAGE_SORT_HEADERS","STATUS_COLOR","active","draft","archived","PRODUCT_VISIBLE_COLUMNS","ProductsHubCard","props","hostId","CreateZone","firestore","data","user","enqueueSnackbar","createHostResource","org","ready","planReady","confirm","gridFilter","selectFields","filtering","clauses","searchWords","some","word","trim","askedSort","setAskedSort","editing","setEditing","creating","setCreating","adjusting","setAdjusting","keysFor","setKeysFor","labelsFor","setLabelsFor","keysText","setKeysText","lastImport","setLastImport","transfer","useTransferLauncher","rows","productDocs","productWindow","hasMore","page","setPage","pageSize","setPageSize","plan","status","productsStatus","fromCache","productsFromCache","declaration","request","search","base","sort","deps","idField","confirmDisappearances","keyDocs","$id","keyPoolTruncated","locationDocs","products","map","product","liftLegacyProduct","comingSoon","filter","productPriceMissing","columnSort","sorts","defaultSort","onSortChange","pageSorts","headers","nowMs","setNowMs","Date","now","anyHeld","heldProductUnits","undefined","timer","setInterval","clearInterval","productCountEpoch","setProductCountEpoch","serverProductCount","setServerProductCount","then","count","catch","loadedProductCount","productCount","productQuota","checkQuota","handleDuplicate","allowed","variant","persist","slug","claim","productDuplicateSlugBase","name","resource","productDuplicateFields","productDuplicateName","epoch","error","message","handleStatus","write","batch","update","updatedAtMs","updatedAt","handleDelete","confirmed","title","description","confirmationText","confirmationButtonProps","color","deletedAt","openImport","scope","onFinished","takeProductImport","createdProductIds","finished","productIds","key","jobId","options","openExport","label","value","handleAdjustSave","stockAdjustmentWrite","variantId","delta","Number","reason","locationId","verdict","subject","unreadable","ledger","ok","roomFor","onCatalogCreated","formatPrice","min","max","productPriceRange","formatStock","total","String","formatHeld","held","productColumns","field","headerName","flex","minWidth","renderCell","sx","noWrap","width","direction","spacing","alignItems","height","size","productSellsOnline","span","component","valueGetter","_value","justifyContent","whiteSpace","onClick","type","find","inventory","id","location","isDefault","startAddProduct","createZone","slot","orgId","header","help","anchor","contentGutterX","contentGutterY","xs","sm","disabled","target","can","severity","used","noun","onCreated","fields","onChange","setClauses","refused","notices","aria-label","columns","loading","hideFooter","filterMode","filterModel","onFilterModelChange","quickFilter","initialState","columnVisibilityModel","noRowsLabel","noRowsDescription","noRowsAction","rowCount","onPageChange","onPageSizeChange","onClose","open","Boolean","maxWidth","fullWidth","display","flexDirection","gap","productKeys","slice","available","assignedAtMs","retired","revokedAtMs","assigned","mt","fontFamily","event","multiline","minRows","keys","split","productId","createdAtMs","prev","select","Object","values","join","heldVariantUnits","placeholder","replace","Math","round","seedFromCache","seedUnreadable","displayName"],"mappings":"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"}
|