@aglyn/plugins-commerce 1.0.0-beta.291 → 1.0.0-beta.292

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/package.json +13 -13
  2. package/src/lib/components/cart-fulfillment.d.ts +3 -0
  3. package/src/lib/components/cart-fulfillment.js.map +1 -1
  4. package/src/lib/components/cart.js +14 -4
  5. package/src/lib/components/cart.js.map +1 -1
  6. package/src/lib/components/console/invoice-send.component.js +1 -1
  7. package/src/lib/components/console/invoice-send.component.js.map +1 -1
  8. package/src/lib/components/console/pos/pos-item-dialog.component.js +3 -2
  9. package/src/lib/components/console/pos/pos-item-dialog.component.js.map +1 -1
  10. package/src/lib/components/console/pos-kiosk-page.component.js +2 -1
  11. package/src/lib/components/console/pos-kiosk-page.component.js.map +1 -1
  12. package/src/lib/components/console/pos-page.component.js +1 -1
  13. package/src/lib/components/console/pos-page.component.js.map +1 -1
  14. package/src/lib/components/console/product-add-ons-fields.component.d.ts +41 -0
  15. package/src/lib/components/console/product-add-ons-fields.component.js +356 -0
  16. package/src/lib/components/console/product-add-ons-fields.component.js.map +1 -0
  17. package/src/lib/components/console/product-editor-dialog.component.js +31 -5
  18. package/src/lib/components/console/product-editor-dialog.component.js.map +1 -1
  19. package/src/lib/components/console/product-register-fields.component.d.ts +4 -5
  20. package/src/lib/components/console/product-register-fields.component.js +8 -197
  21. package/src/lib/components/console/product-register-fields.component.js.map +1 -1
  22. package/src/lib/components/console/products-hub-zone.component.js +6 -0
  23. package/src/lib/components/console/products-hub-zone.component.js.map +1 -1
  24. package/src/lib/components/console/quote-pay-link.component.d.ts +8 -1
  25. package/src/lib/components/console/quote-pay-link.component.js +29 -3
  26. package/src/lib/components/console/quote-pay-link.component.js.map +1 -1
  27. package/src/lib/components/console/quote-send.component.js +1 -4
  28. package/src/lib/components/console/quote-send.component.js.map +1 -1
  29. package/src/lib/components/product-add-ons.d.ts +28 -0
  30. package/src/lib/components/product-add-ons.js +136 -0
  31. package/src/lib/components/product-add-ons.js.map +1 -0
  32. package/src/lib/components/product-detail.d.ts +16 -0
  33. package/src/lib/components/product-detail.js +112 -19
  34. package/src/lib/components/product-detail.js.map +1 -1
  35. package/src/lib/components/product-grid.d.ts +8 -2
  36. package/src/lib/components/product-grid.js +6 -1
  37. package/src/lib/components/product-grid.js.map +1 -1
  38. package/src/lib/model/commerce-cart.d.ts +15 -3
  39. package/src/lib/model/commerce-cart.js +10 -3
  40. package/src/lib/model/commerce-cart.js.map +1 -1
  41. package/src/lib/model/commerce-invoices.d.ts +7 -0
  42. package/src/lib/model/commerce-invoices.js +11 -0
  43. package/src/lib/model/commerce-invoices.js.map +1 -1
  44. package/src/lib/model/commerce-orders.d.ts +6 -3
  45. package/src/lib/model/commerce-orders.js +3 -0
  46. package/src/lib/model/commerce-orders.js.map +1 -1
  47. package/src/lib/model/commerce-pos-kiosk.d.ts +1 -1
  48. package/src/lib/model/commerce-pos-kiosk.js +3 -2
  49. package/src/lib/model/commerce-pos-kiosk.js.map +1 -1
  50. package/src/lib/model/commerce-quote-email.d.ts +14 -0
  51. package/src/lib/model/commerce-quote-email.js +25 -9
  52. package/src/lib/model/commerce-quote-email.js.map +1 -1
  53. package/src/lib/model/commerce-quotes.js +1 -1
  54. package/src/lib/model/commerce-quotes.js.map +1 -1
  55. package/src/lib/model/commerce.d.ts +18 -0
  56. package/src/lib/model/commerce.js +26 -0
  57. package/src/lib/model/commerce.js.map +1 -1
  58. package/src/lib/model/product-modifiers.d.ts +74 -4
  59. package/src/lib/model/product-modifiers.js +131 -10
  60. package/src/lib/model/product-modifiers.js.map +1 -1
  61. package/src/lib/server/billing-webhook.js +64 -43
  62. package/src/lib/server/billing-webhook.js.map +1 -1
  63. package/src/lib/server/cart-checkout.js +14 -2
  64. package/src/lib/server/cart-checkout.js.map +1 -1
  65. package/src/lib/server/cart.d.ts +5 -0
  66. package/src/lib/server/cart.js +41 -4
  67. package/src/lib/server/cart.js.map +1 -1
  68. package/src/lib/server/catalog.d.ts +6 -0
  69. package/src/lib/server/catalog.js +2 -0
  70. package/src/lib/server/catalog.js.map +1 -1
  71. package/src/lib/server/checkout-extras.js +3 -1
  72. package/src/lib/server/checkout-extras.js.map +1 -1
  73. package/src/lib/server/checkout.js +28 -3
  74. package/src/lib/server/checkout.js.map +1 -1
  75. package/src/lib/server/local-fulfillment.js +5 -1
  76. package/src/lib/server/local-fulfillment.js.map +1 -1
  77. package/src/lib/server/pos-offline-sync.js +1 -1
  78. package/src/lib/server/pos-offline-sync.js.map +1 -1
  79. package/src/lib/server/pos-order.js +1 -1
  80. package/src/lib/server/pos-order.js.map +1 -1
  81. package/src/lib/server/product.d.ts +26 -0
  82. package/src/lib/server/product.js +18 -0
  83. package/src/lib/server/product.js.map +1 -1
  84. package/src/lib/server/products-write.js +5 -2
  85. package/src/lib/server/products-write.js.map +1 -1
  86. package/src/lib/server/quote-email.js.map +1 -1
  87. package/src/lib/utils/add-to-cart.d.ts +8 -0
  88. package/src/lib/utils/add-to-cart.js +10 -2
  89. package/src/lib/utils/add-to-cart.js.map +1 -1
  90. package/src/lib/utils/quote-email-content.d.ts +1 -0
  91. package/src/lib/utils/quote-email-content.js +7 -4
  92. package/src/lib/utils/quote-email-content.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/invoice-send.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use client'\n\nimport * as CommerceModel from '../../model'\nimport { siteContactEmail, resolveHostToken, type HostTokenSource } from '@aglyn/aglyn/app-utils/host-tokens'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useFirestore, useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Button,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { doc, getDoc } from 'firebase/firestore'\nimport { useCallback, useEffect, useRef, useState } from 'react'\n\n/** `Oct 29, 2026, 3:04 PM`. */\nconst sentAt = (atMs: number) =>\n new Date(atMs).toLocaleString('en-US', { dateStyle: 'medium', timeStyle: 'short' })\n\n/**\n * The order's (or the quote's) last invoice email, as the console says it:\n * `Invoice sent Oct 29, 2026, 3:04 PM to jordan@example.com.` Null when none\n * was sent.\n */\nexport function invoiceSentLine(order: Pick<CommerceModel.HostOrder, 'invoiceSends'>): string | null {\n const last = CommerceModel.lastInvoiceSend(order)\n if (!last) return null\n return `${last.paid ? 'Paid invoice' : 'Invoice'} sent ${sentAt(Number(last.atMs))} to ${last.to || 'the customer'}.`\n}\n\nexport interface InvoiceSendDialogProps {\n open: boolean\n onClose: () => void\n hostId: string\n orderId: string\n order: CommerceModel.HostOrder\n}\n\n/**\n * Send invoice (AGL-4099), for an order or a quote: a short form (To,\n * Subject, Message) that emails the invoice in the site's look with the\n * invoice PDF attached. Unpaid, the email carries a Pay button to the order's\n * lasting pay link on the business's site; paid, it is the customer's record.\n * A quote's invoice lands in the quote's conversation, and an order's in its\n * own. The site's name and contact email are read once when the form opens,\n * so the dialog that offers it listens to nothing more.\n */\nexport function InvoiceSendDialog(props: InvoiceSendDialogProps) {\n const { open, onClose, hostId, orderId, order } = props\n const firestore = useFirestore()\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const [busy, setBusy] = useState(false)\n const [to, setTo] = useState('')\n const [subject, setSubject] = useState('')\n const [message, setMessage] = useState('')\n const [contact, setContact] = useState<string | null>(null)\n\n /*\n * One attempt per form (AGL-1697's rule): a retried Send presents the same\n * key and is answered from the first, so the customer never gets two; a\n * changed form is a new attempt.\n */\n const attemptKey = useRef('')\n useEffect(() => {\n attemptKey.current = ''\n }, [to, subject, message])\n\n useEffect(() => {\n if (!open) return\n let live = true\n setTo(CommerceModel.invoiceSendRecipient(order, ''))\n setSubject(CommerceModel.invoiceEmailDefaultSubject(order, '', orderId))\n setMessage('')\n void getDoc(doc(firestore, 'hosts', hostId))\n .then((snapshot) => {\n if (!live) return\n const host = (snapshot.data() ?? {}) as Record<string, unknown>\n const businessName = String(resolveHostToken('businessName', host as HostTokenSource) ?? '').trim()\n setSubject(CommerceModel.invoiceEmailDefaultSubject(order, businessName, orderId))\n const listed = siteContactEmail(host as never)\n setContact(listed && /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(listed) ? listed : null)\n })\n .catch(() => undefined)\n return () => {\n live = false\n }\n // Filled once per opening; edits after that are the member's.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open])\n\n const paid = CommerceModel.invoiceIsPaid(order)\n const refused = CommerceModel.invoiceSendRefusal(order, orderId, to.trim())\n // The address is the form's to fix; every other refusal is the order's, said up front.\n const blocker = refused && refused !== 'no-address' && refused !== 'bad-address' ? refused : null\n const threaded = Boolean(CommerceModel.invoiceEmailThread(order))\n const number = CommerceModel.formatInvoiceNumber(order, orderId)\n\n const send = useCallback(async () => {\n setBusy(true)\n try {\n if (!attemptKey.current) {\n attemptKey.current = globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`\n }\n const response = await authorizedFetch(user, '/api/commerce/order-invoice-send', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', 'Idempotency-Key': attemptKey.current },\n body: JSON.stringify({ hostId, orderId, to, subject, message }),\n })\n const payload = await response.json().catch(() => ({}))\n if (!response.ok) {\n return void enqueueSnackbar(payload?.error ?? 'The invoice could not be sent', { variant: 'error', allowDuplicate: true })\n }\n enqueueSnackbar(`${number} sent to ${String(payload.to ?? to)}`, { variant: 'success', persist: false })\n onClose()\n } finally {\n setBusy(false)\n }\n }, [user, hostId, orderId, to, subject, message, number, enqueueSnackbar, onClose])\n\n return (\n <Dialog open={open} onClose={busy ? undefined : onClose} maxWidth=\"sm\" fullWidth aria-labelledby=\"invoice-send-title\">\n <DialogTitle id=\"invoice-send-title\">{`Send invoice: ${number}`}</DialogTitle>\n <DialogContent>\n <Stack spacing={2} sx={{ pt: 1 }}>\n {blocker ? (\n <Typography variant=\"body2\" color=\"error\">\n {CommerceModel.invoiceSendRefusalMessage(blocker)}\n </Typography>\n ) : null}\n <TextField\n label=\"To\"\n type=\"email\"\n value={to}\n onChange={(event) => setTo(event.target.value)}\n required\n fullWidth\n disabled={busy}\n />\n <TextField\n label=\"Subject\"\n value={subject}\n onChange={(event) => setSubject(event.target.value)}\n fullWidth\n disabled={busy}\n slotProps={{ htmlInput: { maxLength: CommerceModel.QUOTE_MAX_SEND_SUBJECT } }}\n helperText={\n threaded\n ? 'Keep the subject the same to keep these emails in one conversation in your customer’s inbox.'\n : undefined\n }\n />\n <TextField\n label=\"Message\"\n value={message}\n onChange={(event) => setMessage(event.target.value)}\n multiline\n minRows={4}\n fullWidth\n disabled={busy}\n slotProps={{ htmlInput: { maxLength: CommerceModel.QUOTE_MAX_SEND_MESSAGE } }}\n helperText=\"Shown at the top of the email, in your words.\"\n />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {(paid\n ? 'The email shows the invoice as paid, with the date it was paid, for your customer’s records. '\n : 'The email shows the invoice number, date, total and what is due, with a button to pay it by card on your site. The button keeps working until it is paid. ') +\n `The invoice is attached as a PDF. Replies go to ${contact || 'your email'}.`}\n </Typography>\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={onClose} disabled={busy}>\n {'Cancel'}\n </Button>\n <Button variant=\"contained\" disabled={busy || !to.trim() || Boolean(blocker)} onClick={() => void send()}>\n {busy ? 'Sending…' : 'Send'}\n </Button>\n </DialogActions>\n </Dialog>\n )\n}\nInvoiceSendDialog.displayName = 'InvoiceSendDialog'\n\nexport default InvoiceSendDialog\n"],"names":["CommerceModel","siteContactEmail","resolveHostToken","useSnackbar","authorizedFetch","useFirestore","useUser","Button","Dialog","DialogActions","DialogContent","DialogTitle","Stack","TextField","Typography","doc","getDoc","useCallback","useEffect","useRef","useState","sentAt","atMs","Date","toLocaleString","dateStyle","timeStyle","invoiceSentLine","order","last","lastInvoiceSend","paid","Number","to","InvoiceSendDialog","props","open","onClose","hostId","orderId","firestore","data","user","enqueueSnackbar","busy","setBusy","setTo","subject","setSubject","message","setMessage","contact","setContact","attemptKey","current","live","invoiceSendRecipient","invoiceEmailDefaultSubject","then","snapshot","host","businessName","String","trim","listed","test","catch","undefined","invoiceIsPaid","refused","invoiceSendRefusal","blocker","threaded","Boolean","invoiceEmailThread","number","formatInvoiceNumber","send","payload","globalThis","crypto","randomUUID","now","Math","random","toString","slice","response","method","headers","body","JSON","stringify","json","ok","error","variant","allowDuplicate","persist","maxWidth","fullWidth","aria-labelledby","id","spacing","sx","pt","color","invoiceSendRefusalMessage","label","type","value","onChange","event","target","required","disabled","slotProps","htmlInput","maxLength","QUOTE_MAX_SEND_SUBJECT","helperText","multiline","minRows","QUOTE_MAX_SEND_MESSAGE","onClick","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/invoice-send.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use client'\n\nimport * as CommerceModel from '../../model'\nimport { siteContactEmail, resolveHostToken, type HostTokenSource } from '@aglyn/aglyn/app-utils/host-tokens'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useFirestore, useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Button,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { doc, getDoc } from 'firebase/firestore'\nimport { useCallback, useEffect, useRef, useState } from 'react'\n\n/** `Oct 29, 2026, 3:04 PM`. */\nconst sentAt = (atMs: number) =>\n new Date(atMs).toLocaleString('en-US', { dateStyle: 'medium', timeStyle: 'short' })\n\n/**\n * The order's (or the quote's) last invoice email, as the console says it:\n * `Invoice sent Oct 29, 2026, 3:04 PM to jordan@example.com.` Null when none\n * was sent.\n */\nexport function invoiceSentLine(order: Pick<CommerceModel.HostOrder, 'invoiceSends'>): string | null {\n const last = CommerceModel.lastInvoiceSend(order)\n if (!last) return null\n return `${last.paid ? 'Paid invoice' : 'Invoice'} sent ${sentAt(Number(last.atMs))} to ${last.to || 'the customer'}.`\n}\n\nexport interface InvoiceSendDialogProps {\n open: boolean\n onClose: () => void\n hostId: string\n orderId: string\n order: CommerceModel.HostOrder\n}\n\n/**\n * Send invoice (AGL-4099), for an order or a quote: a short form (To,\n * Subject, Message) that emails the invoice in the site's look with the\n * invoice PDF attached. Unpaid, the email carries a Pay button to the order's\n * lasting pay link on the business's site; paid, it is the customer's record.\n * A quote's invoice lands in the quote's conversation, and an order's in its\n * own. The site's name and contact email are read once when the form opens,\n * so the dialog that offers it listens to nothing more.\n */\nexport function InvoiceSendDialog(props: InvoiceSendDialogProps) {\n const { open, onClose, hostId, orderId, order } = props\n const firestore = useFirestore()\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const [busy, setBusy] = useState(false)\n const [to, setTo] = useState('')\n const [subject, setSubject] = useState('')\n const [message, setMessage] = useState('')\n const [contact, setContact] = useState<string | null>(null)\n\n /*\n * One attempt per form (AGL-1697's rule): a retried Send presents the same\n * key and is answered from the first, so the customer never gets two; a\n * changed form is a new attempt.\n */\n const attemptKey = useRef('')\n useEffect(() => {\n attemptKey.current = ''\n }, [to, subject, message])\n\n useEffect(() => {\n if (!open) return\n let live = true\n setTo(CommerceModel.invoiceSendPrefill(order))\n setSubject(CommerceModel.invoiceEmailDefaultSubject(order, '', orderId))\n setMessage('')\n void getDoc(doc(firestore, 'hosts', hostId))\n .then((snapshot) => {\n if (!live) return\n const host = (snapshot.data() ?? {}) as Record<string, unknown>\n const businessName = String(resolveHostToken('businessName', host as HostTokenSource) ?? '').trim()\n setSubject(CommerceModel.invoiceEmailDefaultSubject(order, businessName, orderId))\n const listed = siteContactEmail(host as never)\n setContact(listed && /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(listed) ? listed : null)\n })\n .catch(() => undefined)\n return () => {\n live = false\n }\n // Filled once per opening; edits after that are the member's.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open])\n\n const paid = CommerceModel.invoiceIsPaid(order)\n const refused = CommerceModel.invoiceSendRefusal(order, orderId, to.trim())\n // The address is the form's to fix; every other refusal is the order's, said up front.\n const blocker = refused && refused !== 'no-address' && refused !== 'bad-address' ? refused : null\n const threaded = Boolean(CommerceModel.invoiceEmailThread(order))\n const number = CommerceModel.formatInvoiceNumber(order, orderId)\n\n const send = useCallback(async () => {\n setBusy(true)\n try {\n if (!attemptKey.current) {\n attemptKey.current = globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`\n }\n const response = await authorizedFetch(user, '/api/commerce/order-invoice-send', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', 'Idempotency-Key': attemptKey.current },\n body: JSON.stringify({ hostId, orderId, to, subject, message }),\n })\n const payload = await response.json().catch(() => ({}))\n if (!response.ok) {\n return void enqueueSnackbar(payload?.error ?? 'The invoice could not be sent', { variant: 'error', allowDuplicate: true })\n }\n enqueueSnackbar(`${number} sent to ${String(payload.to ?? to)}`, { variant: 'success', persist: false })\n onClose()\n } finally {\n setBusy(false)\n }\n }, [user, hostId, orderId, to, subject, message, number, enqueueSnackbar, onClose])\n\n return (\n <Dialog open={open} onClose={busy ? undefined : onClose} maxWidth=\"sm\" fullWidth aria-labelledby=\"invoice-send-title\">\n <DialogTitle id=\"invoice-send-title\">{`Send invoice: ${number}`}</DialogTitle>\n <DialogContent>\n <Stack spacing={2} sx={{ pt: 1 }}>\n {blocker ? (\n <Typography variant=\"body2\" color=\"error\">\n {CommerceModel.invoiceSendRefusalMessage(blocker)}\n </Typography>\n ) : null}\n <TextField\n label=\"To\"\n type=\"email\"\n value={to}\n onChange={(event) => setTo(event.target.value)}\n required\n fullWidth\n disabled={busy}\n />\n <TextField\n label=\"Subject\"\n value={subject}\n onChange={(event) => setSubject(event.target.value)}\n fullWidth\n disabled={busy}\n slotProps={{ htmlInput: { maxLength: CommerceModel.QUOTE_MAX_SEND_SUBJECT } }}\n helperText={\n threaded\n ? 'Keep the subject the same to keep these emails in one conversation in your customer’s inbox.'\n : undefined\n }\n />\n <TextField\n label=\"Message\"\n value={message}\n onChange={(event) => setMessage(event.target.value)}\n multiline\n minRows={4}\n fullWidth\n disabled={busy}\n slotProps={{ htmlInput: { maxLength: CommerceModel.QUOTE_MAX_SEND_MESSAGE } }}\n helperText=\"Shown at the top of the email, in your words.\"\n />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {(paid\n ? 'The email shows the invoice as paid, with the date it was paid, for your customer’s records. '\n : 'The email shows the invoice number, date, total and what is due, with a button to pay it by card on your site. The button keeps working until it is paid. ') +\n `The invoice is attached as a PDF. Replies go to ${contact || 'your email'}.`}\n </Typography>\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={onClose} disabled={busy}>\n {'Cancel'}\n </Button>\n <Button variant=\"contained\" disabled={busy || !to.trim() || Boolean(blocker)} onClick={() => void send()}>\n {busy ? 'Sending…' : 'Send'}\n </Button>\n </DialogActions>\n </Dialog>\n )\n}\nInvoiceSendDialog.displayName = 'InvoiceSendDialog'\n\nexport default InvoiceSendDialog\n"],"names":["CommerceModel","siteContactEmail","resolveHostToken","useSnackbar","authorizedFetch","useFirestore","useUser","Button","Dialog","DialogActions","DialogContent","DialogTitle","Stack","TextField","Typography","doc","getDoc","useCallback","useEffect","useRef","useState","sentAt","atMs","Date","toLocaleString","dateStyle","timeStyle","invoiceSentLine","order","last","lastInvoiceSend","paid","Number","to","InvoiceSendDialog","props","open","onClose","hostId","orderId","firestore","data","user","enqueueSnackbar","busy","setBusy","setTo","subject","setSubject","message","setMessage","contact","setContact","attemptKey","current","live","invoiceSendPrefill","invoiceEmailDefaultSubject","then","snapshot","host","businessName","String","trim","listed","test","catch","undefined","invoiceIsPaid","refused","invoiceSendRefusal","blocker","threaded","Boolean","invoiceEmailThread","number","formatInvoiceNumber","send","payload","globalThis","crypto","randomUUID","now","Math","random","toString","slice","response","method","headers","body","JSON","stringify","json","ok","error","variant","allowDuplicate","persist","maxWidth","fullWidth","aria-labelledby","id","spacing","sx","pt","color","invoiceSendRefusalMessage","label","type","value","onChange","event","target","required","disabled","slotProps","htmlInput","maxLength","QUOTE_MAX_SEND_SUBJECT","helperText","multiline","minRows","QUOTE_MAX_SEND_MESSAGE","onClick","displayName"],"mappings":"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"}
@@ -80,7 +80,7 @@ function soldOut(variant) {
80
80
  ]);
81
81
  if (!product) return null;
82
82
  const variant = options.length > 0 ? variantFor(product, picked) : product.variants.find((item)=>item.id === variantId);
83
- const chosen = CommerceModel.resolveLineModifiers(product, modifiers);
83
+ const chosen = CommerceModel.resolveLineModifiers(product, modifiers, variant == null ? void 0 : variant.id);
84
84
  const unitCents = variant ? Math.round(Number(variant.priceUsd) * 100) + (chosen.ok ? chosen.extraCents : 0) : 0;
85
85
  const missing = groups.find((group)=>modifiers.filter((pick)=>pick.groupId === group.id).length < group.min);
86
86
  const canAdd = Boolean(variant) && chosen.ok && quantity >= 1 && !(props.blockSoldOut && soldOut(variant));
@@ -228,7 +228,8 @@ function soldOut(variant) {
228
228
  children: group.options.map((option)=>{
229
229
  const selected = modifiers.some((pick)=>pick.groupId === group.id && pick.optionId === option.id);
230
230
  return /*#__PURE__*/ _jsx(Chip, {
231
- label: option.priceCents ? `${option.name} +${money(option.priceCents)}` : option.name,
231
+ label: // A choice priced by variant shows its price on this one (AGL-4100).
232
+ CommerceModel.modifierOptionPriceCents(option, variant == null ? void 0 : variant.id) ? `${option.name} +${money(CommerceModel.modifierOptionPriceCents(option, variant == null ? void 0 : variant.id))}` : option.name,
232
233
  color: selected ? 'primary' : 'default',
233
234
  variant: selected ? 'filled' : 'outlined',
234
235
  "aria-pressed": selected,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../../../libs/plugins/commerce/src/lib/components/console/pos/pos-item-dialog.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 Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n IconButton,\n Stack,\n TextField,\n ToggleButton,\n ToggleButtonGroup,\n Typography,\n useMediaQuery,\n useTheme,\n} from '@mui/material'\nimport { useEffect, useMemo, useState } from 'react'\nimport * as CommerceModel from '../../../model'\nimport { usd } from './pos-api'\nimport { POS_TOUCH_PX } from './pos-product-grid.component'\n\n/** What the dialog hands back: one basket line's worth of choices. */\nexport interface PosItemChoice {\n variant: CommerceModel.ProductVariant\n modifiers: CommerceModel.ModifierSelection[]\n quantity: number\n}\n\nexport interface PosItemDialogProps {\n /** The product being added or edited; `null` closes the dialog. */\n product: (CommerceModel.HostProduct & { $id: string }) | null\n /** An existing line's choices, when the cashier tapped a basket line. */\n initial?: { variantId?: string; modifiers?: CommerceModel.ModifierSelection[]; quantity: number }\n onClose: () => void\n onConfirm: (choice: PosItemChoice) => void\n /** Editing only: takes the line out of the basket. */\n onRemove?: () => void\n /**\n * How a price reads; the register's dollars by default. The self-service\n * kiosk (AGL-3623) passes the store's currency.\n */\n formatMoney?: (cents: number) => string\n /**\n * A sold-out variant cannot be added, rather than warned about: a kiosk's\n * customer is not holding the goods the way a cashier is (AGL-3623).\n */\n blockSoldOut?: boolean\n /** The most of one line that can be added; 99 at the register. */\n maxQuantity?: number\n}\n\n/** Whether the register must ask before adding: a choice of variant or any modifier. */\nexport function posItemNeedsChoice(product: CommerceModel.HostProduct): boolean {\n return (\n (product.variants?.length ?? 0) > 1 ||\n CommerceModel.productModifierGroups(product).length > 0\n )\n}\n\n/** The variant whose options match every picked value, or undefined. */\nfunction variantFor(\n product: CommerceModel.HostProduct,\n picked: Record<string, string>,\n): CommerceModel.ProductVariant | undefined {\n return product.variants.find((variant) =>\n (product.options ?? []).every((option) => variant.options?.[option.name] === picked[option.name]),\n )\n}\n\nfunction variantLabel(variant: CommerceModel.ProductVariant): string {\n return Object.values(variant.options ?? {}).join(' / ') || 'Default'\n}\n\nfunction soldOut(variant: CommerceModel.ProductVariant | undefined): boolean {\n return variant?.inventory != null && variant.inventory <= 0\n}\n\n/**\n * The register's item sheet (AGL-3607): the size, color or other variant,\n * then each modifier group — required ones marked, \"pick one\" groups as a\n * single toggle row and \"pick up to N\" groups as chips — and the quantity,\n * with the line's price on the Add button as it changes. Full screen on a\n * phone, a dialog on a tablet; every target is at least 44px.\n *\n * The price shown is the register's estimate. The server prices the line\n * again from the product when the sale is charged.\n */\nexport function PosItemDialog(props: PosItemDialogProps) {\n const { product, initial } = props\n const money = props.formatMoney ?? usd\n const maxQuantity = props.maxQuantity ?? 99\n const theme = useTheme()\n const phone = useMediaQuery(theme.breakpoints.down('sm'))\n const groups = useMemo(\n () => (product ? CommerceModel.productModifierGroups(product) : []),\n [product],\n )\n const options = product?.options ?? []\n const [picked, setPicked] = useState<Record<string, string>>({})\n const [variantId, setVariantId] = useState('')\n const [modifiers, setModifiers] = useState<CommerceModel.ModifierSelection[]>([])\n const [quantity, setQuantity] = useState(1)\n\n useEffect(() => {\n if (!product) return\n const start =\n product.variants.find((variant) => variant.id === initial?.variantId) ??\n product.variants.find((variant) => !soldOut(variant)) ??\n product.variants[0]\n setVariantId(start?.id ?? '')\n setPicked({ ...(start?.options ?? {}) })\n setModifiers(initial?.modifiers ?? [])\n setQuantity(initial?.quantity ?? 1)\n }, [product, initial])\n\n if (!product) return null\n\n const variant =\n options.length > 0\n ? variantFor(product, picked)\n : product.variants.find((item) => item.id === variantId)\n const chosen = CommerceModel.resolveLineModifiers(product, modifiers)\n const unitCents = variant\n ? Math.round(Number(variant.priceUsd) * 100) + (chosen.ok ? chosen.extraCents : 0)\n : 0\n const missing = groups.find(\n (group) => modifiers.filter((pick) => pick.groupId === group.id).length < group.min,\n )\n const canAdd =\n Boolean(variant) && chosen.ok && quantity >= 1 && !(props.blockSoldOut && soldOut(variant))\n\n const toggle = (group: CommerceModel.ProductModifierGroup, optionId: string) => {\n setModifiers((current) => {\n const inGroup = current.filter((pick) => pick.groupId === group.id)\n const has = inGroup.some((pick) => pick.optionId === optionId)\n if (has) return current.filter((pick) => !(pick.groupId === group.id && pick.optionId === optionId))\n // A pick-one group swaps its choice; a pick-many group stops at its max.\n if (group.max === 1) {\n return [...current.filter((pick) => pick.groupId !== group.id), { groupId: group.id, optionId }]\n }\n if (inGroup.length >= group.max) return current\n return [...current, { groupId: group.id, optionId }]\n })\n }\n\n return (\n <Dialog open onClose={props.onClose} fullScreen={phone} fullWidth maxWidth=\"sm\">\n <DialogTitle>{product.name}</DialogTitle>\n <DialogContent dividers>\n <Stack spacing={3}>\n {options.length > 0\n ? options.map((option) => (\n <Box key={option.name} role=\"group\" aria-label={option.name}>\n <Typography variant=\"subtitle1\" component=\"h3\" sx={{ mb: 1 }}>\n {option.name}\n </Typography>\n <ToggleButtonGroup\n exclusive\n value={picked[option.name] ?? null}\n onChange={(_event, value: string | null) => {\n if (value) setPicked((current) => ({ ...current, [option.name]: value }))\n }}\n sx={{ flexWrap: 'wrap', gap: 1 }}\n >\n {option.values.map((value) => (\n <ToggleButton key={value} value={value} sx={{ minHeight: POS_TOUCH_PX, minWidth: 88 }}>\n {value}\n </ToggleButton>\n ))}\n </ToggleButtonGroup>\n </Box>\n ))\n : product.variants.length > 1 ? (\n <Box role=\"group\" aria-label=\"Variant\">\n <ToggleButtonGroup\n exclusive\n value={variantId || null}\n onChange={(_event, value: string | null) => value && setVariantId(value)}\n sx={{ flexWrap: 'wrap', gap: 1 }}\n >\n {product.variants.map((item) => (\n <ToggleButton key={item.id} value={item.id} sx={{ minHeight: POS_TOUCH_PX, minWidth: 88 }}>\n {`${variantLabel(item)} · ${money(Math.round(Number(item.priceUsd) * 100))}`}\n </ToggleButton>\n ))}\n </ToggleButtonGroup>\n </Box>\n ) : null}\n {options.length > 0 && !variant ? (\n <Typography variant=\"body2\" color=\"warning.main\">\n {'That combination is not sold. Pick another.'}\n </Typography>\n ) : null}\n {variant && soldOut(variant) ? (\n <Typography variant=\"body2\" color=\"warning.main\">\n {props.blockSoldOut ? 'Sold out' : 'Out of stock: selling it takes the count below zero.'}\n </Typography>\n ) : null}\n {groups.map((group) => {\n const count = modifiers.filter((pick) => pick.groupId === group.id).length\n const rule = CommerceModel.modifierGroupRequired(group)\n ? group.max === 1\n ? 'Required · pick 1'\n : `Required · pick ${group.min} to ${group.max}`\n : group.max === 1\n ? 'Optional · pick 1'\n : `Optional · up to ${group.max}`\n return (\n <Box key={group.id} role=\"group\" aria-label={group.name}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'baseline', mb: 1 }}>\n <Typography variant=\"subtitle1\" component=\"h3\">\n {group.name}\n </Typography>\n <Typography\n variant=\"caption\"\n color={count < group.min ? 'warning.main' : 'text.secondary'}\n >\n {rule}\n </Typography>\n </Stack>\n <Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>\n {group.options.map((option) => {\n const selected = modifiers.some(\n (pick) => pick.groupId === group.id && pick.optionId === option.id,\n )\n return (\n <Chip\n key={option.id}\n label={option.priceCents ? `${option.name} +${money(option.priceCents)}` : option.name}\n color={selected ? 'primary' : 'default'}\n variant={selected ? 'filled' : 'outlined'}\n aria-pressed={selected}\n onClick={() => toggle(group, option.id)}\n disabled={!selected && group.max > 1 && count >= group.max}\n sx={{ minHeight: POS_TOUCH_PX, px: 1 }}\n />\n )\n })}\n </Box>\n </Box>\n )\n })}\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography variant=\"subtitle1\" component=\"h3\" sx={{ flex: 1 }}>\n {'Quantity'}\n </Typography>\n <IconButton\n aria-label=\"One fewer\"\n onClick={() => setQuantity((value) => Math.max(1, value - 1))}\n sx={{ width: POS_TOUCH_PX, height: POS_TOUCH_PX, border: 1, borderColor: 'divider' }}\n >\n {'−'}\n </IconButton>\n <TextField\n value={quantity}\n onChange={(event) => {\n const next = Math.round(Number(event.target.value.replace(/\\D/g, '')))\n setQuantity(Math.max(1, Math.min(maxQuantity, next || 1)))\n }}\n sx={{ width: 72 }}\n slotProps={{\n htmlInput: { inputMode: 'numeric', 'aria-label': 'Quantity', style: { textAlign: 'center' } },\n }}\n />\n <IconButton\n aria-label=\"One more\"\n onClick={() => setQuantity((value) => Math.min(maxQuantity, value + 1))}\n sx={{ width: POS_TOUCH_PX, height: POS_TOUCH_PX, border: 1, borderColor: 'divider' }}\n >\n {'+'}\n </IconButton>\n </Stack>\n </Stack>\n </DialogContent>\n <DialogActions sx={{ p: 2, gap: 1 }}>\n {props.onRemove ? (\n <Button color=\"error\" onClick={props.onRemove} sx={{ minHeight: POS_TOUCH_PX, mr: 'auto' }}>\n {'Remove'}\n </Button>\n ) : null}\n <Button onClick={props.onClose} sx={{ minHeight: POS_TOUCH_PX }}>\n {'Cancel'}\n </Button>\n <Button\n variant=\"contained\"\n disabled={!canAdd}\n onClick={() => {\n if (!variant || !chosen.ok) return\n props.onConfirm({ variant, modifiers, quantity })\n }}\n sx={{ minHeight: POS_TOUCH_PX, minWidth: 160 }}\n >\n {missing\n ? `Choose ${missing.name.toLowerCase()}`\n : `${props.onRemove ? 'Update' : 'Add'} · ${money(unitCents * quantity)}`}\n </Button>\n </DialogActions>\n </Dialog>\n )\n}\nPosItemDialog.displayName = 'PosItemDialog'\n"],"names":["Box","Button","Chip","Dialog","DialogActions","DialogContent","DialogTitle","IconButton","Stack","TextField","ToggleButton","ToggleButtonGroup","Typography","useMediaQuery","useTheme","useEffect","useMemo","useState","CommerceModel","usd","POS_TOUCH_PX","posItemNeedsChoice","product","variants","length","productModifierGroups","variantFor","picked","find","variant","options","every","option","name","variantLabel","Object","values","join","soldOut","inventory","PosItemDialog","props","initial","money","formatMoney","maxQuantity","theme","phone","breakpoints","down","groups","setPicked","variantId","setVariantId","modifiers","setModifiers","quantity","setQuantity","start","id","item","chosen","resolveLineModifiers","unitCents","Math","round","Number","priceUsd","ok","extraCents","missing","group","filter","pick","groupId","min","canAdd","Boolean","blockSoldOut","toggle","optionId","current","inGroup","has","some","max","open","onClose","fullScreen","fullWidth","maxWidth","dividers","spacing","map","role","aria-label","component","sx","mb","exclusive","value","onChange","_event","flexWrap","gap","minHeight","minWidth","color","count","rule","modifierGroupRequired","direction","alignItems","display","selected","label","priceCents","aria-pressed","onClick","disabled","px","flex","width","height","border","borderColor","event","next","target","replace","slotProps","htmlInput","inputMode","style","textAlign","p","onRemove","mr","onConfirm","toLowerCase","displayName"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;;AAEA,SACEA,GAAG,EACHC,MAAM,EACNC,IAAI,EACJC,MAAM,EACNC,aAAa,EACbC,aAAa,EACbC,WAAW,EACXC,UAAU,EACVC,KAAK,EACLC,SAAS,EACTC,YAAY,EACZC,iBAAiB,EACjBC,UAAU,EACVC,aAAa,EACbC,QAAQ,QACH,gBAAe;AACtB,SAASC,SAAS,EAAEC,OAAO,EAAEC,QAAQ,QAAQ,QAAO;AACpD,YAAYC,mBAAmB,0BAAgB;AAC/C,SAASC,GAAG,QAAQ,eAAW;AAC/B,SAASC,YAAY,QAAQ,kCAA8B;AAgC3D,sFAAsF,GACtF,OAAO,SAASC,mBAAmBC,OAAkC;;QAEhEA;IADH,OACE,UAACA,oBAAAA,QAAQC,QAAQ,qBAAhBD,kBAAkBE,MAAM,mBAAI,KAAK,KAClCN,cAAcO,qBAAqB,CAACH,SAASE,MAAM,GAAG;AAE1D;AAEA,sEAAsE,GACtE,SAASE,WACPJ,OAAkC,EAClCK,MAA8B;IAE9B,OAAOL,QAAQC,QAAQ,CAACK,IAAI,CAAC,CAACC;YAC3BP;eAAD,EAACA,mBAAAA,QAAQQ,OAAO,YAAfR,mBAAmB,EAAE,EAAES,KAAK,CAAC,CAACC;gBAAWH;mBAAAA,EAAAA,mBAAAA,QAAQC,OAAO,qBAAfD,gBAAiB,CAACG,OAAOC,IAAI,CAAC,MAAKN,MAAM,CAACK,OAAOC,IAAI,CAAC;;;AAEpG;AAEA,SAASC,aAAaL,OAAqC;QACpCA;IAArB,OAAOM,OAAOC,MAAM,EAACP,mBAAAA,QAAQC,OAAO,YAAfD,mBAAmB,CAAC,GAAGQ,IAAI,CAAC,UAAU;AAC7D;AAEA,SAASC,QAAQT,OAAiD;IAChE,OAAOA,CAAAA,2BAAAA,QAASU,SAAS,KAAI,QAAQV,QAAQU,SAAS,IAAI;AAC5D;AAEA;;;;;;;;;CASC,GACD,OAAO,SAASC,cAAcC,KAAyB;QAEvCA,oBACMA;IAFpB,MAAM,EAAEnB,OAAO,EAAEoB,OAAO,EAAE,GAAGD;IAC7B,MAAME,SAAQF,qBAAAA,MAAMG,WAAW,YAAjBH,qBAAqBtB;IACnC,MAAM0B,eAAcJ,qBAAAA,MAAMI,WAAW,YAAjBJ,qBAAqB;IACzC,MAAMK,QAAQhC;IACd,MAAMiC,QAAQlC,cAAciC,MAAME,WAAW,CAACC,IAAI,CAAC;IACnD,MAAMC,SAASlC,QACb,IAAOM,UAAUJ,cAAcO,qBAAqB,CAACH,WAAW,EAAE,EAClE;QAACA;KAAQ;IAEX,MAAMQ,kBAAUR,2BAAAA,QAASQ,OAAO,mBAAI,EAAE;IACtC,MAAM,CAACH,QAAQwB,UAAU,GAAGlC,SAAiC,CAAC;IAC9D,MAAM,CAACmC,WAAWC,aAAa,GAAGpC,SAAS;IAC3C,MAAM,CAACqC,WAAWC,aAAa,GAAGtC,SAA4C,EAAE;IAChF,MAAM,CAACuC,UAAUC,YAAY,GAAGxC,SAAS;IAEzCF,UAAU;YAGNO,MAAAA;QAFF,IAAI,CAACA,SAAS;QACd,MAAMoC,SACJpC,QAAAA,yBAAAA,QAAQC,QAAQ,CAACK,IAAI,CAAC,CAACC,UAAYA,QAAQ8B,EAAE,MAAKjB,2BAAAA,QAASU,SAAS,cAApE9B,yBACAA,QAAQC,QAAQ,CAACK,IAAI,CAAC,CAACC,UAAY,CAACS,QAAQT,qBAD5CP,OAEAA,QAAQC,QAAQ,CAAC,EAAE;QACrB8B,sBAAaK,yBAAAA,MAAOC,EAAE,oBAAI;QAC1BR,UAAU,sBAAMO,yBAAAA,MAAO5B,OAAO,oBAAI,CAAC;QACnCyB,sBAAab,2BAAAA,QAASY,SAAS,oBAAI,EAAE;QACrCG,qBAAYf,2BAAAA,QAASc,QAAQ,oBAAI;IACnC,GAAG;QAAClC;QAASoB;KAAQ;IAErB,IAAI,CAACpB,SAAS,OAAO;IAErB,MAAMO,UACJC,QAAQN,MAAM,GAAG,IACbE,WAAWJ,SAASK,UACpBL,QAAQC,QAAQ,CAACK,IAAI,CAAC,CAACgC,OAASA,KAAKD,EAAE,KAAKP;IAClD,MAAMS,SAAS3C,cAAc4C,oBAAoB,CAACxC,SAASgC;IAC3D,MAAMS,YAAYlC,UACdmC,KAAKC,KAAK,CAACC,OAAOrC,QAAQsC,QAAQ,IAAI,OAAQN,CAAAA,OAAOO,EAAE,GAAGP,OAAOQ,UAAU,GAAG,CAAA,IAC9E;IACJ,MAAMC,UAAUpB,OAAOtB,IAAI,CACzB,CAAC2C,QAAUjB,UAAUkB,MAAM,CAAC,CAACC,OAASA,KAAKC,OAAO,KAAKH,MAAMZ,EAAE,EAAEnC,MAAM,GAAG+C,MAAMI,GAAG;IAErF,MAAMC,SACJC,QAAQhD,YAAYgC,OAAOO,EAAE,IAAIZ,YAAY,KAAK,CAAEf,CAAAA,MAAMqC,YAAY,IAAIxC,QAAQT,QAAO;IAE3F,MAAMkD,SAAS,CAACR,OAA2CS;QACzDzB,aAAa,CAAC0B;YACZ,MAAMC,UAAUD,QAAQT,MAAM,CAAC,CAACC,OAASA,KAAKC,OAAO,KAAKH,MAAMZ,EAAE;YAClE,MAAMwB,MAAMD,QAAQE,IAAI,CAAC,CAACX,OAASA,KAAKO,QAAQ,KAAKA;YACrD,IAAIG,KAAK,OAAOF,QAAQT,MAAM,CAAC,CAACC,OAAS,CAAEA,CAAAA,KAAKC,OAAO,KAAKH,MAAMZ,EAAE,IAAIc,KAAKO,QAAQ,KAAKA,QAAO;YACjG,yEAAyE;YACzE,IAAIT,MAAMc,GAAG,KAAK,GAAG;gBACnB,OAAO;uBAAIJ,QAAQT,MAAM,CAAC,CAACC,OAASA,KAAKC,OAAO,KAAKH,MAAMZ,EAAE;oBAAG;wBAAEe,SAASH,MAAMZ,EAAE;wBAAEqB;oBAAS;iBAAE;YAClG;YACA,IAAIE,QAAQ1D,MAAM,IAAI+C,MAAMc,GAAG,EAAE,OAAOJ;YACxC,OAAO;mBAAIA;gBAAS;oBAAEP,SAASH,MAAMZ,EAAE;oBAAEqB;gBAAS;aAAE;QACtD;IACF;IAEA,qBACE,MAAC7E;QAAOmF,IAAI;QAACC,SAAS9C,MAAM8C,OAAO;QAAEC,YAAYzC;QAAO0C,SAAS;QAACC,UAAS;;0BACzE,KAACpF;0BAAagB,QAAQW,IAAI;;0BAC1B,KAAC5B;gBAAcsF,QAAQ;0BACrB,cAAA,MAACnF;oBAAMoF,SAAS;;wBACb9D,QAAQN,MAAM,GAAG,IACdM,QAAQ+D,GAAG,CAAC,CAAC7D;gCAOAL;iDANX,MAAC3B;gCAAsB8F,MAAK;gCAAQC,cAAY/D,OAAOC,IAAI;;kDACzD,KAACrB;wCAAWiB,SAAQ;wCAAYmE,WAAU;wCAAKC,IAAI;4CAAEC,IAAI;wCAAE;kDACxDlE,OAAOC,IAAI;;kDAEd,KAACtB;wCACCwF,SAAS;wCACTC,KAAK,GAAEzE,sBAAAA,MAAM,CAACK,OAAOC,IAAI,CAAC,YAAnBN,sBAAuB;wCAC9B0E,UAAU,CAACC,QAAQF;4CACjB,IAAIA,OAAOjD,UAAU,CAAC8B,UAAa,aAAKA;oDAAS,CAACjD,OAAOC,IAAI,CAAC,EAAEmE;;wCAClE;wCACAH,IAAI;4CAAEM,UAAU;4CAAQC,KAAK;wCAAE;kDAE9BxE,OAAOI,MAAM,CAACyD,GAAG,CAAC,CAACO,sBAClB,KAAC1F;gDAAyB0F,OAAOA;gDAAOH,IAAI;oDAAEQ,WAAWrF;oDAAcsF,UAAU;gDAAG;0DACjFN;+CADgBA;;;+BAbfpE,OAAOC,IAAI;6BAoBvBX,QAAQC,QAAQ,CAACC,MAAM,GAAG,kBACxB,KAACxB;4BAAI8F,MAAK;4BAAQC,cAAW;sCAC3B,cAAA,KAACpF;gCACCwF,SAAS;gCACTC,OAAOhD,aAAa;gCACpBiD,UAAU,CAACC,QAAQF,QAAyBA,SAAS/C,aAAa+C;gCAClEH,IAAI;oCAAEM,UAAU;oCAAQC,KAAK;gCAAE;0CAE9BlF,QAAQC,QAAQ,CAACsE,GAAG,CAAC,CAACjC,qBACrB,KAAClD;wCAA2B0F,OAAOxC,KAAKD,EAAE;wCAAEsC,IAAI;4CAAEQ,WAAWrF;4CAAcsF,UAAU;wCAAG;kDACrF,GAAGxE,aAAa0B,MAAM,GAAG,EAAEjB,MAAMqB,KAAKC,KAAK,CAACC,OAAON,KAAKO,QAAQ,IAAI,OAAO;uCAD3DP,KAAKD,EAAE;;6BAM9B;wBACP7B,QAAQN,MAAM,GAAG,KAAK,CAACK,wBACtB,KAACjB;4BAAWiB,SAAQ;4BAAQ8E,OAAM;sCAC/B;6BAED;wBACH9E,WAAWS,QAAQT,yBAClB,KAACjB;4BAAWiB,SAAQ;4BAAQ8E,OAAM;sCAC/BlE,MAAMqC,YAAY,GAAG,aAAa;6BAEnC;wBACH5B,OAAO2C,GAAG,CAAC,CAACtB;4BACX,MAAMqC,QAAQtD,UAAUkB,MAAM,CAAC,CAACC,OAASA,KAAKC,OAAO,KAAKH,MAAMZ,EAAE,EAAEnC,MAAM;4BAC1E,MAAMqF,OAAO3F,cAAc4F,qBAAqB,CAACvC,SAC7CA,MAAMc,GAAG,KAAK,IACZ,sBACA,CAAC,gBAAgB,EAAEd,MAAMI,GAAG,CAAC,IAAI,EAAEJ,MAAMc,GAAG,EAAE,GAChDd,MAAMc,GAAG,KAAK,IACZ,sBACA,CAAC,iBAAiB,EAAEd,MAAMc,GAAG,EAAE;4BACrC,qBACE,MAACrF;gCAAmB8F,MAAK;gCAAQC,cAAYxB,MAAMtC,IAAI;;kDACrD,MAACzB;wCAAMuG,WAAU;wCAAMnB,SAAS;wCAAGK,IAAI;4CAAEe,YAAY;4CAAYd,IAAI;wCAAE;;0DACrE,KAACtF;gDAAWiB,SAAQ;gDAAYmE,WAAU;0DACvCzB,MAAMtC,IAAI;;0DAEb,KAACrB;gDACCiB,SAAQ;gDACR8E,OAAOC,QAAQrC,MAAMI,GAAG,GAAG,iBAAiB;0DAE3CkC;;;;kDAGL,KAAC7G;wCAAIiG,IAAI;4CAAEgB,SAAS;4CAAQV,UAAU;4CAAQC,KAAK;wCAAE;kDAClDjC,MAAMzC,OAAO,CAAC+D,GAAG,CAAC,CAAC7D;4CAClB,MAAMkF,WAAW5D,UAAU8B,IAAI,CAC7B,CAACX,OAASA,KAAKC,OAAO,KAAKH,MAAMZ,EAAE,IAAIc,KAAKO,QAAQ,KAAKhD,OAAO2B,EAAE;4CAEpE,qBACE,KAACzD;gDAECiH,OAAOnF,OAAOoF,UAAU,GAAG,GAAGpF,OAAOC,IAAI,CAAC,EAAE,EAAEU,MAAMX,OAAOoF,UAAU,GAAG,GAAGpF,OAAOC,IAAI;gDACtF0E,OAAOO,WAAW,YAAY;gDAC9BrF,SAASqF,WAAW,WAAW;gDAC/BG,gBAAcH;gDACdI,SAAS,IAAMvC,OAAOR,OAAOvC,OAAO2B,EAAE;gDACtC4D,UAAU,CAACL,YAAY3C,MAAMc,GAAG,GAAG,KAAKuB,SAASrC,MAAMc,GAAG;gDAC1DY,IAAI;oDAAEQ,WAAWrF;oDAAcoG,IAAI;gDAAE;+CAPhCxF,OAAO2B,EAAE;wCAUpB;;;+BA7BMY,MAAMZ,EAAE;wBAiCtB;sCACA,MAACnD;4BAAMuG,WAAU;4BAAMnB,SAAS;4BAAGK,IAAI;gCAAEe,YAAY;4BAAS;;8CAC5D,KAACpG;oCAAWiB,SAAQ;oCAAYmE,WAAU;oCAAKC,IAAI;wCAAEwB,MAAM;oCAAE;8CAC1D;;8CAEH,KAAClH;oCACCwF,cAAW;oCACXuB,SAAS,IAAM7D,YAAY,CAAC2C,QAAUpC,KAAKqB,GAAG,CAAC,GAAGe,QAAQ;oCAC1DH,IAAI;wCAAEyB,OAAOtG;wCAAcuG,QAAQvG;wCAAcwG,QAAQ;wCAAGC,aAAa;oCAAU;8CAElF;;8CAEH,KAACpH;oCACC2F,OAAO5C;oCACP6C,UAAU,CAACyB;wCACT,MAAMC,OAAO/D,KAAKC,KAAK,CAACC,OAAO4D,MAAME,MAAM,CAAC5B,KAAK,CAAC6B,OAAO,CAAC,OAAO;wCACjExE,YAAYO,KAAKqB,GAAG,CAAC,GAAGrB,KAAKW,GAAG,CAAC9B,aAAakF,QAAQ;oCACxD;oCACA9B,IAAI;wCAAEyB,OAAO;oCAAG;oCAChBQ,WAAW;wCACTC,WAAW;4CAAEC,WAAW;4CAAW,cAAc;4CAAYC,OAAO;gDAAEC,WAAW;4CAAS;wCAAE;oCAC9F;;8CAEF,KAAC/H;oCACCwF,cAAW;oCACXuB,SAAS,IAAM7D,YAAY,CAAC2C,QAAUpC,KAAKW,GAAG,CAAC9B,aAAauD,QAAQ;oCACpEH,IAAI;wCAAEyB,OAAOtG;wCAAcuG,QAAQvG;wCAAcwG,QAAQ;wCAAGC,aAAa;oCAAU;8CAElF;;;;;;;0BAKT,MAACzH;gBAAc6F,IAAI;oBAAEsC,GAAG;oBAAG/B,KAAK;gBAAE;;oBAC/B/D,MAAM+F,QAAQ,iBACb,KAACvI;wBAAO0G,OAAM;wBAAQW,SAAS7E,MAAM+F,QAAQ;wBAAEvC,IAAI;4BAAEQ,WAAWrF;4BAAcqH,IAAI;wBAAO;kCACtF;yBAED;kCACJ,KAACxI;wBAAOqH,SAAS7E,MAAM8C,OAAO;wBAAEU,IAAI;4BAAEQ,WAAWrF;wBAAa;kCAC3D;;kCAEH,KAACnB;wBACC4B,SAAQ;wBACR0F,UAAU,CAAC3C;wBACX0C,SAAS;4BACP,IAAI,CAACzF,WAAW,CAACgC,OAAOO,EAAE,EAAE;4BAC5B3B,MAAMiG,SAAS,CAAC;gCAAE7G;gCAASyB;gCAAWE;4BAAS;wBACjD;wBACAyC,IAAI;4BAAEQ,WAAWrF;4BAAcsF,UAAU;wBAAI;kCAE5CpC,UACG,CAAC,OAAO,EAAEA,QAAQrC,IAAI,CAAC0G,WAAW,IAAI,GACtC,GAAGlG,MAAM+F,QAAQ,GAAG,WAAW,MAAM,GAAG,EAAE7F,MAAMoB,YAAYP,WAAW;;;;;;AAKrF;AACAhB,cAAcoG,WAAW,GAAG"}
1
+ {"version":3,"sources":["../../../../../../../../../libs/plugins/commerce/src/lib/components/console/pos/pos-item-dialog.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 Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n IconButton,\n Stack,\n TextField,\n ToggleButton,\n ToggleButtonGroup,\n Typography,\n useMediaQuery,\n useTheme,\n} from '@mui/material'\nimport { useEffect, useMemo, useState } from 'react'\nimport * as CommerceModel from '../../../model'\nimport { usd } from './pos-api'\nimport { POS_TOUCH_PX } from './pos-product-grid.component'\n\n/** What the dialog hands back: one basket line's worth of choices. */\nexport interface PosItemChoice {\n variant: CommerceModel.ProductVariant\n modifiers: CommerceModel.ModifierSelection[]\n quantity: number\n}\n\nexport interface PosItemDialogProps {\n /** The product being added or edited; `null` closes the dialog. */\n product: (CommerceModel.HostProduct & { $id: string }) | null\n /** An existing line's choices, when the cashier tapped a basket line. */\n initial?: { variantId?: string; modifiers?: CommerceModel.ModifierSelection[]; quantity: number }\n onClose: () => void\n onConfirm: (choice: PosItemChoice) => void\n /** Editing only: takes the line out of the basket. */\n onRemove?: () => void\n /**\n * How a price reads; the register's dollars by default. The self-service\n * kiosk (AGL-3623) passes the store's currency.\n */\n formatMoney?: (cents: number) => string\n /**\n * A sold-out variant cannot be added, rather than warned about: a kiosk's\n * customer is not holding the goods the way a cashier is (AGL-3623).\n */\n blockSoldOut?: boolean\n /** The most of one line that can be added; 99 at the register. */\n maxQuantity?: number\n}\n\n/** Whether the register must ask before adding: a choice of variant or any modifier. */\nexport function posItemNeedsChoice(product: CommerceModel.HostProduct): boolean {\n return (\n (product.variants?.length ?? 0) > 1 ||\n CommerceModel.productModifierGroups(product).length > 0\n )\n}\n\n/** The variant whose options match every picked value, or undefined. */\nfunction variantFor(\n product: CommerceModel.HostProduct,\n picked: Record<string, string>,\n): CommerceModel.ProductVariant | undefined {\n return product.variants.find((variant) =>\n (product.options ?? []).every((option) => variant.options?.[option.name] === picked[option.name]),\n )\n}\n\nfunction variantLabel(variant: CommerceModel.ProductVariant): string {\n return Object.values(variant.options ?? {}).join(' / ') || 'Default'\n}\n\nfunction soldOut(variant: CommerceModel.ProductVariant | undefined): boolean {\n return variant?.inventory != null && variant.inventory <= 0\n}\n\n/**\n * The register's item sheet (AGL-3607): the size, color or other variant,\n * then each modifier group — required ones marked, \"pick one\" groups as a\n * single toggle row and \"pick up to N\" groups as chips — and the quantity,\n * with the line's price on the Add button as it changes. Full screen on a\n * phone, a dialog on a tablet; every target is at least 44px.\n *\n * The price shown is the register's estimate. The server prices the line\n * again from the product when the sale is charged.\n */\nexport function PosItemDialog(props: PosItemDialogProps) {\n const { product, initial } = props\n const money = props.formatMoney ?? usd\n const maxQuantity = props.maxQuantity ?? 99\n const theme = useTheme()\n const phone = useMediaQuery(theme.breakpoints.down('sm'))\n const groups = useMemo(\n () => (product ? CommerceModel.productModifierGroups(product) : []),\n [product],\n )\n const options = product?.options ?? []\n const [picked, setPicked] = useState<Record<string, string>>({})\n const [variantId, setVariantId] = useState('')\n const [modifiers, setModifiers] = useState<CommerceModel.ModifierSelection[]>([])\n const [quantity, setQuantity] = useState(1)\n\n useEffect(() => {\n if (!product) return\n const start =\n product.variants.find((variant) => variant.id === initial?.variantId) ??\n product.variants.find((variant) => !soldOut(variant)) ??\n product.variants[0]\n setVariantId(start?.id ?? '')\n setPicked({ ...(start?.options ?? {}) })\n setModifiers(initial?.modifiers ?? [])\n setQuantity(initial?.quantity ?? 1)\n }, [product, initial])\n\n if (!product) return null\n\n const variant =\n options.length > 0\n ? variantFor(product, picked)\n : product.variants.find((item) => item.id === variantId)\n const chosen = CommerceModel.resolveLineModifiers(product, modifiers, variant?.id)\n const unitCents = variant\n ? Math.round(Number(variant.priceUsd) * 100) + (chosen.ok ? chosen.extraCents : 0)\n : 0\n const missing = groups.find(\n (group) => modifiers.filter((pick) => pick.groupId === group.id).length < group.min,\n )\n const canAdd =\n Boolean(variant) && chosen.ok && quantity >= 1 && !(props.blockSoldOut && soldOut(variant))\n\n const toggle = (group: CommerceModel.ProductModifierGroup, optionId: string) => {\n setModifiers((current) => {\n const inGroup = current.filter((pick) => pick.groupId === group.id)\n const has = inGroup.some((pick) => pick.optionId === optionId)\n if (has) return current.filter((pick) => !(pick.groupId === group.id && pick.optionId === optionId))\n // A pick-one group swaps its choice; a pick-many group stops at its max.\n if (group.max === 1) {\n return [...current.filter((pick) => pick.groupId !== group.id), { groupId: group.id, optionId }]\n }\n if (inGroup.length >= group.max) return current\n return [...current, { groupId: group.id, optionId }]\n })\n }\n\n return (\n <Dialog open onClose={props.onClose} fullScreen={phone} fullWidth maxWidth=\"sm\">\n <DialogTitle>{product.name}</DialogTitle>\n <DialogContent dividers>\n <Stack spacing={3}>\n {options.length > 0\n ? options.map((option) => (\n <Box key={option.name} role=\"group\" aria-label={option.name}>\n <Typography variant=\"subtitle1\" component=\"h3\" sx={{ mb: 1 }}>\n {option.name}\n </Typography>\n <ToggleButtonGroup\n exclusive\n value={picked[option.name] ?? null}\n onChange={(_event, value: string | null) => {\n if (value) setPicked((current) => ({ ...current, [option.name]: value }))\n }}\n sx={{ flexWrap: 'wrap', gap: 1 }}\n >\n {option.values.map((value) => (\n <ToggleButton key={value} value={value} sx={{ minHeight: POS_TOUCH_PX, minWidth: 88 }}>\n {value}\n </ToggleButton>\n ))}\n </ToggleButtonGroup>\n </Box>\n ))\n : product.variants.length > 1 ? (\n <Box role=\"group\" aria-label=\"Variant\">\n <ToggleButtonGroup\n exclusive\n value={variantId || null}\n onChange={(_event, value: string | null) => value && setVariantId(value)}\n sx={{ flexWrap: 'wrap', gap: 1 }}\n >\n {product.variants.map((item) => (\n <ToggleButton key={item.id} value={item.id} sx={{ minHeight: POS_TOUCH_PX, minWidth: 88 }}>\n {`${variantLabel(item)} · ${money(Math.round(Number(item.priceUsd) * 100))}`}\n </ToggleButton>\n ))}\n </ToggleButtonGroup>\n </Box>\n ) : null}\n {options.length > 0 && !variant ? (\n <Typography variant=\"body2\" color=\"warning.main\">\n {'That combination is not sold. Pick another.'}\n </Typography>\n ) : null}\n {variant && soldOut(variant) ? (\n <Typography variant=\"body2\" color=\"warning.main\">\n {props.blockSoldOut ? 'Sold out' : 'Out of stock: selling it takes the count below zero.'}\n </Typography>\n ) : null}\n {groups.map((group) => {\n const count = modifiers.filter((pick) => pick.groupId === group.id).length\n const rule = CommerceModel.modifierGroupRequired(group)\n ? group.max === 1\n ? 'Required · pick 1'\n : `Required · pick ${group.min} to ${group.max}`\n : group.max === 1\n ? 'Optional · pick 1'\n : `Optional · up to ${group.max}`\n return (\n <Box key={group.id} role=\"group\" aria-label={group.name}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'baseline', mb: 1 }}>\n <Typography variant=\"subtitle1\" component=\"h3\">\n {group.name}\n </Typography>\n <Typography\n variant=\"caption\"\n color={count < group.min ? 'warning.main' : 'text.secondary'}\n >\n {rule}\n </Typography>\n </Stack>\n <Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>\n {group.options.map((option) => {\n const selected = modifiers.some(\n (pick) => pick.groupId === group.id && pick.optionId === option.id,\n )\n return (\n <Chip\n key={option.id}\n label={\n // A choice priced by variant shows its price on this one (AGL-4100).\n CommerceModel.modifierOptionPriceCents(option, variant?.id)\n ? `${option.name} +${money(CommerceModel.modifierOptionPriceCents(option, variant?.id))}`\n : option.name\n }\n color={selected ? 'primary' : 'default'}\n variant={selected ? 'filled' : 'outlined'}\n aria-pressed={selected}\n onClick={() => toggle(group, option.id)}\n disabled={!selected && group.max > 1 && count >= group.max}\n sx={{ minHeight: POS_TOUCH_PX, px: 1 }}\n />\n )\n })}\n </Box>\n </Box>\n )\n })}\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography variant=\"subtitle1\" component=\"h3\" sx={{ flex: 1 }}>\n {'Quantity'}\n </Typography>\n <IconButton\n aria-label=\"One fewer\"\n onClick={() => setQuantity((value) => Math.max(1, value - 1))}\n sx={{ width: POS_TOUCH_PX, height: POS_TOUCH_PX, border: 1, borderColor: 'divider' }}\n >\n {'−'}\n </IconButton>\n <TextField\n value={quantity}\n onChange={(event) => {\n const next = Math.round(Number(event.target.value.replace(/\\D/g, '')))\n setQuantity(Math.max(1, Math.min(maxQuantity, next || 1)))\n }}\n sx={{ width: 72 }}\n slotProps={{\n htmlInput: { inputMode: 'numeric', 'aria-label': 'Quantity', style: { textAlign: 'center' } },\n }}\n />\n <IconButton\n aria-label=\"One more\"\n onClick={() => setQuantity((value) => Math.min(maxQuantity, value + 1))}\n sx={{ width: POS_TOUCH_PX, height: POS_TOUCH_PX, border: 1, borderColor: 'divider' }}\n >\n {'+'}\n </IconButton>\n </Stack>\n </Stack>\n </DialogContent>\n <DialogActions sx={{ p: 2, gap: 1 }}>\n {props.onRemove ? (\n <Button color=\"error\" onClick={props.onRemove} sx={{ minHeight: POS_TOUCH_PX, mr: 'auto' }}>\n {'Remove'}\n </Button>\n ) : null}\n <Button onClick={props.onClose} sx={{ minHeight: POS_TOUCH_PX }}>\n {'Cancel'}\n </Button>\n <Button\n variant=\"contained\"\n disabled={!canAdd}\n onClick={() => {\n if (!variant || !chosen.ok) return\n props.onConfirm({ variant, modifiers, quantity })\n }}\n sx={{ minHeight: POS_TOUCH_PX, minWidth: 160 }}\n >\n {missing\n ? `Choose ${missing.name.toLowerCase()}`\n : `${props.onRemove ? 'Update' : 'Add'} · ${money(unitCents * quantity)}`}\n </Button>\n </DialogActions>\n </Dialog>\n )\n}\nPosItemDialog.displayName = 'PosItemDialog'\n"],"names":["Box","Button","Chip","Dialog","DialogActions","DialogContent","DialogTitle","IconButton","Stack","TextField","ToggleButton","ToggleButtonGroup","Typography","useMediaQuery","useTheme","useEffect","useMemo","useState","CommerceModel","usd","POS_TOUCH_PX","posItemNeedsChoice","product","variants","length","productModifierGroups","variantFor","picked","find","variant","options","every","option","name","variantLabel","Object","values","join","soldOut","inventory","PosItemDialog","props","initial","money","formatMoney","maxQuantity","theme","phone","breakpoints","down","groups","setPicked","variantId","setVariantId","modifiers","setModifiers","quantity","setQuantity","start","id","item","chosen","resolveLineModifiers","unitCents","Math","round","Number","priceUsd","ok","extraCents","missing","group","filter","pick","groupId","min","canAdd","Boolean","blockSoldOut","toggle","optionId","current","inGroup","has","some","max","open","onClose","fullScreen","fullWidth","maxWidth","dividers","spacing","map","role","aria-label","component","sx","mb","exclusive","value","onChange","_event","flexWrap","gap","minHeight","minWidth","color","count","rule","modifierGroupRequired","direction","alignItems","display","selected","label","modifierOptionPriceCents","aria-pressed","onClick","disabled","px","flex","width","height","border","borderColor","event","next","target","replace","slotProps","htmlInput","inputMode","style","textAlign","p","onRemove","mr","onConfirm","toLowerCase","displayName"],"mappings":"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"}
@@ -83,7 +83,8 @@ import { useKioskIdle } from "./pos-kiosk/use-kiosk-idle.js";
83
83
  }, label ? {
84
84
  label
85
85
  } : {}, {
86
- unitCents: variant.priceCents + picked.reduce((sum, option)=>sum + option.priceCents, 0)
86
+ // A choice priced by variant adds its price on this one (AGL-4100).
87
+ unitCents: variant.priceCents + picked.reduce((sum, option)=>sum + CommerceModel.modifierOptionPriceCents(option, variant.id), 0)
87
88
  });
88
89
  }
89
90
  /**
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/pos-kiosk-page.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 type { ConsolePublicPageProps } from '@aglyn/aglyn'\nimport Alert from '@mui/material/Alert'\nimport Box from '@mui/material/Box'\nimport Button from '@mui/material/Button'\nimport CircularProgress from '@mui/material/CircularProgress'\nimport Container from '@mui/material/Container'\nimport Dialog from '@mui/material/Dialog'\nimport DialogActions from '@mui/material/DialogActions'\nimport DialogContent from '@mui/material/DialogContent'\nimport DialogContentText from '@mui/material/DialogContentText'\nimport DialogTitle from '@mui/material/DialogTitle'\nimport Stack from '@mui/material/Stack'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport * as CommerceModel from '../../model'\nimport { newAttemptKey, posNativeBridge } from './pos/pos-api'\nimport { PosItemDialog, posItemNeedsChoice, type PosItemChoice } from './pos/pos-item-dialog.component'\nimport { PosDisplayBrandMark, PosDisplayBrandScreen } from './pos-display/brand-screen'\nimport { PosDisplayPairingScreen } from './pos-display/pairing-screen'\nimport { displayMoney, type PosDisplayAnswer } from './pos-display/pos-display-api'\nimport { PosDisplayReceiptScreen } from './pos-display/receipt-screen'\nimport { PosDisplayTipScreen, posDisplayTouchSx } from './pos-display/tip-screen'\nimport { KioskDone, KioskPaying, KioskReview, type KioskPayOptions } from './pos-kiosk/kiosk-checkout'\nimport { KioskMenu, type KioskCartLine } from './pos-kiosk/kiosk-menu'\nimport { KioskStaffControl } from './pos-kiosk/kiosk-staff'\nimport {\n clearKioskCustomerStorage,\n kioskApi,\n readKioskToken,\n storeKioskToken,\n} from './pos-kiosk/pos-kiosk-api'\nimport { useKioskIdle } from './pos-kiosk/use-kiosk-idle'\n\n/** How often the kiosk asks after a card payment in flight. */\nexport const POS_KIOSK_PAYMENT_POLL_MS = 1_500\n\ntype Phase =\n | { kind: 'start' }\n | { kind: 'menu' }\n | { kind: 'review' }\n | { kind: 'tip'; method: 'reader' | 'tap' }\n | { kind: 'paying'; method: 'reader' | 'tap'; paymentId: string | null; chargeCents: number; failure: string | null }\n | { kind: 'receipt' }\n | { kind: 'done' }\n\ntype TipAnswer = { tipChoice: 'percent' | 'custom' | 'none'; tipPercent?: number; tipCents?: number }\n\n/** A product as the register's item sheet takes it, from the kiosk's catalog. */\nexport function kioskItemProduct(product: CommerceModel.PosKioskProduct) {\n return {\n $id: product.id,\n name: product.name,\n slug: product.id,\n type: 'physical',\n status: 'active',\n options: product.options,\n modifierGroups: product.modifierGroups,\n variants: product.variants.map((variant) => ({\n id: variant.id,\n options: variant.options,\n priceUsd: variant.priceCents / 100,\n // A sold-out variant reads as zero on hand; anything else as untracked,\n // so the kiosk never learns or shows a stock count.\n inventory: variant.soldOut ? 0 : null,\n })),\n } as unknown as CommerceModel.HostProduct & { $id: string }\n}\n\n/** The cart line a choice makes: same item and choices merge. */\nexport function kioskCartLine(\n product: CommerceModel.PosKioskProduct,\n choice: PosItemChoice,\n): KioskCartLine {\n const variant = product.variants.find((candidate) => candidate.id === choice.variant.id) ?? product.variants[0]!\n const picked = choice.modifiers\n .map((pick) => {\n const group = product.modifierGroups.find((candidate) => candidate.id === pick.groupId)\n return group?.options.find((option) => option.id === pick.optionId)\n })\n .filter((option): option is CommerceModel.ProductModifierOption => Boolean(option))\n const label = CommerceModel.lineLabelWithModifiers(\n Object.keys(variant.options).length ? Object.values(variant.options).join(' / ') : undefined,\n picked,\n )\n return {\n key: `${product.id}:${variant.id}:${CommerceModel.modifierSelectionKey(choice.modifiers)}`,\n productId: product.id,\n ...(variant.id !== 'default' || product.variants.length > 1 ? { variantId: variant.id } : {}),\n quantity: Math.min(CommerceModel.POS_KIOSK_MAX_QUANTITY, choice.quantity),\n ...(choice.modifiers.length ? { modifiers: choice.modifiers } : {}),\n name: product.name,\n ...(label ? { label } : {}),\n unitCents: variant.priceCents + picked.reduce((sum, option) => sum + option.priceCents, 0),\n }\n}\n\n/**\n * The self-service kiosk (AGL-3623), served at `/kiosk/commerce/pos-kiosk`\n * with no staff session: a customer builds their own order and pays on the\n * register's card reader or Tap to Pay, or sends it to the counter.\n *\n * It holds one secret — the kiosk token from pairing — and everything the\n * customer chooses or types lives in this component's memory only. The idle\n * reset (and \"Done\", and \"Start over\") voids an unpaid order, drops the\n * cart, the tip and any email or phone, sweeps the kiosk's storage keys, and\n * remounts every screen, so the next customer finds nothing of the last.\n */\nexport default function PosKioskPage(_props: ConsolePublicPageProps) {\n const [token, setToken] = useState<string | null | undefined>(undefined)\n const [context, setContext] = useState<CommerceModel.PosKioskContext | null>(null)\n const [catalog, setCatalog] = useState<CommerceModel.PosKioskCatalog | null>(null)\n const [catalogLoading, setCatalogLoading] = useState(false)\n const [categoryId, setCategoryId] = useState<string | null>(null)\n const [phase, setPhase] = useState<Phase>({ kind: 'start' })\n const [lines, setLines] = useState<KioskCartLine[]>([])\n const [itemProduct, setItemProduct] = useState<CommerceModel.PosKioskProduct | null>(null)\n const [cartOpen, setCartOpen] = useState(false)\n const [sale, setSale] = useState<CommerceModel.PosKioskSale | null>(null)\n const [tip, setTip] = useState<TipAnswer>({ tipChoice: 'none' })\n const [busy, setBusy] = useState(false)\n const [notice, setNotice] = useState<string | null>(null)\n const [offline, setOffline] = useState(false)\n // Bumped by every reset: keys every screen, so inputs never carry over.\n const [turn, setTurn] = useState(0)\n // One attempt per cart, and one per press of a payment button.\n const checkoutKey = useRef<string | null>(null)\n const payKey = useRef<string | null>(null)\n const saleRef = useRef(sale)\n saleRef.current = sale\n const phaseRef = useRef(phase)\n phaseRef.current = phase\n\n useEffect(() => {\n setToken(readKioskToken())\n }, [])\n\n const unpaired = useCallback(() => {\n storeKioskToken(null)\n clearKioskCustomerStorage()\n setToken(null)\n setContext(null)\n setCatalog(null)\n setSale(null)\n setLines([])\n setPhase({ kind: 'start' })\n }, [])\n\n /** A response that says the kiosk is no longer paired ends the session. */\n const lost = useCallback(\n (status: number) => {\n if (status === 401) {\n unpaired()\n return true\n }\n return false\n },\n [unpaired],\n )\n\n // The kiosk's settings and the menu: read when paired, and again every\n // ten minutes while idle on the start screen, so an edit reaches it.\n const refresh = useCallback(async () => {\n if (!token) return\n const result = await kioskApi.context(token)\n if (lost(result.status)) return\n setOffline(!result.ok && result.status === 0)\n if (result.ok && result.value) setContext(result.value)\n }, [token, lost])\n\n useEffect(() => {\n void refresh()\n const timer = setInterval(() => {\n if (phaseRef.current.kind === 'start') void refresh()\n }, 10 * 60 * 1000)\n return () => clearInterval(timer)\n }, [refresh])\n\n useEffect(() => {\n if (!token) return undefined\n let active = true\n setCatalogLoading(true)\n void kioskApi.catalog(token, categoryId).then((result) => {\n if (!active) return\n setCatalogLoading(false)\n if (lost(result.status)) return\n if (result.ok && result.value) setCatalog(result.value)\n else setOffline(result.status === 0)\n })\n return () => {\n active = false\n }\n }, [token, categoryId, turn, lost])\n\n const currency = context?.currency ?? catalog?.currency ?? 'usd'\n const money = useCallback((cents: number) => displayMoney(cents, currency), [currency])\n\n /**\n * THE RESET: the unpaid order voided, every customer detail dropped from\n * memory and storage, and the screens remounted on the start screen.\n */\n const reset = useCallback(() => {\n const open = saleRef.current\n if (token && open && open.status === 'open') void kioskApi.abandon(token, open.orderId)\n setSale(null)\n setLines([])\n setTip({ tipChoice: 'none' })\n setItemProduct(null)\n setCartOpen(false)\n setNotice(null)\n setCategoryId(null)\n checkoutKey.current = null\n payKey.current = null\n clearKioskCustomerStorage()\n setPhase({ kind: 'start' })\n setTurn((value) => value + 1)\n }, [token])\n\n const idle = useKioskIdle({\n // Never while a card is being collected: the customer is at the reader.\n active: Boolean(token) && phase.kind !== 'start' && phase.kind !== 'paying',\n idleSeconds: context?.idleSeconds ?? CommerceModel.POS_KIOSK_IDLE_SECONDS_DEFAULT,\n warningSeconds: CommerceModel.POS_KIOSK_IDLE_WARNING_SECONDS,\n onExpire: reset,\n })\n\n // The order-number screen returns to the start on its own.\n useEffect(() => {\n if (phase.kind !== 'done') return undefined\n const timer = setTimeout(reset, CommerceModel.POS_KIOSK_DONE_SECONDS * 1000)\n return () => clearTimeout(timer)\n }, [phase.kind, reset])\n\n const addChoice = useCallback((product: CommerceModel.PosKioskProduct, choice: PosItemChoice) => {\n const line = kioskCartLine(product, choice)\n checkoutKey.current = null\n setLines((current) => {\n const existing = current.find((candidate) => candidate.key === line.key)\n if (existing) {\n return current.map((candidate) =>\n candidate.key === line.key\n ? { ...candidate, quantity: Math.min(CommerceModel.POS_KIOSK_MAX_QUANTITY, candidate.quantity + line.quantity) }\n : candidate,\n )\n }\n if (current.length >= CommerceModel.POS_KIOSK_MAX_LINES) return current\n return [...current, line]\n })\n }, [])\n\n const tapProduct = useCallback(\n (product: CommerceModel.PosKioskProduct) => {\n const asItem = kioskItemProduct(product)\n if (posItemNeedsChoice(asItem)) {\n setItemProduct(product)\n return\n }\n const variant = asItem.variants[0]\n if (!variant || product.variants[0]?.soldOut) return\n addChoice(product, { variant, modifiers: [], quantity: 1 })\n },\n [addChoice],\n )\n\n const checkout = useCallback(async () => {\n if (!token || busy || lines.length === 0) return\n if (!checkoutKey.current) checkoutKey.current = newAttemptKey()\n setBusy(true)\n setNotice(null)\n const result = await kioskApi.checkout(\n token,\n lines.map(({ productId, variantId, quantity, modifiers }) => ({\n productId,\n ...(variantId ? { variantId } : {}),\n quantity,\n ...(modifiers ? { modifiers } : {}),\n })),\n checkoutKey.current,\n )\n setBusy(false)\n if (lost(result.status)) return\n if (!result.ok || !result.value?.sale) {\n // A refused cart is changed before it is tried again.\n checkoutKey.current = null\n setNotice(result.error ?? 'Your order could not be priced. Try again.')\n return\n }\n setSale(result.value.sale)\n setCartOpen(false)\n setPhase({ kind: 'review' })\n }, [token, busy, lines, lost])\n\n const backToMenu = useCallback(() => {\n const open = saleRef.current\n if (token && open && open.status === 'open') void kioskApi.abandon(token, open.orderId)\n setSale(null)\n checkoutKey.current = null\n setNotice(null)\n setPhase({ kind: 'menu' })\n }, [token])\n\n const sendToCounter = useCallback(async () => {\n const open = saleRef.current\n if (!token || !open) return\n setBusy(true)\n const result = await kioskApi.counter(token, open.orderId)\n setBusy(false)\n if (lost(result.status)) return\n if (!result.ok || !result.value?.sale) {\n setNotice(result.error ?? 'Your order could not be sent to the counter.')\n return\n }\n setSale(result.value.sale)\n setLines([])\n setPhase({ kind: 'done' })\n }, [token, lost])\n\n const startPayment = useCallback(\n async (method: 'reader' | 'tap', answer: TipAnswer) => {\n const open = saleRef.current\n if (!token || !open) return\n if (!payKey.current) payKey.current = newAttemptKey()\n setBusy(true)\n setNotice(null)\n const result = await kioskApi.pay(token, { orderId: open.orderId, method, ...answer }, payKey.current)\n if (lost(result.status)) return setBusy(false)\n if (!result.ok || !result.value) {\n setBusy(false)\n payKey.current = null\n setNotice(result.error ?? 'The card payment could not be started.')\n setPhase({ kind: 'review' })\n return\n }\n const { sale: next, paymentId, clientSecret, paymentIntentId } = result.value\n setSale(next)\n const tipCents = CommerceModel.posKioskTipCents(answer.tipChoice, {\n baseCents: open.dueCents,\n enabled: true,\n percentages: context?.tipping.percentages ?? [],\n percent: answer.tipPercent,\n cents: answer.tipCents,\n })\n const chargeCents = open.dueCents + (tipCents ?? 0)\n setPhase({ kind: 'paying', method, paymentId: paymentId ?? null, chargeCents, failure: null })\n setBusy(false)\n if (method === 'tap') {\n const bridge = posNativeBridge()\n if (!bridge || !clientSecret || !paymentIntentId) {\n setPhase({ kind: 'paying', method, paymentId: paymentId ?? null, chargeCents, failure: 'Tap to Pay is not available on this device.' })\n return\n }\n const collected = await bridge\n .collectCardPayment({ paymentIntentId, clientSecret, amountCents: chargeCents })\n .catch(() => ({ status: 'failed' as const, message: undefined }))\n if (collected.status !== 'collected') {\n setPhase({\n kind: 'paying',\n method,\n paymentId: paymentId ?? null,\n chargeCents,\n failure: collected.status === 'canceled' ? 'The payment was canceled.' : (collected.message ?? 'The card was not read.'),\n })\n }\n }\n },\n [token, lost, context],\n )\n\n // The card in flight, asked after until it is paid or it fails.\n const paymentId = phase.kind === 'paying' ? phase.paymentId : null\n const paymentFailed = phase.kind === 'paying' ? Boolean(phase.failure) : false\n useEffect(() => {\n if (!token || !paymentId || paymentFailed) return undefined\n const open = saleRef.current\n if (!open) return undefined\n let active = true\n const timer = setInterval(async () => {\n const result = await kioskApi.paymentStatus(token, open.orderId, paymentId)\n if (!active) return\n if (lost(result.status)) return\n const next = result.value?.sale\n if (!next) return\n setSale(next)\n if (next.status === 'paid') {\n clearInterval(timer)\n setLines([])\n payKey.current = null\n setPhase({ kind: 'receipt' })\n return\n }\n const status = next.payment?.id === paymentId ? next.payment.status : ''\n if (status === 'failed' || status === 'canceled') {\n clearInterval(timer)\n payKey.current = null\n setPhase((current) =>\n current.kind === 'paying'\n ? { ...current, failure: next.payment?.failureMessage ?? 'The card was declined.' }\n : current,\n )\n }\n }, POS_KIOSK_PAYMENT_POLL_MS)\n return () => {\n active = false\n clearInterval(timer)\n }\n }, [token, paymentId, paymentFailed, lost])\n\n const cancelPayment = useCallback(async () => {\n const open = saleRef.current\n if (!token || !open || phase.kind !== 'paying') return\n setBusy(true)\n if (phase.paymentId && !phase.failure) await kioskApi.cancelPayment(token, open.orderId, phase.paymentId)\n const fresh = await kioskApi.sale(token, open.orderId)\n setBusy(false)\n payKey.current = null\n if (fresh.value?.sale) setSale(fresh.value.sale)\n if (fresh.value?.sale.status === 'paid') {\n setPhase({ kind: 'receipt' })\n return\n }\n setPhase({ kind: 'review' })\n }, [token, phase])\n\n const answerReceipt = useCallback(\n async (answer: PosDisplayAnswer) => {\n const paid = saleRef.current\n if (!token || !paid || !answer.receiptChannel) return\n setBusy(true)\n const result = await kioskApi.receipt(token, {\n orderId: paid.orderId,\n channel: answer.receiptChannel,\n ...(answer.email ? { to: answer.email } : {}),\n ...(answer.phone ? { to: answer.phone } : {}),\n ...(answer.marketingOptIn ? { marketingOptIn: true } : {}),\n })\n setBusy(false)\n if (lost(result.status)) return\n if (!result.ok) {\n setNotice(result.error ?? 'Your receipt could not be sent. Ask at the counter.')\n }\n setPhase({ kind: 'done' })\n },\n [token, lost],\n )\n\n const pay: KioskPayOptions = useMemo(\n () => ({\n reader: Boolean(context?.payments.cardPresent && context.payments.reader),\n tap: Boolean(context?.payments.cardPresent && posNativeBridge()),\n counter: Boolean(context?.payments.payAtCounter),\n }),\n [context],\n )\n\n const choosePay = useCallback(\n (method: 'reader' | 'tap') => {\n if (context?.tipping.enabled) {\n setPhase({ kind: 'tip', method })\n return\n }\n setTip({ tipChoice: 'none' })\n void startPayment(method, { tipChoice: 'none' })\n },\n [context, startPayment],\n )\n\n if (token === undefined) {\n return (\n <Box sx={{ minHeight: '100dvh', display: 'grid', placeItems: 'center' }}>\n <CircularProgress aria-label=\"Loading\" />\n </Box>\n )\n }\n\n if (!token) {\n return (\n <PosDisplayPairingScreen\n mode=\"kiosk\"\n onPaired={(next) => {\n storeKioskToken(next)\n setToken(next)\n }}\n />\n )\n }\n\n const branding = context?.branding ?? null\n let screen\n if (phase.kind === 'start') {\n screen = (\n <PosDisplayBrandScreen branding={branding} message={branding?.message || 'Order here'}>\n <Button\n variant=\"contained\"\n size=\"large\"\n onClick={() => setPhase({ kind: 'menu' })}\n sx={(theme) => ({ ...posDisplayTouchSx(theme), px: 6 })}\n >\n {'Start order'}\n </Button>\n </PosDisplayBrandScreen>\n )\n } else if (phase.kind === 'menu') {\n screen = (\n <>\n {notice ? (\n <Alert severity=\"warning\" onClose={() => setNotice(null)} sx={{ m: 2, mb: 0 }}>\n {notice}\n </Alert>\n ) : null}\n <KioskMenu\n catalog={catalog}\n loading={catalogLoading && !catalog}\n categoryId={categoryId}\n onCategory={setCategoryId}\n onProduct={tapProduct}\n lines={lines}\n currency={currency}\n onReview={() => void checkout()}\n onQuantity={(key, quantity) => {\n checkoutKey.current = null\n setLines((current) =>\n quantity <= 0\n ? current.filter((line) => line.key !== key)\n : current.map((line) => (line.key === key ? { ...line, quantity } : line)),\n )\n }}\n cartOpen={cartOpen}\n onCartOpen={setCartOpen}\n busy={busy}\n />\n </>\n )\n } else if (phase.kind === 'review' && sale) {\n screen = (\n <KioskReview\n sale={sale}\n currency={currency}\n pay={pay}\n busy={busy}\n notice={notice}\n onPay={choosePay}\n onCounter={() => void sendToCounter()}\n onBack={backToMenu}\n />\n )\n } else if (phase.kind === 'tip' && sale && context) {\n screen = (\n <Container maxWidth=\"md\" sx={{ py: 4 }}>\n <Stack spacing={4} sx={{ alignItems: 'center' }}>\n <PosDisplayBrandMark branding={branding} />\n <PosDisplayTipScreen\n tip={{ baseCents: sale.dueCents, percentages: context.tipping.percentages, allowCustom: true }}\n promptId={`kiosk-${sale.orderId}`}\n busy={busy}\n currency={currency}\n onAnswer={(answer) => {\n const next: TipAnswer = {\n tipChoice: answer.tipChoice ?? 'none',\n ...(answer.tipPercent ? { tipPercent: answer.tipPercent } : {}),\n ...(answer.tipChoice === 'custom' ? { tipCents: answer.tipCents ?? 0 } : {}),\n }\n setTip(next)\n void startPayment(phase.method, next)\n }}\n />\n <Button onClick={() => setPhase({ kind: 'review' })} disabled={busy}>\n {'Back'}\n </Button>\n </Stack>\n </Container>\n )\n } else if (phase.kind === 'paying' && sale) {\n screen = (\n <KioskPaying\n chargeCents={phase.chargeCents}\n currency={currency}\n method={phase.method}\n failure={phase.failure}\n testMode={Boolean(context?.testMode && phase.method === 'reader')}\n busy={busy}\n onCancel={() => void cancelPayment()}\n onRetry={() => void startPayment(phase.method, tip)}\n onCounter={pay.counter ? () => void sendToCounter() : null}\n onSimulate={\n phase.paymentId\n ? () => void kioskApi.simulate(token, sale.orderId, phase.paymentId as string)\n : null\n }\n />\n )\n } else if (phase.kind === 'receipt' && sale && context) {\n screen = (\n <Container maxWidth=\"md\" sx={{ py: 4 }}>\n <Stack spacing={4} sx={{ alignItems: 'center' }}>\n <PosDisplayBrandMark branding={branding} />\n <PosDisplayReceiptScreen\n receipt={{ channels: context.receipts, offerMarketing: context.offerMarketing }}\n promptId={`kiosk-${sale.orderId}`}\n busy={busy}\n onAnswer={(answer) => void answerReceipt(answer)}\n />\n </Stack>\n </Container>\n )\n } else if (phase.kind === 'done' && sale) {\n screen = <KioskDone sale={sale} currency={currency} onDone={reset} />\n } else {\n screen = <PosDisplayBrandScreen branding={branding} busy />\n }\n\n return (\n <Box key={turn} sx={{ position: 'relative', minHeight: '100dvh' }}>\n {offline ? (\n <Alert severity=\"warning\" sx={{ position: 'fixed', top: 0, left: 0, right: 0, zIndex: (theme) => theme.zIndex.modal + 1 }}>\n {'Reconnecting…'}\n </Alert>\n ) : null}\n {screen}\n <PosItemDialog\n product={itemProduct ? kioskItemProduct(itemProduct) : null}\n onClose={() => setItemProduct(null)}\n onConfirm={(choice) => {\n const product = itemProduct\n setItemProduct(null)\n if (product) addChoice(product, choice)\n }}\n formatMoney={money}\n blockSoldOut\n maxQuantity={CommerceModel.POS_KIOSK_MAX_QUANTITY}\n />\n <Dialog open={idle.warning !== null} onClose={idle.stillHere} maxWidth=\"xs\" fullWidth>\n <DialogTitle>{'Still there?'}</DialogTitle>\n <DialogContent>\n <DialogContentText>\n {`This order will be cleared in ${idle.warning ?? 0} seconds.`}\n </DialogContentText>\n </DialogContent>\n <DialogActions>\n <Button onClick={reset}>{'Start over'}</Button>\n <Button variant=\"contained\" onClick={idle.stillHere}>\n {'I’m still here'}\n </Button>\n </DialogActions>\n </Dialog>\n {phase.kind === 'start' || phase.kind === 'menu' ? (\n <KioskStaffControl token={token} onExit={unpaired} onUnauthorized={unpaired} />\n ) : null}\n </Box>\n )\n}\n\nPosKioskPage.displayName = 'PosKioskPage'\n"],"names":["Alert","Box","Button","CircularProgress","Container","Dialog","DialogActions","DialogContent","DialogContentText","DialogTitle","Stack","useCallback","useEffect","useMemo","useRef","useState","CommerceModel","newAttemptKey","posNativeBridge","PosItemDialog","posItemNeedsChoice","PosDisplayBrandMark","PosDisplayBrandScreen","PosDisplayPairingScreen","displayMoney","PosDisplayReceiptScreen","PosDisplayTipScreen","posDisplayTouchSx","KioskDone","KioskPaying","KioskReview","KioskMenu","KioskStaffControl","clearKioskCustomerStorage","kioskApi","readKioskToken","storeKioskToken","useKioskIdle","POS_KIOSK_PAYMENT_POLL_MS","kioskItemProduct","product","$id","id","name","slug","type","status","options","modifierGroups","variants","map","variant","priceUsd","priceCents","inventory","soldOut","kioskCartLine","choice","find","candidate","picked","modifiers","pick","group","groupId","option","optionId","filter","Boolean","label","lineLabelWithModifiers","Object","keys","length","values","join","undefined","key","modifierSelectionKey","productId","variantId","quantity","Math","min","POS_KIOSK_MAX_QUANTITY","unitCents","reduce","sum","PosKioskPage","_props","context","idle","token","setToken","setContext","catalog","setCatalog","catalogLoading","setCatalogLoading","categoryId","setCategoryId","phase","setPhase","kind","lines","setLines","itemProduct","setItemProduct","cartOpen","setCartOpen","sale","setSale","tip","setTip","tipChoice","busy","setBusy","notice","setNotice","offline","setOffline","turn","setTurn","checkoutKey","payKey","saleRef","current","phaseRef","unpaired","lost","refresh","result","ok","value","timer","setInterval","clearInterval","active","then","currency","money","cents","reset","open","abandon","orderId","idleSeconds","POS_KIOSK_IDLE_SECONDS_DEFAULT","warningSeconds","POS_KIOSK_IDLE_WARNING_SECONDS","onExpire","setTimeout","POS_KIOSK_DONE_SECONDS","clearTimeout","addChoice","line","existing","POS_KIOSK_MAX_LINES","tapProduct","asItem","checkout","error","backToMenu","sendToCounter","counter","startPayment","method","answer","pay","next","paymentId","clientSecret","paymentIntentId","tipCents","posKioskTipCents","baseCents","dueCents","enabled","percentages","tipping","percent","tipPercent","chargeCents","failure","bridge","collected","collectCardPayment","amountCents","catch","message","paymentFailed","paymentStatus","payment","failureMessage","cancelPayment","fresh","answerReceipt","paid","receiptChannel","receipt","channel","email","to","phone","marketingOptIn","reader","payments","cardPresent","tap","payAtCounter","choosePay","sx","minHeight","display","placeItems","aria-label","mode","onPaired","branding","screen","size","onClick","theme","px","severity","onClose","m","mb","loading","onCategory","onProduct","onReview","onQuantity","onCartOpen","onPay","onCounter","onBack","maxWidth","py","spacing","alignItems","allowCustom","promptId","onAnswer","disabled","testMode","onCancel","onRetry","onSimulate","simulate","channels","receipts","offerMarketing","onDone","position","top","left","right","zIndex","modal","onConfirm","formatMoney","blockSoldOut","maxQuantity","warning","stillHere","fullWidth","onExit","onUnauthorized","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/pos-kiosk-page.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 type { ConsolePublicPageProps } from '@aglyn/aglyn'\nimport Alert from '@mui/material/Alert'\nimport Box from '@mui/material/Box'\nimport Button from '@mui/material/Button'\nimport CircularProgress from '@mui/material/CircularProgress'\nimport Container from '@mui/material/Container'\nimport Dialog from '@mui/material/Dialog'\nimport DialogActions from '@mui/material/DialogActions'\nimport DialogContent from '@mui/material/DialogContent'\nimport DialogContentText from '@mui/material/DialogContentText'\nimport DialogTitle from '@mui/material/DialogTitle'\nimport Stack from '@mui/material/Stack'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport * as CommerceModel from '../../model'\nimport { newAttemptKey, posNativeBridge } from './pos/pos-api'\nimport { PosItemDialog, posItemNeedsChoice, type PosItemChoice } from './pos/pos-item-dialog.component'\nimport { PosDisplayBrandMark, PosDisplayBrandScreen } from './pos-display/brand-screen'\nimport { PosDisplayPairingScreen } from './pos-display/pairing-screen'\nimport { displayMoney, type PosDisplayAnswer } from './pos-display/pos-display-api'\nimport { PosDisplayReceiptScreen } from './pos-display/receipt-screen'\nimport { PosDisplayTipScreen, posDisplayTouchSx } from './pos-display/tip-screen'\nimport { KioskDone, KioskPaying, KioskReview, type KioskPayOptions } from './pos-kiosk/kiosk-checkout'\nimport { KioskMenu, type KioskCartLine } from './pos-kiosk/kiosk-menu'\nimport { KioskStaffControl } from './pos-kiosk/kiosk-staff'\nimport {\n clearKioskCustomerStorage,\n kioskApi,\n readKioskToken,\n storeKioskToken,\n} from './pos-kiosk/pos-kiosk-api'\nimport { useKioskIdle } from './pos-kiosk/use-kiosk-idle'\n\n/** How often the kiosk asks after a card payment in flight. */\nexport const POS_KIOSK_PAYMENT_POLL_MS = 1_500\n\ntype Phase =\n | { kind: 'start' }\n | { kind: 'menu' }\n | { kind: 'review' }\n | { kind: 'tip'; method: 'reader' | 'tap' }\n | { kind: 'paying'; method: 'reader' | 'tap'; paymentId: string | null; chargeCents: number; failure: string | null }\n | { kind: 'receipt' }\n | { kind: 'done' }\n\ntype TipAnswer = { tipChoice: 'percent' | 'custom' | 'none'; tipPercent?: number; tipCents?: number }\n\n/** A product as the register's item sheet takes it, from the kiosk's catalog. */\nexport function kioskItemProduct(product: CommerceModel.PosKioskProduct) {\n return {\n $id: product.id,\n name: product.name,\n slug: product.id,\n type: 'physical',\n status: 'active',\n options: product.options,\n modifierGroups: product.modifierGroups,\n variants: product.variants.map((variant) => ({\n id: variant.id,\n options: variant.options,\n priceUsd: variant.priceCents / 100,\n // A sold-out variant reads as zero on hand; anything else as untracked,\n // so the kiosk never learns or shows a stock count.\n inventory: variant.soldOut ? 0 : null,\n })),\n } as unknown as CommerceModel.HostProduct & { $id: string }\n}\n\n/** The cart line a choice makes: same item and choices merge. */\nexport function kioskCartLine(\n product: CommerceModel.PosKioskProduct,\n choice: PosItemChoice,\n): KioskCartLine {\n const variant = product.variants.find((candidate) => candidate.id === choice.variant.id) ?? product.variants[0]!\n const picked = choice.modifiers\n .map((pick) => {\n const group = product.modifierGroups.find((candidate) => candidate.id === pick.groupId)\n return group?.options.find((option) => option.id === pick.optionId)\n })\n .filter((option): option is CommerceModel.ProductModifierOption => Boolean(option))\n const label = CommerceModel.lineLabelWithModifiers(\n Object.keys(variant.options).length ? Object.values(variant.options).join(' / ') : undefined,\n picked,\n )\n return {\n key: `${product.id}:${variant.id}:${CommerceModel.modifierSelectionKey(choice.modifiers)}`,\n productId: product.id,\n ...(variant.id !== 'default' || product.variants.length > 1 ? { variantId: variant.id } : {}),\n quantity: Math.min(CommerceModel.POS_KIOSK_MAX_QUANTITY, choice.quantity),\n ...(choice.modifiers.length ? { modifiers: choice.modifiers } : {}),\n name: product.name,\n ...(label ? { label } : {}),\n // A choice priced by variant adds its price on this one (AGL-4100).\n unitCents:\n variant.priceCents +\n picked.reduce((sum, option) => sum + CommerceModel.modifierOptionPriceCents(option, variant.id), 0),\n }\n}\n\n/**\n * The self-service kiosk (AGL-3623), served at `/kiosk/commerce/pos-kiosk`\n * with no staff session: a customer builds their own order and pays on the\n * register's card reader or Tap to Pay, or sends it to the counter.\n *\n * It holds one secret — the kiosk token from pairing — and everything the\n * customer chooses or types lives in this component's memory only. The idle\n * reset (and \"Done\", and \"Start over\") voids an unpaid order, drops the\n * cart, the tip and any email or phone, sweeps the kiosk's storage keys, and\n * remounts every screen, so the next customer finds nothing of the last.\n */\nexport default function PosKioskPage(_props: ConsolePublicPageProps) {\n const [token, setToken] = useState<string | null | undefined>(undefined)\n const [context, setContext] = useState<CommerceModel.PosKioskContext | null>(null)\n const [catalog, setCatalog] = useState<CommerceModel.PosKioskCatalog | null>(null)\n const [catalogLoading, setCatalogLoading] = useState(false)\n const [categoryId, setCategoryId] = useState<string | null>(null)\n const [phase, setPhase] = useState<Phase>({ kind: 'start' })\n const [lines, setLines] = useState<KioskCartLine[]>([])\n const [itemProduct, setItemProduct] = useState<CommerceModel.PosKioskProduct | null>(null)\n const [cartOpen, setCartOpen] = useState(false)\n const [sale, setSale] = useState<CommerceModel.PosKioskSale | null>(null)\n const [tip, setTip] = useState<TipAnswer>({ tipChoice: 'none' })\n const [busy, setBusy] = useState(false)\n const [notice, setNotice] = useState<string | null>(null)\n const [offline, setOffline] = useState(false)\n // Bumped by every reset: keys every screen, so inputs never carry over.\n const [turn, setTurn] = useState(0)\n // One attempt per cart, and one per press of a payment button.\n const checkoutKey = useRef<string | null>(null)\n const payKey = useRef<string | null>(null)\n const saleRef = useRef(sale)\n saleRef.current = sale\n const phaseRef = useRef(phase)\n phaseRef.current = phase\n\n useEffect(() => {\n setToken(readKioskToken())\n }, [])\n\n const unpaired = useCallback(() => {\n storeKioskToken(null)\n clearKioskCustomerStorage()\n setToken(null)\n setContext(null)\n setCatalog(null)\n setSale(null)\n setLines([])\n setPhase({ kind: 'start' })\n }, [])\n\n /** A response that says the kiosk is no longer paired ends the session. */\n const lost = useCallback(\n (status: number) => {\n if (status === 401) {\n unpaired()\n return true\n }\n return false\n },\n [unpaired],\n )\n\n // The kiosk's settings and the menu: read when paired, and again every\n // ten minutes while idle on the start screen, so an edit reaches it.\n const refresh = useCallback(async () => {\n if (!token) return\n const result = await kioskApi.context(token)\n if (lost(result.status)) return\n setOffline(!result.ok && result.status === 0)\n if (result.ok && result.value) setContext(result.value)\n }, [token, lost])\n\n useEffect(() => {\n void refresh()\n const timer = setInterval(() => {\n if (phaseRef.current.kind === 'start') void refresh()\n }, 10 * 60 * 1000)\n return () => clearInterval(timer)\n }, [refresh])\n\n useEffect(() => {\n if (!token) return undefined\n let active = true\n setCatalogLoading(true)\n void kioskApi.catalog(token, categoryId).then((result) => {\n if (!active) return\n setCatalogLoading(false)\n if (lost(result.status)) return\n if (result.ok && result.value) setCatalog(result.value)\n else setOffline(result.status === 0)\n })\n return () => {\n active = false\n }\n }, [token, categoryId, turn, lost])\n\n const currency = context?.currency ?? catalog?.currency ?? 'usd'\n const money = useCallback((cents: number) => displayMoney(cents, currency), [currency])\n\n /**\n * THE RESET: the unpaid order voided, every customer detail dropped from\n * memory and storage, and the screens remounted on the start screen.\n */\n const reset = useCallback(() => {\n const open = saleRef.current\n if (token && open && open.status === 'open') void kioskApi.abandon(token, open.orderId)\n setSale(null)\n setLines([])\n setTip({ tipChoice: 'none' })\n setItemProduct(null)\n setCartOpen(false)\n setNotice(null)\n setCategoryId(null)\n checkoutKey.current = null\n payKey.current = null\n clearKioskCustomerStorage()\n setPhase({ kind: 'start' })\n setTurn((value) => value + 1)\n }, [token])\n\n const idle = useKioskIdle({\n // Never while a card is being collected: the customer is at the reader.\n active: Boolean(token) && phase.kind !== 'start' && phase.kind !== 'paying',\n idleSeconds: context?.idleSeconds ?? CommerceModel.POS_KIOSK_IDLE_SECONDS_DEFAULT,\n warningSeconds: CommerceModel.POS_KIOSK_IDLE_WARNING_SECONDS,\n onExpire: reset,\n })\n\n // The order-number screen returns to the start on its own.\n useEffect(() => {\n if (phase.kind !== 'done') return undefined\n const timer = setTimeout(reset, CommerceModel.POS_KIOSK_DONE_SECONDS * 1000)\n return () => clearTimeout(timer)\n }, [phase.kind, reset])\n\n const addChoice = useCallback((product: CommerceModel.PosKioskProduct, choice: PosItemChoice) => {\n const line = kioskCartLine(product, choice)\n checkoutKey.current = null\n setLines((current) => {\n const existing = current.find((candidate) => candidate.key === line.key)\n if (existing) {\n return current.map((candidate) =>\n candidate.key === line.key\n ? { ...candidate, quantity: Math.min(CommerceModel.POS_KIOSK_MAX_QUANTITY, candidate.quantity + line.quantity) }\n : candidate,\n )\n }\n if (current.length >= CommerceModel.POS_KIOSK_MAX_LINES) return current\n return [...current, line]\n })\n }, [])\n\n const tapProduct = useCallback(\n (product: CommerceModel.PosKioskProduct) => {\n const asItem = kioskItemProduct(product)\n if (posItemNeedsChoice(asItem)) {\n setItemProduct(product)\n return\n }\n const variant = asItem.variants[0]\n if (!variant || product.variants[0]?.soldOut) return\n addChoice(product, { variant, modifiers: [], quantity: 1 })\n },\n [addChoice],\n )\n\n const checkout = useCallback(async () => {\n if (!token || busy || lines.length === 0) return\n if (!checkoutKey.current) checkoutKey.current = newAttemptKey()\n setBusy(true)\n setNotice(null)\n const result = await kioskApi.checkout(\n token,\n lines.map(({ productId, variantId, quantity, modifiers }) => ({\n productId,\n ...(variantId ? { variantId } : {}),\n quantity,\n ...(modifiers ? { modifiers } : {}),\n })),\n checkoutKey.current,\n )\n setBusy(false)\n if (lost(result.status)) return\n if (!result.ok || !result.value?.sale) {\n // A refused cart is changed before it is tried again.\n checkoutKey.current = null\n setNotice(result.error ?? 'Your order could not be priced. Try again.')\n return\n }\n setSale(result.value.sale)\n setCartOpen(false)\n setPhase({ kind: 'review' })\n }, [token, busy, lines, lost])\n\n const backToMenu = useCallback(() => {\n const open = saleRef.current\n if (token && open && open.status === 'open') void kioskApi.abandon(token, open.orderId)\n setSale(null)\n checkoutKey.current = null\n setNotice(null)\n setPhase({ kind: 'menu' })\n }, [token])\n\n const sendToCounter = useCallback(async () => {\n const open = saleRef.current\n if (!token || !open) return\n setBusy(true)\n const result = await kioskApi.counter(token, open.orderId)\n setBusy(false)\n if (lost(result.status)) return\n if (!result.ok || !result.value?.sale) {\n setNotice(result.error ?? 'Your order could not be sent to the counter.')\n return\n }\n setSale(result.value.sale)\n setLines([])\n setPhase({ kind: 'done' })\n }, [token, lost])\n\n const startPayment = useCallback(\n async (method: 'reader' | 'tap', answer: TipAnswer) => {\n const open = saleRef.current\n if (!token || !open) return\n if (!payKey.current) payKey.current = newAttemptKey()\n setBusy(true)\n setNotice(null)\n const result = await kioskApi.pay(token, { orderId: open.orderId, method, ...answer }, payKey.current)\n if (lost(result.status)) return setBusy(false)\n if (!result.ok || !result.value) {\n setBusy(false)\n payKey.current = null\n setNotice(result.error ?? 'The card payment could not be started.')\n setPhase({ kind: 'review' })\n return\n }\n const { sale: next, paymentId, clientSecret, paymentIntentId } = result.value\n setSale(next)\n const tipCents = CommerceModel.posKioskTipCents(answer.tipChoice, {\n baseCents: open.dueCents,\n enabled: true,\n percentages: context?.tipping.percentages ?? [],\n percent: answer.tipPercent,\n cents: answer.tipCents,\n })\n const chargeCents = open.dueCents + (tipCents ?? 0)\n setPhase({ kind: 'paying', method, paymentId: paymentId ?? null, chargeCents, failure: null })\n setBusy(false)\n if (method === 'tap') {\n const bridge = posNativeBridge()\n if (!bridge || !clientSecret || !paymentIntentId) {\n setPhase({ kind: 'paying', method, paymentId: paymentId ?? null, chargeCents, failure: 'Tap to Pay is not available on this device.' })\n return\n }\n const collected = await bridge\n .collectCardPayment({ paymentIntentId, clientSecret, amountCents: chargeCents })\n .catch(() => ({ status: 'failed' as const, message: undefined }))\n if (collected.status !== 'collected') {\n setPhase({\n kind: 'paying',\n method,\n paymentId: paymentId ?? null,\n chargeCents,\n failure: collected.status === 'canceled' ? 'The payment was canceled.' : (collected.message ?? 'The card was not read.'),\n })\n }\n }\n },\n [token, lost, context],\n )\n\n // The card in flight, asked after until it is paid or it fails.\n const paymentId = phase.kind === 'paying' ? phase.paymentId : null\n const paymentFailed = phase.kind === 'paying' ? Boolean(phase.failure) : false\n useEffect(() => {\n if (!token || !paymentId || paymentFailed) return undefined\n const open = saleRef.current\n if (!open) return undefined\n let active = true\n const timer = setInterval(async () => {\n const result = await kioskApi.paymentStatus(token, open.orderId, paymentId)\n if (!active) return\n if (lost(result.status)) return\n const next = result.value?.sale\n if (!next) return\n setSale(next)\n if (next.status === 'paid') {\n clearInterval(timer)\n setLines([])\n payKey.current = null\n setPhase({ kind: 'receipt' })\n return\n }\n const status = next.payment?.id === paymentId ? next.payment.status : ''\n if (status === 'failed' || status === 'canceled') {\n clearInterval(timer)\n payKey.current = null\n setPhase((current) =>\n current.kind === 'paying'\n ? { ...current, failure: next.payment?.failureMessage ?? 'The card was declined.' }\n : current,\n )\n }\n }, POS_KIOSK_PAYMENT_POLL_MS)\n return () => {\n active = false\n clearInterval(timer)\n }\n }, [token, paymentId, paymentFailed, lost])\n\n const cancelPayment = useCallback(async () => {\n const open = saleRef.current\n if (!token || !open || phase.kind !== 'paying') return\n setBusy(true)\n if (phase.paymentId && !phase.failure) await kioskApi.cancelPayment(token, open.orderId, phase.paymentId)\n const fresh = await kioskApi.sale(token, open.orderId)\n setBusy(false)\n payKey.current = null\n if (fresh.value?.sale) setSale(fresh.value.sale)\n if (fresh.value?.sale.status === 'paid') {\n setPhase({ kind: 'receipt' })\n return\n }\n setPhase({ kind: 'review' })\n }, [token, phase])\n\n const answerReceipt = useCallback(\n async (answer: PosDisplayAnswer) => {\n const paid = saleRef.current\n if (!token || !paid || !answer.receiptChannel) return\n setBusy(true)\n const result = await kioskApi.receipt(token, {\n orderId: paid.orderId,\n channel: answer.receiptChannel,\n ...(answer.email ? { to: answer.email } : {}),\n ...(answer.phone ? { to: answer.phone } : {}),\n ...(answer.marketingOptIn ? { marketingOptIn: true } : {}),\n })\n setBusy(false)\n if (lost(result.status)) return\n if (!result.ok) {\n setNotice(result.error ?? 'Your receipt could not be sent. Ask at the counter.')\n }\n setPhase({ kind: 'done' })\n },\n [token, lost],\n )\n\n const pay: KioskPayOptions = useMemo(\n () => ({\n reader: Boolean(context?.payments.cardPresent && context.payments.reader),\n tap: Boolean(context?.payments.cardPresent && posNativeBridge()),\n counter: Boolean(context?.payments.payAtCounter),\n }),\n [context],\n )\n\n const choosePay = useCallback(\n (method: 'reader' | 'tap') => {\n if (context?.tipping.enabled) {\n setPhase({ kind: 'tip', method })\n return\n }\n setTip({ tipChoice: 'none' })\n void startPayment(method, { tipChoice: 'none' })\n },\n [context, startPayment],\n )\n\n if (token === undefined) {\n return (\n <Box sx={{ minHeight: '100dvh', display: 'grid', placeItems: 'center' }}>\n <CircularProgress aria-label=\"Loading\" />\n </Box>\n )\n }\n\n if (!token) {\n return (\n <PosDisplayPairingScreen\n mode=\"kiosk\"\n onPaired={(next) => {\n storeKioskToken(next)\n setToken(next)\n }}\n />\n )\n }\n\n const branding = context?.branding ?? null\n let screen\n if (phase.kind === 'start') {\n screen = (\n <PosDisplayBrandScreen branding={branding} message={branding?.message || 'Order here'}>\n <Button\n variant=\"contained\"\n size=\"large\"\n onClick={() => setPhase({ kind: 'menu' })}\n sx={(theme) => ({ ...posDisplayTouchSx(theme), px: 6 })}\n >\n {'Start order'}\n </Button>\n </PosDisplayBrandScreen>\n )\n } else if (phase.kind === 'menu') {\n screen = (\n <>\n {notice ? (\n <Alert severity=\"warning\" onClose={() => setNotice(null)} sx={{ m: 2, mb: 0 }}>\n {notice}\n </Alert>\n ) : null}\n <KioskMenu\n catalog={catalog}\n loading={catalogLoading && !catalog}\n categoryId={categoryId}\n onCategory={setCategoryId}\n onProduct={tapProduct}\n lines={lines}\n currency={currency}\n onReview={() => void checkout()}\n onQuantity={(key, quantity) => {\n checkoutKey.current = null\n setLines((current) =>\n quantity <= 0\n ? current.filter((line) => line.key !== key)\n : current.map((line) => (line.key === key ? { ...line, quantity } : line)),\n )\n }}\n cartOpen={cartOpen}\n onCartOpen={setCartOpen}\n busy={busy}\n />\n </>\n )\n } else if (phase.kind === 'review' && sale) {\n screen = (\n <KioskReview\n sale={sale}\n currency={currency}\n pay={pay}\n busy={busy}\n notice={notice}\n onPay={choosePay}\n onCounter={() => void sendToCounter()}\n onBack={backToMenu}\n />\n )\n } else if (phase.kind === 'tip' && sale && context) {\n screen = (\n <Container maxWidth=\"md\" sx={{ py: 4 }}>\n <Stack spacing={4} sx={{ alignItems: 'center' }}>\n <PosDisplayBrandMark branding={branding} />\n <PosDisplayTipScreen\n tip={{ baseCents: sale.dueCents, percentages: context.tipping.percentages, allowCustom: true }}\n promptId={`kiosk-${sale.orderId}`}\n busy={busy}\n currency={currency}\n onAnswer={(answer) => {\n const next: TipAnswer = {\n tipChoice: answer.tipChoice ?? 'none',\n ...(answer.tipPercent ? { tipPercent: answer.tipPercent } : {}),\n ...(answer.tipChoice === 'custom' ? { tipCents: answer.tipCents ?? 0 } : {}),\n }\n setTip(next)\n void startPayment(phase.method, next)\n }}\n />\n <Button onClick={() => setPhase({ kind: 'review' })} disabled={busy}>\n {'Back'}\n </Button>\n </Stack>\n </Container>\n )\n } else if (phase.kind === 'paying' && sale) {\n screen = (\n <KioskPaying\n chargeCents={phase.chargeCents}\n currency={currency}\n method={phase.method}\n failure={phase.failure}\n testMode={Boolean(context?.testMode && phase.method === 'reader')}\n busy={busy}\n onCancel={() => void cancelPayment()}\n onRetry={() => void startPayment(phase.method, tip)}\n onCounter={pay.counter ? () => void sendToCounter() : null}\n onSimulate={\n phase.paymentId\n ? () => void kioskApi.simulate(token, sale.orderId, phase.paymentId as string)\n : null\n }\n />\n )\n } else if (phase.kind === 'receipt' && sale && context) {\n screen = (\n <Container maxWidth=\"md\" sx={{ py: 4 }}>\n <Stack spacing={4} sx={{ alignItems: 'center' }}>\n <PosDisplayBrandMark branding={branding} />\n <PosDisplayReceiptScreen\n receipt={{ channels: context.receipts, offerMarketing: context.offerMarketing }}\n promptId={`kiosk-${sale.orderId}`}\n busy={busy}\n onAnswer={(answer) => void answerReceipt(answer)}\n />\n </Stack>\n </Container>\n )\n } else if (phase.kind === 'done' && sale) {\n screen = <KioskDone sale={sale} currency={currency} onDone={reset} />\n } else {\n screen = <PosDisplayBrandScreen branding={branding} busy />\n }\n\n return (\n <Box key={turn} sx={{ position: 'relative', minHeight: '100dvh' }}>\n {offline ? (\n <Alert severity=\"warning\" sx={{ position: 'fixed', top: 0, left: 0, right: 0, zIndex: (theme) => theme.zIndex.modal + 1 }}>\n {'Reconnecting…'}\n </Alert>\n ) : null}\n {screen}\n <PosItemDialog\n product={itemProduct ? kioskItemProduct(itemProduct) : null}\n onClose={() => setItemProduct(null)}\n onConfirm={(choice) => {\n const product = itemProduct\n setItemProduct(null)\n if (product) addChoice(product, choice)\n }}\n formatMoney={money}\n blockSoldOut\n maxQuantity={CommerceModel.POS_KIOSK_MAX_QUANTITY}\n />\n <Dialog open={idle.warning !== null} onClose={idle.stillHere} maxWidth=\"xs\" fullWidth>\n <DialogTitle>{'Still there?'}</DialogTitle>\n <DialogContent>\n <DialogContentText>\n {`This order will be cleared in ${idle.warning ?? 0} seconds.`}\n </DialogContentText>\n </DialogContent>\n <DialogActions>\n <Button onClick={reset}>{'Start over'}</Button>\n <Button variant=\"contained\" onClick={idle.stillHere}>\n {'I’m still here'}\n </Button>\n </DialogActions>\n </Dialog>\n {phase.kind === 'start' || phase.kind === 'menu' ? (\n <KioskStaffControl token={token} onExit={unpaired} onUnauthorized={unpaired} />\n ) : null}\n </Box>\n )\n}\n\nPosKioskPage.displayName = 'PosKioskPage'\n"],"names":["Alert","Box","Button","CircularProgress","Container","Dialog","DialogActions","DialogContent","DialogContentText","DialogTitle","Stack","useCallback","useEffect","useMemo","useRef","useState","CommerceModel","newAttemptKey","posNativeBridge","PosItemDialog","posItemNeedsChoice","PosDisplayBrandMark","PosDisplayBrandScreen","PosDisplayPairingScreen","displayMoney","PosDisplayReceiptScreen","PosDisplayTipScreen","posDisplayTouchSx","KioskDone","KioskPaying","KioskReview","KioskMenu","KioskStaffControl","clearKioskCustomerStorage","kioskApi","readKioskToken","storeKioskToken","useKioskIdle","POS_KIOSK_PAYMENT_POLL_MS","kioskItemProduct","product","$id","id","name","slug","type","status","options","modifierGroups","variants","map","variant","priceUsd","priceCents","inventory","soldOut","kioskCartLine","choice","find","candidate","picked","modifiers","pick","group","groupId","option","optionId","filter","Boolean","label","lineLabelWithModifiers","Object","keys","length","values","join","undefined","key","modifierSelectionKey","productId","variantId","quantity","Math","min","POS_KIOSK_MAX_QUANTITY","unitCents","reduce","sum","modifierOptionPriceCents","PosKioskPage","_props","context","idle","token","setToken","setContext","catalog","setCatalog","catalogLoading","setCatalogLoading","categoryId","setCategoryId","phase","setPhase","kind","lines","setLines","itemProduct","setItemProduct","cartOpen","setCartOpen","sale","setSale","tip","setTip","tipChoice","busy","setBusy","notice","setNotice","offline","setOffline","turn","setTurn","checkoutKey","payKey","saleRef","current","phaseRef","unpaired","lost","refresh","result","ok","value","timer","setInterval","clearInterval","active","then","currency","money","cents","reset","open","abandon","orderId","idleSeconds","POS_KIOSK_IDLE_SECONDS_DEFAULT","warningSeconds","POS_KIOSK_IDLE_WARNING_SECONDS","onExpire","setTimeout","POS_KIOSK_DONE_SECONDS","clearTimeout","addChoice","line","existing","POS_KIOSK_MAX_LINES","tapProduct","asItem","checkout","error","backToMenu","sendToCounter","counter","startPayment","method","answer","pay","next","paymentId","clientSecret","paymentIntentId","tipCents","posKioskTipCents","baseCents","dueCents","enabled","percentages","tipping","percent","tipPercent","chargeCents","failure","bridge","collected","collectCardPayment","amountCents","catch","message","paymentFailed","paymentStatus","payment","failureMessage","cancelPayment","fresh","answerReceipt","paid","receiptChannel","receipt","channel","email","to","phone","marketingOptIn","reader","payments","cardPresent","tap","payAtCounter","choosePay","sx","minHeight","display","placeItems","aria-label","mode","onPaired","branding","screen","size","onClick","theme","px","severity","onClose","m","mb","loading","onCategory","onProduct","onReview","onQuantity","onCartOpen","onPay","onCounter","onBack","maxWidth","py","spacing","alignItems","allowCustom","promptId","onAnswer","disabled","testMode","onCancel","onRetry","onSimulate","simulate","channels","receipts","offerMarketing","onDone","position","top","left","right","zIndex","modal","onConfirm","formatMoney","blockSoldOut","maxQuantity","warning","stillHere","fullWidth","onExit","onUnauthorized","displayName"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;;AAGA,OAAOA,WAAW,sBAAqB;AACvC,OAAOC,SAAS,oBAAmB;AACnC,OAAOC,YAAY,uBAAsB;AACzC,OAAOC,sBAAsB,iCAAgC;AAC7D,OAAOC,eAAe,0BAAyB;AAC/C,OAAOC,YAAY,uBAAsB;AACzC,OAAOC,mBAAmB,8BAA6B;AACvD,OAAOC,mBAAmB,8BAA6B;AACvD,OAAOC,uBAAuB,kCAAiC;AAC/D,OAAOC,iBAAiB,4BAA2B;AACnD,OAAOC,WAAW,sBAAqB;AACvC,SAASC,WAAW,EAAEC,SAAS,EAAEC,OAAO,EAAEC,MAAM,EAAEC,QAAQ,QAAQ,QAAO;AACzE,YAAYC,mBAAmB,uBAAa;AAC5C,SAASC,aAAa,EAAEC,eAAe,QAAQ,mBAAe;AAC9D,SAASC,aAAa,EAAEC,kBAAkB,QAA4B,qCAAiC;AACvG,SAASC,mBAAmB,EAAEC,qBAAqB,QAAQ,gCAA4B;AACvF,SAASC,uBAAuB,QAAQ,kCAA8B;AACtE,SAASC,YAAY,QAA+B,mCAA+B;AACnF,SAASC,uBAAuB,QAAQ,kCAA8B;AACtE,SAASC,mBAAmB,EAAEC,iBAAiB,QAAQ,8BAA0B;AACjF,SAASC,SAAS,EAAEC,WAAW,EAAEC,WAAW,QAA8B,gCAA4B;AACtG,SAASC,SAAS,QAA4B,4BAAwB;AACtE,SAASC,iBAAiB,QAAQ,6BAAyB;AAC3D,SACEC,yBAAyB,EACzBC,QAAQ,EACRC,cAAc,EACdC,eAAe,QACV,+BAA2B;AAClC,SAASC,YAAY,QAAQ,gCAA4B;AAEzD,6DAA6D,GAC7D,OAAO,MAAMC,4BAA4B,KAAK;AAa9C,+EAA+E,GAC/E,OAAO,SAASC,iBAAiBC,OAAsC;IACrE,OAAO;QACLC,KAAKD,QAAQE,EAAE;QACfC,MAAMH,QAAQG,IAAI;QAClBC,MAAMJ,QAAQE,EAAE;QAChBG,MAAM;QACNC,QAAQ;QACRC,SAASP,QAAQO,OAAO;QACxBC,gBAAgBR,QAAQQ,cAAc;QACtCC,UAAUT,QAAQS,QAAQ,CAACC,GAAG,CAAC,CAACC,UAAa,CAAA;gBAC3CT,IAAIS,QAAQT,EAAE;gBACdK,SAASI,QAAQJ,OAAO;gBACxBK,UAAUD,QAAQE,UAAU,GAAG;gBAC/B,wEAAwE;gBACxE,oDAAoD;gBACpDC,WAAWH,QAAQI,OAAO,GAAG,IAAI;YACnC,CAAA;IACF;AACF;AAEA,+DAA+D,GAC/D,OAAO,SAASC,cACdhB,OAAsC,EACtCiB,MAAqB;QAELjB;IAAhB,MAAMW,WAAUX,yBAAAA,QAAQS,QAAQ,CAACS,IAAI,CAAC,CAACC,YAAcA,UAAUjB,EAAE,KAAKe,OAAON,OAAO,CAACT,EAAE,aAAvEF,yBAA4EA,QAAQS,QAAQ,CAAC,EAAE;IAC/G,MAAMW,SAASH,OAAOI,SAAS,CAC5BX,GAAG,CAAC,CAACY;QACJ,MAAMC,QAAQvB,QAAQQ,cAAc,CAACU,IAAI,CAAC,CAACC,YAAcA,UAAUjB,EAAE,KAAKoB,KAAKE,OAAO;QACtF,OAAOD,yBAAAA,MAAOhB,OAAO,CAACW,IAAI,CAAC,CAACO,SAAWA,OAAOvB,EAAE,KAAKoB,KAAKI,QAAQ;IACpE,GACCC,MAAM,CAAC,CAACF,SAA0DG,QAAQH;IAC7E,MAAMI,QAAQrD,cAAcsD,sBAAsB,CAChDC,OAAOC,IAAI,CAACrB,QAAQJ,OAAO,EAAE0B,MAAM,GAAGF,OAAOG,MAAM,CAACvB,QAAQJ,OAAO,EAAE4B,IAAI,CAAC,SAASC,WACnFhB;IAEF,OAAO;QACLiB,KAAK,GAAGrC,QAAQE,EAAE,CAAC,CAAC,EAAES,QAAQT,EAAE,CAAC,CAAC,EAAE1B,cAAc8D,oBAAoB,CAACrB,OAAOI,SAAS,GAAG;QAC1FkB,WAAWvC,QAAQE,EAAE;OACjBS,QAAQT,EAAE,KAAK,aAAaF,QAAQS,QAAQ,CAACwB,MAAM,GAAG,IAAI;QAAEO,WAAW7B,QAAQT,EAAE;IAAC,IAAI,CAAC;QAC3FuC,UAAUC,KAAKC,GAAG,CAACnE,cAAcoE,sBAAsB,EAAE3B,OAAOwB,QAAQ;OACpExB,OAAOI,SAAS,CAACY,MAAM,GAAG;QAAEZ,WAAWJ,OAAOI,SAAS;IAAC,IAAI,CAAC;QACjElB,MAAMH,QAAQG,IAAI;OACd0B,QAAQ;QAAEA;IAAM,IAAI,CAAC;QACzB,oEAAoE;QACpEgB,WACElC,QAAQE,UAAU,GAClBO,OAAO0B,MAAM,CAAC,CAACC,KAAKtB,SAAWsB,MAAMvE,cAAcwE,wBAAwB,CAACvB,QAAQd,QAAQT,EAAE,GAAG;;AAEvG;AAEA;;;;;;;;;;CAUC,GACD,eAAe,SAAS+C,aAAaC,MAA8B;QAsFhDC,2BAwb2BC;IA7gB5C,MAAM,CAACC,OAAOC,SAAS,GAAG/E,SAAoC6D;IAC9D,MAAM,CAACe,SAASI,WAAW,GAAGhF,SAA+C;IAC7E,MAAM,CAACiF,SAASC,WAAW,GAAGlF,SAA+C;IAC7E,MAAM,CAACmF,gBAAgBC,kBAAkB,GAAGpF,SAAS;IACrD,MAAM,CAACqF,YAAYC,cAAc,GAAGtF,SAAwB;IAC5D,MAAM,CAACuF,OAAOC,SAAS,GAAGxF,SAAgB;QAAEyF,MAAM;IAAQ;IAC1D,MAAM,CAACC,OAAOC,SAAS,GAAG3F,SAA0B,EAAE;IACtD,MAAM,CAAC4F,aAAaC,eAAe,GAAG7F,SAA+C;IACrF,MAAM,CAAC8F,UAAUC,YAAY,GAAG/F,SAAS;IACzC,MAAM,CAACgG,MAAMC,QAAQ,GAAGjG,SAA4C;IACpE,MAAM,CAACkG,KAAKC,OAAO,GAAGnG,SAAoB;QAAEoG,WAAW;IAAO;IAC9D,MAAM,CAACC,MAAMC,QAAQ,GAAGtG,SAAS;IACjC,MAAM,CAACuG,QAAQC,UAAU,GAAGxG,SAAwB;IACpD,MAAM,CAACyG,SAASC,WAAW,GAAG1G,SAAS;IACvC,wEAAwE;IACxE,MAAM,CAAC2G,MAAMC,QAAQ,GAAG5G,SAAS;IACjC,+DAA+D;IAC/D,MAAM6G,cAAc9G,OAAsB;IAC1C,MAAM+G,SAAS/G,OAAsB;IACrC,MAAMgH,UAAUhH,OAAOiG;IACvBe,QAAQC,OAAO,GAAGhB;IAClB,MAAMiB,WAAWlH,OAAOwF;IACxB0B,SAASD,OAAO,GAAGzB;IAEnB1F,UAAU;QACRkF,SAAS3D;IACX,GAAG,EAAE;IAEL,MAAM8F,WAAWtH,YAAY;QAC3ByB,gBAAgB;QAChBH;QACA6D,SAAS;QACTC,WAAW;QACXE,WAAW;QACXe,QAAQ;QACRN,SAAS,EAAE;QACXH,SAAS;YAAEC,MAAM;QAAQ;IAC3B,GAAG,EAAE;IAEL,yEAAyE,GACzE,MAAM0B,OAAOvH,YACX,CAACmC;QACC,IAAIA,WAAW,KAAK;YAClBmF;YACA,OAAO;QACT;QACA,OAAO;IACT,GACA;QAACA;KAAS;IAGZ,uEAAuE;IACvE,qEAAqE;IACrE,MAAME,UAAUxH,YAAY;QAC1B,IAAI,CAACkF,OAAO;QACZ,MAAMuC,SAAS,MAAMlG,SAASyD,OAAO,CAACE;QACtC,IAAIqC,KAAKE,OAAOtF,MAAM,GAAG;QACzB2E,WAAW,CAACW,OAAOC,EAAE,IAAID,OAAOtF,MAAM,KAAK;QAC3C,IAAIsF,OAAOC,EAAE,IAAID,OAAOE,KAAK,EAAEvC,WAAWqC,OAAOE,KAAK;IACxD,GAAG;QAACzC;QAAOqC;KAAK;IAEhBtH,UAAU;QACR,KAAKuH;QACL,MAAMI,QAAQC,YAAY;YACxB,IAAIR,SAASD,OAAO,CAACvB,IAAI,KAAK,SAAS,KAAK2B;QAC9C,GAAG,KAAK,KAAK;QACb,OAAO,IAAMM,cAAcF;IAC7B,GAAG;QAACJ;KAAQ;IAEZvH,UAAU;QACR,IAAI,CAACiF,OAAO,OAAOjB;QACnB,IAAI8D,SAAS;QACbvC,kBAAkB;QAClB,KAAKjE,SAAS8D,OAAO,CAACH,OAAOO,YAAYuC,IAAI,CAAC,CAACP;YAC7C,IAAI,CAACM,QAAQ;YACbvC,kBAAkB;YAClB,IAAI+B,KAAKE,OAAOtF,MAAM,GAAG;YACzB,IAAIsF,OAAOC,EAAE,IAAID,OAAOE,KAAK,EAAErC,WAAWmC,OAAOE,KAAK;iBACjDb,WAAWW,OAAOtF,MAAM,KAAK;QACpC;QACA,OAAO;YACL4F,SAAS;QACX;IACF,GAAG;QAAC7C;QAAOO;QAAYsB;QAAMQ;KAAK;IAElC,MAAMU,YAAWjD,gBAAAA,2BAAAA,QAASiD,QAAQ,oBAAI5C,2BAAAA,QAAS4C,QAAQ,YAAtCjD,OAA0C;IAC3D,MAAMkD,QAAQlI,YAAY,CAACmI,QAAkBtH,aAAasH,OAAOF,WAAW;QAACA;KAAS;IAEtF;;;GAGC,GACD,MAAMG,QAAQpI,YAAY;QACxB,MAAMqI,OAAOlB,QAAQC,OAAO;QAC5B,IAAIlC,SAASmD,QAAQA,KAAKlG,MAAM,KAAK,QAAQ,KAAKZ,SAAS+G,OAAO,CAACpD,OAAOmD,KAAKE,OAAO;QACtFlC,QAAQ;QACRN,SAAS,EAAE;QACXQ,OAAO;YAAEC,WAAW;QAAO;QAC3BP,eAAe;QACfE,YAAY;QACZS,UAAU;QACVlB,cAAc;QACduB,YAAYG,OAAO,GAAG;QACtBF,OAAOE,OAAO,GAAG;QACjB9F;QACAsE,SAAS;YAAEC,MAAM;QAAQ;QACzBmB,QAAQ,CAACW,QAAUA,QAAQ;IAC7B,GAAG;QAACzC;KAAM;IAEV,MAAMD,OAAOvD,aAAa;QACxB,wEAAwE;QACxEqG,QAAQtE,QAAQyB,UAAUS,MAAME,IAAI,KAAK,WAAWF,MAAME,IAAI,KAAK;QACnE2C,WAAW,WAAExD,2BAAAA,QAASwD,WAAW,oBAAInI,cAAcoI,8BAA8B;QACjFC,gBAAgBrI,cAAcsI,8BAA8B;QAC5DC,UAAUR;IACZ;IAEA,2DAA2D;IAC3DnI,UAAU;QACR,IAAI0F,MAAME,IAAI,KAAK,QAAQ,OAAO5B;QAClC,MAAM2D,QAAQiB,WAAWT,OAAO/H,cAAcyI,sBAAsB,GAAG;QACvE,OAAO,IAAMC,aAAanB;IAC5B,GAAG;QAACjC,MAAME,IAAI;QAAEuC;KAAM;IAEtB,MAAMY,YAAYhJ,YAAY,CAAC6B,SAAwCiB;QACrE,MAAMmG,OAAOpG,cAAchB,SAASiB;QACpCmE,YAAYG,OAAO,GAAG;QACtBrB,SAAS,CAACqB;YACR,MAAM8B,WAAW9B,QAAQrE,IAAI,CAAC,CAACC,YAAcA,UAAUkB,GAAG,KAAK+E,KAAK/E,GAAG;YACvE,IAAIgF,UAAU;gBACZ,OAAO9B,QAAQ7E,GAAG,CAAC,CAACS,YAClBA,UAAUkB,GAAG,KAAK+E,KAAK/E,GAAG,GACtB,aAAKlB;wBAAWsB,UAAUC,KAAKC,GAAG,CAACnE,cAAcoE,sBAAsB,EAAEzB,UAAUsB,QAAQ,GAAG2E,KAAK3E,QAAQ;yBAC3GtB;YAER;YACA,IAAIoE,QAAQtD,MAAM,IAAIzD,cAAc8I,mBAAmB,EAAE,OAAO/B;YAChE,OAAO;mBAAIA;gBAAS6B;aAAK;QAC3B;IACF,GAAG,EAAE;IAEL,MAAMG,aAAapJ,YACjB,CAAC6B;YAOiBA;QANhB,MAAMwH,SAASzH,iBAAiBC;QAChC,IAAIpB,mBAAmB4I,SAAS;YAC9BpD,eAAepE;YACf;QACF;QACA,MAAMW,UAAU6G,OAAO/G,QAAQ,CAAC,EAAE;QAClC,IAAI,CAACE,aAAWX,qBAAAA,QAAQS,QAAQ,CAAC,EAAE,qBAAnBT,mBAAqBe,OAAO,GAAE;QAC9CoG,UAAUnH,SAAS;YAAEW;YAASU,WAAW,EAAE;YAAEoB,UAAU;QAAE;IAC3D,GACA;QAAC0E;KAAU;IAGb,MAAMM,WAAWtJ,YAAY;YAiBRyH;QAhBnB,IAAI,CAACvC,SAASuB,QAAQX,MAAMhC,MAAM,KAAK,GAAG;QAC1C,IAAI,CAACmD,YAAYG,OAAO,EAAEH,YAAYG,OAAO,GAAG9G;QAChDoG,QAAQ;QACRE,UAAU;QACV,MAAMa,SAAS,MAAMlG,SAAS+H,QAAQ,CACpCpE,OACAY,MAAMvD,GAAG,CAAC,CAAC,EAAE6B,SAAS,EAAEC,SAAS,EAAEC,QAAQ,EAAEpB,SAAS,EAAE,GAAM;gBAC5DkB;eACIC,YAAY;gBAAEA;YAAU,IAAI,CAAC;gBACjCC;eACIpB,YAAY;gBAAEA;YAAU,IAAI,CAAC,KAEnC+D,YAAYG,OAAO;QAErBV,QAAQ;QACR,IAAIa,KAAKE,OAAOtF,MAAM,GAAG;QACzB,IAAI,CAACsF,OAAOC,EAAE,IAAI,GAACD,gBAAAA,OAAOE,KAAK,qBAAZF,cAAcrB,IAAI,GAAE;gBAG3BqB;YAFV,sDAAsD;YACtDR,YAAYG,OAAO,GAAG;YACtBR,WAAUa,gBAAAA,OAAO8B,KAAK,YAAZ9B,gBAAgB;YAC1B;QACF;QACApB,QAAQoB,OAAOE,KAAK,CAACvB,IAAI;QACzBD,YAAY;QACZP,SAAS;YAAEC,MAAM;QAAS;IAC5B,GAAG;QAACX;QAAOuB;QAAMX;QAAOyB;KAAK;IAE7B,MAAMiC,aAAaxJ,YAAY;QAC7B,MAAMqI,OAAOlB,QAAQC,OAAO;QAC5B,IAAIlC,SAASmD,QAAQA,KAAKlG,MAAM,KAAK,QAAQ,KAAKZ,SAAS+G,OAAO,CAACpD,OAAOmD,KAAKE,OAAO;QACtFlC,QAAQ;QACRY,YAAYG,OAAO,GAAG;QACtBR,UAAU;QACVhB,SAAS;YAAEC,MAAM;QAAO;IAC1B,GAAG;QAACX;KAAM;IAEV,MAAMuE,gBAAgBzJ,YAAY;YAObyH;QANnB,MAAMY,OAAOlB,QAAQC,OAAO;QAC5B,IAAI,CAAClC,SAAS,CAACmD,MAAM;QACrB3B,QAAQ;QACR,MAAMe,SAAS,MAAMlG,SAASmI,OAAO,CAACxE,OAAOmD,KAAKE,OAAO;QACzD7B,QAAQ;QACR,IAAIa,KAAKE,OAAOtF,MAAM,GAAG;QACzB,IAAI,CAACsF,OAAOC,EAAE,IAAI,GAACD,gBAAAA,OAAOE,KAAK,qBAAZF,cAAcrB,IAAI,GAAE;gBAC3BqB;YAAVb,WAAUa,gBAAAA,OAAO8B,KAAK,YAAZ9B,gBAAgB;YAC1B;QACF;QACApB,QAAQoB,OAAOE,KAAK,CAACvB,IAAI;QACzBL,SAAS,EAAE;QACXH,SAAS;YAAEC,MAAM;QAAO;IAC1B,GAAG;QAACX;QAAOqC;KAAK;IAEhB,MAAMoC,eAAe3J,YACnB,OAAO4J,QAA0BC;;QAC/B,MAAMxB,OAAOlB,QAAQC,OAAO;QAC5B,IAAI,CAAClC,SAAS,CAACmD,MAAM;QACrB,IAAI,CAACnB,OAAOE,OAAO,EAAEF,OAAOE,OAAO,GAAG9G;QACtCoG,QAAQ;QACRE,UAAU;QACV,MAAMa,SAAS,MAAMlG,SAASuI,GAAG,CAAC5E,OAAO;YAAEqD,SAASF,KAAKE,OAAO;YAAEqB;WAAWC,SAAU3C,OAAOE,OAAO;QACrG,IAAIG,KAAKE,OAAOtF,MAAM,GAAG,OAAOuE,QAAQ;QACxC,IAAI,CAACe,OAAOC,EAAE,IAAI,CAACD,OAAOE,KAAK,EAAE;gBAGrBF;YAFVf,QAAQ;YACRQ,OAAOE,OAAO,GAAG;YACjBR,WAAUa,gBAAAA,OAAO8B,KAAK,YAAZ9B,gBAAgB;YAC1B7B,SAAS;gBAAEC,MAAM;YAAS;YAC1B;QACF;QACA,MAAM,EAAEO,MAAM2D,IAAI,EAAEC,SAAS,EAAEC,YAAY,EAAEC,eAAe,EAAE,GAAGzC,OAAOE,KAAK;QAC7EtB,QAAQ0D;QACR,MAAMI,WAAW9J,cAAc+J,gBAAgB,CAACP,OAAOrD,SAAS,EAAE;YAChE6D,WAAWhC,KAAKiC,QAAQ;YACxBC,SAAS;YACTC,WAAW,UAAExF,2BAAAA,QAASyF,OAAO,CAACD,WAAW,mBAAI,EAAE;YAC/CE,SAASb,OAAOc,UAAU;YAC1BxC,OAAO0B,OAAOM,QAAQ;QACxB;QACA,MAAMS,cAAcvC,KAAKiC,QAAQ,IAAIH,mBAAAA,WAAY;QACjDvE,SAAS;YAAEC,MAAM;YAAU+D;YAAQI,SAAS,EAAEA,oBAAAA,YAAa;YAAMY;YAAaC,SAAS;QAAK;QAC5FnE,QAAQ;QACR,IAAIkD,WAAW,OAAO;YACpB,MAAMkB,SAASvK;YACf,IAAI,CAACuK,UAAU,CAACb,gBAAgB,CAACC,iBAAiB;gBAChDtE,SAAS;oBAAEC,MAAM;oBAAU+D;oBAAQI,SAAS,EAAEA,oBAAAA,YAAa;oBAAMY;oBAAaC,SAAS;gBAA8C;gBACrI;YACF;YACA,MAAME,YAAY,MAAMD,OACrBE,kBAAkB,CAAC;gBAAEd;gBAAiBD;gBAAcgB,aAAaL;YAAY,GAC7EM,KAAK,CAAC,IAAO,CAAA;oBAAE/I,QAAQ;oBAAmBgJ,SAASlH;gBAAU,CAAA;YAChE,IAAI8G,UAAU5I,MAAM,KAAK,aAAa;oBAMwC4I;gBAL5EnF,SAAS;oBACPC,MAAM;oBACN+D;oBACAI,SAAS,EAAEA,oBAAAA,YAAa;oBACxBY;oBACAC,SAASE,UAAU5I,MAAM,KAAK,aAAa,+BAA+B4I,qBAAAA,UAAUI,OAAO,YAAjBJ,qBAAqB;gBACjG;YACF;QACF;IACF,GACA;QAAC7F;QAAOqC;QAAMvC;KAAQ;IAGxB,gEAAgE;IAChE,MAAMgF,YAAYrE,MAAME,IAAI,KAAK,WAAWF,MAAMqE,SAAS,GAAG;IAC9D,MAAMoB,gBAAgBzF,MAAME,IAAI,KAAK,WAAWpC,QAAQkC,MAAMkF,OAAO,IAAI;IACzE5K,UAAU;QACR,IAAI,CAACiF,SAAS,CAAC8E,aAAaoB,eAAe,OAAOnH;QAClD,MAAMoE,OAAOlB,QAAQC,OAAO;QAC5B,IAAI,CAACiB,MAAM,OAAOpE;QAClB,IAAI8D,SAAS;QACb,MAAMH,QAAQC,YAAY;gBAIXJ,eAUEsC;YAbf,MAAMtC,SAAS,MAAMlG,SAAS8J,aAAa,CAACnG,OAAOmD,KAAKE,OAAO,EAAEyB;YACjE,IAAI,CAACjC,QAAQ;YACb,IAAIR,KAAKE,OAAOtF,MAAM,GAAG;YACzB,MAAM4H,QAAOtC,gBAAAA,OAAOE,KAAK,qBAAZF,cAAcrB,IAAI;YAC/B,IAAI,CAAC2D,MAAM;YACX1D,QAAQ0D;YACR,IAAIA,KAAK5H,MAAM,KAAK,QAAQ;gBAC1B2F,cAAcF;gBACd7B,SAAS,EAAE;gBACXmB,OAAOE,OAAO,GAAG;gBACjBxB,SAAS;oBAAEC,MAAM;gBAAU;gBAC3B;YACF;YACA,MAAM1D,SAAS4H,EAAAA,gBAAAA,KAAKuB,OAAO,qBAAZvB,cAAchI,EAAE,MAAKiI,YAAYD,KAAKuB,OAAO,CAACnJ,MAAM,GAAG;YACtE,IAAIA,WAAW,YAAYA,WAAW,YAAY;gBAChD2F,cAAcF;gBACdV,OAAOE,OAAO,GAAG;gBACjBxB,SAAS,CAACwB;;wBAEmB2C;2BAD3B3C,QAAQvB,IAAI,KAAK,WACb,aAAKuB;wBAASyD,OAAO,WAAEd,gBAAAA,KAAKuB,OAAO,qBAAZvB,cAAcwB,cAAc,mBAAI;yBACvDnE;;YAER;QACF,GAAGzF;QACH,OAAO;YACLoG,SAAS;YACTD,cAAcF;QAChB;IACF,GAAG;QAAC1C;QAAO8E;QAAWoB;QAAe7D;KAAK;IAE1C,MAAMiE,gBAAgBxL,YAAY;YAQ5ByL,cACAA;QARJ,MAAMpD,OAAOlB,QAAQC,OAAO;QAC5B,IAAI,CAAClC,SAAS,CAACmD,QAAQ1C,MAAME,IAAI,KAAK,UAAU;QAChDa,QAAQ;QACR,IAAIf,MAAMqE,SAAS,IAAI,CAACrE,MAAMkF,OAAO,EAAE,MAAMtJ,SAASiK,aAAa,CAACtG,OAAOmD,KAAKE,OAAO,EAAE5C,MAAMqE,SAAS;QACxG,MAAMyB,QAAQ,MAAMlK,SAAS6E,IAAI,CAAClB,OAAOmD,KAAKE,OAAO;QACrD7B,QAAQ;QACRQ,OAAOE,OAAO,GAAG;QACjB,KAAIqE,eAAAA,MAAM9D,KAAK,qBAAX8D,aAAarF,IAAI,EAAEC,QAAQoF,MAAM9D,KAAK,CAACvB,IAAI;QAC/C,IAAIqF,EAAAA,gBAAAA,MAAM9D,KAAK,qBAAX8D,cAAarF,IAAI,CAACjE,MAAM,MAAK,QAAQ;YACvCyD,SAAS;gBAAEC,MAAM;YAAU;YAC3B;QACF;QACAD,SAAS;YAAEC,MAAM;QAAS;IAC5B,GAAG;QAACX;QAAOS;KAAM;IAEjB,MAAM+F,gBAAgB1L,YACpB,OAAO6J;QACL,MAAM8B,OAAOxE,QAAQC,OAAO;QAC5B,IAAI,CAAClC,SAAS,CAACyG,QAAQ,CAAC9B,OAAO+B,cAAc,EAAE;QAC/ClF,QAAQ;QACR,MAAMe,SAAS,MAAMlG,SAASsK,OAAO,CAAC3G,OAAO;YAC3CqD,SAASoD,KAAKpD,OAAO;YACrBuD,SAASjC,OAAO+B,cAAc;WAC1B/B,OAAOkC,KAAK,GAAG;YAAEC,IAAInC,OAAOkC,KAAK;QAAC,IAAI,CAAC,GACvClC,OAAOoC,KAAK,GAAG;YAAED,IAAInC,OAAOoC,KAAK;QAAC,IAAI,CAAC,GACvCpC,OAAOqC,cAAc,GAAG;YAAEA,gBAAgB;QAAK,IAAI,CAAC;QAE1DxF,QAAQ;QACR,IAAIa,KAAKE,OAAOtF,MAAM,GAAG;QACzB,IAAI,CAACsF,OAAOC,EAAE,EAAE;gBACJD;YAAVb,WAAUa,gBAAAA,OAAO8B,KAAK,YAAZ9B,gBAAgB;QAC5B;QACA7B,SAAS;YAAEC,MAAM;QAAO;IAC1B,GACA;QAACX;QAAOqC;KAAK;IAGf,MAAMuC,MAAuB5J,QAC3B,IAAO,CAAA;YACLiM,QAAQ1I,QAAQuB,CAAAA,2BAAAA,QAASoH,QAAQ,CAACC,WAAW,KAAIrH,QAAQoH,QAAQ,CAACD,MAAM;YACxEG,KAAK7I,QAAQuB,CAAAA,2BAAAA,QAASoH,QAAQ,CAACC,WAAW,KAAI9L;YAC9CmJ,SAASjG,QAAQuB,2BAAAA,QAASoH,QAAQ,CAACG,YAAY;QACjD,CAAA,GACA;QAACvH;KAAQ;IAGX,MAAMwH,YAAYxM,YAChB,CAAC4J;QACC,IAAI5E,2BAAAA,QAASyF,OAAO,CAACF,OAAO,EAAE;YAC5B3E,SAAS;gBAAEC,MAAM;gBAAO+D;YAAO;YAC/B;QACF;QACArD,OAAO;YAAEC,WAAW;QAAO;QAC3B,KAAKmD,aAAaC,QAAQ;YAAEpD,WAAW;QAAO;IAChD,GACA;QAACxB;QAAS2E;KAAa;IAGzB,IAAIzE,UAAUjB,WAAW;QACvB,qBACE,KAAC3E;YAAImN,IAAI;gBAAEC,WAAW;gBAAUC,SAAS;gBAAQC,YAAY;YAAS;sBACpE,cAAA,KAACpN;gBAAiBqN,cAAW;;;IAGnC;IAEA,IAAI,CAAC3H,OAAO;QACV,qBACE,KAACtE;YACCkM,MAAK;YACLC,UAAU,CAAChD;gBACTtI,gBAAgBsI;gBAChB5E,SAAS4E;YACX;;IAGN;IAEA,MAAMiD,oBAAWhI,2BAAAA,QAASgI,QAAQ,oBAAI;IACtC,IAAIC;IACJ,IAAItH,MAAME,IAAI,KAAK,SAAS;QAC1BoH,uBACE,KAACtM;YAAsBqM,UAAUA;YAAU7B,SAAS6B,CAAAA,4BAAAA,SAAU7B,OAAO,KAAI;sBACvE,cAAA,KAAC5L;gBACCiD,SAAQ;gBACR0K,MAAK;gBACLC,SAAS,IAAMvH,SAAS;wBAAEC,MAAM;oBAAO;gBACvC4G,IAAI,CAACW,QAAW,aAAKpM,kBAAkBoM;wBAAQC,IAAI;;0BAElD;;;IAIT,OAAO,IAAI1H,MAAME,IAAI,KAAK,QAAQ;QAChCoH,uBACE;;gBACGtG,uBACC,KAACtH;oBAAMiO,UAAS;oBAAUC,SAAS,IAAM3G,UAAU;oBAAO6F,IAAI;wBAAEe,GAAG;wBAAGC,IAAI;oBAAE;8BACzE9G;qBAED;8BACJ,KAACvF;oBACCiE,SAASA;oBACTqI,SAASnI,kBAAkB,CAACF;oBAC5BI,YAAYA;oBACZkI,YAAYjI;oBACZkI,WAAWxE;oBACXtD,OAAOA;oBACPmC,UAAUA;oBACV4F,UAAU,IAAM,KAAKvE;oBACrBwE,YAAY,CAAC5J,KAAKI;wBAChB2C,YAAYG,OAAO,GAAG;wBACtBrB,SAAS,CAACqB,UACR9C,YAAY,IACR8C,QAAQ5D,MAAM,CAAC,CAACyF,OAASA,KAAK/E,GAAG,KAAKA,OACtCkD,QAAQ7E,GAAG,CAAC,CAAC0G,OAAUA,KAAK/E,GAAG,KAAKA,MAAM,aAAK+E;oCAAM3E;qCAAa2E;oBAE1E;oBACA/C,UAAUA;oBACV6H,YAAY5H;oBACZM,MAAMA;;;;IAId,OAAO,IAAId,MAAME,IAAI,KAAK,YAAYO,MAAM;QAC1C6G,uBACE,KAAC9L;YACCiF,MAAMA;YACN6B,UAAUA;YACV6B,KAAKA;YACLrD,MAAMA;YACNE,QAAQA;YACRqH,OAAOxB;YACPyB,WAAW,IAAM,KAAKxE;YACtByE,QAAQ1E;;IAGd,OAAO,IAAI7D,MAAME,IAAI,KAAK,SAASO,QAAQpB,SAAS;QAClDiI,uBACE,KAACxN;YAAU0O,UAAS;YAAK1B,IAAI;gBAAE2B,IAAI;YAAE;sBACnC,cAAA,MAACrO;gBAAMsO,SAAS;gBAAG5B,IAAI;oBAAE6B,YAAY;gBAAS;;kCAC5C,KAAC5N;wBAAoBsM,UAAUA;;kCAC/B,KAACjM;wBACCuF,KAAK;4BAAE+D,WAAWjE,KAAKkE,QAAQ;4BAAEE,aAAaxF,QAAQyF,OAAO,CAACD,WAAW;4BAAE+D,aAAa;wBAAK;wBAC7FC,UAAU,CAAC,MAAM,EAAEpI,KAAKmC,OAAO,EAAE;wBACjC9B,MAAMA;wBACNwB,UAAUA;wBACVwG,UAAU,CAAC5E;gCAEIA,mBAEqCA;4BAHlD,MAAME,OAAkB;gCACtBvD,SAAS,GAAEqD,oBAAAA,OAAOrD,SAAS,YAAhBqD,oBAAoB;+BAC3BA,OAAOc,UAAU,GAAG;gCAAEA,YAAYd,OAAOc,UAAU;4BAAC,IAAI,CAAC,GACzDd,OAAOrD,SAAS,KAAK,WAAW;gCAAE2D,QAAQ,GAAEN,mBAAAA,OAAOM,QAAQ,YAAfN,mBAAmB;4BAAE,IAAI,CAAC;4BAE5EtD,OAAOwD;4BACP,KAAKJ,aAAahE,MAAMiE,MAAM,EAAEG;wBAClC;;kCAEF,KAACxK;wBAAO4N,SAAS,IAAMvH,SAAS;gCAAEC,MAAM;4BAAS;wBAAI6I,UAAUjI;kCAC5D;;;;;IAKX,OAAO,IAAId,MAAME,IAAI,KAAK,YAAYO,MAAM;QAC1C6G,uBACE,KAAC/L;YACC0J,aAAajF,MAAMiF,WAAW;YAC9B3C,UAAUA;YACV2B,QAAQjE,MAAMiE,MAAM;YACpBiB,SAASlF,MAAMkF,OAAO;YACtB8D,UAAUlL,QAAQuB,CAAAA,2BAAAA,QAAS2J,QAAQ,KAAIhJ,MAAMiE,MAAM,KAAK;YACxDnD,MAAMA;YACNmI,UAAU,IAAM,KAAKpD;YACrBqD,SAAS,IAAM,KAAKlF,aAAahE,MAAMiE,MAAM,EAAEtD;YAC/C2H,WAAWnE,IAAIJ,OAAO,GAAG,IAAM,KAAKD,kBAAkB;YACtDqF,YACEnJ,MAAMqE,SAAS,GACX,IAAM,KAAKzI,SAASwN,QAAQ,CAAC7J,OAAOkB,KAAKmC,OAAO,EAAE5C,MAAMqE,SAAS,IACjE;;IAIZ,OAAO,IAAIrE,MAAME,IAAI,KAAK,aAAaO,QAAQpB,SAAS;QACtDiI,uBACE,KAACxN;YAAU0O,UAAS;YAAK1B,IAAI;gBAAE2B,IAAI;YAAE;sBACnC,cAAA,MAACrO;gBAAMsO,SAAS;gBAAG5B,IAAI;oBAAE6B,YAAY;gBAAS;;kCAC5C,KAAC5N;wBAAoBsM,UAAUA;;kCAC/B,KAAClM;wBACC+K,SAAS;4BAAEmD,UAAUhK,QAAQiK,QAAQ;4BAAEC,gBAAgBlK,QAAQkK,cAAc;wBAAC;wBAC9EV,UAAU,CAAC,MAAM,EAAEpI,KAAKmC,OAAO,EAAE;wBACjC9B,MAAMA;wBACNgI,UAAU,CAAC5E,SAAW,KAAK6B,cAAc7B;;;;;IAKnD,OAAO,IAAIlE,MAAME,IAAI,KAAK,UAAUO,MAAM;QACxC6G,uBAAS,KAAChM;YAAUmF,MAAMA;YAAM6B,UAAUA;YAAUkH,QAAQ/G;;IAC9D,OAAO;QACL6E,uBAAS,KAACtM;YAAsBqM,UAAUA;YAAUvG,IAAI;;IAC1D;IAEA,qBACE,MAACnH;QAAemN,IAAI;YAAE2C,UAAU;YAAY1C,WAAW;QAAS;;YAC7D7F,wBACC,KAACxH;gBAAMiO,UAAS;gBAAUb,IAAI;oBAAE2C,UAAU;oBAASC,KAAK;oBAAGC,MAAM;oBAAGC,OAAO;oBAAGC,QAAQ,CAACpC,QAAUA,MAAMoC,MAAM,CAACC,KAAK,GAAG;gBAAE;0BACrH;iBAED;YACHxC;0BACD,KAACzM;gBACCqB,SAASmE,cAAcpE,iBAAiBoE,eAAe;gBACvDuH,SAAS,IAAMtH,eAAe;gBAC9ByJ,WAAW,CAAC5M;oBACV,MAAMjB,UAAUmE;oBAChBC,eAAe;oBACf,IAAIpE,SAASmH,UAAUnH,SAASiB;gBAClC;gBACA6M,aAAazH;gBACb0H,YAAY;gBACZC,aAAaxP,cAAcoE,sBAAsB;;0BAEnD,MAAC/E;gBAAO2I,MAAMpD,KAAK6K,OAAO,KAAK;gBAAMvC,SAAStI,KAAK8K,SAAS;gBAAE5B,UAAS;gBAAK6B,SAAS;;kCACnF,KAAClQ;kCAAa;;kCACd,KAACF;kCACC,cAAA,KAACC;sCACE,CAAC,8BAA8B,GAAEoF,gBAAAA,KAAK6K,OAAO,YAAZ7K,gBAAgB,EAAE,SAAS,CAAC;;;kCAGlE,MAACtF;;0CACC,KAACJ;gCAAO4N,SAAS/E;0CAAQ;;0CACzB,KAAC7I;gCAAOiD,SAAQ;gCAAY2K,SAASlI,KAAK8K,SAAS;0CAChD;;;;;;YAINpK,MAAME,IAAI,KAAK,WAAWF,MAAME,IAAI,KAAK,uBACxC,KAACxE;gBAAkB6D,OAAOA;gBAAO+K,QAAQ3I;gBAAU4I,gBAAgB5I;iBACjE;;OAnCIP;AAsCd;AAEAjC,aAAaqL,WAAW,GAAG"}
@@ -158,7 +158,7 @@ import { PosOfflineBanner, PosOfflineCheckout, usePosOffline } from "./pos-offli
158
158
  /** The basket line an item-sheet choice makes: label and estimate from the product. */ export function registerLineFor(product, choice) {
159
159
  var _variant_options, _variant_options1;
160
160
  const { variant, modifiers, quantity } = choice;
161
- const resolved = CommerceModel.resolveLineModifiers(product, modifiers);
161
+ const resolved = CommerceModel.resolveLineModifiers(product, modifiers, variant == null ? void 0 : variant.id);
162
162
  const picked = resolved.ok ? resolved.modifiers : [];
163
163
  const label = CommerceModel.lineLabelWithModifiers(Object.keys((_variant_options = variant.options) != null ? _variant_options : {}).length ? Object.values((_variant_options1 = variant.options) != null ? _variant_options1 : {}).join(' / ') : undefined, picked);
164
164
  return _extends({