@aglyn/plugins-commerce 1.0.0-beta.218 → 1.0.0-beta.219

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 (95) hide show
  1. package/README.md +1 -0
  2. package/package.json +13 -13
  3. package/src/index.d.ts +1 -0
  4. package/src/index.js +1 -19
  5. package/src/index.js.map +1 -1
  6. package/src/lib/components/cart.js +2 -1
  7. package/src/lib/components/cart.js.map +1 -1
  8. package/src/lib/components/console/catalog-organization-card.component.js +44 -18
  9. package/src/lib/components/console/catalog-organization-card.component.js.map +1 -1
  10. package/src/lib/components/console/host-orders-card.component.js +2 -1
  11. package/src/lib/components/console/host-orders-card.component.js.map +1 -1
  12. package/src/lib/components/console/member-posts-card.component.js +1 -1
  13. package/src/lib/components/console/member-posts-card.component.js.map +1 -1
  14. package/src/lib/components/console/order-detail-dialog.component.js +8 -7
  15. package/src/lib/components/console/order-detail-dialog.component.js.map +1 -1
  16. package/src/lib/components/console/payments-settings-card.component.js +1 -1
  17. package/src/lib/components/console/payments-settings-card.component.js.map +1 -1
  18. package/src/lib/components/console/product-editor-dialog.component.js +114 -63
  19. package/src/lib/components/console/product-editor-dialog.component.js.map +1 -1
  20. package/src/lib/components/console/product-zones.d.ts +184 -0
  21. package/src/lib/components/console/product-zones.js +54 -0
  22. package/src/lib/components/console/product-zones.js.map +1 -0
  23. package/src/lib/components/console/products-hub-card.component.js +3 -2
  24. package/src/lib/components/console/products-hub-card.component.js.map +1 -1
  25. package/src/lib/components/console/products-hub-zone.component.d.ts +1 -1
  26. package/src/lib/components/console/products-hub-zone.component.js +2 -1
  27. package/src/lib/components/console/products-hub-zone.component.js.map +1 -1
  28. package/src/lib/components/console/store-settings-card.component.js +2 -2
  29. package/src/lib/components/console/store-settings-card.component.js.map +1 -1
  30. package/src/lib/components/console/storefront-tax-summary-card.component.js +1 -1
  31. package/src/lib/components/console/storefront-tax-summary-card.component.js.map +1 -1
  32. package/src/lib/components/product-detail.js +2 -1
  33. package/src/lib/components/product-detail.js.map +1 -1
  34. package/src/lib/components/product-grid.js +1 -1
  35. package/src/lib/components/product-grid.js.map +1 -1
  36. package/src/lib/constants/storefront-payments.d.ts +33 -0
  37. package/src/lib/constants/storefront-payments.js +35 -0
  38. package/src/lib/constants/storefront-payments.js.map +1 -0
  39. package/src/lib/declarations.server.d.ts +3 -1
  40. package/src/lib/declarations.server.js +19 -1
  41. package/src/lib/declarations.server.js.map +1 -1
  42. package/src/lib/model/commerce-record-routes.d.ts +27 -0
  43. package/src/lib/model/commerce-record-routes.js +72 -0
  44. package/src/lib/model/commerce-record-routes.js.map +1 -0
  45. package/src/lib/model/commerce-search-sources.d.ts +21 -0
  46. package/src/lib/model/commerce-search-sources.js +40 -0
  47. package/src/lib/model/commerce-search-sources.js.map +1 -0
  48. package/src/lib/model/storefront-tax-summary.d.ts +121 -0
  49. package/src/lib/model/storefront-tax-summary.js +148 -0
  50. package/src/lib/model/storefront-tax-summary.js.map +1 -0
  51. package/src/lib/plan-entitlements.d.ts +35 -0
  52. package/src/lib/plan-entitlements.js +54 -0
  53. package/src/lib/plan-entitlements.js.map +1 -0
  54. package/src/lib/plugin.js +9 -0
  55. package/src/lib/plugin.js.map +1 -1
  56. package/src/lib/server/billing-webhook.js +135 -168
  57. package/src/lib/server/billing-webhook.js.map +1 -1
  58. package/src/lib/server/cart-checkout.js +2 -2
  59. package/src/lib/server/cart-checkout.js.map +1 -1
  60. package/src/lib/server/checkout.js +2 -2
  61. package/src/lib/server/checkout.js.map +1 -1
  62. package/src/lib/server/connect.js +5 -5
  63. package/src/lib/server/connect.js.map +1 -1
  64. package/src/lib/server/draft-order.js +2 -2
  65. package/src/lib/server/draft-order.js.map +1 -1
  66. package/src/lib/server/membership-content.js +1 -1
  67. package/src/lib/server/membership-content.js.map +1 -1
  68. package/src/lib/server/pos-order.js +2 -2
  69. package/src/lib/server/pos-order.js.map +1 -1
  70. package/src/lib/server/product-record-index.d.ts +19 -0
  71. package/src/lib/server/product-record-index.js +115 -0
  72. package/src/lib/server/product-record-index.js.map +1 -0
  73. package/src/lib/server/reserve.js +2 -2
  74. package/src/lib/server/reserve.js.map +1 -1
  75. package/src/lib/server/revenue-source.d.ts +130 -0
  76. package/src/lib/server/revenue-source.js +286 -0
  77. package/src/lib/server/revenue-source.js.map +1 -0
  78. package/src/lib/server/tax-profile.d.ts +8 -5
  79. package/src/lib/server/tax-profile.js +22 -5
  80. package/src/lib/server/tax-profile.js.map +1 -1
  81. package/src/lib/server/tax-return-source.d.ts +125 -0
  82. package/src/lib/server/tax-return-source.js +524 -0
  83. package/src/lib/server/tax-return-source.js.map +1 -0
  84. package/src/lib/server/tax-summary.d.ts +92 -0
  85. package/src/lib/server/tax-summary.js +258 -0
  86. package/src/lib/server/tax-summary.js.map +1 -0
  87. package/src/lib/server.d.ts +1 -0
  88. package/src/lib/server.js +23 -0
  89. package/src/lib/server.js.map +1 -1
  90. package/src/lib/tenant-emails.d.ts +33 -0
  91. package/src/lib/tenant-emails.js +511 -0
  92. package/src/lib/tenant-emails.js.map +1 -0
  93. package/src/lib/testing/stand-in-crm-record-routes.d.ts +25 -0
  94. package/src/lib/testing/stand-in-crm-record-routes.js +46 -0
  95. package/src/lib/testing/stand-in-crm-record-routes.js.map +1 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/store-settings-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Button,\n FormControlLabel,\n MenuItem,\n Stack,\n Switch,\n TextField,\n Typography,\n} from '@mui/material'\nimport { doc } from 'firebase/firestore'\nimport { useCallback, useState } from 'react'\nimport { useFirestore, useUser } from '@aglyn/tenant-feature-instance'\nimport {\n useFirestoreDoc,\n writeGuardedBySeed,\n} from '@aglyn/tenant-feature-instance'\nimport { hostPublicOrigin, pluginDocsHelp } from '@aglyn/aglyn'\nimport { writeSiteWideChange } from '@aglyn/tenant-feature-instance/hooks/helpers/site-wide-change'\n\nexport interface StoreSettingsCardProps {\n hostId: string\n}\n\ninterface StoreSettings {\n /** Screen rendered for /products/{slug} with product tokens (AGL-292). */\n pdpScreenId?: string\n /** Screen rendered for /collections/{slug} (AGL-298). */\n collectionScreenId?: string\n currency?: string\n guestCheckout?: boolean\n termsUrl?: string\n receiptFooter?: string\n}\n\n/**\n * Store settings (AGL-295) on `hosts/{hostId}/settings/store`: the PDP\n * template screen (drives /products/{slug}), currency display, checkout\n * preferences, and receipt branding. Tax and shipping have their own\n * cards; store password/maintenance live in Site Protection.\n */\nexport function StoreSettingsCard(props: StoreSettingsCardProps) {\n const { hostId } = props\n const firestore = useFirestore()\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const {\n data: store,\n status: storeStatus,\n /**\n * The settings doc this form is seeded from is unconfirmed by the server\n * (AGL-1358). `current` is `draft ?? {…store}` and the save writes all\n * six keys, so `merge: true` protects only the tax and shipping maps that\n * belong to other cards — never this card's own fields. Against a cached\n * read, changing the currency rewrites `guestCheckout`, and the two\n * template ids fall back to `|| null`, which is how a storefront's\n * product and collection URLs start returning 404.\n */\n fromCache: storeFromCache,\n } = useFirestoreDoc<any>(\n () => doc(firestore, 'hosts', hostId, 'settings', 'store'),\n [firestore, hostId],\n )\n const { data: host } = useFirestoreDoc<any>(\n () => doc(firestore, 'hosts', hostId),\n [firestore, hostId],\n )\n const [draft, setDraft] = useState<StoreSettings | null>(null)\n const current: StoreSettings = draft ?? {\n pdpScreenId: store?.pdpScreenId ?? '',\n collectionScreenId: store?.collectionScreenId ?? '',\n currency: store?.currency ?? 'USD',\n guestCheckout: store?.guestCheckout !== false,\n termsUrl: store?.termsUrl ?? '',\n receiptFooter: store?.receiptFooter ?? '',\n }\n const update = (patch: Partial<StoreSettings>) =>\n setDraft({ ...current, ...patch })\n\n /**\n * Google Merchant Center feed address (AGL-2249).\n *\n * `server/feed.ts` has served this since AGL-299, and the only place the\n * URL was ever written down was that file's own docblock — so the feature\n * was built, deployed, and undiscoverable. A feed nobody can find is a\n * shopping channel nobody switches on.\n *\n * Through `hostPublicOrigin` rather than a hand-built string, because a\n * store on a custom domain must be told ITS name: the feed's `<link>`\n * elements are built from the request host, so submitting the `.aglyn.app`\n * address for a site that serves on `shop.example.com` would put the wrong\n * domain on every item in Merchant Center.\n *\n * Undefined until the host doc lands, and undefined forever for a host with\n * neither a cname nor a subdomain — `hostPublicOrigin` returns undefined\n * rather than a half-built URL, and a copyable half-URL is worse than none.\n */\n const feedUrl = host\n ? (() => {\n const origin = hostPublicOrigin(host)\n return origin\n ? `${origin}/api/commerce/feed?hostId=${hostId}`\n : undefined\n })()\n : undefined\n\n // Host screen directory: {screenId: path} (AGL-292 template pick).\n const screenEntries = Object.entries(\n (host?.screens ?? {}) as Record<string, string>,\n ).sort(([, a], [, b]) => String(a).localeCompare(String(b)))\n\n const handleSave = useCallback(async () => {\n /**\n * Refuse a save whose seed the server never confirmed (AGL-1358).\n *\n * There is no create path to exempt, and that is deliberate rather than\n * an oversight: unlike a new row at a fresh uid, this writes a FIXED\n * document path, so the all-defaults `current` produced when the cache\n * has never seen this doc is not the harmless blank a fresh uid would\n * be. It would put `USD`, guest checkout on and two null template ids\n * over whatever the server really holds.\n *\n * The guard WRAPS the write — an early return is a shape you can keep\n * while losing the protection.\n */\n const verdict = await writeGuardedBySeed(\n {\n subject: 'store settings',\n unreadable: storeStatus === 'error',\n fromCache: storeFromCache,\n },\n async () => {\n // The product and collection templates, the currency and the\n // checkout rules render on the live store with no publish step, so\n // the save carries the site's cache drop with it.\n await writeSiteWideChange({\n firestore,\n user,\n hostId,\n write: (batch) =>\n batch.set(\n doc(firestore, 'hosts', hostId, 'settings', 'store'),\n {\n pdpScreenId: current.pdpScreenId || null,\n collectionScreenId: current.collectionScreenId || null,\n currency: current.currency ?? 'USD',\n guestCheckout: current.guestCheckout !== false,\n termsUrl: current.termsUrl || null,\n receiptFooter: current.receiptFooter || null,\n },\n { merge: true },\n ),\n })\n },\n )\n // Before `setDraft(null)`, so a refusal keeps every typed value on\n // screen. A save that silently does nothing sends the user back to\n // retype a form that will be refused again just as quietly.\n if (!verdict.ok) {\n return void enqueueSnackbar(verdict.message, {\n variant: 'warning',\n persist: false,\n })\n }\n setDraft(null)\n enqueueSnackbar('Store settings saved', {\n variant: 'success',\n persist: false,\n })\n }, [\n current,\n firestore,\n hostId,\n user,\n enqueueSnackbar,\n storeStatus,\n storeFromCache,\n ])\n\n return (\n <CardDisplay\n header={'Store settings'}\n help={pluginDocsHelp('commerce')}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1.5}>\n <TextField\n label=\"Product page template\"\n value={current.pdpScreenId ?? ''}\n onChange={(event) => update({ pdpScreenId: event.target.value })}\n size=\"small\"\n select\n helperText={\n 'Screen rendered at /products/{slug} — design it in the ' +\n 'besigner with the Product detail block and {{product.*}} tokens.'\n }\n >\n <MenuItem value=\"\">{'None (product URLs 404)'}</MenuItem>\n {screenEntries.map(([screenId, path]) => (\n <MenuItem key={screenId} value={screenId}>\n {`/${path === '/' ? '' : path}`}\n </MenuItem>\n ))}\n </TextField>\n <TextField\n label=\"Collection page template\"\n value={current.collectionScreenId ?? ''}\n onChange={(event) =>\n update({ collectionScreenId: event.target.value })\n }\n size=\"small\"\n select\n helperText={\n 'Screen rendered at /collections/{slug} — drop a Product grid ' +\n 'with source \"A collection\" and {{collection.*}} tokens.'\n }\n >\n <MenuItem value=\"\">{'None (collection URLs 404)'}</MenuItem>\n {screenEntries.map(([screenId, path]) => (\n <MenuItem key={screenId} value={screenId}>\n {`/${path === '/' ? '' : path}`}\n </MenuItem>\n ))}\n </TextField>\n <TextField\n label=\"Currency\"\n value={current.currency ?? 'USD'}\n onChange={(event) => update({ currency: event.target.value })}\n size=\"small\"\n select\n sx={{ maxWidth: 200 }}\n helperText=\"Charges settle in USD; more currencies are coming\"\n >\n <MenuItem value=\"USD\">{'USD ($)'}</MenuItem>\n </TextField>\n <FormControlLabel\n control={\n <Switch\n size=\"small\"\n checked={current.guestCheckout !== false}\n onChange={(event) =>\n update({ guestCheckout: event.target.checked })\n }\n />\n }\n label=\"Allow guest checkout (no account required)\"\n />\n <TextField\n label=\"Terms URL\"\n value={current.termsUrl ?? ''}\n onChange={(event) => update({ termsUrl: event.target.value })}\n size=\"small\"\n placeholder=\"/terms\"\n helperText=\"Linked from checkout when set\"\n />\n <TextField\n label=\"Receipt footer\"\n value={current.receiptFooter ?? ''}\n onChange={(event) => update({ receiptFooter: event.target.value })}\n size=\"small\"\n multiline\n minRows={2}\n placeholder=\"Thanks for supporting our shop!\"\n />\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Password protection and maintenance mode live under Site ' +\n 'Protection.'}\n </Typography>\n <Button\n variant=\"contained\"\n color=\"primary\"\n size=\"small\"\n disabled={!draft}\n onClick={handleSave}\n sx={{ alignSelf: 'flex-start' }}\n >\n {'Save store settings'}\n </Button>\n <Stack spacing={0.5}>\n <Typography variant=\"subtitle2\">{'Product feed'}</Typography>\n {feedUrl ? (\n <>\n <TextField\n label=\"Google Merchant Center feed URL\"\n value={feedUrl}\n size=\"small\"\n slotProps={{ input: { readOnly: true } }}\n onFocus={(event) => event.target.select()}\n helperText={\n 'Paste this into Merchant Center → Products → Feeds as a ' +\n 'scheduled fetch. It lists every active product with price ' +\n 'and availability, and refreshes itself.'\n }\n />\n <Button\n size=\"small\"\n sx={{ alignSelf: 'flex-start' }}\n onClick={() => {\n void navigator.clipboard?.writeText(feedUrl).then(() =>\n enqueueSnackbar('Feed URL copied', {\n variant: 'success',\n persist: false,\n }),\n )\n }}\n >\n {'Copy feed URL'}\n </Button>\n </>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Publish this site to a subdomain or custom domain to get its ' +\n 'Google Merchant Center feed URL.'}\n </Typography>\n )}\n </Stack>\n </Stack>\n </CardDisplay>\n )\n}\nStoreSettingsCard.displayName = 'StoreSettingsCard'\n\nexport default StoreSettingsCard\n"],"names":["CardDisplay","useSnackbar","Button","FormControlLabel","MenuItem","Stack","Switch","TextField","Typography","doc","useCallback","useState","useFirestore","useUser","useFirestoreDoc","writeGuardedBySeed","hostPublicOrigin","pluginDocsHelp","writeSiteWideChange","StoreSettingsCard","props","current","hostId","firestore","data","user","enqueueSnackbar","store","status","storeStatus","fromCache","storeFromCache","host","draft","setDraft","pdpScreenId","collectionScreenId","currency","guestCheckout","termsUrl","receiptFooter","update","patch","feedUrl","origin","undefined","screenEntries","Object","entries","screens","sort","a","b","String","localeCompare","handleSave","verdict","subject","unreadable","write","batch","set","merge","ok","message","variant","persist","header","help","contentGutterX","contentGutterY","spacing","label","value","onChange","event","target","size","select","helperText","map","screenId","path","sx","maxWidth","control","checked","placeholder","multiline","minRows","color","disabled","onClick","alignSelf","slotProps","input","readOnly","onFocus","navigator","clipboard","writeText","then","displayName"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;;AAEA,SAASA,WAAW,QAAQ,uBAAsB;AAClD,SAASC,WAAW,QAAQ,8BAA6B;AACzD,SACEC,MAAM,EACNC,gBAAgB,EAChBC,QAAQ,EACRC,KAAK,EACLC,MAAM,EACNC,SAAS,EACTC,UAAU,QACL,gBAAe;AACtB,SAASC,GAAG,QAAQ,qBAAoB;AACxC,SAASC,WAAW,EAAEC,QAAQ,QAAQ,QAAO;AAC7C,SAASC,YAAY,EAAEC,OAAO,QAAQ,iCAAgC;AACtE,SACEC,eAAe,EACfC,kBAAkB,QACb,iCAAgC;AACvC,SAASC,gBAAgB,EAAEC,cAAc,QAAQ,eAAc;AAC/D,SAASC,mBAAmB,QAAQ,gEAA+D;AAiBnG;;;;;CAKC,GACD,OAAO,SAASC,kBAAkBC,KAA6B;iDAoJ9CC,sBAkBAA,6BAoBAA,mBAuBAA,mBAQAA;IAxNf,MAAM,EAAEC,MAAM,EAAE,GAAGF;IACnB,MAAMG,YAAYX;IAClB,MAAM,EAAEY,MAAMC,IAAI,EAAE,GAAGZ;IACvB,MAAM,EAAEa,eAAe,EAAE,GAAGzB;IAC5B,MAAM,EACJuB,MAAMG,KAAK,EACXC,QAAQC,WAAW,EACnB;;;;;;;;KAQC,GACDC,WAAWC,cAAc,EAC1B,GAAGjB,gBACF,IAAML,IAAIc,WAAW,SAASD,QAAQ,YAAY,UAClD;QAACC;QAAWD;KAAO;IAErB,MAAM,EAAEE,MAAMQ,IAAI,EAAE,GAAGlB,gBACrB,IAAML,IAAIc,WAAW,SAASD,SAC9B;QAACC;QAAWD;KAAO;IAErB,MAAM,CAACW,OAAOC,SAAS,GAAGvB,SAA+B;IACzD,MAAMU,UAAyBY,gBAAAA,QAAS;QACtCE,WAAW,UAAER,yBAAAA,MAAOQ,WAAW,mBAAI;QACnCC,kBAAkB,WAAET,yBAAAA,MAAOS,kBAAkB,oBAAI;QACjDC,QAAQ,WAAEV,yBAAAA,MAAOU,QAAQ,oBAAI;QAC7BC,eAAeX,CAAAA,yBAAAA,MAAOW,aAAa,MAAK;QACxCC,QAAQ,WAAEZ,yBAAAA,MAAOY,QAAQ,oBAAI;QAC7BC,aAAa,WAAEb,yBAAAA,MAAOa,aAAa,oBAAI;IACzC;IACA,MAAMC,SAAS,CAACC,QACdR,SAAS,aAAKb,SAAYqB;IAE5B;;;;;;;;;;;;;;;;;GAiBC,GACD,MAAMC,UAAUX,OACZ,AAAC,CAAA;QACC,MAAMY,SAAS5B,iBAAiBgB;QAChC,OAAOY,SACH,GAAGA,OAAO,0BAA0B,EAAEtB,QAAQ,GAC9CuB;IACN,CAAA,MACAA;IAEJ,mEAAmE;IACnE,MAAMC,gBAAgBC,OAAOC,OAAO,UACjChB,wBAAAA,KAAMiB,OAAO,oBAAI,CAAC,GACnBC,IAAI,CAAC,CAAC,GAAGC,EAAE,EAAE,GAAGC,EAAE,GAAKC,OAAOF,GAAGG,aAAa,CAACD,OAAOD;IAExD,MAAMG,aAAa7C,YAAY;QAC7B;;;;;;;;;;;;KAYC,GACD,MAAM8C,UAAU,MAAMzC,mBACpB;YACE0C,SAAS;YACTC,YAAY7B,gBAAgB;YAC5BC,WAAWC;QACb,GACA;YACE,6DAA6D;YAC7D,mEAAmE;YACnE,kDAAkD;YAClD,MAAMb,oBAAoB;gBACxBK;gBACAE;gBACAH;gBACAqC,OAAO,CAACC;wBAMQvC;2BALduC,MAAMC,GAAG,CACPpD,IAAIc,WAAW,SAASD,QAAQ,YAAY,UAC5C;wBACEa,aAAad,QAAQc,WAAW,IAAI;wBACpCC,oBAAoBf,QAAQe,kBAAkB,IAAI;wBAClDC,QAAQ,GAAEhB,oBAAAA,QAAQgB,QAAQ,YAAhBhB,oBAAoB;wBAC9BiB,eAAejB,QAAQiB,aAAa,KAAK;wBACzCC,UAAUlB,QAAQkB,QAAQ,IAAI;wBAC9BC,eAAenB,QAAQmB,aAAa,IAAI;oBAC1C,GACA;wBAAEsB,OAAO;oBAAK;;YAEpB;QACF;QAEF,mEAAmE;QACnE,mEAAmE;QACnE,4DAA4D;QAC5D,IAAI,CAACN,QAAQO,EAAE,EAAE;YACf,OAAO,KAAKrC,gBAAgB8B,QAAQQ,OAAO,EAAE;gBAC3CC,SAAS;gBACTC,SAAS;YACX;QACF;QACAhC,SAAS;QACTR,gBAAgB,wBAAwB;YACtCuC,SAAS;YACTC,SAAS;QACX;IACF,GAAG;QACD7C;QACAE;QACAD;QACAG;QACAC;QACAG;QACAE;KACD;IAED,qBACE,KAAC/B;QACCmE,QAAQ;QACRC,MAAMnD,eAAe;QACrBoD,cAAc;QACdC,cAAc;kBAEd,cAAA,MAACjE;YAAMkE,SAAS;;8BACd,MAAChE;oBACCiE,OAAM;oBACNC,KAAK,GAAEpD,uBAAAA,QAAQc,WAAW,YAAnBd,uBAAuB;oBAC9BqD,UAAU,CAACC,QAAUlC,OAAO;4BAAEN,aAAawC,MAAMC,MAAM,CAACH,KAAK;wBAAC;oBAC9DI,MAAK;oBACLC,MAAM;oBACNC,YACE,4DACA;;sCAGF,KAAC3E;4BAASqE,OAAM;sCAAI;;wBACnB3B,cAAckC,GAAG,CAAC,CAAC,CAACC,UAAUC,KAAK,iBAClC,KAAC9E;gCAAwBqE,OAAOQ;0CAC7B,CAAC,CAAC,EAAEC,SAAS,MAAM,KAAKA,MAAM;+BADlBD;;;8BAKnB,MAAC1E;oBACCiE,OAAM;oBACNC,KAAK,GAAEpD,8BAAAA,QAAQe,kBAAkB,YAA1Bf,8BAA8B;oBACrCqD,UAAU,CAACC,QACTlC,OAAO;4BAAEL,oBAAoBuC,MAAMC,MAAM,CAACH,KAAK;wBAAC;oBAElDI,MAAK;oBACLC,MAAM;oBACNC,YACE,kEACA;;sCAGF,KAAC3E;4BAASqE,OAAM;sCAAI;;wBACnB3B,cAAckC,GAAG,CAAC,CAAC,CAACC,UAAUC,KAAK,iBAClC,KAAC9E;gCAAwBqE,OAAOQ;0CAC7B,CAAC,CAAC,EAAEC,SAAS,MAAM,KAAKA,MAAM;+BADlBD;;;8BAKnB,KAAC1E;oBACCiE,OAAM;oBACNC,KAAK,GAAEpD,oBAAAA,QAAQgB,QAAQ,YAAhBhB,oBAAoB;oBAC3BqD,UAAU,CAACC,QAAUlC,OAAO;4BAAEJ,UAAUsC,MAAMC,MAAM,CAACH,KAAK;wBAAC;oBAC3DI,MAAK;oBACLC,MAAM;oBACNK,IAAI;wBAAEC,UAAU;oBAAI;oBACpBL,YAAW;8BAEX,cAAA,KAAC3E;wBAASqE,OAAM;kCAAO;;;8BAEzB,KAACtE;oBACCkF,uBACE,KAAC/E;wBACCuE,MAAK;wBACLS,SAASjE,QAAQiB,aAAa,KAAK;wBACnCoC,UAAU,CAACC,QACTlC,OAAO;gCAAEH,eAAeqC,MAAMC,MAAM,CAACU,OAAO;4BAAC;;oBAInDd,OAAM;;8BAER,KAACjE;oBACCiE,OAAM;oBACNC,KAAK,GAAEpD,oBAAAA,QAAQkB,QAAQ,YAAhBlB,oBAAoB;oBAC3BqD,UAAU,CAACC,QAAUlC,OAAO;4BAAEF,UAAUoC,MAAMC,MAAM,CAACH,KAAK;wBAAC;oBAC3DI,MAAK;oBACLU,aAAY;oBACZR,YAAW;;8BAEb,KAACxE;oBACCiE,OAAM;oBACNC,KAAK,GAAEpD,yBAAAA,QAAQmB,aAAa,YAArBnB,yBAAyB;oBAChCqD,UAAU,CAACC,QAAUlC,OAAO;4BAAED,eAAemC,MAAMC,MAAM,CAACH,KAAK;wBAAC;oBAChEI,MAAK;oBACLW,SAAS;oBACTC,SAAS;oBACTF,aAAY;;8BAEd,KAAC/E;oBAAWyD,SAAQ;oBAAUyB,OAAM;8BACjC,8DACC;;8BAEJ,KAACxF;oBACC+D,SAAQ;oBACRyB,OAAM;oBACNb,MAAK;oBACLc,UAAU,CAAC1D;oBACX2D,SAASrC;oBACT4B,IAAI;wBAAEU,WAAW;oBAAa;8BAE7B;;8BAEH,MAACxF;oBAAMkE,SAAS;;sCACd,KAAC/D;4BAAWyD,SAAQ;sCAAa;;wBAChCtB,wBACC;;8CACE,KAACpC;oCACCiE,OAAM;oCACNC,OAAO9B;oCACPkC,MAAK;oCACLiB,WAAW;wCAAEC,OAAO;4CAAEC,UAAU;wCAAK;oCAAE;oCACvCC,SAAS,CAACtB,QAAUA,MAAMC,MAAM,CAACE,MAAM;oCACvCC,YACE,6DACA,+DACA;;8CAGJ,KAAC7E;oCACC2E,MAAK;oCACLM,IAAI;wCAAEU,WAAW;oCAAa;oCAC9BD,SAAS;4CACFM;wCAAL,OAAKA,uBAAAA,UAAUC,SAAS,qBAAnBD,qBAAqBE,SAAS,CAACzD,SAAS0D,IAAI,CAAC,IAChD3E,gBAAgB,mBAAmB;gDACjCuC,SAAS;gDACTC,SAAS;4CACX;oCAEJ;8CAEC;;;2CAIL,KAAC1D;4BAAWyD,SAAQ;4BAAQyB,OAAM;sCAC/B,kEACC;;;;;;;AAOhB;AACAvE,kBAAkBmF,WAAW,GAAG;AAEhC,eAAenF,kBAAiB"}
1
+ {"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/store-settings-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Button,\n FormControlLabel,\n MenuItem,\n Stack,\n Switch,\n TextField,\n Typography,\n} from '@mui/material'\nimport { doc } from 'firebase/firestore'\nimport { useCallback, useState } from 'react'\nimport { useFirestore, useUser } from '@aglyn/tenant-feature-instance'\nimport {\n useFirestoreDoc,\n writeGuardedBySeed,\n} from '@aglyn/tenant-feature-instance'\nimport { hostPublicOrigin, pluginDocsHelp } from '@aglyn/aglyn'\nimport { writeSiteWideChange } from '@aglyn/tenant-feature-instance/hooks/helpers/site-wide-change'\n\nexport interface StoreSettingsCardProps {\n hostId: string\n}\n\ninterface StoreSettings {\n /** Screen rendered for /products/{slug} with product tokens (AGL-292). */\n pdpScreenId?: string\n /** Screen rendered for /collections/{slug} (AGL-298). */\n collectionScreenId?: string\n currency?: string\n guestCheckout?: boolean\n termsUrl?: string\n receiptFooter?: string\n}\n\n/**\n * Store settings (AGL-295) on `hosts/{hostId}/settings/store`: the PDP\n * template screen (drives /products/{slug}), currency display, checkout\n * preferences, and receipt branding. Tax and shipping have their own\n * cards; store password/maintenance live in Site Protection.\n */\nexport function StoreSettingsCard(props: StoreSettingsCardProps) {\n const { hostId } = props\n const firestore = useFirestore()\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const {\n data: store,\n status: storeStatus,\n /**\n * The settings doc this form is seeded from is unconfirmed by the server\n * (AGL-1358). `current` is `draft ?? {…store}` and the save writes all\n * six keys, so `merge: true` protects only the tax and shipping maps that\n * belong to other cards — never this card's own fields. Against a cached\n * read, changing the currency rewrites `guestCheckout`, and the two\n * template ids fall back to `|| null`, which is how a storefront's\n * product and collection URLs start returning 404.\n */\n fromCache: storeFromCache,\n } = useFirestoreDoc<any>(\n () => doc(firestore, 'hosts', hostId, 'settings', 'store'),\n [firestore, hostId],\n )\n const { data: host } = useFirestoreDoc<any>(\n () => doc(firestore, 'hosts', hostId),\n [firestore, hostId],\n )\n const [draft, setDraft] = useState<StoreSettings | null>(null)\n const current: StoreSettings = draft ?? {\n pdpScreenId: store?.pdpScreenId ?? '',\n collectionScreenId: store?.collectionScreenId ?? '',\n currency: store?.currency ?? 'USD',\n guestCheckout: store?.guestCheckout !== false,\n termsUrl: store?.termsUrl ?? '',\n receiptFooter: store?.receiptFooter ?? '',\n }\n const update = (patch: Partial<StoreSettings>) =>\n setDraft({ ...current, ...patch })\n\n /**\n * Google Merchant Center feed address (AGL-2249).\n *\n * `server/feed.ts` has served this since AGL-299, and the only place the\n * URL was ever written down was that file's own docblock — so the feature\n * was built, deployed, and undiscoverable. A feed nobody can find is a\n * shopping channel nobody switches on.\n *\n * Through `hostPublicOrigin` rather than a hand-built string, because a\n * store on a custom domain must be told ITS name: the feed's `<link>`\n * elements are built from the request host, so submitting the `.aglyn.app`\n * address for a site that serves on `shop.example.com` would put the wrong\n * domain on every item in Merchant Center.\n *\n * Undefined until the host doc lands, and undefined forever for a host with\n * neither a cname nor a subdomain — `hostPublicOrigin` returns undefined\n * rather than a half-built URL, and a copyable half-URL is worse than none.\n */\n const feedUrl = host\n ? (() => {\n const origin = hostPublicOrigin(host)\n return origin\n ? `${origin}/api/commerce/feed?hostId=${hostId}`\n : undefined\n })()\n : undefined\n\n // Host screen directory: {screenId: path} (AGL-292 template pick).\n const screenEntries = Object.entries(\n (host?.screens ?? {}) as Record<string, string>,\n ).sort(([, a], [, b]) => String(a).localeCompare(String(b)))\n\n const handleSave = useCallback(async () => {\n /**\n * Refuse a save whose seed the server never confirmed (AGL-1358).\n *\n * There is no create path to exempt, and that is deliberate rather than\n * an oversight: unlike a new row at a fresh uid, this writes a FIXED\n * document path, so the all-defaults `current` produced when the cache\n * has never seen this doc is not the harmless blank a fresh uid would\n * be. It would put `USD`, guest checkout on and two null template ids\n * over whatever the server really holds.\n *\n * The guard WRAPS the write — an early return is a shape you can keep\n * while losing the protection.\n */\n const verdict = await writeGuardedBySeed(\n {\n subject: 'store settings',\n unreadable: storeStatus === 'error',\n fromCache: storeFromCache,\n },\n async () => {\n // The product and collection templates, the currency and the\n // checkout rules render on the live store with no publish step, so\n // the save carries the site's cache drop with it.\n await writeSiteWideChange({\n firestore,\n user,\n hostId,\n write: (batch) =>\n batch.set(\n doc(firestore, 'hosts', hostId, 'settings', 'store'),\n {\n pdpScreenId: current.pdpScreenId || null,\n collectionScreenId: current.collectionScreenId || null,\n currency: current.currency ?? 'USD',\n guestCheckout: current.guestCheckout !== false,\n termsUrl: current.termsUrl || null,\n receiptFooter: current.receiptFooter || null,\n },\n { merge: true },\n ),\n })\n },\n )\n // Before `setDraft(null)`, so a refusal keeps every typed value on\n // screen. A save that silently does nothing sends the user back to\n // retype a form that will be refused again just as quietly.\n if (!verdict.ok) {\n return void enqueueSnackbar(verdict.message, {\n variant: 'warning',\n persist: false,\n })\n }\n setDraft(null)\n enqueueSnackbar('Store settings saved', {\n variant: 'success',\n persist: false,\n })\n }, [\n current,\n firestore,\n hostId,\n user,\n enqueueSnackbar,\n storeStatus,\n storeFromCache,\n ])\n\n return (\n <CardDisplay\n header={'Store settings'}\n help={pluginDocsHelp('commerce')}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1.5}>\n <TextField\n label=\"Product page template\"\n value={current.pdpScreenId ?? ''}\n onChange={(event) => update({ pdpScreenId: event.target.value })}\n size=\"small\"\n select\n helperText={\n 'Page rendered at /products/{slug} — design it in the ' +\n 'Besigner with the Product detail block and {{product.*}} tokens.'\n }\n >\n <MenuItem value=\"\">{'None (product URLs 404)'}</MenuItem>\n {screenEntries.map(([screenId, path]) => (\n <MenuItem key={screenId} value={screenId}>\n {`/${path === '/' ? '' : path}`}\n </MenuItem>\n ))}\n </TextField>\n <TextField\n label=\"Collection page template\"\n value={current.collectionScreenId ?? ''}\n onChange={(event) =>\n update({ collectionScreenId: event.target.value })\n }\n size=\"small\"\n select\n helperText={\n 'Page rendered at /collections/{slug} — drop a Product grid ' +\n 'with source \"A collection\" and {{collection.*}} tokens.'\n }\n >\n <MenuItem value=\"\">{'None (collection URLs 404)'}</MenuItem>\n {screenEntries.map(([screenId, path]) => (\n <MenuItem key={screenId} value={screenId}>\n {`/${path === '/' ? '' : path}`}\n </MenuItem>\n ))}\n </TextField>\n <TextField\n label=\"Currency\"\n value={current.currency ?? 'USD'}\n onChange={(event) => update({ currency: event.target.value })}\n size=\"small\"\n select\n sx={{ maxWidth: 200 }}\n helperText=\"Charges settle in USD; more currencies are coming\"\n >\n <MenuItem value=\"USD\">{'USD ($)'}</MenuItem>\n </TextField>\n <FormControlLabel\n control={\n <Switch\n size=\"small\"\n checked={current.guestCheckout !== false}\n onChange={(event) =>\n update({ guestCheckout: event.target.checked })\n }\n />\n }\n label=\"Allow guest checkout (no account required)\"\n />\n <TextField\n label=\"Terms URL\"\n value={current.termsUrl ?? ''}\n onChange={(event) => update({ termsUrl: event.target.value })}\n size=\"small\"\n placeholder=\"/terms\"\n helperText=\"Linked from checkout when set\"\n />\n <TextField\n label=\"Receipt footer\"\n value={current.receiptFooter ?? ''}\n onChange={(event) => update({ receiptFooter: event.target.value })}\n size=\"small\"\n multiline\n minRows={2}\n placeholder=\"Thanks for supporting our shop!\"\n />\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Password protection and maintenance mode live under Site ' +\n 'Protection.'}\n </Typography>\n <Button\n variant=\"contained\"\n color=\"primary\"\n size=\"small\"\n disabled={!draft}\n onClick={handleSave}\n sx={{ alignSelf: 'flex-start' }}\n >\n {'Save store settings'}\n </Button>\n <Stack spacing={0.5}>\n <Typography variant=\"subtitle2\">{'Product feed'}</Typography>\n {feedUrl ? (\n <>\n <TextField\n label=\"Google Merchant Center feed URL\"\n value={feedUrl}\n size=\"small\"\n slotProps={{ input: { readOnly: true } }}\n onFocus={(event) => event.target.select()}\n helperText={\n 'Paste this into Merchant Center → Products → Feeds as a ' +\n 'scheduled fetch. It lists every active product with price ' +\n 'and availability, and refreshes itself.'\n }\n />\n <Button\n size=\"small\"\n sx={{ alignSelf: 'flex-start' }}\n onClick={() => {\n void navigator.clipboard?.writeText(feedUrl).then(() =>\n enqueueSnackbar('Feed URL copied', {\n variant: 'success',\n persist: false,\n }),\n )\n }}\n >\n {'Copy feed URL'}\n </Button>\n </>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Publish this site to a subdomain or custom domain to get its ' +\n 'Google Merchant Center feed URL.'}\n </Typography>\n )}\n </Stack>\n </Stack>\n </CardDisplay>\n )\n}\nStoreSettingsCard.displayName = 'StoreSettingsCard'\n\nexport default StoreSettingsCard\n"],"names":["CardDisplay","useSnackbar","Button","FormControlLabel","MenuItem","Stack","Switch","TextField","Typography","doc","useCallback","useState","useFirestore","useUser","useFirestoreDoc","writeGuardedBySeed","hostPublicOrigin","pluginDocsHelp","writeSiteWideChange","StoreSettingsCard","props","current","hostId","firestore","data","user","enqueueSnackbar","store","status","storeStatus","fromCache","storeFromCache","host","draft","setDraft","pdpScreenId","collectionScreenId","currency","guestCheckout","termsUrl","receiptFooter","update","patch","feedUrl","origin","undefined","screenEntries","Object","entries","screens","sort","a","b","String","localeCompare","handleSave","verdict","subject","unreadable","write","batch","set","merge","ok","message","variant","persist","header","help","contentGutterX","contentGutterY","spacing","label","value","onChange","event","target","size","select","helperText","map","screenId","path","sx","maxWidth","control","checked","placeholder","multiline","minRows","color","disabled","onClick","alignSelf","slotProps","input","readOnly","onFocus","navigator","clipboard","writeText","then","displayName"],"mappings":"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"}
@@ -112,7 +112,7 @@ const usd = (cents)=>`$${(cents / 100).toFixed(2)}`;
112
112
  from: selected.from,
113
113
  to: selected.to
114
114
  });
115
- const response = await authorizedFetch(user, `/api/hosts/tax-summary?${search.toString()}`);
115
+ const response = await authorizedFetch(user, `/api/commerce/tax-summary?${search.toString()}`);
116
116
  if (!response.ok) throw new Error('tax summary failed');
117
117
  return await response.json();
118
118
  }, [
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/storefront-tax-summary-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use client'\n\nimport { PLATFORM_BRAND_NAME } from '@aglyn/aglyn/app-utils/platform-brand'\n\nimport { pluginDocsHelp } from '@aglyn/aglyn'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { ScrollTable } from '@aglyn/shared-ui-jsx/components/scroll-table.component'\nimport {\n Alert,\n Box,\n Button,\n Divider,\n MenuItem,\n Stack,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\n\nexport interface StorefrontTaxSummaryCardProps {\n hostId: string\n}\n\nconst usd = (cents: number) => `$${(cents / 100).toFixed(2)}`\n\ninterface Jurisdiction {\n transactionCount: number\n totalSalesCents: number\n taxableSalesCents: number\n taxCollectedCents: number\n}\n\ninterface Bucket {\n transactionCount: number\n grossCents: number\n taxableSalesCents: number\n taxCollectedCents: number\n byJurisdiction: Record<string, Jurisdiction>\n}\n\ninterface Summary {\n periodStart: string\n periodEnd: string\n transactionCount: number\n aglynLiable: Bucket\n merchantManual: Bucket\n connectedAccountLiable: Bucket\n attention: {\n rowsMissingTaxableBase: number\n rowsMissingAddress: number\n nonUsdRows: number\n rowsMissingPaidAt: number\n rowsUnclassified: number\n }\n}\n\ninterface Payload {\n summary: Summary\n truncated: boolean\n undatedRows: number\n caveats: { refundsNotReflected: boolean }\n}\n\n/** The last twelve calendar months, newest first, as UTC month bounds. */\nfunction monthOptions(): Array<{ value: string; label: string; from: string; to: string }> {\n const now = new Date()\n return Array.from({ length: 12 }, (_item, index) => {\n const start = new Date(\n Date.UTC(now.getUTCFullYear(), now.getUTCMonth() - index, 1),\n )\n const end = new Date(\n Date.UTC(now.getUTCFullYear(), now.getUTCMonth() - index + 1, 1),\n )\n return {\n value: start.toISOString(),\n label: start.toLocaleDateString(undefined, {\n month: 'long',\n year: 'numeric',\n timeZone: 'UTC',\n }),\n from: start.toISOString(),\n to: end.toISOString(),\n }\n })\n}\n\n/**\n * The sales tax this merchant's own storefront collected (AGL-2440).\n *\n * `storefrontTaxCollected` has recorded every taxed storefront sale since\n * AGL-1904 and every reader of it was Aglyn's — the staff tax return, the DSAR\n * export. The merchant, whose sales these are, could see one order's\n * `totals.taxCents` at a time and nothing else. This card is that absence\n * closed, and closing it is all it does.\n *\n * ## THE NUMBER, NOT THE LABEL\n *\n * This card presents the buckets SEPARATELY and prints no per-row verdict on\n * who must remit which one.\n *\n * The copy may not say that Aglyn makes no determination: Aglyn IS a\n * marketplace facilitator for storefront sales and the Terms publish that at\n * §10.7 (AGL-1956), so such a line would contradict the company's own\n * contract.\n *\n * Separate buckets and no per-row verdict still hold, because §10.7 is\n * narrow: it binds Aglyn only where applicable law gives it a collection\n * obligation for the transaction in question, which is a per-sale,\n * per-jurisdiction test and not a fact about a merchant. So the copy still\n * describes HOW each figure was computed — who calculated the rate, and\n * against whose registration — and adds only what §10.7 itself says about the\n * bucket Aglyn holds. There is still no \"yours to remit\" column and no total\n * across the buckets. A merged \"tax collected\" figure would be the\n * banned determination wearing a neutral name: it would sum tax Aglyn holds\n * and remits with tax the merchant owes, the exact conflation the module warns\n * about in bold — *the two store modes are DIFFERENT FACTS and must never be\n * summed*. That is why the three groups never share a total row, and why the\n * grand total in the header is a TRANSACTION COUNT rather than an amount.\n *\n * ## Why the caveats are on the card and not in a tooltip\n *\n * Both are properties of the DATA, not of the law, and both change what a\n * merchant should do with the number: refunds are not yet reflected, so the\n * figure over-states whenever they have refunded; and a row whose taxable base\n * Stripe did not state is counted out loud rather than treated as a zero-base\n * sale. A figure someone may file from cannot carry its completeness caveats\n * behind a hover.\n *\n * ## Why it is route-backed and not a listener\n *\n * `storefrontTaxCollected` is denied to every client in the rules — the\n * collection spans every merchant and a row carries a shopper's address. The\n * server route applies the `hostId` boundary. Nothing here can read another\n * merchant's sales because nothing here reads the collection at all.\n */\nexport function StorefrontTaxSummaryCard(props: StorefrontTaxSummaryCardProps) {\n const { hostId } = props\n const { data: user } = useUser()\n const months = useMemo(() => monthOptions(), [])\n const [period, setPeriod] = useState(months[0].value)\n const [payload, setPayload] = useState<Payload | null>(null)\n /**\n * Three outcomes, not two (the AGL-1380 rule). \"No tax collected\" is a claim\n * about this merchant's sales; rendering it because a request FAILED would\n * tell a merchant who collected tax that they collected none — on the one\n * surface where that mistake could reach a filing.\n */\n const [loadState, setLoadState] = useState<'pending' | 'error' | 'loaded'>(\n 'pending',\n )\n const [retryNonce, setRetryNonce] = useState(0)\n\n const selected = months.find((month) => month.value === period) ?? months[0]\n\n const load = useCallback(async () => {\n const search = new URLSearchParams({\n hostId,\n from: selected.from,\n to: selected.to,\n })\n const response = await authorizedFetch(\n user,\n `/api/hosts/tax-summary?${search.toString()}`,\n )\n if (!response.ok) throw new Error('tax summary failed')\n return (await response.json()) as Payload\n }, [user, hostId, selected.from, selected.to])\n\n useEffect(() => {\n if (!user) return\n let cancelled = false\n setLoadState('pending')\n load()\n .then((next) => {\n if (cancelled) return\n setPayload(next)\n setLoadState('loaded')\n })\n .catch(() => {\n if (!cancelled) setLoadState('error')\n })\n return () => {\n cancelled = true\n }\n }, [load, user, retryNonce])\n\n const groups = payload\n ? ([\n {\n key: 'aglynLiable',\n bucket: payload.summary.aglynLiable,\n title: `Tax Stripe calculated against ${PLATFORM_BRAND_NAME}’s registrations`,\n how:\n 'Automatic tax was on for these sales. Stripe computed the rate ' +\n `against the tax registrations on ${PLATFORM_BRAND_NAME}’s platform ` +\n `account, and ${PLATFORM_BRAND_NAME} holds what was collected.`,\n },\n {\n key: 'merchantManual',\n bucket: payload.summary.merchantManual,\n title: 'Tax your store calculated at your own rate',\n how:\n 'Automatic tax was off for these sales. The rate came from the ' +\n 'tax settings you configured on this store, applied to your own ' +\n 'declared origin.',\n },\n {\n key: 'connectedAccountLiable',\n bucket: payload.summary.connectedAccountLiable,\n title: 'Tax Stripe calculated against your connected account',\n how:\n 'Automatic tax was on and Stripe named your connected account ' +\n 'as the liable party for these sales.',\n },\n ] as const)\n : []\n\n return (\n <CardDisplay\n header={'Storefront sales tax'}\n subheader={'What your storefront collected, grouped by how it was calculated.'}\n help={pluginDocsHelp('commerce', {\n anchor: '#storefront-sales-tax',\n excerpt:\n 'What your storefront collected in sales tax, grouped by how each ' +\n `figure was calculated. ${PLATFORM_BRAND_NAME} does not provide tax advice.`,\n })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={2}>\n <TextField\n select\n size=\"small\"\n label=\"Period\"\n value={period}\n onChange={(event) => setPeriod(event.target.value)}\n sx={{ maxWidth: '16rem' }}\n >\n {months.map((month) => (\n <MenuItem key={month.value} value={month.value}>\n {month.label}\n </MenuItem>\n ))}\n </TextField>\n\n {loadState === 'pending' ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n Loading your sales tax…\n </Typography>\n ) : loadState === 'error' || !payload ? (\n <Alert\n severity=\"warning\"\n action={\n <Button\n size=\"small\"\n color=\"inherit\"\n onClick={() => setRetryNonce((nonce) => nonce + 1)}\n >\n Retry\n </Button>\n }\n >\n We couldn’t load your sales tax for this period. This figure is\n blank because the request failed, not because no tax was collected.\n </Alert>\n ) : (\n <>\n {/*\n THE NON-ADVISORY NOTE, first and unmissable. Rendered ABOVE the\n numbers because it governs how all of them should be read.\n\n It now states the position Terms §10.7 publishes (AGL-1956)\n instead of the sentence it used to carry — \"Aglyn does not make\n that determination for you\" — which the Terms themselves have\n contradicted since 2026-08-24. What it still does NOT do is\n reach a per-merchant or per-jurisdiction verdict: §10.7 binds\n Aglyn only where applicable law gives it an obligation, so the\n note describes which bucket Aglyn holds and remits and leaves\n the rest where the merchant's own rate put it.\n */}\n <Alert severity=\"info\">\n These figures show what was collected and how each was\n calculated. For storefront sales {PLATFORM_BRAND_NAME} acts as a\n marketplace facilitator: where applicable law gives{' '}\n {PLATFORM_BRAND_NAME} a collection obligation, the tax it\n calculated is added on top at checkout, held by{' '}\n {PLATFORM_BRAND_NAME} rather than transferred to your connected\n payment account, and remitted by {PLATFORM_BRAND_NAME}. Tax\n calculated at a rate you configured yourself stays yours. See\n Terms of Service §10.7.{' '}\n <strong>\n {PLATFORM_BRAND_NAME} does not provide tax advice.\n </strong>{' '}\n Please confirm your obligations with a qualified tax\n professional.\n </Alert>\n\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`${payload.summary.transactionCount} taxed ${\n payload.summary.transactionCount === 1 ? 'sale' : 'sales'\n } in this period.`}\n </Typography>\n\n {payload.summary.transactionCount === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n No storefront sale carried tax in this period.\n </Typography>\n ) : null}\n\n {groups\n .filter((group) => group.bucket.transactionCount > 0)\n .map((group) => (\n <Box key={group.key}>\n <Divider sx={{ mb: 1.5 }} />\n <Typography variant=\"subtitle2\">{group.title}</Typography>\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n component=\"div\"\n sx={{ mb: 1 }}\n >\n {group.how}\n </Typography>\n <Stack\n direction=\"row\"\n spacing={3}\n sx={{ flexWrap: 'wrap', rowGap: 1, mb: 1 }}\n >\n <Box>\n <Typography variant=\"h6\">\n {usd(group.bucket.taxCollectedCents)}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n tax collected\n </Typography>\n </Box>\n <Box>\n <Typography variant=\"h6\">\n {usd(group.bucket.grossCents)}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n gross, tax included\n </Typography>\n </Box>\n <Box>\n <Typography variant=\"h6\">\n {group.bucket.transactionCount}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {group.bucket.transactionCount === 1\n ? 'sale'\n : 'sales'}\n </Typography>\n </Box>\n </Stack>\n <ScrollTable size=\"small\">\n <TableHead>\n <TableRow>\n <TableCell>{'Jurisdiction'}</TableCell>\n <TableCell align=\"right\">{'Sales'}</TableCell>\n <TableCell align=\"right\">{'Taxable base'}</TableCell>\n <TableCell align=\"right\">{'Tax'}</TableCell>\n </TableRow>\n </TableHead>\n <TableBody>\n {Object.entries(\n group.bucket.byJurisdiction as Record<\n string,\n Jurisdiction\n >,\n ).map(([code, jurisdiction]) => (\n <TableRow key={code}>\n <TableCell>\n {code === 'unknown' ? 'Not stated' : code}\n </TableCell>\n <TableCell align=\"right\">\n {usd(jurisdiction.totalSalesCents)}\n </TableCell>\n <TableCell align=\"right\">\n {usd(jurisdiction.taxableSalesCents)}\n </TableCell>\n <TableCell align=\"right\">\n {usd(jurisdiction.taxCollectedCents)}\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </ScrollTable>\n </Box>\n ))}\n\n {/*\n WHAT THE NUMBER DOES NOT YET INCLUDE. Facts about the data, on\n the card rather than behind a hover, because each one changes\n what a merchant should do with the figure.\n */}\n <Divider />\n <Stack spacing={0.5}>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Refunds are not reflected. A refunded sale keeps its full ' +\n 'tax here, so these figures over-state whenever you have ' +\n 'refunded in this period.'}\n </Typography>\n {payload.summary.attention.rowsMissingTaxableBase > 0 ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`${payload.summary.attention.rowsMissingTaxableBase} sale(s) collected tax but Stripe did not state the amount the rate was applied to. Their tax is counted; their taxable base is not, so “Taxable base” above is understated.`}\n </Typography>\n ) : null}\n {payload.summary.attention.rowsMissingAddress > 0 ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`${payload.summary.attention.rowsMissingAddress} sale(s) carried no customer address and are grouped under “Not stated”.`}\n </Typography>\n ) : null}\n {payload.summary.attention.nonUsdRows > 0 ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`${payload.summary.attention.nonUsdRows} sale(s) were in a currency other than USD and are summed here without conversion.`}\n </Typography>\n ) : null}\n {payload.summary.attention.rowsUnclassified > 0 ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`${payload.summary.attention.rowsUnclassified} sale(s) could not be classified and are excluded from every group above rather than guessed into one.`}\n </Typography>\n ) : null}\n {payload.undatedRows > 0 ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`${payload.undatedRows} recorded sale(s) carry no payment date and fall outside every period, including this one.`}\n </Typography>\n ) : null}\n {payload.truncated ? (\n <Typography variant=\"caption\" color=\"warning.main\">\n {'This period has more sales than one report can read. The ' +\n 'figures above are PARTIAL — narrow the period.'}\n </Typography>\n ) : null}\n </Stack>\n </>\n )}\n </Stack>\n </CardDisplay>\n )\n}\nStorefrontTaxSummaryCard.displayName = 'StorefrontTaxSummaryCard'\n\nexport default StorefrontTaxSummaryCard\n"],"names":["PLATFORM_BRAND_NAME","pluginDocsHelp","CardDisplay","ScrollTable","Alert","Box","Button","Divider","MenuItem","Stack","TableBody","TableCell","TableHead","TableRow","TextField","Typography","useCallback","useEffect","useMemo","useState","useUser","authorizedFetch","usd","cents","toFixed","monthOptions","now","Date","Array","from","length","_item","index","start","UTC","getUTCFullYear","getUTCMonth","end","value","toISOString","label","toLocaleDateString","undefined","month","year","timeZone","to","StorefrontTaxSummaryCard","props","months","hostId","data","user","period","setPeriod","payload","setPayload","loadState","setLoadState","retryNonce","setRetryNonce","selected","find","load","search","URLSearchParams","response","toString","ok","Error","json","cancelled","then","next","catch","groups","key","bucket","summary","aglynLiable","title","how","merchantManual","connectedAccountLiable","header","subheader","help","anchor","excerpt","contentGutterX","contentGutterY","spacing","select","size","onChange","event","target","sx","maxWidth","map","variant","color","severity","action","onClick","nonce","strong","transactionCount","filter","group","mb","component","direction","flexWrap","rowGap","taxCollectedCents","grossCents","align","Object","entries","byJurisdiction","code","jurisdiction","totalSalesCents","taxableSalesCents","attention","rowsMissingTaxableBase","rowsMissingAddress","nonUsdRows","rowsUnclassified","undatedRows","truncated","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/storefront-tax-summary-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use client'\n\nimport { PLATFORM_BRAND_NAME } from '@aglyn/aglyn/app-utils/platform-brand'\n\nimport { pluginDocsHelp } from '@aglyn/aglyn'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { ScrollTable } from '@aglyn/shared-ui-jsx/components/scroll-table.component'\nimport {\n Alert,\n Box,\n Button,\n Divider,\n MenuItem,\n Stack,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\n\nexport interface StorefrontTaxSummaryCardProps {\n hostId: string\n}\n\nconst usd = (cents: number) => `$${(cents / 100).toFixed(2)}`\n\ninterface Jurisdiction {\n transactionCount: number\n totalSalesCents: number\n taxableSalesCents: number\n taxCollectedCents: number\n}\n\ninterface Bucket {\n transactionCount: number\n grossCents: number\n taxableSalesCents: number\n taxCollectedCents: number\n byJurisdiction: Record<string, Jurisdiction>\n}\n\ninterface Summary {\n periodStart: string\n periodEnd: string\n transactionCount: number\n aglynLiable: Bucket\n merchantManual: Bucket\n connectedAccountLiable: Bucket\n attention: {\n rowsMissingTaxableBase: number\n rowsMissingAddress: number\n nonUsdRows: number\n rowsMissingPaidAt: number\n rowsUnclassified: number\n }\n}\n\ninterface Payload {\n summary: Summary\n truncated: boolean\n undatedRows: number\n caveats: { refundsNotReflected: boolean }\n}\n\n/** The last twelve calendar months, newest first, as UTC month bounds. */\nfunction monthOptions(): Array<{ value: string; label: string; from: string; to: string }> {\n const now = new Date()\n return Array.from({ length: 12 }, (_item, index) => {\n const start = new Date(\n Date.UTC(now.getUTCFullYear(), now.getUTCMonth() - index, 1),\n )\n const end = new Date(\n Date.UTC(now.getUTCFullYear(), now.getUTCMonth() - index + 1, 1),\n )\n return {\n value: start.toISOString(),\n label: start.toLocaleDateString(undefined, {\n month: 'long',\n year: 'numeric',\n timeZone: 'UTC',\n }),\n from: start.toISOString(),\n to: end.toISOString(),\n }\n })\n}\n\n/**\n * The sales tax this merchant's own storefront collected (AGL-2440).\n *\n * `storefrontTaxCollected` has recorded every taxed storefront sale since\n * AGL-1904 and every reader of it was Aglyn's — the staff tax return, the DSAR\n * export. The merchant, whose sales these are, could see one order's\n * `totals.taxCents` at a time and nothing else. This card is that absence\n * closed, and closing it is all it does.\n *\n * ## THE NUMBER, NOT THE LABEL\n *\n * This card presents the buckets SEPARATELY and prints no per-row verdict on\n * who must remit which one.\n *\n * The copy may not say that Aglyn makes no determination: Aglyn IS a\n * marketplace facilitator for storefront sales and the Terms publish that at\n * §10.7 (AGL-1956), so such a line would contradict the company's own\n * contract.\n *\n * Separate buckets and no per-row verdict still hold, because §10.7 is\n * narrow: it binds Aglyn only where applicable law gives it a collection\n * obligation for the transaction in question, which is a per-sale,\n * per-jurisdiction test and not a fact about a merchant. So the copy still\n * describes HOW each figure was computed — who calculated the rate, and\n * against whose registration — and adds only what §10.7 itself says about the\n * bucket Aglyn holds. There is still no \"yours to remit\" column and no total\n * across the buckets. A merged \"tax collected\" figure would be the\n * banned determination wearing a neutral name: it would sum tax Aglyn holds\n * and remits with tax the merchant owes, the exact conflation the module warns\n * about in bold — *the two store modes are DIFFERENT FACTS and must never be\n * summed*. That is why the three groups never share a total row, and why the\n * grand total in the header is a TRANSACTION COUNT rather than an amount.\n *\n * ## Why the caveats are on the card and not in a tooltip\n *\n * Both are properties of the DATA, not of the law, and both change what a\n * merchant should do with the number: refunds are not yet reflected, so the\n * figure over-states whenever they have refunded; and a row whose taxable base\n * Stripe did not state is counted out loud rather than treated as a zero-base\n * sale. A figure someone may file from cannot carry its completeness caveats\n * behind a hover.\n *\n * ## Why it is route-backed and not a listener\n *\n * `storefrontTaxCollected` is denied to every client in the rules — the\n * collection spans every merchant and a row carries a shopper's address. The\n * server route applies the `hostId` boundary. Nothing here can read another\n * merchant's sales because nothing here reads the collection at all.\n */\nexport function StorefrontTaxSummaryCard(props: StorefrontTaxSummaryCardProps) {\n const { hostId } = props\n const { data: user } = useUser()\n const months = useMemo(() => monthOptions(), [])\n const [period, setPeriod] = useState(months[0].value)\n const [payload, setPayload] = useState<Payload | null>(null)\n /**\n * Three outcomes, not two (the AGL-1380 rule). \"No tax collected\" is a claim\n * about this merchant's sales; rendering it because a request FAILED would\n * tell a merchant who collected tax that they collected none — on the one\n * surface where that mistake could reach a filing.\n */\n const [loadState, setLoadState] = useState<'pending' | 'error' | 'loaded'>(\n 'pending',\n )\n const [retryNonce, setRetryNonce] = useState(0)\n\n const selected = months.find((month) => month.value === period) ?? months[0]\n\n const load = useCallback(async () => {\n const search = new URLSearchParams({\n hostId,\n from: selected.from,\n to: selected.to,\n })\n const response = await authorizedFetch(\n user,\n `/api/commerce/tax-summary?${search.toString()}`,\n )\n if (!response.ok) throw new Error('tax summary failed')\n return (await response.json()) as Payload\n }, [user, hostId, selected.from, selected.to])\n\n useEffect(() => {\n if (!user) return\n let cancelled = false\n setLoadState('pending')\n load()\n .then((next) => {\n if (cancelled) return\n setPayload(next)\n setLoadState('loaded')\n })\n .catch(() => {\n if (!cancelled) setLoadState('error')\n })\n return () => {\n cancelled = true\n }\n }, [load, user, retryNonce])\n\n const groups = payload\n ? ([\n {\n key: 'aglynLiable',\n bucket: payload.summary.aglynLiable,\n title: `Tax Stripe calculated against ${PLATFORM_BRAND_NAME}’s registrations`,\n how:\n 'Automatic tax was on for these sales. Stripe computed the rate ' +\n `against the tax registrations on ${PLATFORM_BRAND_NAME}’s platform ` +\n `account, and ${PLATFORM_BRAND_NAME} holds what was collected.`,\n },\n {\n key: 'merchantManual',\n bucket: payload.summary.merchantManual,\n title: 'Tax your store calculated at your own rate',\n how:\n 'Automatic tax was off for these sales. The rate came from the ' +\n 'tax settings you configured on this store, applied to your own ' +\n 'declared origin.',\n },\n {\n key: 'connectedAccountLiable',\n bucket: payload.summary.connectedAccountLiable,\n title: 'Tax Stripe calculated against your connected account',\n how:\n 'Automatic tax was on and Stripe named your connected account ' +\n 'as the liable party for these sales.',\n },\n ] as const)\n : []\n\n return (\n <CardDisplay\n header={'Storefront sales tax'}\n subheader={'What your storefront collected, grouped by how it was calculated.'}\n help={pluginDocsHelp('commerce', {\n anchor: '#storefront-sales-tax',\n excerpt:\n 'What your storefront collected in sales tax, grouped by how each ' +\n `figure was calculated. ${PLATFORM_BRAND_NAME} does not provide tax advice.`,\n })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={2}>\n <TextField\n select\n size=\"small\"\n label=\"Period\"\n value={period}\n onChange={(event) => setPeriod(event.target.value)}\n sx={{ maxWidth: '16rem' }}\n >\n {months.map((month) => (\n <MenuItem key={month.value} value={month.value}>\n {month.label}\n </MenuItem>\n ))}\n </TextField>\n\n {loadState === 'pending' ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n Loading your sales tax…\n </Typography>\n ) : loadState === 'error' || !payload ? (\n <Alert\n severity=\"warning\"\n action={\n <Button\n size=\"small\"\n color=\"inherit\"\n onClick={() => setRetryNonce((nonce) => nonce + 1)}\n >\n Retry\n </Button>\n }\n >\n We couldn’t load your sales tax for this period. This figure is\n blank because the request failed, not because no tax was collected.\n </Alert>\n ) : (\n <>\n {/*\n THE NON-ADVISORY NOTE, first and unmissable. Rendered ABOVE the\n numbers because it governs how all of them should be read.\n\n It now states the position Terms §10.7 publishes (AGL-1956)\n instead of the sentence it used to carry — \"Aglyn does not make\n that determination for you\" — which the Terms themselves have\n contradicted since 2026-08-24. What it still does NOT do is\n reach a per-merchant or per-jurisdiction verdict: §10.7 binds\n Aglyn only where applicable law gives it an obligation, so the\n note describes which bucket Aglyn holds and remits and leaves\n the rest where the merchant's own rate put it.\n */}\n <Alert severity=\"info\">\n These figures show what was collected and how each was\n calculated. For storefront sales {PLATFORM_BRAND_NAME} acts as a\n marketplace facilitator: where applicable law gives{' '}\n {PLATFORM_BRAND_NAME} a collection obligation, the tax it\n calculated is added on top at checkout, held by{' '}\n {PLATFORM_BRAND_NAME} rather than transferred to your connected\n payment account, and remitted by {PLATFORM_BRAND_NAME}. Tax\n calculated at a rate you configured yourself stays yours. See\n Terms of Service §10.7.{' '}\n <strong>\n {PLATFORM_BRAND_NAME} does not provide tax advice.\n </strong>{' '}\n Please confirm your obligations with a qualified tax\n professional.\n </Alert>\n\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`${payload.summary.transactionCount} taxed ${\n payload.summary.transactionCount === 1 ? 'sale' : 'sales'\n } in this period.`}\n </Typography>\n\n {payload.summary.transactionCount === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n No storefront sale carried tax in this period.\n </Typography>\n ) : null}\n\n {groups\n .filter((group) => group.bucket.transactionCount > 0)\n .map((group) => (\n <Box key={group.key}>\n <Divider sx={{ mb: 1.5 }} />\n <Typography variant=\"subtitle2\">{group.title}</Typography>\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n component=\"div\"\n sx={{ mb: 1 }}\n >\n {group.how}\n </Typography>\n <Stack\n direction=\"row\"\n spacing={3}\n sx={{ flexWrap: 'wrap', rowGap: 1, mb: 1 }}\n >\n <Box>\n <Typography variant=\"h6\">\n {usd(group.bucket.taxCollectedCents)}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n tax collected\n </Typography>\n </Box>\n <Box>\n <Typography variant=\"h6\">\n {usd(group.bucket.grossCents)}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n gross, tax included\n </Typography>\n </Box>\n <Box>\n <Typography variant=\"h6\">\n {group.bucket.transactionCount}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {group.bucket.transactionCount === 1\n ? 'sale'\n : 'sales'}\n </Typography>\n </Box>\n </Stack>\n <ScrollTable size=\"small\">\n <TableHead>\n <TableRow>\n <TableCell>{'Jurisdiction'}</TableCell>\n <TableCell align=\"right\">{'Sales'}</TableCell>\n <TableCell align=\"right\">{'Taxable base'}</TableCell>\n <TableCell align=\"right\">{'Tax'}</TableCell>\n </TableRow>\n </TableHead>\n <TableBody>\n {Object.entries(\n group.bucket.byJurisdiction as Record<\n string,\n Jurisdiction\n >,\n ).map(([code, jurisdiction]) => (\n <TableRow key={code}>\n <TableCell>\n {code === 'unknown' ? 'Not stated' : code}\n </TableCell>\n <TableCell align=\"right\">\n {usd(jurisdiction.totalSalesCents)}\n </TableCell>\n <TableCell align=\"right\">\n {usd(jurisdiction.taxableSalesCents)}\n </TableCell>\n <TableCell align=\"right\">\n {usd(jurisdiction.taxCollectedCents)}\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </ScrollTable>\n </Box>\n ))}\n\n {/*\n WHAT THE NUMBER DOES NOT YET INCLUDE. Facts about the data, on\n the card rather than behind a hover, because each one changes\n what a merchant should do with the figure.\n */}\n <Divider />\n <Stack spacing={0.5}>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Refunds are not reflected. A refunded sale keeps its full ' +\n 'tax here, so these figures over-state whenever you have ' +\n 'refunded in this period.'}\n </Typography>\n {payload.summary.attention.rowsMissingTaxableBase > 0 ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`${payload.summary.attention.rowsMissingTaxableBase} sale(s) collected tax but Stripe did not state the amount the rate was applied to. Their tax is counted; their taxable base is not, so “Taxable base” above is understated.`}\n </Typography>\n ) : null}\n {payload.summary.attention.rowsMissingAddress > 0 ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`${payload.summary.attention.rowsMissingAddress} sale(s) carried no customer address and are grouped under “Not stated”.`}\n </Typography>\n ) : null}\n {payload.summary.attention.nonUsdRows > 0 ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`${payload.summary.attention.nonUsdRows} sale(s) were in a currency other than USD and are summed here without conversion.`}\n </Typography>\n ) : null}\n {payload.summary.attention.rowsUnclassified > 0 ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`${payload.summary.attention.rowsUnclassified} sale(s) could not be classified and are excluded from every group above rather than guessed into one.`}\n </Typography>\n ) : null}\n {payload.undatedRows > 0 ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`${payload.undatedRows} recorded sale(s) carry no payment date and fall outside every period, including this one.`}\n </Typography>\n ) : null}\n {payload.truncated ? (\n <Typography variant=\"caption\" color=\"warning.main\">\n {'This period has more sales than one report can read. The ' +\n 'figures above are PARTIAL — narrow the period.'}\n </Typography>\n ) : null}\n </Stack>\n </>\n )}\n </Stack>\n </CardDisplay>\n )\n}\nStorefrontTaxSummaryCard.displayName = 'StorefrontTaxSummaryCard'\n\nexport default StorefrontTaxSummaryCard\n"],"names":["PLATFORM_BRAND_NAME","pluginDocsHelp","CardDisplay","ScrollTable","Alert","Box","Button","Divider","MenuItem","Stack","TableBody","TableCell","TableHead","TableRow","TextField","Typography","useCallback","useEffect","useMemo","useState","useUser","authorizedFetch","usd","cents","toFixed","monthOptions","now","Date","Array","from","length","_item","index","start","UTC","getUTCFullYear","getUTCMonth","end","value","toISOString","label","toLocaleDateString","undefined","month","year","timeZone","to","StorefrontTaxSummaryCard","props","months","hostId","data","user","period","setPeriod","payload","setPayload","loadState","setLoadState","retryNonce","setRetryNonce","selected","find","load","search","URLSearchParams","response","toString","ok","Error","json","cancelled","then","next","catch","groups","key","bucket","summary","aglynLiable","title","how","merchantManual","connectedAccountLiable","header","subheader","help","anchor","excerpt","contentGutterX","contentGutterY","spacing","select","size","onChange","event","target","sx","maxWidth","map","variant","color","severity","action","onClick","nonce","strong","transactionCount","filter","group","mb","component","direction","flexWrap","rowGap","taxCollectedCents","grossCents","align","Object","entries","byJurisdiction","code","jurisdiction","totalSalesCents","taxableSalesCents","attention","rowsMissingTaxableBase","rowsMissingAddress","nonUsdRows","rowsUnclassified","undatedRows","truncated","displayName"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;AAEA,SAASA,mBAAmB,QAAQ,wCAAuC;AAE3E,SAASC,cAAc,QAAQ,eAAc;AAC7C,SAASC,WAAW,QAAQ,uBAAsB;AAClD,SAASC,WAAW,QAAQ,yDAAwD;AACpF,SACEC,KAAK,EACLC,GAAG,EACHC,MAAM,EACNC,OAAO,EACPC,QAAQ,EACRC,KAAK,EACLC,SAAS,EACTC,SAAS,EACTC,SAAS,EACTC,QAAQ,EACRC,SAAS,EACTC,UAAU,QACL,gBAAe;AACtB,SAASC,WAAW,EAAEC,SAAS,EAAEC,OAAO,EAAEC,QAAQ,QAAQ,QAAO;AACjE,SAASC,OAAO,QAAQ,iCAAgC;AACxD,SAASC,eAAe,QAAQ,2CAA0C;AAM1E,MAAMC,MAAM,CAACC,QAAkB,CAAC,CAAC,EAAE,AAACA,CAAAA,QAAQ,GAAE,EAAGC,OAAO,CAAC,IAAI;AAwC7D,wEAAwE,GACxE,SAASC;IACP,MAAMC,MAAM,IAAIC;IAChB,OAAOC,MAAMC,IAAI,CAAC;QAAEC,QAAQ;IAAG,GAAG,CAACC,OAAOC;QACxC,MAAMC,QAAQ,IAAIN,KAChBA,KAAKO,GAAG,CAACR,IAAIS,cAAc,IAAIT,IAAIU,WAAW,KAAKJ,OAAO;QAE5D,MAAMK,MAAM,IAAIV,KACdA,KAAKO,GAAG,CAACR,IAAIS,cAAc,IAAIT,IAAIU,WAAW,KAAKJ,QAAQ,GAAG;QAEhE,OAAO;YACLM,OAAOL,MAAMM,WAAW;YACxBC,OAAOP,MAAMQ,kBAAkB,CAACC,WAAW;gBACzCC,OAAO;gBACPC,MAAM;gBACNC,UAAU;YACZ;YACAhB,MAAMI,MAAMM,WAAW;YACvBO,IAAIT,IAAIE,WAAW;QACrB;IACF;AACF;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgDC,GACD,OAAO,SAASQ,yBAAyBC,KAAoC;QAiB1DC;IAhBjB,MAAM,EAAEC,MAAM,EAAE,GAAGF;IACnB,MAAM,EAAEG,MAAMC,IAAI,EAAE,GAAGhC;IACvB,MAAM6B,SAAS/B,QAAQ,IAAMO,gBAAgB,EAAE;IAC/C,MAAM,CAAC4B,QAAQC,UAAU,GAAGnC,SAAS8B,MAAM,CAAC,EAAE,CAACX,KAAK;IACpD,MAAM,CAACiB,SAASC,WAAW,GAAGrC,SAAyB;IACvD;;;;;GAKC,GACD,MAAM,CAACsC,WAAWC,aAAa,GAAGvC,SAChC;IAEF,MAAM,CAACwC,YAAYC,cAAc,GAAGzC,SAAS;IAE7C,MAAM0C,YAAWZ,eAAAA,OAAOa,IAAI,CAAC,CAACnB,QAAUA,MAAML,KAAK,KAAKe,mBAAvCJ,eAAkDA,MAAM,CAAC,EAAE;IAE5E,MAAMc,OAAO/C,YAAY;QACvB,MAAMgD,SAAS,IAAIC,gBAAgB;YACjCf;YACArB,MAAMgC,SAAShC,IAAI;YACnBiB,IAAIe,SAASf,EAAE;QACjB;QACA,MAAMoB,WAAW,MAAM7C,gBACrB+B,MACA,CAAC,0BAA0B,EAAEY,OAAOG,QAAQ,IAAI;QAElD,IAAI,CAACD,SAASE,EAAE,EAAE,MAAM,IAAIC,MAAM;QAClC,OAAQ,MAAMH,SAASI,IAAI;IAC7B,GAAG;QAAClB;QAAMF;QAAQW,SAAShC,IAAI;QAAEgC,SAASf,EAAE;KAAC;IAE7C7B,UAAU;QACR,IAAI,CAACmC,MAAM;QACX,IAAImB,YAAY;QAChBb,aAAa;QACbK,OACGS,IAAI,CAAC,CAACC;YACL,IAAIF,WAAW;YACff,WAAWiB;YACXf,aAAa;QACf,GACCgB,KAAK,CAAC;YACL,IAAI,CAACH,WAAWb,aAAa;QAC/B;QACF,OAAO;YACLa,YAAY;QACd;IACF,GAAG;QAACR;QAAMX;QAAMO;KAAW;IAE3B,MAAMgB,SAASpB,UACV;QACC;YACEqB,KAAK;YACLC,QAAQtB,QAAQuB,OAAO,CAACC,WAAW;YACnCC,OAAO,CAAC,8BAA8B,EAAEhF,oBAAoB,gBAAgB,CAAC;YAC7EiF,KACE,oEACA,CAAC,iCAAiC,EAAEjF,oBAAoB,YAAY,CAAC,GACrE,CAAC,aAAa,EAAEA,oBAAoB,0BAA0B,CAAC;QACnE;QACA;YACE4E,KAAK;YACLC,QAAQtB,QAAQuB,OAAO,CAACI,cAAc;YACtCF,OAAO;YACPC,KACE,mEACA,oEACA;QACJ;QACA;YACEL,KAAK;YACLC,QAAQtB,QAAQuB,OAAO,CAACK,sBAAsB;YAC9CH,OAAO;YACPC,KACE,kEACA;QACJ;KACD,GACD,EAAE;IAEN,qBACE,KAAC/E;QACCkF,QAAQ;QACRC,WAAW;QACXC,MAAMrF,eAAe,YAAY;YAC/BsF,QAAQ;YACRC,SACE,sEACA,CAAC,uBAAuB,EAAExF,oBAAoB,6BAA6B,CAAC;QAChF;QACAyF,cAAc;QACdC,cAAc;kBAEd,cAAA,MAACjF;YAAMkF,SAAS;;8BACd,KAAC7E;oBACC8E,MAAM;oBACNC,MAAK;oBACLrD,OAAM;oBACNF,OAAOe;oBACPyC,UAAU,CAACC,QAAUzC,UAAUyC,MAAMC,MAAM,CAAC1D,KAAK;oBACjD2D,IAAI;wBAAEC,UAAU;oBAAQ;8BAEvBjD,OAAOkD,GAAG,CAAC,CAACxD,sBACX,KAACnC;4BAA2B8B,OAAOK,MAAML,KAAK;sCAC3CK,MAAMH,KAAK;2BADCG,MAAML,KAAK;;gBAM7BmB,cAAc,0BACb,KAAC1C;oBAAWqF,SAAQ;oBAAQC,OAAM;8BAAiB;qBAGjD5C,cAAc,WAAW,CAACF,wBAC5B,KAACnD;oBACCkG,UAAS;oBACTC,sBACE,KAACjG;wBACCuF,MAAK;wBACLQ,OAAM;wBACNG,SAAS,IAAM5C,cAAc,CAAC6C,QAAUA,QAAQ;kCACjD;;8BAIJ;mCAKD;;sCAcE,MAACrG;4BAAMkG,UAAS;;gCAAO;gCAEatG;gCAAoB;gCACF;gCACnDA;gCAAoB;gCAC2B;gCAC/CA;gCAAoB;gCACaA;gCAAoB;gCAE9B;8CACxB,MAAC0G;;wCACE1G;wCAAoB;;;gCACb;gCAAI;;;sCAKhB,KAACe;4BAAWqF,SAAQ;4BAAQC,OAAM;sCAC/B,GAAG9C,QAAQuB,OAAO,CAAC6B,gBAAgB,CAAC,OAAO,EAC1CpD,QAAQuB,OAAO,CAAC6B,gBAAgB,KAAK,IAAI,SAAS,QACnD,gBAAgB,CAAC;;wBAGnBpD,QAAQuB,OAAO,CAAC6B,gBAAgB,KAAK,kBACpC,KAAC5F;4BAAWqF,SAAQ;4BAAQC,OAAM;sCAAiB;6BAGjD;wBAEH1B,OACEiC,MAAM,CAAC,CAACC,QAAUA,MAAMhC,MAAM,CAAC8B,gBAAgB,GAAG,GAClDR,GAAG,CAAC,CAACU,sBACJ,MAACxG;;kDACC,KAACE;wCAAQ0F,IAAI;4CAAEa,IAAI;wCAAI;;kDACvB,KAAC/F;wCAAWqF,SAAQ;kDAAaS,MAAM7B,KAAK;;kDAC5C,KAACjE;wCACCqF,SAAQ;wCACRC,OAAM;wCACNU,WAAU;wCACVd,IAAI;4CAAEa,IAAI;wCAAE;kDAEXD,MAAM5B,GAAG;;kDAEZ,MAACxE;wCACCuG,WAAU;wCACVrB,SAAS;wCACTM,IAAI;4CAAEgB,UAAU;4CAAQC,QAAQ;4CAAGJ,IAAI;wCAAE;;0DAEzC,MAACzG;;kEACC,KAACU;wDAAWqF,SAAQ;kEACjB9E,IAAIuF,MAAMhC,MAAM,CAACsC,iBAAiB;;kEAErC,KAACpG;wDAAWqF,SAAQ;wDAAUC,OAAM;kEAAiB;;;;0DAIvD,MAAChG;;kEACC,KAACU;wDAAWqF,SAAQ;kEACjB9E,IAAIuF,MAAMhC,MAAM,CAACuC,UAAU;;kEAE9B,KAACrG;wDAAWqF,SAAQ;wDAAUC,OAAM;kEAAiB;;;;0DAIvD,MAAChG;;kEACC,KAACU;wDAAWqF,SAAQ;kEACjBS,MAAMhC,MAAM,CAAC8B,gBAAgB;;kEAEhC,KAAC5F;wDAAWqF,SAAQ;wDAAUC,OAAM;kEACjCQ,MAAMhC,MAAM,CAAC8B,gBAAgB,KAAK,IAC/B,SACA;;;;;;kDAIV,MAACxG;wCAAY0F,MAAK;;0DAChB,KAACjF;0DACC,cAAA,MAACC;;sEACC,KAACF;sEAAW;;sEACZ,KAACA;4DAAU0G,OAAM;sEAAS;;sEAC1B,KAAC1G;4DAAU0G,OAAM;sEAAS;;sEAC1B,KAAC1G;4DAAU0G,OAAM;sEAAS;;;;;0DAG9B,KAAC3G;0DACE4G,OAAOC,OAAO,CACbV,MAAMhC,MAAM,CAAC2C,cAAc,EAI3BrB,GAAG,CAAC,CAAC,CAACsB,MAAMC,aAAa,iBACvB,MAAC7G;;0EACC,KAACF;0EACE8G,SAAS,YAAY,eAAeA;;0EAEvC,KAAC9G;gEAAU0G,OAAM;0EACd/F,IAAIoG,aAAaC,eAAe;;0EAEnC,KAAChH;gEAAU0G,OAAM;0EACd/F,IAAIoG,aAAaE,iBAAiB;;0EAErC,KAACjH;gEAAU0G,OAAM;0EACd/F,IAAIoG,aAAaP,iBAAiB;;;uDAXxBM;;;;;+BA3DfZ,MAAMjC,GAAG;sCAoFvB,KAACrE;sCACD,MAACE;4BAAMkF,SAAS;;8CACd,KAAC5E;oCAAWqF,SAAQ;oCAAUC,OAAM;8CACjC,+DACC,6DACA;;gCAEH9C,QAAQuB,OAAO,CAAC+C,SAAS,CAACC,sBAAsB,GAAG,kBAClD,KAAC/G;oCAAWqF,SAAQ;oCAAUC,OAAM;8CACjC,GAAG9C,QAAQuB,OAAO,CAAC+C,SAAS,CAACC,sBAAsB,CAAC,4KAA4K,CAAC;qCAElO;gCACHvE,QAAQuB,OAAO,CAAC+C,SAAS,CAACE,kBAAkB,GAAG,kBAC9C,KAAChH;oCAAWqF,SAAQ;oCAAUC,OAAM;8CACjC,GAAG9C,QAAQuB,OAAO,CAAC+C,SAAS,CAACE,kBAAkB,CAAC,wEAAwE,CAAC;qCAE1H;gCACHxE,QAAQuB,OAAO,CAAC+C,SAAS,CAACG,UAAU,GAAG,kBACtC,KAACjH;oCAAWqF,SAAQ;oCAAUC,OAAM;8CACjC,GAAG9C,QAAQuB,OAAO,CAAC+C,SAAS,CAACG,UAAU,CAAC,kFAAkF,CAAC;qCAE5H;gCACHzE,QAAQuB,OAAO,CAAC+C,SAAS,CAACI,gBAAgB,GAAG,kBAC5C,KAAClH;oCAAWqF,SAAQ;oCAAUC,OAAM;8CACjC,GAAG9C,QAAQuB,OAAO,CAAC+C,SAAS,CAACI,gBAAgB,CAAC,sGAAsG,CAAC;qCAEtJ;gCACH1E,QAAQ2E,WAAW,GAAG,kBACrB,KAACnH;oCAAWqF,SAAQ;oCAAUC,OAAM;8CACjC,GAAG9C,QAAQ2E,WAAW,CAAC,0FAA0F,CAAC;qCAEnH;gCACH3E,QAAQ4E,SAAS,iBAChB,KAACpH;oCAAWqF,SAAQ;oCAAUC,OAAM;8CACjC,8DACC;qCAEF;;;;;;;;AAOlB;AACAtD,yBAAyBqF,WAAW,GAAG;AAEvC,eAAerF,yBAAwB"}
@@ -18,7 +18,8 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
18
18
  * limitations under the License.
19
19
  */ import * as Aglyn from "@aglyn/aglyn";
20
20
  import { buildAddToCartParams, buildBeginCheckoutParams, trackEvent, trackEventBeforeNavigation } from "@aglyn/aglyn/app-utils/analytics-events";
21
- import { isPaymentsNotConfigured, storefrontPaymentsNotConfiguredText } from "@aglyn/aglyn/app-utils/payments-configured";
21
+ import { isPaymentsNotConfigured } from "@aglyn/aglyn/app-utils/payments-configured";
22
+ import { storefrontPaymentsNotConfiguredText } from "../constants/storefront-payments.js";
22
23
  import * as CommerceModel from "../model/index.js";
23
24
  import { mdiTagOutline } from "@aglyn/shared-data-mdi";
24
25
  import Alert from "@mui/material/Alert";
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/components/product-detail.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport * as Aglyn from '@aglyn/aglyn'\nimport {\n buildAddToCartParams,\n buildBeginCheckoutParams,\n trackEvent,\n trackEventBeforeNavigation,\n type AnalyticsItem,\n} from '@aglyn/aglyn/app-utils/analytics-events'\nimport {\n isPaymentsNotConfigured,\n storefrontPaymentsNotConfiguredText,\n} from '@aglyn/aglyn/app-utils/payments-configured'\nimport * as CommerceModel from '../model'\nimport { mdiTagOutline } from '@aglyn/shared-data-mdi'\nimport Alert from '@mui/material/Alert'\nimport Box from '@mui/material/Box'\nimport Button from '@mui/material/Button'\nimport Chip from '@mui/material/Chip'\nimport MenuItem from '@mui/material/MenuItem'\nimport Skeleton from '@mui/material/Skeleton'\nimport TextField from '@mui/material/TextField'\nimport ToggleButton from '@mui/material/ToggleButton'\nimport ToggleButtonGroup from '@mui/material/ToggleButtonGroup'\nimport Typography from '@mui/material/Typography'\nimport { Suspense, forwardRef, lazy, useEffect, useMemo, useRef, useState } from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport { generatePresetId } from '../utils/generate-preset-id'\nimport { useStorefrontPurchaseEvent } from '../utils/use-storefront-purchase-event'\nimport { CART_UPDATED_EVENT } from './cart'\nimport { ID as PRODUCT_REVIEWS_ID } from './product-reviews'\nimport { ID as RELATED_PRODUCTS_ID } from './related-products'\nimport { readLocalWishlist, toggleWishlist } from './wishlist'\nimport { StorefrontPaymentElementFallback } from './storefront-payment-element-fallback'\n\n/**\n * The Payment Element (AGL-1944), behind a lazy boundary rather than a plain\n * import. Stripe.js and its React wrapper are a large dependency, the flag is\n * OFF, and a static import would put the whole SDK into the bundle of every\n * product page on every published site to serve a code path no shopper reaches.\n * Split, it is fetched by the shoppers who actually get the in-page form.\n */\nconst StorefrontPaymentElement = lazy(() =>\n import('./storefront-payment-element').then((module) => ({\n default: module.StorefrontPaymentElement,\n })),\n)\n\n// Component ids are persisted in screen documents; never rename.\nexport const ID: Aglyn.ComponentId = 'product-detail'\n\nexport interface ProductDetailProps {\n /** Product slug; blank resolves from /products/{slug} in the URL. */\n slug?: string\n buyLabel?: string\n /** Hide the description block (design it separately with tokens). */\n hideDescription?: boolean\n /** Offer a discount/coupon code field before the buy button. */\n showCoupon?: boolean\n}\n\ninterface DetailVariant {\n id: string\n options?: Record<string, string>\n priceUsd: number\n compareAtPriceUsd?: number\n soldOut: boolean\n imageUrl?: string\n}\n\ninterface Detail {\n id: string\n name: string\n slug: string\n description?: string\n mediaUrls: string[]\n options: CommerceModel.ProductOption[]\n variants: DetailVariant[]\n /** Recurring billing (AGL-303); framing + buyer choice on the PDP. */\n subscription?: { interval: 'month' | 'year'; trialDays?: number }\n /** Buyer picks one-time or subscribe at the same price (AGL-545). */\n subscriptionOptional?: boolean\n}\n\nconst SAMPLE: Detail = {\n id: 'sample',\n name: 'Sample product',\n slug: '#',\n description: 'Drop this block on your product page template.',\n mediaUrls: [],\n options: [{ name: 'Size', values: ['S', 'M', 'L'] }],\n variants: [\n { id: 'a', options: { Size: 'S' }, priceUsd: 29, soldOut: false },\n { id: 'b', options: { Size: 'M' }, priceUsd: 29, soldOut: false },\n { id: 'c', options: { Size: 'L' }, priceUsd: 32, soldOut: true },\n ],\n}\n\n/**\n * The GA4 `items` entry for one line of this product.\n *\n * `item_id` is the PRODUCT id and never the variant's, matching the\n * `view_item` above and the cart's lines below: GA joins the funnel on that\n * id, and a `begin_checkout` keyed on a variant would report a product nothing\n * else on the storefront had ever mentioned.\n *\n * `price` is the resolved variant's, which arrived from the server's product\n * payload — the same figure `checkout` prices the charge from, so the reported\n * value and the amount Stripe collects come from one source.\n */\nfunction buyItem(\n product: Detail,\n variant: DetailVariant | undefined,\n quantity: number,\n): AnalyticsItem {\n return {\n item_id: product.id,\n item_name: product.name,\n price: variant?.priceUsd,\n quantity,\n }\n}\n\nfunction slugFromLocation(): string {\n if (typeof window === 'undefined') return ''\n const match = window.location.pathname.match(/\\/products\\/([^/?#]+)/)\n return match ? decodeURIComponent(match[1]) : ''\n}\n\n/**\n * Product detail block (AGL-292): gallery, option pickers that resolve\n * the variant (price/media/stock follow), quantity, and a buy button\n * that charges through the server-priced checkout with the selected\n * variant. Resolves its product from the /products/{slug} URL unless a\n * slug prop pins it; sample data on the inert canvas.\n */\nconst ProductDetail = forwardRef<HTMLDivElement, ProductDetailProps>(\n (props, ref) => {\n const { slug: slugProp, buyLabel, hideDescription, showCoupon, ...rest } =\n props\n // Node styles ride the renderer-merged sx; recompose (stack.ts pattern).\n const nodeSx = Array.isArray(props['sx']) ? props['sx'] : [props['sx']]\n const site = Aglyn.useSite()\n const siteFetch = Aglyn.useSiteFetch()\n const { hostId } = site\n\n // The single-product (\"buy now\") path returns the shopper to this page\n // rather than to the cart, so the PDP is the second of the two surfaces\n // that has to be able to report `purchase` (AGL-1641). Both mount points\n // share one guard, so a page carrying a cart AND a PDP still sends one.\n useStorefrontPurchaseEvent(hostId, siteFetch)\n // Seeded from the server-resolved page data (AGL-659) so the PDP renders\n // its real content in the SSR HTML. Starting at null meant the server\n // emitted a <Skeleton> and the crawler got a product page with no\n // product in it. Absent in the besigner/preview, where the effect below\n // still fetches — so this is an optimisation, not a new requirement.\n const seededProduct = (\n site.pageData as { commerce?: { product?: Detail } } | undefined\n )?.commerce?.product\n const [detail, setDetail] = useState<Detail | null | 'missing'>(\n seededProduct ?? null,\n )\n const [selections, setSelections] = useState<Record<string, string>>({})\n const [quantity, setQuantity] = useState(1)\n /** A discount or coupon code typed on the product page. */\n const [coupon, setCoupon] = useState('')\n const [activeImage, setActiveImage] = useState(0)\n // `unconfigured` is not an `error` with softer words (AGL-2019). A store\n // with no Stripe key answers 501, and rendering that at `severity=\"error\"`\n // tells a shopper their payment failed when nothing was ever attempted —\n // the same distinction `ask` already makes, and the same one the cart's\n // 423 lockdown branch makes. It is the terminal one: it latches Buy off,\n // because every further click gets the identical refusal.\n const [status, setStatus] = useState<\n 'idle' | 'sending' | 'error' | 'ask' | 'unconfigured'\n >('idle')\n const [message, setMessage] = useState('')\n // Buy-now's half of AGL-1721: asked only when the server refuses to price\n // shipping without a destination, so a digital product, a subscription, or\n // a merchant whose rates are the same everywhere never shows it. `null` is\n // \"not asked\" — the field appearing is the server's answer, not a guess.\n const [shipTo, setShipTo] = useState('')\n const [shipCountries, setShipCountries] = useState<string[] | null>(null)\n const [added, setAdded] = useState(false)\n const [wishlisted, setWishlisted] = useState(false)\n const [notifyEmail, setNotifyEmail] = useState('')\n const [notifyState, setNotifyState] = useState<'idle' | 'done'>('idle')\n // Buyer-chosen billing (AGL-545): only meaningful when the product\n // is subscriptionOptional; defaults to a one-time purchase.\n const [billing, setBilling] = useState<CommerceModel.CheckoutBillingChoice>(\n 'once',\n )\n /**\n * The in-page checkout session (AGL-1944), null on every store until the\n * flag AND a publishable key say otherwise. A `clientSecret` is Stripe's\n * instruction to the browser; holding it says nothing about money having\n * moved, and nothing in this component ever claims it has.\n */\n const [nativeCheckout, setNativeCheckout] = useState<{\n clientSecret: string\n publishableKey: string\n } | null>(null)\n\n const slug = slugProp || slugFromLocation()\n\n useEffect(() => {\n if (hostId && resolvedId) {\n setWishlisted(readLocalWishlist(hostId).includes(resolvedId))\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [hostId, detail])\n\n useEffect(() => {\n if (!hostId || !slug) return\n // Already delivered with the page (AGL-659) — refetching the identical\n // payload on hydrate would just be a wasted round trip per visitor.\n if (seededProduct && seededProduct.slug === slug) return\n let active = true\n void fetch(\n `/api/commerce/product?hostId=${encodeURIComponent(hostId)}` +\n `&slug=${encodeURIComponent(slug)}`,\n )\n .then((response) => (response.ok ? response.json() : null))\n .then((payload) => {\n if (!active) return\n const product = payload?.product as Detail | undefined\n setDetail(product ?? 'missing')\n if (product) {\n // GA4 ecommerce mirror (AGL-327), through the shared taxonomy\n // since AGL-1591 rather than raw gtag — same consent gate, same\n // sanitizer, and the name is now checked by the compiler.\n trackEvent('view_item', {\n items: [{ item_id: product.id, item_name: product.name }],\n })\n const first =\n product.variants.find((variant) => !variant.soldOut) ??\n product.variants[0]\n setSelections(first?.options ?? {})\n }\n })\n .catch(() => {\n if (active) setDetail('missing')\n })\n return () => {\n active = false\n }\n }, [hostId, slug])\n\n const resolvedId =\n hostId && detail && detail !== 'missing' ? detail.id : null\n const resolved: Detail | null = hostId\n ? detail === 'missing'\n ? null\n : detail\n : SAMPLE\n\n const variant = useMemo(() => {\n if (!resolved) return undefined\n return (\n CommerceModel.findVariant(\n { variants: resolved.variants as any },\n selections,\n ) as DetailVariant | undefined\n ) ?? resolved.variants[0]\n }, [resolved, selections])\n\n // Add to cart (AGL-293) beside instant buy; badge refresh via event.\n const handleAddToCart = async () => {\n if (!hostId || !resolved || !variant) return\n setAdded(false)\n const response = await siteFetch('/api/commerce/cart', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n hostId,\n action: 'add',\n productId: resolved.id,\n variantId: variant.id,\n quantity,\n }),\n }).catch(() => null)\n if (response?.ok) {\n setAdded(true)\n // Priced (AGL-1591's shape, completed): the resolved variant's price\n // and the chosen quantity are both known here and both come from the\n // server's product payload, so GA4's \"value added to cart\" is a real\n // number rather than the empty column an items-only hit leaves. The\n // `value` describes what was JUST ADDED, not the cart's new total —\n // see `buildAddToCartParams`.\n trackEvent(\n 'add_to_cart',\n buildAddToCartParams({\n items: [buyItem(resolved, variant, quantity)],\n }),\n )\n window.dispatchEvent(new Event(CART_UPDATED_EVENT))\n }\n }\n\n /**\n * Idempotency key for ONE buy-now attempt (AGL-1697).\n *\n * Minted lazily on the first buy click, retired whenever the purchase\n * changes shape — a different product, variant, quantity, billing choice\n * or destination is a different attempt. A retry of THIS attempt presents\n * the same key, so the server replays the original session instead of\n * opening a second one (for a subscription product, a second RECURRING\n * subscription).\n */\n const attemptKey = useRef('')\n useEffect(() => {\n attemptKey.current = ''\n // `coupon` is in here because it CHANGES THE PRICE. Without it a shopper\n // who buys, comes back, types a code and buys again presents the same\n // key, and the server replays the original full-price session — a quoted\n // number that is not the number charged, which is the whole defect class\n // this path has been cleared of.\n }, [resolved?.id, variant?.id, quantity, billing, shipTo, coupon])\n\n const handleBuy = async () => {\n if (!hostId || !resolved || !variant || status === 'sending') return\n setStatus('sending')\n setMessage('')\n if (!attemptKey.current) {\n attemptKey.current =\n globalThis.crypto?.randomUUID?.() ??\n `${Date.now()}-${Math.random().toString(36).slice(2)}`\n }\n try {\n const response = await siteFetch('/api/commerce/checkout', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Idempotency-Key': attemptKey.current,\n },\n body: JSON.stringify({\n hostId,\n productId: resolved.id,\n variantId: variant.id,\n quantity,\n // Billing choice (AGL-545): the server re-validates against\n // the product doc, so this is a request, not an instruction.\n ...(resolved.subscriptionOptional ? { billing } : {}),\n // Likewise a request (AGL-1721): the server resolves this\n // country's rates AND restricts the session to addresses in it,\n // so naming a cheap zone here cannot ship a parcel anywhere else.\n ...(shipTo ? { shippingCountry: shipTo } : {}),\n // The server resolves this against the discounts hub first and the\n // legacy coupons second, and refuses a code it cannot apply with a\n // reason — never a silent full-price charge.\n ...(coupon.trim() ? { couponCode: coupon.trim() } : {}),\n }),\n })\n const payload = await response.json().catch(() => ({}))\n // In-page payment (AGL-1944). A native session returns a client secret\n // and NO url — the two are mutually exclusive by construction, so the\n // redirect branch below is unreachable in native mode and vice versa.\n // Checked FIRST so a server that somehow sent both cannot silently\n // navigate the shopper away from the form we just decided to show.\n if (response.ok && payload?.clientSecret && payload?.publishableKey) {\n // Buy-now's `begin_checkout`. The cart path has reported this since\n // AGL-1591 and this one never did, so a storefront whose shoppers\n // skip the cart showed `view_item` → `add_to_cart` → nothing →\n // `purchase`: GA4's shopping funnel put every single-product sale in\n // the \"abandoned checkout\" bucket, and the merchant's checkout rate\n // read as a collapse rather than as an unmeasured path.\n //\n // Reported on BOTH branches, and from the same place, for the reason\n // the cart states: a count that halved the day the in-page payment\n // flag flipped would look like a conversion collapse.\n trackEvent(\n 'begin_checkout',\n buildBeginCheckoutParams({\n items: [buyItem(resolved, variant, quantity)],\n }),\n )\n setNativeCheckout({\n clientSecret: String(payload.clientSecret),\n publishableKey: String(payload.publishableKey),\n })\n setStatus('idle')\n return\n }\n if (response.ok && payload?.url) {\n // Awaited before the redirect (AGL-1580), for the reason spelled out\n // on the cart's matching branch: delivery here is a synchronous\n // `window.gtag` call today and the await costs nothing, but the\n // property that makes a bare call safe is invisible from this file.\n await trackEventBeforeNavigation(\n 'begin_checkout',\n buildBeginCheckoutParams({\n items: [buyItem(resolved, variant, quantity)],\n }),\n )\n window.location.assign(payload.url)\n return\n }\n // Buy-now's half of AGL-2019. The server's own 501 wording is not\n // rendered to a visitor; this surface is public.\n if (isPaymentsNotConfigured(response.status)) {\n setMessage(storefrontPaymentsNotConfiguredText())\n setStatus('unconfigured')\n return\n }\n if (payload?.needsShippingCountry) {\n setShipCountries(\n (payload.shippingCountries as string[] | undefined)?.length\n ? (payload.shippingCountries as string[])\n : [...CommerceModel.CHECKOUT_SHIPPING_COUNTRIES],\n )\n setMessage(String(payload?.error ?? ''))\n // An unserved destination is a refusal and reads as one; being\n // asked the first time is not.\n setStatus(shipTo ? 'error' : 'ask')\n return\n }\n setMessage(String(payload?.error ?? ''))\n setStatus('error')\n } catch {\n setStatus('error')\n }\n }\n\n if (hostId && detail === null) {\n return (\n <Box ref={ref} {...rest} sx={[{ display: 'flex', gap: 3 }, ...nodeSx]}>\n <Skeleton variant=\"rectangular\" width={320} height={320} />\n <Box sx={{ flex: 1 }}>\n <Skeleton width=\"60%\" height={36} />\n <Skeleton width=\"25%\" />\n <Skeleton width=\"90%\" />\n </Box>\n </Box>\n )\n }\n if (!resolved) {\n return (\n <Box ref={ref} {...rest} sx={[{ p: 3 }, ...nodeSx]}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Product not found.'}\n </Typography>\n </Box>\n )\n }\n\n // Every rendered `<img src>` on this page goes through\n // `siteRelativeMediaSrc` (AGL-1726). Commerce called NO resolver at all,\n // which cost it two things at once: a `media:` reference — writable via\n // the resources API and the CSV importer — would have reached an `<img>`\n // as the literal string, and a stored absolute\n // `https://{subdomain}.aglyn.app/api/media/cdn/…` stayed absolute, so a\n // white-label storefront on the customer's own domain served product\n // photos naming OUR platform, cross-origin, in page source and in every\n // visitor's request. Production carried exactly one such document; the\n // resolver fixes it at READ time, so no backfill stands between the\n // defect and the fix.\n //\n // Deliberately NOT applied to `resolved.mediaUrls` wholesale: the\n // schema.org `image` array below is read out of band by a crawler, where\n // an absolute URL is the point. That one wants `absoluteMediaSrc` with\n // the site's own origin, which is the tenant page's job (AGL-1725).\n const galleryImage = Aglyn.siteRelativeMediaSrc(\n variant?.imageUrl ?? resolved.mediaUrls[activeImage] ?? resolved.mediaUrls[0],\n { hostId },\n )\n\n // Subscription framing (AGL-545): subscription-only products price as\n // $X/mo|/yr with a \"Subscribe\" button; subscriptionOptional products\n // surface the one-time/subscribe toggle instead. Same price either way\n // — the server re-prices from the product doc regardless.\n const subscription = resolved.subscription\n const intervalSuffix = subscription?.interval === 'year' ? '/yr' : '/mo'\n const subscribing =\n Boolean(subscription) &&\n (!resolved.subscriptionOptional || billing === 'subscribe')\n\n // schema.org Product/Offer (AGL-299). Kept ONLY for the case where this\n // block renders without server-seeded page data — the besigner preview,\n // or a page whose resolver didn't supply it. When the tenant page seeds\n // us it emits the same Product node server-side (AGL-660), and two\n // Product nodes for one item is worse than one, so we stand down.\n const structuredData =\n hostId && resolvedId && !seededProduct\n ? {\n '@context': 'https://schema.org',\n '@type': 'Product',\n name: resolved.name,\n ...(resolved.description\n ? { description: resolved.description }\n : {}),\n ...(resolved.mediaUrls.length\n ? { image: resolved.mediaUrls }\n : {}),\n offers: {\n '@type': 'Offer',\n priceCurrency: 'USD',\n price: String(variant?.priceUsd ?? 0),\n availability: variant?.soldOut\n ? 'https://schema.org/OutOfStock'\n : 'https://schema.org/InStock',\n },\n }\n : null\n\n return (\n <Box\n ref={ref}\n {...rest}\n sx={[\n {\n display: 'flex',\n gap: 3,\n flexDirection: { xs: 'column', md: 'row' },\n },\n ...nodeSx,\n ]}\n >\n <Box sx={{ flex: 1, minWidth: 0 }}>\n {galleryImage ? (\n <Box\n component=\"img\"\n src={galleryImage}\n alt={resolved.name}\n // DELIBERATELY NOT DEFERRED (AGL-2486). This is the gallery\n // hero at the top of a product page — the one image on this\n // surface that really is the LCP candidate, and the browser\n // default here is already `eager`. Spreading\n // `DEFERRED_IMAGE_ATTRIBUTES` over it to \"finish the job\" is\n // the obvious next edit and it re-introduces the bug this\n // issue opened with: an LCP image not discovered until after\n // layout has run. The thumbnail strip below it IS deferred.\n //\n // No `fetchpriority` either, for the reason written out at\n // `image.tsx`: `high` is a claim about every other request in\n // flight, and a page whose real LCP is the product NAME would\n // pay for that claim.\n sx={{\n width: '100%',\n aspectRatio: '1 / 1',\n objectFit: 'cover',\n borderRadius: 1,\n }}\n />\n ) : (\n <Box\n sx={{\n width: '100%',\n aspectRatio: '1 / 1',\n bgcolor: 'action.hover',\n borderRadius: 1,\n }}\n />\n )}\n {resolved.mediaUrls.length > 1 ? (\n <Box sx={{ display: 'flex', gap: 1, mt: 1, flexWrap: 'wrap' }}>\n {resolved.mediaUrls.map((url, index) => (\n <Box\n key={`${url}-${index}`}\n component=\"img\"\n // Same resolver as the hero (AGL-1726) — a thumbnail is an\n // `<img>` on the customer's page and leaks exactly as much.\n src={Aglyn.siteRelativeMediaSrc(url, { hostId })}\n alt=\"\"\n onClick={() => setActiveImage(index)}\n sx={{\n width: 56,\n height: 56,\n objectFit: 'cover',\n borderRadius: 1,\n cursor: 'pointer',\n border: 2,\n borderColor:\n index === activeImage ? 'primary.main' : 'transparent',\n }}\n // Deferred (AGL-2486): 56px thumbnails that exist to swap\n // the hero above. They were fetching eagerly ALONGSIDE\n // that hero, so a five-image gallery had the hero\n // competing with four thumbnails nobody had asked for yet.\n {...Aglyn.DEFERRED_IMAGE_ATTRIBUTES}\n />\n ))}\n </Box>\n ) : null}\n </Box>\n {structuredData ? (\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{ __html: Aglyn.safeJsonLd(structuredData) }}\n />\n ) : null}\n <Box sx={{ flex: 1, minWidth: 0 }}>\n <Typography variant=\"h4\" component=\"h1\" gutterBottom>\n {resolved.name}\n </Typography>\n <Box sx={{ display: 'flex', gap: 1, alignItems: 'center', mb: 2 }}>\n <Typography variant=\"h6\">\n {`$${variant?.priceUsd ?? 0}${subscribing ? intervalSuffix : ''}`}\n </Typography>\n {subscribing && subscription?.trialDays ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`${subscription.trialDays}-day free trial`}\n </Typography>\n ) : null}\n {variant?.compareAtPriceUsd ? (\n <>\n <Typography\n variant=\"body1\"\n color=\"text.disabled\"\n sx={{ textDecoration: 'line-through' }}\n >\n {`$${variant.compareAtPriceUsd}`}\n </Typography>\n <Chip label=\"Sale\" size=\"small\" color=\"primary\" />\n </>\n ) : null}\n {variant?.soldOut ? (\n <Chip label=\"Sold out\" size=\"small\" variant=\"outlined\" />\n ) : null}\n <Button\n size=\"small\"\n onClick={async () => {\n if (hostId && resolved.id !== 'sample') {\n setWishlisted(await toggleWishlist(hostId, resolved.id, siteFetch))\n }\n }}\n >\n {wishlisted ? '♥ Saved' : '♡ Save'}\n </Button>\n </Box>\n {subscription && resolved.subscriptionOptional ? (\n <ToggleButtonGroup\n value={billing}\n exclusive\n size=\"small\"\n onChange={(_event, value) => {\n if (value === 'once' || value === 'subscribe') {\n setBilling(value)\n }\n }}\n sx={{ mb: 2 }}\n >\n <ToggleButton value=\"once\">\n {`One-time $${variant?.priceUsd ?? 0}`}\n </ToggleButton>\n <ToggleButton value=\"subscribe\">\n {`Subscribe $${variant?.priceUsd ?? 0}${intervalSuffix}`}\n </ToggleButton>\n </ToggleButtonGroup>\n ) : null}\n {resolved.options.map((option) => (\n <TextField\n key={option.name}\n label={option.name}\n value={selections[option.name] ?? ''}\n onChange={(event) =>\n setSelections((prev) => ({\n ...prev,\n [option.name]: event.target.value,\n }))\n }\n size=\"small\"\n select\n fullWidth\n sx={{ mb: 1.5 }}\n >\n {option.values.map((value) => (\n <MenuItem key={value} value={value}>\n {value}\n </MenuItem>\n ))}\n </TextField>\n ))}\n {showCoupon ? (\n <TextField\n label=\"Discount code\"\n value={coupon}\n onChange={(event) => setCoupon(event.target.value)}\n size=\"small\"\n sx={{ mb: 2, display: 'block' }}\n />\n ) : null}\n <Box sx={{ display: 'flex', gap: 1.5, alignItems: 'center', mb: 2 }}>\n <TextField\n label=\"Qty\"\n value={quantity}\n onChange={(event) =>\n setQuantity(\n Math.max(\n 1,\n Math.min(99, Math.round(Number(event.target.value)) || 1),\n ),\n )\n }\n size=\"small\"\n sx={{ width: 80 }}\n slotProps={{ htmlInput: { inputMode: 'numeric' } }}\n />\n <Button\n variant=\"outlined\"\n color=\"primary\"\n size=\"large\"\n disabled={!hostId || variant?.soldOut}\n onClick={handleAddToCart}\n >\n {added ? 'Added ✓' : 'Add to cart'}\n </Button>\n <Button\n variant=\"contained\"\n color=\"primary\"\n size=\"large\"\n disabled={\n !hostId ||\n status === 'sending' ||\n variant?.soldOut ||\n // No payments on this deployment: the same 501 every time, so\n // the control latches off rather than inviting a retry that\n // cannot succeed (AGL-2019).\n status === 'unconfigured' ||\n // Asked but unanswered: the server would only refuse again.\n (shipCountries !== null && !shipTo)\n }\n onClick={handleBuy}\n sx={{ flex: 1 }}\n >\n {variant?.soldOut\n ? 'Sold out'\n : status === 'sending'\n ? // \"Redirecting\" is a lie once the form opens in place, and\n // it is the shopper's only clue about what is about to\n // happen to their page (AGL-1944).\n nativeCheckout\n ? 'Opening…'\n : 'Redirecting…'\n : buyLabel || (subscribing ? 'Subscribe' : 'Buy now')}\n </Button>\n </Box>\n {nativeCheckout ? (\n <Suspense fallback={<StorefrontPaymentElementFallback />}>\n <StorefrontPaymentElement\n clientSecret={nativeCheckout.clientSecret}\n publishableKey={nativeCheckout.publishableKey}\n payLabel={buyLabel || (subscribing ? 'Subscribe' : 'Pay now')}\n // Cancelling drops the form and leaves the session open —\n // deliberately. The shopper may come back to the same\n // attempt key, and an abandoned session is what the AGL-323\n // recovery emails are built on. Nothing is expired from the\n // browser: that is a write against the merchant's account.\n onCancel={() => setNativeCheckout(null)}\n />\n </Suspense>\n ) : null}\n {shipCountries ? (\n <TextField\n select\n label=\"Ship to\"\n value={shipTo}\n onChange={(event) => setShipTo(event.target.value)}\n size=\"small\"\n fullWidth\n helperText=\"Shipping rates depend on where this is going\"\n sx={{ mb: 2 }}\n >\n {shipCountries.map((code) => (\n <MenuItem key={code} value={code}>\n {CommerceModel.CHECKOUT_SHIPPING_COUNTRY_NAMES[code] ?? code}\n </MenuItem>\n ))}\n </TextField>\n ) : null}\n {status === 'ask' || status === 'unconfigured' ? (\n <Alert severity=\"info\" sx={{ mb: 2 }}>\n {message}\n </Alert>\n ) : null}\n {status === 'error' ? (\n <Alert severity=\"error\" sx={{ mb: 2 }}>\n {message || 'Checkout is unavailable right now.'}\n </Alert>\n ) : null}\n {variant?.soldOut && hostId ? (\n notifyState === 'done' ? (\n <Alert severity=\"success\" sx={{ mb: 2 }}>\n {'We will email you when it is back.'}\n </Alert>\n ) : (\n <Box sx={{ display: 'flex', gap: 1, mb: 2 }}>\n <TextField\n placeholder=\"you@example.com\"\n type=\"email\"\n // Third instance of the nameless-input defect (AGL-2392).\n // Named for its job rather than \"Email address\": this row\n // appears inside a product page that may also carry the\n // newsletter block, and two fields both announced as\n // \"Email address\" is barely better than none.\n slotProps={{\n htmlInput: {\n 'aria-label': 'Email address for back-in-stock alert',\n },\n }}\n value={notifyEmail}\n onChange={(event) => setNotifyEmail(event.target.value)}\n size=\"small\"\n sx={{ flex: 1 }}\n />\n <Button\n size=\"small\"\n variant=\"outlined\"\n disabled={!notifyEmail.trim()}\n onClick={async () => {\n const response = await siteFetch(\n '/api/commerce/notify-restock',\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n hostId,\n productId: resolved.id,\n email: notifyEmail,\n }),\n },\n ).catch(() => null)\n if (response?.ok) setNotifyState('done')\n }}\n >\n {'Notify me'}\n </Button>\n </Box>\n )\n ) : null}\n {!hideDescription && resolved.description ? (\n <Typography variant=\"body1\" color=\"text.secondary\">\n {resolved.description}\n </Typography>\n ) : null}\n </Box>\n </Box>\n )\n },\n)\nProductDetail.displayName = 'AglynProductDetail'\n\nexport const schema: Aglyn.ComponentSchema<ProductDetailProps> = {\n $id: ID,\n pluginId: BUNDLE_ID,\n displayName: 'Product detail',\n description: \"One product's gallery, options and buy button.\",\n category: Aglyn.ComponentCategory.COMMERCE,\n icon: { path: mdiTagOutline.path, sx: { color: '#2e7d32' } },\n flags: { selfClosing: Aglyn.FEATURE_FLAG.ENABLED },\n attributes: [\n {\n name: 'slug',\n label: 'Product slug',\n description:\n 'Pin to one product; blank follows the /products/{slug} URL ' +\n '(product page template).',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'buyLabel',\n label: 'Buy button label',\n description: 'Defaults to \"Buy now\".',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'hideDescription',\n label: 'Hide description',\n description: 'Design the description separately with tokens.',\n component: Aglyn.FieldComponentType.CHECKBOX,\n },\n {\n // Off by default, so no existing page changes shape on deploy. The cart\n // has carried this field all along and the product page had none, so a\n // shopper buying the same goods through Buy now had nowhere to enter a\n // code the merchant had advertised.\n name: 'showCoupon',\n label: 'Show discount code field',\n description: 'Lets a buyer enter a discount or coupon code before buying.',\n component: Aglyn.FieldComponentType.CHECKBOX,\n },\n ],\n}\n\nexport const presets: Aglyn.PresetSchema[] = [\n {\n $id: generatePresetId(ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Product detail',\n pluginId: BUNDLE_ID,\n description: 'Gallery, variant picker, and buy button for one product',\n category: Aglyn.ComponentCategory.COMMERCE,\n icon: { path: mdiTagOutline.path, sx: { color: '#2e7d32' } },\n data: {\n $id: null,\n componentId: ID,\n pluginId: BUNDLE_ID,\n props: {},\n },\n },\n {\n // Product page (AGL-561): the commerce-standard PDP subtree —\n // breadcrumb, detail, related products, reviews — for the product\n // page template screen. `{{product.name}}` resolves there via the\n // site page resolver's token pass (AGL-292); node shapes follow\n // the Sections & Blocks convention (AGL-539) and reference only\n // persisted component ids.\n $id: generatePresetId(ID, 'page'),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Product page',\n pluginId: BUNDLE_ID,\n description:\n 'Breadcrumb, product detail, related products, and reviews',\n category: Aglyn.ComponentCategory.COMMERCE,\n icon: { path: mdiTagOutline.path, sx: { color: '#2e7d32' } },\n data: {\n $id: null,\n componentId: 'muiStack',\n pluginId: Aglyn.MUI_BUNDLE_ID,\n props: { spacing: 4 },\n // Styling on the node's own sx, never in props (AGL-1346): both\n // records render, but only `node.sx` is the one the Styles panel can\n // read, edit or clear.\n sx: { paddingTop: 2, paddingBottom: 2 },\n nodes: [\n {\n $id: null,\n componentId: 'muiStack',\n pluginId: Aglyn.MUI_BUNDLE_ID,\n props: { direction: 'row', spacing: 1 },\n sx: { alignItems: 'center' },\n nodes: [\n {\n $id: null,\n componentId: 'muiScreenLink',\n pluginId: Aglyn.MUI_BUNDLE_ID,\n props: { children: 'Shop', size: 'small', color: 'inherit' },\n },\n {\n $id: null,\n componentId: 'muiTypography',\n pluginId: Aglyn.MUI_BUNDLE_ID,\n props: { variant: 'body2', children: '/ {{product.name}}' },\n },\n ],\n },\n {\n $id: null,\n componentId: ID,\n pluginId: BUNDLE_ID,\n props: {},\n },\n {\n $id: null,\n componentId: RELATED_PRODUCTS_ID,\n pluginId: BUNDLE_ID,\n props: {},\n },\n {\n $id: null,\n componentId: PRODUCT_REVIEWS_ID,\n pluginId: BUNDLE_ID,\n props: {},\n },\n ],\n },\n },\n]\n\nexport default ProductDetail\n"],"names":["Aglyn","buildAddToCartParams","buildBeginCheckoutParams","trackEvent","trackEventBeforeNavigation","isPaymentsNotConfigured","storefrontPaymentsNotConfiguredText","CommerceModel","mdiTagOutline","Alert","Box","Button","Chip","MenuItem","Skeleton","TextField","ToggleButton","ToggleButtonGroup","Typography","Suspense","forwardRef","lazy","useEffect","useMemo","useRef","useState","BUNDLE_ID","generatePresetId","useStorefrontPurchaseEvent","CART_UPDATED_EVENT","ID","PRODUCT_REVIEWS_ID","RELATED_PRODUCTS_ID","readLocalWishlist","toggleWishlist","StorefrontPaymentElementFallback","StorefrontPaymentElement","then","module","default","SAMPLE","id","name","slug","description","mediaUrls","options","values","variants","Size","priceUsd","soldOut","buyItem","product","variant","quantity","item_id","item_name","price","slugFromLocation","window","match","location","pathname","decodeURIComponent","ProductDetail","props","ref","site","slugProp","buyLabel","hideDescription","showCoupon","rest","nodeSx","Array","isArray","useSite","siteFetch","useSiteFetch","hostId","seededProduct","pageData","commerce","detail","setDetail","selections","setSelections","setQuantity","coupon","setCoupon","activeImage","setActiveImage","status","setStatus","message","setMessage","shipTo","setShipTo","shipCountries","setShipCountries","added","setAdded","wishlisted","setWishlisted","notifyEmail","setNotifyEmail","notifyState","setNotifyState","billing","setBilling","nativeCheckout","setNativeCheckout","resolvedId","includes","active","fetch","encodeURIComponent","response","ok","json","payload","items","first","find","catch","resolved","undefined","findVariant","handleAddToCart","method","headers","body","JSON","stringify","action","productId","variantId","dispatchEvent","Event","attemptKey","current","handleBuy","globalThis","crypto","randomUUID","Date","now","Math","random","toString","slice","subscriptionOptional","shippingCountry","trim","couponCode","clientSecret","publishableKey","String","url","assign","needsShippingCountry","shippingCountries","length","CHECKOUT_SHIPPING_COUNTRIES","error","sx","display","gap","width","height","flex","p","color","galleryImage","siteRelativeMediaSrc","imageUrl","subscription","intervalSuffix","interval","subscribing","Boolean","structuredData","image","offers","priceCurrency","availability","flexDirection","xs","md","minWidth","component","src","alt","aspectRatio","objectFit","borderRadius","bgcolor","mt","flexWrap","map","index","onClick","cursor","border","borderColor","DEFERRED_IMAGE_ATTRIBUTES","script","type","dangerouslySetInnerHTML","__html","safeJsonLd","gutterBottom","alignItems","mb","trialDays","compareAtPriceUsd","textDecoration","label","size","value","exclusive","onChange","_event","option","event","prev","target","select","fullWidth","max","min","round","Number","slotProps","htmlInput","inputMode","disabled","fallback","payLabel","onCancel","helperText","code","CHECKOUT_SHIPPING_COUNTRY_NAMES","severity","placeholder","email","displayName","schema","$id","pluginId","category","ComponentCategory","COMMERCE","icon","path","flags","selfClosing","FEATURE_FLAG","ENABLED","attributes","FieldComponentType","TEXT_FIELD","CHECKBOX","presets","NodeType","PRESET","data","componentId","MUI_BUNDLE_ID","spacing","paddingTop","paddingBottom","nodes","direction","children"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/components/product-detail.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport * as Aglyn from '@aglyn/aglyn'\nimport {\n buildAddToCartParams,\n buildBeginCheckoutParams,\n trackEvent,\n trackEventBeforeNavigation,\n type AnalyticsItem,\n} from '@aglyn/aglyn/app-utils/analytics-events'\nimport { isPaymentsNotConfigured } from '@aglyn/aglyn/app-utils/payments-configured'\nimport { storefrontPaymentsNotConfiguredText } from '../constants/storefront-payments'\nimport * as CommerceModel from '../model'\nimport { mdiTagOutline } from '@aglyn/shared-data-mdi'\nimport Alert from '@mui/material/Alert'\nimport Box from '@mui/material/Box'\nimport Button from '@mui/material/Button'\nimport Chip from '@mui/material/Chip'\nimport MenuItem from '@mui/material/MenuItem'\nimport Skeleton from '@mui/material/Skeleton'\nimport TextField from '@mui/material/TextField'\nimport ToggleButton from '@mui/material/ToggleButton'\nimport ToggleButtonGroup from '@mui/material/ToggleButtonGroup'\nimport Typography from '@mui/material/Typography'\nimport { Suspense, forwardRef, lazy, useEffect, useMemo, useRef, useState } from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport { generatePresetId } from '../utils/generate-preset-id'\nimport { useStorefrontPurchaseEvent } from '../utils/use-storefront-purchase-event'\nimport { CART_UPDATED_EVENT } from './cart'\nimport { ID as PRODUCT_REVIEWS_ID } from './product-reviews'\nimport { ID as RELATED_PRODUCTS_ID } from './related-products'\nimport { readLocalWishlist, toggleWishlist } from './wishlist'\nimport { StorefrontPaymentElementFallback } from './storefront-payment-element-fallback'\n\n/**\n * The Payment Element (AGL-1944), behind a lazy boundary rather than a plain\n * import. Stripe.js and its React wrapper are a large dependency, the flag is\n * OFF, and a static import would put the whole SDK into the bundle of every\n * product page on every published site to serve a code path no shopper reaches.\n * Split, it is fetched by the shoppers who actually get the in-page form.\n */\nconst StorefrontPaymentElement = lazy(() =>\n import('./storefront-payment-element').then((module) => ({\n default: module.StorefrontPaymentElement,\n })),\n)\n\n// Component ids are persisted in screen documents; never rename.\nexport const ID: Aglyn.ComponentId = 'product-detail'\n\nexport interface ProductDetailProps {\n /** Product slug; blank resolves from /products/{slug} in the URL. */\n slug?: string\n buyLabel?: string\n /** Hide the description block (design it separately with tokens). */\n hideDescription?: boolean\n /** Offer a discount/coupon code field before the buy button. */\n showCoupon?: boolean\n}\n\ninterface DetailVariant {\n id: string\n options?: Record<string, string>\n priceUsd: number\n compareAtPriceUsd?: number\n soldOut: boolean\n imageUrl?: string\n}\n\ninterface Detail {\n id: string\n name: string\n slug: string\n description?: string\n mediaUrls: string[]\n options: CommerceModel.ProductOption[]\n variants: DetailVariant[]\n /** Recurring billing (AGL-303); framing + buyer choice on the PDP. */\n subscription?: { interval: 'month' | 'year'; trialDays?: number }\n /** Buyer picks one-time or subscribe at the same price (AGL-545). */\n subscriptionOptional?: boolean\n}\n\nconst SAMPLE: Detail = {\n id: 'sample',\n name: 'Sample product',\n slug: '#',\n description: 'Drop this block on your product page template.',\n mediaUrls: [],\n options: [{ name: 'Size', values: ['S', 'M', 'L'] }],\n variants: [\n { id: 'a', options: { Size: 'S' }, priceUsd: 29, soldOut: false },\n { id: 'b', options: { Size: 'M' }, priceUsd: 29, soldOut: false },\n { id: 'c', options: { Size: 'L' }, priceUsd: 32, soldOut: true },\n ],\n}\n\n/**\n * The GA4 `items` entry for one line of this product.\n *\n * `item_id` is the PRODUCT id and never the variant's, matching the\n * `view_item` above and the cart's lines below: GA joins the funnel on that\n * id, and a `begin_checkout` keyed on a variant would report a product nothing\n * else on the storefront had ever mentioned.\n *\n * `price` is the resolved variant's, which arrived from the server's product\n * payload — the same figure `checkout` prices the charge from, so the reported\n * value and the amount Stripe collects come from one source.\n */\nfunction buyItem(\n product: Detail,\n variant: DetailVariant | undefined,\n quantity: number,\n): AnalyticsItem {\n return {\n item_id: product.id,\n item_name: product.name,\n price: variant?.priceUsd,\n quantity,\n }\n}\n\nfunction slugFromLocation(): string {\n if (typeof window === 'undefined') return ''\n const match = window.location.pathname.match(/\\/products\\/([^/?#]+)/)\n return match ? decodeURIComponent(match[1]) : ''\n}\n\n/**\n * Product detail block (AGL-292): gallery, option pickers that resolve\n * the variant (price/media/stock follow), quantity, and a buy button\n * that charges through the server-priced checkout with the selected\n * variant. Resolves its product from the /products/{slug} URL unless a\n * slug prop pins it; sample data on the inert canvas.\n */\nconst ProductDetail = forwardRef<HTMLDivElement, ProductDetailProps>(\n (props, ref) => {\n const { slug: slugProp, buyLabel, hideDescription, showCoupon, ...rest } =\n props\n // Node styles ride the renderer-merged sx; recompose (stack.ts pattern).\n const nodeSx = Array.isArray(props['sx']) ? props['sx'] : [props['sx']]\n const site = Aglyn.useSite()\n const siteFetch = Aglyn.useSiteFetch()\n const { hostId } = site\n\n // The single-product (\"buy now\") path returns the shopper to this page\n // rather than to the cart, so the PDP is the second of the two surfaces\n // that has to be able to report `purchase` (AGL-1641). Both mount points\n // share one guard, so a page carrying a cart AND a PDP still sends one.\n useStorefrontPurchaseEvent(hostId, siteFetch)\n // Seeded from the server-resolved page data (AGL-659) so the PDP renders\n // its real content in the SSR HTML. Starting at null meant the server\n // emitted a <Skeleton> and the crawler got a product page with no\n // product in it. Absent in the besigner/preview, where the effect below\n // still fetches — so this is an optimisation, not a new requirement.\n const seededProduct = (\n site.pageData as { commerce?: { product?: Detail } } | undefined\n )?.commerce?.product\n const [detail, setDetail] = useState<Detail | null | 'missing'>(\n seededProduct ?? null,\n )\n const [selections, setSelections] = useState<Record<string, string>>({})\n const [quantity, setQuantity] = useState(1)\n /** A discount or coupon code typed on the product page. */\n const [coupon, setCoupon] = useState('')\n const [activeImage, setActiveImage] = useState(0)\n // `unconfigured` is not an `error` with softer words (AGL-2019). A store\n // with no Stripe key answers 501, and rendering that at `severity=\"error\"`\n // tells a shopper their payment failed when nothing was ever attempted —\n // the same distinction `ask` already makes, and the same one the cart's\n // 423 lockdown branch makes. It is the terminal one: it latches Buy off,\n // because every further click gets the identical refusal.\n const [status, setStatus] = useState<\n 'idle' | 'sending' | 'error' | 'ask' | 'unconfigured'\n >('idle')\n const [message, setMessage] = useState('')\n // Buy-now's half of AGL-1721: asked only when the server refuses to price\n // shipping without a destination, so a digital product, a subscription, or\n // a merchant whose rates are the same everywhere never shows it. `null` is\n // \"not asked\" — the field appearing is the server's answer, not a guess.\n const [shipTo, setShipTo] = useState('')\n const [shipCountries, setShipCountries] = useState<string[] | null>(null)\n const [added, setAdded] = useState(false)\n const [wishlisted, setWishlisted] = useState(false)\n const [notifyEmail, setNotifyEmail] = useState('')\n const [notifyState, setNotifyState] = useState<'idle' | 'done'>('idle')\n // Buyer-chosen billing (AGL-545): only meaningful when the product\n // is subscriptionOptional; defaults to a one-time purchase.\n const [billing, setBilling] = useState<CommerceModel.CheckoutBillingChoice>(\n 'once',\n )\n /**\n * The in-page checkout session (AGL-1944), null on every store until the\n * flag AND a publishable key say otherwise. A `clientSecret` is Stripe's\n * instruction to the browser; holding it says nothing about money having\n * moved, and nothing in this component ever claims it has.\n */\n const [nativeCheckout, setNativeCheckout] = useState<{\n clientSecret: string\n publishableKey: string\n } | null>(null)\n\n const slug = slugProp || slugFromLocation()\n\n useEffect(() => {\n if (hostId && resolvedId) {\n setWishlisted(readLocalWishlist(hostId).includes(resolvedId))\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [hostId, detail])\n\n useEffect(() => {\n if (!hostId || !slug) return\n // Already delivered with the page (AGL-659) — refetching the identical\n // payload on hydrate would just be a wasted round trip per visitor.\n if (seededProduct && seededProduct.slug === slug) return\n let active = true\n void fetch(\n `/api/commerce/product?hostId=${encodeURIComponent(hostId)}` +\n `&slug=${encodeURIComponent(slug)}`,\n )\n .then((response) => (response.ok ? response.json() : null))\n .then((payload) => {\n if (!active) return\n const product = payload?.product as Detail | undefined\n setDetail(product ?? 'missing')\n if (product) {\n // GA4 ecommerce mirror (AGL-327), through the shared taxonomy\n // since AGL-1591 rather than raw gtag — same consent gate, same\n // sanitizer, and the name is now checked by the compiler.\n trackEvent('view_item', {\n items: [{ item_id: product.id, item_name: product.name }],\n })\n const first =\n product.variants.find((variant) => !variant.soldOut) ??\n product.variants[0]\n setSelections(first?.options ?? {})\n }\n })\n .catch(() => {\n if (active) setDetail('missing')\n })\n return () => {\n active = false\n }\n }, [hostId, slug])\n\n const resolvedId =\n hostId && detail && detail !== 'missing' ? detail.id : null\n const resolved: Detail | null = hostId\n ? detail === 'missing'\n ? null\n : detail\n : SAMPLE\n\n const variant = useMemo(() => {\n if (!resolved) return undefined\n return (\n CommerceModel.findVariant(\n { variants: resolved.variants as any },\n selections,\n ) as DetailVariant | undefined\n ) ?? resolved.variants[0]\n }, [resolved, selections])\n\n // Add to cart (AGL-293) beside instant buy; badge refresh via event.\n const handleAddToCart = async () => {\n if (!hostId || !resolved || !variant) return\n setAdded(false)\n const response = await siteFetch('/api/commerce/cart', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n hostId,\n action: 'add',\n productId: resolved.id,\n variantId: variant.id,\n quantity,\n }),\n }).catch(() => null)\n if (response?.ok) {\n setAdded(true)\n // Priced (AGL-1591's shape, completed): the resolved variant's price\n // and the chosen quantity are both known here and both come from the\n // server's product payload, so GA4's \"value added to cart\" is a real\n // number rather than the empty column an items-only hit leaves. The\n // `value` describes what was JUST ADDED, not the cart's new total —\n // see `buildAddToCartParams`.\n trackEvent(\n 'add_to_cart',\n buildAddToCartParams({\n items: [buyItem(resolved, variant, quantity)],\n }),\n )\n window.dispatchEvent(new Event(CART_UPDATED_EVENT))\n }\n }\n\n /**\n * Idempotency key for ONE buy-now attempt (AGL-1697).\n *\n * Minted lazily on the first buy click, retired whenever the purchase\n * changes shape — a different product, variant, quantity, billing choice\n * or destination is a different attempt. A retry of THIS attempt presents\n * the same key, so the server replays the original session instead of\n * opening a second one (for a subscription product, a second RECURRING\n * subscription).\n */\n const attemptKey = useRef('')\n useEffect(() => {\n attemptKey.current = ''\n // `coupon` is in here because it CHANGES THE PRICE. Without it a shopper\n // who buys, comes back, types a code and buys again presents the same\n // key, and the server replays the original full-price session — a quoted\n // number that is not the number charged, which is the whole defect class\n // this path has been cleared of.\n }, [resolved?.id, variant?.id, quantity, billing, shipTo, coupon])\n\n const handleBuy = async () => {\n if (!hostId || !resolved || !variant || status === 'sending') return\n setStatus('sending')\n setMessage('')\n if (!attemptKey.current) {\n attemptKey.current =\n globalThis.crypto?.randomUUID?.() ??\n `${Date.now()}-${Math.random().toString(36).slice(2)}`\n }\n try {\n const response = await siteFetch('/api/commerce/checkout', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Idempotency-Key': attemptKey.current,\n },\n body: JSON.stringify({\n hostId,\n productId: resolved.id,\n variantId: variant.id,\n quantity,\n // Billing choice (AGL-545): the server re-validates against\n // the product doc, so this is a request, not an instruction.\n ...(resolved.subscriptionOptional ? { billing } : {}),\n // Likewise a request (AGL-1721): the server resolves this\n // country's rates AND restricts the session to addresses in it,\n // so naming a cheap zone here cannot ship a parcel anywhere else.\n ...(shipTo ? { shippingCountry: shipTo } : {}),\n // The server resolves this against the discounts hub first and the\n // legacy coupons second, and refuses a code it cannot apply with a\n // reason — never a silent full-price charge.\n ...(coupon.trim() ? { couponCode: coupon.trim() } : {}),\n }),\n })\n const payload = await response.json().catch(() => ({}))\n // In-page payment (AGL-1944). A native session returns a client secret\n // and NO url — the two are mutually exclusive by construction, so the\n // redirect branch below is unreachable in native mode and vice versa.\n // Checked FIRST so a server that somehow sent both cannot silently\n // navigate the shopper away from the form we just decided to show.\n if (response.ok && payload?.clientSecret && payload?.publishableKey) {\n // Buy-now's `begin_checkout`. The cart path has reported this since\n // AGL-1591 and this one never did, so a storefront whose shoppers\n // skip the cart showed `view_item` → `add_to_cart` → nothing →\n // `purchase`: GA4's shopping funnel put every single-product sale in\n // the \"abandoned checkout\" bucket, and the merchant's checkout rate\n // read as a collapse rather than as an unmeasured path.\n //\n // Reported on BOTH branches, and from the same place, for the reason\n // the cart states: a count that halved the day the in-page payment\n // flag flipped would look like a conversion collapse.\n trackEvent(\n 'begin_checkout',\n buildBeginCheckoutParams({\n items: [buyItem(resolved, variant, quantity)],\n }),\n )\n setNativeCheckout({\n clientSecret: String(payload.clientSecret),\n publishableKey: String(payload.publishableKey),\n })\n setStatus('idle')\n return\n }\n if (response.ok && payload?.url) {\n // Awaited before the redirect (AGL-1580), for the reason spelled out\n // on the cart's matching branch: delivery here is a synchronous\n // `window.gtag` call today and the await costs nothing, but the\n // property that makes a bare call safe is invisible from this file.\n await trackEventBeforeNavigation(\n 'begin_checkout',\n buildBeginCheckoutParams({\n items: [buyItem(resolved, variant, quantity)],\n }),\n )\n window.location.assign(payload.url)\n return\n }\n // Buy-now's half of AGL-2019. The server's own 501 wording is not\n // rendered to a visitor; this surface is public.\n if (isPaymentsNotConfigured(response.status)) {\n setMessage(storefrontPaymentsNotConfiguredText())\n setStatus('unconfigured')\n return\n }\n if (payload?.needsShippingCountry) {\n setShipCountries(\n (payload.shippingCountries as string[] | undefined)?.length\n ? (payload.shippingCountries as string[])\n : [...CommerceModel.CHECKOUT_SHIPPING_COUNTRIES],\n )\n setMessage(String(payload?.error ?? ''))\n // An unserved destination is a refusal and reads as one; being\n // asked the first time is not.\n setStatus(shipTo ? 'error' : 'ask')\n return\n }\n setMessage(String(payload?.error ?? ''))\n setStatus('error')\n } catch {\n setStatus('error')\n }\n }\n\n if (hostId && detail === null) {\n return (\n <Box ref={ref} {...rest} sx={[{ display: 'flex', gap: 3 }, ...nodeSx]}>\n <Skeleton variant=\"rectangular\" width={320} height={320} />\n <Box sx={{ flex: 1 }}>\n <Skeleton width=\"60%\" height={36} />\n <Skeleton width=\"25%\" />\n <Skeleton width=\"90%\" />\n </Box>\n </Box>\n )\n }\n if (!resolved) {\n return (\n <Box ref={ref} {...rest} sx={[{ p: 3 }, ...nodeSx]}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Product not found.'}\n </Typography>\n </Box>\n )\n }\n\n // Every rendered `<img src>` on this page goes through\n // `siteRelativeMediaSrc` (AGL-1726). Commerce called NO resolver at all,\n // which cost it two things at once: a `media:` reference — writable via\n // the resources API and the CSV importer — would have reached an `<img>`\n // as the literal string, and a stored absolute\n // `https://{subdomain}.aglyn.app/api/media/cdn/…` stayed absolute, so a\n // white-label storefront on the customer's own domain served product\n // photos naming OUR platform, cross-origin, in page source and in every\n // visitor's request. Production carried exactly one such document; the\n // resolver fixes it at READ time, so no backfill stands between the\n // defect and the fix.\n //\n // Deliberately NOT applied to `resolved.mediaUrls` wholesale: the\n // schema.org `image` array below is read out of band by a crawler, where\n // an absolute URL is the point. That one wants `absoluteMediaSrc` with\n // the site's own origin, which is the tenant page's job (AGL-1725).\n const galleryImage = Aglyn.siteRelativeMediaSrc(\n variant?.imageUrl ?? resolved.mediaUrls[activeImage] ?? resolved.mediaUrls[0],\n { hostId },\n )\n\n // Subscription framing (AGL-545): subscription-only products price as\n // $X/mo|/yr with a \"Subscribe\" button; subscriptionOptional products\n // surface the one-time/subscribe toggle instead. Same price either way\n // — the server re-prices from the product doc regardless.\n const subscription = resolved.subscription\n const intervalSuffix = subscription?.interval === 'year' ? '/yr' : '/mo'\n const subscribing =\n Boolean(subscription) &&\n (!resolved.subscriptionOptional || billing === 'subscribe')\n\n // schema.org Product/Offer (AGL-299). Kept ONLY for the case where this\n // block renders without server-seeded page data — the besigner preview,\n // or a page whose resolver didn't supply it. When the tenant page seeds\n // us it emits the same Product node server-side (AGL-660), and two\n // Product nodes for one item is worse than one, so we stand down.\n const structuredData =\n hostId && resolvedId && !seededProduct\n ? {\n '@context': 'https://schema.org',\n '@type': 'Product',\n name: resolved.name,\n ...(resolved.description\n ? { description: resolved.description }\n : {}),\n ...(resolved.mediaUrls.length\n ? { image: resolved.mediaUrls }\n : {}),\n offers: {\n '@type': 'Offer',\n priceCurrency: 'USD',\n price: String(variant?.priceUsd ?? 0),\n availability: variant?.soldOut\n ? 'https://schema.org/OutOfStock'\n : 'https://schema.org/InStock',\n },\n }\n : null\n\n return (\n <Box\n ref={ref}\n {...rest}\n sx={[\n {\n display: 'flex',\n gap: 3,\n flexDirection: { xs: 'column', md: 'row' },\n },\n ...nodeSx,\n ]}\n >\n <Box sx={{ flex: 1, minWidth: 0 }}>\n {galleryImage ? (\n <Box\n component=\"img\"\n src={galleryImage}\n alt={resolved.name}\n // DELIBERATELY NOT DEFERRED (AGL-2486). This is the gallery\n // hero at the top of a product page — the one image on this\n // surface that really is the LCP candidate, and the browser\n // default here is already `eager`. Spreading\n // `DEFERRED_IMAGE_ATTRIBUTES` over it to \"finish the job\" is\n // the obvious next edit and it re-introduces the bug this\n // issue opened with: an LCP image not discovered until after\n // layout has run. The thumbnail strip below it IS deferred.\n //\n // No `fetchpriority` either, for the reason written out at\n // `image.tsx`: `high` is a claim about every other request in\n // flight, and a page whose real LCP is the product NAME would\n // pay for that claim.\n sx={{\n width: '100%',\n aspectRatio: '1 / 1',\n objectFit: 'cover',\n borderRadius: 1,\n }}\n />\n ) : (\n <Box\n sx={{\n width: '100%',\n aspectRatio: '1 / 1',\n bgcolor: 'action.hover',\n borderRadius: 1,\n }}\n />\n )}\n {resolved.mediaUrls.length > 1 ? (\n <Box sx={{ display: 'flex', gap: 1, mt: 1, flexWrap: 'wrap' }}>\n {resolved.mediaUrls.map((url, index) => (\n <Box\n key={`${url}-${index}`}\n component=\"img\"\n // Same resolver as the hero (AGL-1726) — a thumbnail is an\n // `<img>` on the customer's page and leaks exactly as much.\n src={Aglyn.siteRelativeMediaSrc(url, { hostId })}\n alt=\"\"\n onClick={() => setActiveImage(index)}\n sx={{\n width: 56,\n height: 56,\n objectFit: 'cover',\n borderRadius: 1,\n cursor: 'pointer',\n border: 2,\n borderColor:\n index === activeImage ? 'primary.main' : 'transparent',\n }}\n // Deferred (AGL-2486): 56px thumbnails that exist to swap\n // the hero above. They were fetching eagerly ALONGSIDE\n // that hero, so a five-image gallery had the hero\n // competing with four thumbnails nobody had asked for yet.\n {...Aglyn.DEFERRED_IMAGE_ATTRIBUTES}\n />\n ))}\n </Box>\n ) : null}\n </Box>\n {structuredData ? (\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{ __html: Aglyn.safeJsonLd(structuredData) }}\n />\n ) : null}\n <Box sx={{ flex: 1, minWidth: 0 }}>\n <Typography variant=\"h4\" component=\"h1\" gutterBottom>\n {resolved.name}\n </Typography>\n <Box sx={{ display: 'flex', gap: 1, alignItems: 'center', mb: 2 }}>\n <Typography variant=\"h6\">\n {`$${variant?.priceUsd ?? 0}${subscribing ? intervalSuffix : ''}`}\n </Typography>\n {subscribing && subscription?.trialDays ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`${subscription.trialDays}-day free trial`}\n </Typography>\n ) : null}\n {variant?.compareAtPriceUsd ? (\n <>\n <Typography\n variant=\"body1\"\n color=\"text.disabled\"\n sx={{ textDecoration: 'line-through' }}\n >\n {`$${variant.compareAtPriceUsd}`}\n </Typography>\n <Chip label=\"Sale\" size=\"small\" color=\"primary\" />\n </>\n ) : null}\n {variant?.soldOut ? (\n <Chip label=\"Sold out\" size=\"small\" variant=\"outlined\" />\n ) : null}\n <Button\n size=\"small\"\n onClick={async () => {\n if (hostId && resolved.id !== 'sample') {\n setWishlisted(await toggleWishlist(hostId, resolved.id, siteFetch))\n }\n }}\n >\n {wishlisted ? '♥ Saved' : '♡ Save'}\n </Button>\n </Box>\n {subscription && resolved.subscriptionOptional ? (\n <ToggleButtonGroup\n value={billing}\n exclusive\n size=\"small\"\n onChange={(_event, value) => {\n if (value === 'once' || value === 'subscribe') {\n setBilling(value)\n }\n }}\n sx={{ mb: 2 }}\n >\n <ToggleButton value=\"once\">\n {`One-time $${variant?.priceUsd ?? 0}`}\n </ToggleButton>\n <ToggleButton value=\"subscribe\">\n {`Subscribe $${variant?.priceUsd ?? 0}${intervalSuffix}`}\n </ToggleButton>\n </ToggleButtonGroup>\n ) : null}\n {resolved.options.map((option) => (\n <TextField\n key={option.name}\n label={option.name}\n value={selections[option.name] ?? ''}\n onChange={(event) =>\n setSelections((prev) => ({\n ...prev,\n [option.name]: event.target.value,\n }))\n }\n size=\"small\"\n select\n fullWidth\n sx={{ mb: 1.5 }}\n >\n {option.values.map((value) => (\n <MenuItem key={value} value={value}>\n {value}\n </MenuItem>\n ))}\n </TextField>\n ))}\n {showCoupon ? (\n <TextField\n label=\"Discount code\"\n value={coupon}\n onChange={(event) => setCoupon(event.target.value)}\n size=\"small\"\n sx={{ mb: 2, display: 'block' }}\n />\n ) : null}\n <Box sx={{ display: 'flex', gap: 1.5, alignItems: 'center', mb: 2 }}>\n <TextField\n label=\"Qty\"\n value={quantity}\n onChange={(event) =>\n setQuantity(\n Math.max(\n 1,\n Math.min(99, Math.round(Number(event.target.value)) || 1),\n ),\n )\n }\n size=\"small\"\n sx={{ width: 80 }}\n slotProps={{ htmlInput: { inputMode: 'numeric' } }}\n />\n <Button\n variant=\"outlined\"\n color=\"primary\"\n size=\"large\"\n disabled={!hostId || variant?.soldOut}\n onClick={handleAddToCart}\n >\n {added ? 'Added ✓' : 'Add to cart'}\n </Button>\n <Button\n variant=\"contained\"\n color=\"primary\"\n size=\"large\"\n disabled={\n !hostId ||\n status === 'sending' ||\n variant?.soldOut ||\n // No payments on this deployment: the same 501 every time, so\n // the control latches off rather than inviting a retry that\n // cannot succeed (AGL-2019).\n status === 'unconfigured' ||\n // Asked but unanswered: the server would only refuse again.\n (shipCountries !== null && !shipTo)\n }\n onClick={handleBuy}\n sx={{ flex: 1 }}\n >\n {variant?.soldOut\n ? 'Sold out'\n : status === 'sending'\n ? // \"Redirecting\" is a lie once the form opens in place, and\n // it is the shopper's only clue about what is about to\n // happen to their page (AGL-1944).\n nativeCheckout\n ? 'Opening…'\n : 'Redirecting…'\n : buyLabel || (subscribing ? 'Subscribe' : 'Buy now')}\n </Button>\n </Box>\n {nativeCheckout ? (\n <Suspense fallback={<StorefrontPaymentElementFallback />}>\n <StorefrontPaymentElement\n clientSecret={nativeCheckout.clientSecret}\n publishableKey={nativeCheckout.publishableKey}\n payLabel={buyLabel || (subscribing ? 'Subscribe' : 'Pay now')}\n // Cancelling drops the form and leaves the session open —\n // deliberately. The shopper may come back to the same\n // attempt key, and an abandoned session is what the AGL-323\n // recovery emails are built on. Nothing is expired from the\n // browser: that is a write against the merchant's account.\n onCancel={() => setNativeCheckout(null)}\n />\n </Suspense>\n ) : null}\n {shipCountries ? (\n <TextField\n select\n label=\"Ship to\"\n value={shipTo}\n onChange={(event) => setShipTo(event.target.value)}\n size=\"small\"\n fullWidth\n helperText=\"Shipping rates depend on where this is going\"\n sx={{ mb: 2 }}\n >\n {shipCountries.map((code) => (\n <MenuItem key={code} value={code}>\n {CommerceModel.CHECKOUT_SHIPPING_COUNTRY_NAMES[code] ?? code}\n </MenuItem>\n ))}\n </TextField>\n ) : null}\n {status === 'ask' || status === 'unconfigured' ? (\n <Alert severity=\"info\" sx={{ mb: 2 }}>\n {message}\n </Alert>\n ) : null}\n {status === 'error' ? (\n <Alert severity=\"error\" sx={{ mb: 2 }}>\n {message || 'Checkout is unavailable right now.'}\n </Alert>\n ) : null}\n {variant?.soldOut && hostId ? (\n notifyState === 'done' ? (\n <Alert severity=\"success\" sx={{ mb: 2 }}>\n {'We will email you when it is back.'}\n </Alert>\n ) : (\n <Box sx={{ display: 'flex', gap: 1, mb: 2 }}>\n <TextField\n placeholder=\"you@example.com\"\n type=\"email\"\n // Third instance of the nameless-input defect (AGL-2392).\n // Named for its job rather than \"Email address\": this row\n // appears inside a product page that may also carry the\n // newsletter block, and two fields both announced as\n // \"Email address\" is barely better than none.\n slotProps={{\n htmlInput: {\n 'aria-label': 'Email address for back-in-stock alert',\n },\n }}\n value={notifyEmail}\n onChange={(event) => setNotifyEmail(event.target.value)}\n size=\"small\"\n sx={{ flex: 1 }}\n />\n <Button\n size=\"small\"\n variant=\"outlined\"\n disabled={!notifyEmail.trim()}\n onClick={async () => {\n const response = await siteFetch(\n '/api/commerce/notify-restock',\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n hostId,\n productId: resolved.id,\n email: notifyEmail,\n }),\n },\n ).catch(() => null)\n if (response?.ok) setNotifyState('done')\n }}\n >\n {'Notify me'}\n </Button>\n </Box>\n )\n ) : null}\n {!hideDescription && resolved.description ? (\n <Typography variant=\"body1\" color=\"text.secondary\">\n {resolved.description}\n </Typography>\n ) : null}\n </Box>\n </Box>\n )\n },\n)\nProductDetail.displayName = 'AglynProductDetail'\n\nexport const schema: Aglyn.ComponentSchema<ProductDetailProps> = {\n $id: ID,\n pluginId: BUNDLE_ID,\n displayName: 'Product detail',\n description: \"One product's gallery, options and buy button.\",\n category: Aglyn.ComponentCategory.COMMERCE,\n icon: { path: mdiTagOutline.path, sx: { color: '#2e7d32' } },\n flags: { selfClosing: Aglyn.FEATURE_FLAG.ENABLED },\n attributes: [\n {\n name: 'slug',\n label: 'Product slug',\n description:\n 'Pin to one product; blank follows the /products/{slug} URL ' +\n '(product page template).',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'buyLabel',\n label: 'Buy button label',\n description: 'Defaults to \"Buy now\".',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'hideDescription',\n label: 'Hide description',\n description: 'Design the description separately with tokens.',\n component: Aglyn.FieldComponentType.CHECKBOX,\n },\n {\n // Off by default, so no existing page changes shape on deploy. The cart\n // has carried this field all along and the product page had none, so a\n // shopper buying the same goods through Buy now had nowhere to enter a\n // code the merchant had advertised.\n name: 'showCoupon',\n label: 'Show discount code field',\n description: 'Lets a buyer enter a discount or coupon code before buying.',\n component: Aglyn.FieldComponentType.CHECKBOX,\n },\n ],\n}\n\nexport const presets: Aglyn.PresetSchema[] = [\n {\n $id: generatePresetId(ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Product detail',\n pluginId: BUNDLE_ID,\n description: 'Gallery, variant picker, and buy button for one product',\n category: Aglyn.ComponentCategory.COMMERCE,\n icon: { path: mdiTagOutline.path, sx: { color: '#2e7d32' } },\n data: {\n $id: null,\n componentId: ID,\n pluginId: BUNDLE_ID,\n props: {},\n },\n },\n {\n // Product page (AGL-561): the commerce-standard PDP subtree —\n // breadcrumb, detail, related products, reviews — for the product\n // page template screen. `{{product.name}}` resolves there via the\n // site page resolver's token pass (AGL-292); node shapes follow\n // the Sections & Blocks convention (AGL-539) and reference only\n // persisted component ids.\n $id: generatePresetId(ID, 'page'),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Product page',\n pluginId: BUNDLE_ID,\n description:\n 'Breadcrumb, product detail, related products, and reviews',\n category: Aglyn.ComponentCategory.COMMERCE,\n icon: { path: mdiTagOutline.path, sx: { color: '#2e7d32' } },\n data: {\n $id: null,\n componentId: 'muiStack',\n pluginId: Aglyn.MUI_BUNDLE_ID,\n props: { spacing: 4 },\n // Styling on the node's own sx, never in props (AGL-1346): both\n // records render, but only `node.sx` is the one the Styles panel can\n // read, edit or clear.\n sx: { paddingTop: 2, paddingBottom: 2 },\n nodes: [\n {\n $id: null,\n componentId: 'muiStack',\n pluginId: Aglyn.MUI_BUNDLE_ID,\n props: { direction: 'row', spacing: 1 },\n sx: { alignItems: 'center' },\n nodes: [\n {\n $id: null,\n componentId: 'muiScreenLink',\n pluginId: Aglyn.MUI_BUNDLE_ID,\n props: { children: 'Shop', size: 'small', color: 'inherit' },\n },\n {\n $id: null,\n componentId: 'muiTypography',\n pluginId: Aglyn.MUI_BUNDLE_ID,\n props: { variant: 'body2', children: '/ {{product.name}}' },\n },\n ],\n },\n {\n $id: null,\n componentId: ID,\n pluginId: BUNDLE_ID,\n props: {},\n },\n {\n $id: null,\n componentId: RELATED_PRODUCTS_ID,\n pluginId: BUNDLE_ID,\n props: {},\n },\n {\n $id: null,\n componentId: PRODUCT_REVIEWS_ID,\n pluginId: BUNDLE_ID,\n props: {},\n },\n ],\n },\n },\n]\n\nexport default ProductDetail\n"],"names":["Aglyn","buildAddToCartParams","buildBeginCheckoutParams","trackEvent","trackEventBeforeNavigation","isPaymentsNotConfigured","storefrontPaymentsNotConfiguredText","CommerceModel","mdiTagOutline","Alert","Box","Button","Chip","MenuItem","Skeleton","TextField","ToggleButton","ToggleButtonGroup","Typography","Suspense","forwardRef","lazy","useEffect","useMemo","useRef","useState","BUNDLE_ID","generatePresetId","useStorefrontPurchaseEvent","CART_UPDATED_EVENT","ID","PRODUCT_REVIEWS_ID","RELATED_PRODUCTS_ID","readLocalWishlist","toggleWishlist","StorefrontPaymentElementFallback","StorefrontPaymentElement","then","module","default","SAMPLE","id","name","slug","description","mediaUrls","options","values","variants","Size","priceUsd","soldOut","buyItem","product","variant","quantity","item_id","item_name","price","slugFromLocation","window","match","location","pathname","decodeURIComponent","ProductDetail","props","ref","site","slugProp","buyLabel","hideDescription","showCoupon","rest","nodeSx","Array","isArray","useSite","siteFetch","useSiteFetch","hostId","seededProduct","pageData","commerce","detail","setDetail","selections","setSelections","setQuantity","coupon","setCoupon","activeImage","setActiveImage","status","setStatus","message","setMessage","shipTo","setShipTo","shipCountries","setShipCountries","added","setAdded","wishlisted","setWishlisted","notifyEmail","setNotifyEmail","notifyState","setNotifyState","billing","setBilling","nativeCheckout","setNativeCheckout","resolvedId","includes","active","fetch","encodeURIComponent","response","ok","json","payload","items","first","find","catch","resolved","undefined","findVariant","handleAddToCart","method","headers","body","JSON","stringify","action","productId","variantId","dispatchEvent","Event","attemptKey","current","handleBuy","globalThis","crypto","randomUUID","Date","now","Math","random","toString","slice","subscriptionOptional","shippingCountry","trim","couponCode","clientSecret","publishableKey","String","url","assign","needsShippingCountry","shippingCountries","length","CHECKOUT_SHIPPING_COUNTRIES","error","sx","display","gap","width","height","flex","p","color","galleryImage","siteRelativeMediaSrc","imageUrl","subscription","intervalSuffix","interval","subscribing","Boolean","structuredData","image","offers","priceCurrency","availability","flexDirection","xs","md","minWidth","component","src","alt","aspectRatio","objectFit","borderRadius","bgcolor","mt","flexWrap","map","index","onClick","cursor","border","borderColor","DEFERRED_IMAGE_ATTRIBUTES","script","type","dangerouslySetInnerHTML","__html","safeJsonLd","gutterBottom","alignItems","mb","trialDays","compareAtPriceUsd","textDecoration","label","size","value","exclusive","onChange","_event","option","event","prev","target","select","fullWidth","max","min","round","Number","slotProps","htmlInput","inputMode","disabled","fallback","payLabel","onCancel","helperText","code","CHECKOUT_SHIPPING_COUNTRY_NAMES","severity","placeholder","email","displayName","schema","$id","pluginId","category","ComponentCategory","COMMERCE","icon","path","flags","selfClosing","FEATURE_FLAG","ENABLED","attributes","FieldComponentType","TEXT_FIELD","CHECKBOX","presets","NodeType","PRESET","data","componentId","MUI_BUNDLE_ID","spacing","paddingTop","paddingBottom","nodes","direction","children"],"mappings":"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"}