@aglyn/plugins-commerce 1.0.0-beta.243 → 1.0.0-beta.246

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 (57) hide show
  1. package/README.md +4 -1
  2. package/package.json +13 -13
  3. package/src/lib/components/commerce-console-page.js +10 -0
  4. package/src/lib/components/commerce-console-page.js.map +1 -1
  5. package/src/lib/components/console/product-zones.d.ts +7 -1
  6. package/src/lib/components/console/product-zones.js.map +1 -1
  7. package/src/lib/components/console/products-hub-zone.component.d.ts +3 -2
  8. package/src/lib/components/console/products-hub-zone.component.js +9 -1
  9. package/src/lib/components/console/products-hub-zone.component.js.map +1 -1
  10. package/src/lib/components/console/review-settings-card.component.d.ts +14 -0
  11. package/src/lib/components/console/review-settings-card.component.js +168 -0
  12. package/src/lib/components/console/review-settings-card.component.js.map +1 -0
  13. package/src/lib/components/console/reviews-moderation-card.component.d.ts +14 -0
  14. package/src/lib/components/console/reviews-moderation-card.component.js +85 -11
  15. package/src/lib/components/console/reviews-moderation-card.component.js.map +1 -1
  16. package/src/lib/components/product-reviews.d.ts +29 -1
  17. package/src/lib/components/product-reviews.js +97 -12
  18. package/src/lib/components/product-reviews.js.map +1 -1
  19. package/src/lib/model/commerce.d.ts +11 -2
  20. package/src/lib/model/commerce.js +14 -2
  21. package/src/lib/model/commerce.js.map +1 -1
  22. package/src/lib/model/index.d.ts +1 -0
  23. package/src/lib/model/index.js +1 -0
  24. package/src/lib/model/index.js.map +1 -1
  25. package/src/lib/model/product-reviews.d.ts +124 -0
  26. package/src/lib/model/product-reviews.js +133 -0
  27. package/src/lib/model/product-reviews.js.map +1 -0
  28. package/src/lib/model/store-setup-content.d.ts +145 -0
  29. package/src/lib/model/store-setup-content.js +813 -0
  30. package/src/lib/model/store-setup-content.js.map +1 -0
  31. package/src/lib/server/membership-reset.js +8 -2
  32. package/src/lib/server/membership-reset.js.map +1 -1
  33. package/src/lib/server/order-event-triggers.d.ts +2 -0
  34. package/src/lib/server/order-event-triggers.js +18 -1
  35. package/src/lib/server/order-event-triggers.js.map +1 -1
  36. package/src/lib/server/review-requests.d.ts +102 -0
  37. package/src/lib/server/review-requests.js +346 -0
  38. package/src/lib/server/review-requests.js.map +1 -0
  39. package/src/lib/server/review-tokens.d.ts +82 -0
  40. package/src/lib/server/review-tokens.js +119 -0
  41. package/src/lib/server/review-tokens.js.map +1 -0
  42. package/src/lib/server/reviews.d.ts +36 -3
  43. package/src/lib/server/reviews.js +310 -52
  44. package/src/lib/server/reviews.js.map +1 -1
  45. package/src/lib/server/store-setup-ai-capability.d.ts +61 -0
  46. package/src/lib/server/store-setup-ai-capability.js +343 -0
  47. package/src/lib/server/store-setup-ai-capability.js.map +1 -0
  48. package/src/lib/server/store-setup-ai-writer.console.d.ts +27 -0
  49. package/src/lib/server/store-setup-ai-writer.console.js +44 -0
  50. package/src/lib/server/store-setup-ai-writer.console.js.map +1 -0
  51. package/src/lib/server/store-setup-ai-writer.d.ts +37 -0
  52. package/src/lib/server/store-setup-ai-writer.js +174 -0
  53. package/src/lib/server/store-setup-ai-writer.js.map +1 -0
  54. package/src/lib/server.js +27 -0
  55. package/src/lib/server.js.map +1 -1
  56. package/src/lib/tenant-emails.js +58 -0
  57. package/src/lib/tenant-emails.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/components/product-reviews.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 { mdiStarOutline } 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 Divider from '@mui/material/Divider'\nimport Rating from '@mui/material/Rating'\nimport TextField from '@mui/material/TextField'\nimport Typography from '@mui/material/Typography'\nimport { forwardRef, useEffect, useState } from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport { generatePresetId } from '../utils/generate-preset-id'\n\n// Component ids are persisted in screen documents; never rename.\nexport const ID: Aglyn.ComponentId = 'product-reviews'\n\nexport interface ProductReviewsProps {\n /** Product id; blank resolves from the /products/{slug} page product. */\n productId?: string\n heading?: string\n /**\n * `product` (default): one product's reviews with the submit form.\n * `store`: the store's latest approved reviews across every product, with\n * the average and count, and no form — for a storefront home. In store\n * scope the block draws its own heading (`heading`, default \"What\n * customers say\") and draws NOTHING when there are no approved reviews,\n * so a page never shows an empty reviews band.\n */\n scope?: 'product' | 'store'\n /** Store scope: how many reviews to show (default 6, at most 12). */\n maxItems?: number\n /** Store scope: the heading's level, 2–4 (default 2). */\n headingLevel?: 2 | 3 | 4 | '2' | '3' | '4'\n}\n\ninterface StoreReviewView {\n id: string\n rating: number\n body: string\n authorName: string\n verified: boolean\n createdAtMs: number\n productName?: string\n productSlug?: string\n}\n\nconst EDITOR_HINT_SX = {\n p: 3,\n border: '1px dashed',\n borderColor: 'divider',\n borderRadius: 1,\n color: 'text.secondary',\n fontSize: 13,\n fontFamily: 'system-ui, sans-serif',\n} as const\n\ninterface ReviewView {\n id: string\n rating: number\n body: string\n authorName: string\n verified: boolean\n reply?: string | null\n createdAtMs: number\n}\n\nasync function resolveProductIdFromSlug(hostId: string): Promise<string> {\n const match = window.location.pathname.match(/\\/products\\/([^/?#]+)/)\n if (!match) return ''\n const response = await fetch(\n `/api/commerce/product?hostId=${encodeURIComponent(hostId)}` +\n `&slug=${encodeURIComponent(decodeURIComponent(match[1]))}`,\n ).catch(() => null)\n if (!response?.ok) return ''\n const payload = await response.json().catch(() => ({}))\n return String(payload?.product?.id ?? '')\n}\n\n/**\n * Product reviews block (AGL-324): approved reviews with verified-buyer\n * badges, seller replies, an aggregate summary (emitted as JSON-LD\n * AggregateRating), and a submit form feeding the moderation queue.\n */\nconst ProductReviews = forwardRef<HTMLDivElement, ProductReviewsProps>(\n (props, ref) => {\n if (props.scope === 'store') return <StoreReviews ref={ref} {...props} />\n return <SingleProductReviews ref={ref} {...props} />\n },\n)\nProductReviews.displayName = 'AglynProductReviews'\n\n/**\n * The store-wide reviews band (`scope: 'store'`): the store's latest APPROVED\n * reviews, read-only stars, the reviewer's first name and initial, a verified\n * badge, and the product reviewed. Real reviews or nothing: with none it\n * renders an empty, zero-height element — never sample reviews on a live page\n * (sample testimonials presented as customers' would be fake reviews). The\n * editor, which has no site, gets a dashed hint instead.\n */\nconst StoreReviews = forwardRef<HTMLDivElement, ProductReviewsProps>(\n (props, ref) => {\n const {\n productId: _productId,\n heading,\n scope: _scope,\n maxItems,\n headingLevel,\n ...rest\n } = props\n const nodeSx = Array.isArray(props['sx']) ? props['sx'] : [props['sx']]\n const { hostId } = Aglyn.useSite()\n const [payload, setPayload] = useState<{\n reviews: StoreReviewView[]\n aggregate: { count: number; average: number }\n } | null>(null)\n const limit = maxItems && Number(maxItems) > 0 ? Math.min(12, Math.floor(Number(maxItems))) : 6\n\n useEffect(() => {\n if (!hostId) return\n let active = true\n void fetch(\n `/api/commerce/reviews?hostId=${encodeURIComponent(hostId)}` +\n `&scope=store&limit=${limit}`,\n )\n .then((response) => (response.ok ? response.json() : null))\n .then((body) => {\n if (!active || !body) return\n setPayload({\n reviews: Array.isArray(body.reviews) ? body.reviews : [],\n aggregate: body.aggregate ?? { count: 0, average: 0 },\n })\n })\n .catch(() => undefined)\n return () => {\n active = false\n }\n }, [hostId, limit])\n\n if (!hostId) {\n return (\n <Box ref={ref} {...rest} sx={[EDITOR_HINT_SX, ...nodeSx]}>\n {'★★★★★ Customer reviews appear here once shoppers review your products'}\n </Box>\n )\n }\n // Loading, failed, or none approved: nothing at all — not the node's\n // padding, not a heading over an empty band.\n if (!payload || payload.reviews.length === 0) {\n return <Box ref={ref} data-store-reviews-empty=\"\" sx={{ display: 'none' }} />\n }\n // A select may store the level as a string; read it as a number.\n const asked = Number(headingLevel)\n const level = asked === 3 || asked === 4 ? asked : 2\n const { aggregate } = payload\n return (\n <Box\n ref={ref}\n {...rest}\n sx={[{ display: 'flex', flexDirection: 'column', gap: 3 }, ...nodeSx]}\n >\n <Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 1, textAlign: 'center' }}>\n <Typography variant={level === 2 ? 'h4' : level === 3 ? 'h5' : 'h6'} component={`h${level}`}>\n {heading || 'What customers say'}\n </Typography>\n {aggregate.count > 0 ? (\n <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>\n <Rating value={aggregate.average} precision={0.1} readOnly size=\"small\" />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`${aggregate.average} out of 5 · ${aggregate.count} ${aggregate.count === 1 ? 'review' : 'reviews'}`}\n </Typography>\n </Box>\n ) : null}\n </Box>\n <Box\n sx={{\n display: 'grid',\n gap: 2,\n gridTemplateColumns: { xs: '1fr', sm: 'repeat(2, 1fr)', md: 'repeat(3, 1fr)' },\n }}\n >\n {payload.reviews.map((review) => (\n <Box\n key={review.id}\n component=\"figure\"\n sx={{\n m: 0,\n p: 2.5,\n display: 'flex',\n flexDirection: 'column',\n gap: 1,\n border: 1,\n borderColor: 'divider',\n borderRadius: 2,\n bgcolor: 'background.paper',\n }}\n >\n <Rating value={review.rating} readOnly size=\"small\" />\n <Typography component=\"blockquote\" variant=\"body1\" sx={{ m: 0, flex: 1 }}>\n {review.body}\n </Typography>\n <Box component=\"figcaption\" sx={{ display: 'flex', flexWrap: 'wrap', gap: 1, alignItems: 'center' }}>\n <Typography variant=\"body2\" sx={{ fontWeight: 600 }}>\n {review.authorName}\n </Typography>\n {review.verified ? (\n <Chip label=\"Verified buyer\" size=\"small\" variant=\"outlined\" color=\"success\" />\n ) : null}\n </Box>\n {review.productName ? (\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n component={review.productSlug ? 'a' : 'span'}\n {...(review.productSlug ? { href: `/products/${review.productSlug}` } : {})}\n sx={{ textDecoration: 'none' }}\n >\n {review.productName}\n </Typography>\n ) : null}\n </Box>\n ))}\n </Box>\n </Box>\n )\n },\n)\nStoreReviews.displayName = 'AglynStoreReviews'\n\nconst SingleProductReviews = forwardRef<HTMLDivElement, ProductReviewsProps>(\n (props, ref) => {\n const {\n productId: productIdProp,\n heading,\n scope: _scope,\n maxItems: _maxItems,\n headingLevel: _headingLevel,\n ...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 { hostId } = Aglyn.useSite()\n const siteFetch = Aglyn.useSiteFetch()\n const [productId, setProductId] = useState(productIdProp ?? '')\n const [reviews, setReviews] = useState<ReviewView[] | null>(null)\n const [aggregate, setAggregate] = useState({ count: 0, average: 0 })\n const [form, setForm] = useState({\n rating: 5,\n body: '',\n authorName: '',\n authorEmail: '',\n })\n const [state, setState] = useState<'idle' | 'busy' | 'done' | 'error'>(\n 'idle',\n )\n\n useEffect(() => {\n if (!hostId) return\n let active = true\n void (async () => {\n const resolved =\n productIdProp || (await resolveProductIdFromSlug(hostId))\n if (!active || !resolved) return\n setProductId(resolved)\n const response = await fetch(\n `/api/commerce/reviews?hostId=${encodeURIComponent(hostId)}` +\n `&productId=${encodeURIComponent(resolved)}`,\n ).catch(() => null)\n if (!active || !response?.ok) return\n const payload = await response.json().catch(() => ({}))\n setReviews(payload?.reviews ?? [])\n setAggregate(payload?.aggregate ?? { count: 0, average: 0 })\n })()\n return () => {\n active = false\n }\n }, [hostId, productIdProp])\n\n const handleSubmit = async () => {\n if (!hostId || !productId || state === 'busy') return\n setState('busy')\n try {\n const response = await siteFetch('/api/commerce/reviews', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ hostId, productId, ...form }),\n })\n setState(response.ok ? 'done' : 'error')\n } catch {\n setState('error')\n }\n }\n\n if (!hostId) {\n return (\n <Box ref={ref} {...rest} sx={[EDITOR_HINT_SX, ...nodeSx]}>\n {'★★★★★ Product reviews render here'}\n </Box>\n )\n }\n\n return (\n <Box ref={ref} {...rest} sx={[{ display: 'flex', flexDirection: 'column', gap: 1.5 }, ...nodeSx]}>\n {/* No JSON-LD here any more (AGL-686). This block used to emit a\n free-standing `AggregateRating` node, which schema.org ignores —\n a rating has to be a PROPERTY of the Product it rates. The PDP's\n server-rendered Product node now carries `aggregateRating`,\n built from the same `readProductReviews` reader this block's API\n uses, so the structured data and the visible stars cannot\n disagree. It also removes the one call site that was primed to\n become stored XSS the moment a reviewer name went into it. */}\n <Typography variant=\"h6\">\n {heading || 'Reviews'}\n {aggregate.count > 0 ? ` (${aggregate.average} ★ · ${aggregate.count})` : ''}\n </Typography>\n {(reviews ?? []).map((review) => (\n <Box key={review.id}>\n <Box sx={{ display: 'flex', gap: 1, alignItems: 'center' }}>\n <Rating value={review.rating} size=\"small\" readOnly />\n <Typography variant=\"body2\" sx={{ fontWeight: 600 }}>\n {review.authorName}\n </Typography>\n {review.verified ? (\n <Chip label=\"Verified buyer\" size=\"small\" variant=\"outlined\" color=\"success\" />\n ) : null}\n </Box>\n <Typography variant=\"body2\" sx={{ mt: 0.5 }}>\n {review.body}\n </Typography>\n {review.reply ? (\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ display: 'block', pl: 2, mt: 0.5, borderLeft: 2, borderColor: 'divider' }}\n >\n {`Seller: ${review.reply}`}\n </Typography>\n ) : null}\n <Divider sx={{ mt: 1.5 }} />\n </Box>\n ))}\n {reviews && reviews.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No reviews yet — be the first.'}\n </Typography>\n ) : null}\n {state === 'done' ? (\n <Alert severity=\"success\">\n {'Thanks — your review is awaiting moderation.'}\n </Alert>\n ) : (\n <Box sx={{ display: 'flex', flexDirection: 'column', gap: 1, maxWidth: 480 }}>\n <Typography variant=\"subtitle2\">{'Write a review'}</Typography>\n <Rating\n value={form.rating}\n onChange={(_event, value) =>\n setForm((prev) => ({ ...prev, rating: value ?? 5 }))\n }\n />\n <TextField\n placeholder=\"What did you think?\"\n value={form.body}\n onChange={(event) =>\n setForm((prev) => ({ ...prev, body: event.target.value }))\n }\n size=\"small\"\n multiline\n minRows={2}\n />\n <Box sx={{ display: 'flex', gap: 1 }}>\n <TextField\n placeholder=\"Name\"\n value={form.authorName}\n onChange={(event) =>\n setForm((prev) => ({ ...prev, authorName: event.target.value }))\n }\n size=\"small\"\n sx={{ flex: 1 }}\n />\n <TextField\n placeholder=\"Email (verifies your purchase)\"\n type=\"email\"\n value={form.authorEmail}\n onChange={(event) =>\n setForm((prev) => ({ ...prev, authorEmail: event.target.value }))\n }\n size=\"small\"\n sx={{ flex: 1.4 }}\n />\n </Box>\n {state === 'error' ? (\n <Alert severity=\"error\">{'Could not submit — check the fields.'}</Alert>\n ) : null}\n <Button\n variant=\"outlined\"\n size=\"small\"\n disabled={state === 'busy' || !form.body.trim() || !form.authorEmail.trim()}\n onClick={handleSubmit}\n sx={{ alignSelf: 'flex-start' }}\n >\n {'Submit review'}\n </Button>\n </Box>\n )}\n </Box>\n )\n },\n)\nSingleProductReviews.displayName = 'AglynSingleProductReviews'\n\nexport const schema: Aglyn.ComponentSchema<ProductReviewsProps> = {\n $id: ID,\n pluginId: BUNDLE_ID,\n displayName: 'Product reviews',\n description:\n 'Approved reviews with verified-buyer badges, and a form to add one.',\n category: Aglyn.ComponentCategory.COMMERCE,\n icon: { path: mdiStarOutline.path, sx: { color: '#2e7d32' } },\n flags: { selfClosing: Aglyn.FEATURE_FLAG.ENABLED },\n attributes: [\n {\n name: 'productId',\n label: 'Product id',\n description:\n 'Whose reviews these are. Blank reads the product from the URL, ' +\n 'which is what you want on a product template; naming one pins the ' +\n 'block to that product wherever it is placed.',\n component: Aglyn.FieldComponentType.PRODUCT_SELECT,\n },\n {\n name: 'heading',\n label: 'Heading',\n description: 'Defaults to \"Reviews\" (\"What customers say\" store-wide).',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n // Default `product`, so a page that already places this block keeps it.\n name: 'scope',\n label: 'Show',\n description:\n 'One product’s reviews with a form, or the store’s latest approved ' +\n 'reviews across every product. Store-wide shows nothing until a ' +\n 'shopper’s review is approved.',\n component: Aglyn.FieldComponentType.SELECT,\n options: [\n { label: 'This product', value: 'product' },\n { label: 'Whole store', value: 'store' },\n ],\n },\n {\n name: 'maxItems',\n label: 'Max reviews',\n description: 'Store-wide: how many to show (default 6, at most 12).',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n type: 'number',\n },\n {\n name: 'headingLevel',\n label: 'Heading level',\n description: 'Store-wide: the heading’s level on the page (default H2).',\n component: Aglyn.FieldComponentType.SELECT,\n options: [\n { label: 'H2', value: '2' },\n { label: 'H3', value: '3' },\n { label: 'H4', value: '4' },\n ],\n },\n ],\n}\n\nexport const presets: Aglyn.PresetSchema[] = [\n {\n $id: generatePresetId(ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Product reviews',\n pluginId: BUNDLE_ID,\n description: 'Verified-buyer reviews with a submit form',\n category: Aglyn.ComponentCategory.COMMERCE,\n icon: { path: mdiStarOutline.path, sx: { color: '#2e7d32' } },\n data: {\n $id: null,\n componentId: ID,\n pluginId: BUNDLE_ID,\n props: {},\n },\n },\n]\n\nexport default ProductReviews\n"],"names":["Aglyn","mdiStarOutline","Alert","Box","Button","Chip","Divider","Rating","TextField","Typography","forwardRef","useEffect","useState","BUNDLE_ID","generatePresetId","ID","EDITOR_HINT_SX","p","border","borderColor","borderRadius","color","fontSize","fontFamily","resolveProductIdFromSlug","hostId","payload","match","window","location","pathname","response","fetch","encodeURIComponent","decodeURIComponent","catch","ok","json","String","product","id","ProductReviews","props","ref","scope","StoreReviews","SingleProductReviews","displayName","productId","_productId","heading","_scope","maxItems","headingLevel","rest","nodeSx","Array","isArray","useSite","setPayload","limit","Number","Math","min","floor","active","then","body","reviews","aggregate","count","average","undefined","sx","length","data-store-reviews-empty","display","asked","level","flexDirection","gap","alignItems","textAlign","variant","component","value","precision","readOnly","size","gridTemplateColumns","xs","sm","md","map","review","m","bgcolor","rating","flex","flexWrap","fontWeight","authorName","verified","label","productName","productSlug","href","textDecoration","productIdProp","_maxItems","_headingLevel","siteFetch","useSiteFetch","setProductId","setReviews","setAggregate","form","setForm","authorEmail","state","setState","resolved","handleSubmit","method","headers","JSON","stringify","mt","reply","pl","borderLeft","severity","maxWidth","onChange","_event","prev","placeholder","event","target","multiline","minRows","type","disabled","trim","onClick","alignSelf","schema","$id","pluginId","description","category","ComponentCategory","COMMERCE","icon","path","flags","selfClosing","FEATURE_FLAG","ENABLED","attributes","name","FieldComponentType","PRODUCT_SELECT","TEXT_FIELD","SELECT","options","presets","NodeType","PRESET","data","componentId"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/components/product-reviews.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 { mdiStarOutline } 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 Divider from '@mui/material/Divider'\nimport Rating from '@mui/material/Rating'\nimport TextField from '@mui/material/TextField'\nimport Typography from '@mui/material/Typography'\nimport { forwardRef, useEffect, useState } from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport { generatePresetId } from '../utils/generate-preset-id'\n\n// Component ids are persisted in screen documents; never rename.\nexport const ID: Aglyn.ComponentId = 'product-reviews'\n\nexport interface ProductReviewsProps {\n /** Product id; blank resolves from the /products/{slug} page product. */\n productId?: string\n heading?: string\n /**\n * `product` (default): one product's reviews with the submit form.\n * `store`: the store's latest approved reviews across every product, with\n * the average and count, and no form — for a storefront home. In store\n * scope the block draws its own heading (`heading`, default \"What\n * customers say\") and draws NOTHING when there are no approved reviews,\n * so a page never shows an empty reviews band.\n */\n scope?: 'product' | 'store'\n /** Store scope: how many reviews to show (default 6, at most 12). */\n maxItems?: number\n /** Store scope: the heading's level, 2–4 (default 2). */\n headingLevel?: 2 | 3 | 4 | '2' | '3' | '4'\n}\n\ninterface StoreReviewView {\n id: string\n rating: number\n body: string\n authorName: string\n verified: boolean\n createdAtMs: number\n productName?: string\n productSlug?: string\n}\n\nconst EDITOR_HINT_SX = {\n p: 3,\n border: '1px dashed',\n borderColor: 'divider',\n borderRadius: 1,\n color: 'text.secondary',\n fontSize: 13,\n fontFamily: 'system-ui, sans-serif',\n} as const\n\ninterface ReviewView {\n id: string\n rating: number\n body: string\n authorName: string\n verified: boolean\n reply?: string | null\n createdAtMs: number\n}\n\n/** What `GET /api/commerce/reviews?mode=eligibility` answers (AGL-3736). */\nexport interface ReviewFormEligibility {\n enabled?: boolean\n policy?: 'verified' | 'anyone'\n signedIn?: boolean\n verifiedVia?: 'member' | 'token' | null\n canReview?: boolean\n needsEmail?: boolean\n tokenProblem?: string\n}\n\n/** Where a shopper signs in to their account on the store. */\nexport const REVIEW_SIGN_IN_HREF = '/account'\n\n/** The review link from the review-request email, if the page carries one. */\nexport function reviewTokenFromLocation(search: string): string {\n const token = new URLSearchParams(search).get('review') ?? ''\n return /^[A-Za-z0-9_-]{32}$/.test(token) ? token : ''\n}\n\n/**\n * Which form the block draws, from the server's answer. Pure, so the three\n * states are specified in one place:\n * - `form`: the visitor may review (verified or, under `anyone`, pending);\n * - `sign-in`: the store takes verified reviews only and this visitor is\n * not one — \"Only customers who bought this can review.\" and a sign-in\n * link (no form: a typed email verifies nothing);\n * - `none`: reviews are off on this plan, or the answer has not come yet.\n */\nexport function reviewFormMode(\n eligibility: ReviewFormEligibility | null,\n): 'form' | 'sign-in' | 'none' {\n if (!eligibility || eligibility.enabled === false) return 'none'\n if (eligibility.canReview) return 'form'\n return 'sign-in'\n}\n\nconst TOKEN_PROBLEM_TEXT: Record<string, string> = {\n review_token_used: 'This review link has already been used.',\n review_token_expired: 'This review link has expired.',\n review_token_invalid: 'This review link is not valid for this product.',\n}\n\nasync function resolveProductIdFromSlug(hostId: string): Promise<string> {\n const match = window.location.pathname.match(/\\/products\\/([^/?#]+)/)\n if (!match) return ''\n const response = await fetch(\n `/api/commerce/product?hostId=${encodeURIComponent(hostId)}` +\n `&slug=${encodeURIComponent(decodeURIComponent(match[1]))}`,\n ).catch(() => null)\n if (!response?.ok) return ''\n const payload = await response.json().catch(() => ({}))\n return String(payload?.product?.id ?? '')\n}\n\n/**\n * Product reviews block (AGL-324): approved reviews with verified-buyer\n * badges, seller replies, an aggregate summary (emitted as JSON-LD\n * AggregateRating), and a submit form. Since AGL-3736 the form asks the server\n * who the visitor is first: a verified buyer (signed in, or arriving with the\n * review-request email's link) gets the form and publishes at once; anyone\n * else sees \"Only customers who bought this can review\" and a sign-in link,\n * unless the store accepts reviews from anyone (held for moderation).\n */\nconst ProductReviews = forwardRef<HTMLDivElement, ProductReviewsProps>(\n (props, ref) => {\n if (props.scope === 'store') return <StoreReviews ref={ref} {...props} />\n return <SingleProductReviews ref={ref} {...props} />\n },\n)\nProductReviews.displayName = 'AglynProductReviews'\n\n/**\n * The store-wide reviews band (`scope: 'store'`): the store's latest APPROVED\n * reviews, read-only stars, the reviewer's first name and initial, a verified\n * badge, and the product reviewed. Real reviews or nothing: with none it\n * renders an empty, zero-height element — never sample reviews on a live page\n * (sample testimonials presented as customers' would be fake reviews). The\n * editor, which has no site, gets a dashed hint instead.\n */\nconst StoreReviews = forwardRef<HTMLDivElement, ProductReviewsProps>(\n (props, ref) => {\n const {\n productId: _productId,\n heading,\n scope: _scope,\n maxItems,\n headingLevel,\n ...rest\n } = props\n const nodeSx = Array.isArray(props['sx']) ? props['sx'] : [props['sx']]\n const { hostId } = Aglyn.useSite()\n const [payload, setPayload] = useState<{\n reviews: StoreReviewView[]\n aggregate: { count: number; average: number }\n } | null>(null)\n const limit = maxItems && Number(maxItems) > 0 ? Math.min(12, Math.floor(Number(maxItems))) : 6\n\n useEffect(() => {\n if (!hostId) return\n let active = true\n void fetch(\n `/api/commerce/reviews?hostId=${encodeURIComponent(hostId)}` +\n `&scope=store&limit=${limit}`,\n )\n .then((response) => (response.ok ? response.json() : null))\n .then((body) => {\n if (!active || !body) return\n setPayload({\n reviews: Array.isArray(body.reviews) ? body.reviews : [],\n aggregate: body.aggregate ?? { count: 0, average: 0 },\n })\n })\n .catch(() => undefined)\n return () => {\n active = false\n }\n }, [hostId, limit])\n\n if (!hostId) {\n return (\n <Box ref={ref} {...rest} sx={[EDITOR_HINT_SX, ...nodeSx]}>\n {'★★★★★ Customer reviews appear here once shoppers review your products'}\n </Box>\n )\n }\n // Loading, failed, or none approved: nothing at all — not the node's\n // padding, not a heading over an empty band.\n if (!payload || payload.reviews.length === 0) {\n return <Box ref={ref} data-store-reviews-empty=\"\" sx={{ display: 'none' }} />\n }\n // A select may store the level as a string; read it as a number.\n const asked = Number(headingLevel)\n const level = asked === 3 || asked === 4 ? asked : 2\n const { aggregate } = payload\n return (\n <Box\n ref={ref}\n {...rest}\n sx={[{ display: 'flex', flexDirection: 'column', gap: 3 }, ...nodeSx]}\n >\n <Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 1, textAlign: 'center' }}>\n <Typography variant={level === 2 ? 'h4' : level === 3 ? 'h5' : 'h6'} component={`h${level}`}>\n {heading || 'What customers say'}\n </Typography>\n {aggregate.count > 0 ? (\n <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>\n <Rating value={aggregate.average} precision={0.1} readOnly size=\"small\" />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`${aggregate.average} out of 5 · ${aggregate.count} ${aggregate.count === 1 ? 'review' : 'reviews'}`}\n </Typography>\n </Box>\n ) : null}\n </Box>\n <Box\n sx={{\n display: 'grid',\n gap: 2,\n gridTemplateColumns: { xs: '1fr', sm: 'repeat(2, 1fr)', md: 'repeat(3, 1fr)' },\n }}\n >\n {payload.reviews.map((review) => (\n <Box\n key={review.id}\n component=\"figure\"\n sx={{\n m: 0,\n p: 2.5,\n display: 'flex',\n flexDirection: 'column',\n gap: 1,\n border: 1,\n borderColor: 'divider',\n borderRadius: 2,\n bgcolor: 'background.paper',\n }}\n >\n <Rating value={review.rating} readOnly size=\"small\" />\n <Typography component=\"blockquote\" variant=\"body1\" sx={{ m: 0, flex: 1 }}>\n {review.body}\n </Typography>\n <Box component=\"figcaption\" sx={{ display: 'flex', flexWrap: 'wrap', gap: 1, alignItems: 'center' }}>\n <Typography variant=\"body2\" sx={{ fontWeight: 600 }}>\n {review.authorName}\n </Typography>\n {review.verified ? (\n <Chip label=\"Verified buyer\" size=\"small\" variant=\"outlined\" color=\"success\" />\n ) : null}\n </Box>\n {review.productName ? (\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n component={review.productSlug ? 'a' : 'span'}\n {...(review.productSlug ? { href: `/products/${review.productSlug}` } : {})}\n sx={{ textDecoration: 'none' }}\n >\n {review.productName}\n </Typography>\n ) : null}\n </Box>\n ))}\n </Box>\n </Box>\n )\n },\n)\nStoreReviews.displayName = 'AglynStoreReviews'\n\nconst SingleProductReviews = forwardRef<HTMLDivElement, ProductReviewsProps>(\n (props, ref) => {\n const {\n productId: productIdProp,\n heading,\n scope: _scope,\n maxItems: _maxItems,\n headingLevel: _headingLevel,\n ...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 { hostId } = Aglyn.useSite()\n const siteFetch = Aglyn.useSiteFetch()\n const [productId, setProductId] = useState(productIdProp ?? '')\n const [reviews, setReviews] = useState<ReviewView[] | null>(null)\n const [aggregate, setAggregate] = useState({ count: 0, average: 0 })\n const [form, setForm] = useState({\n rating: 5,\n body: '',\n authorName: '',\n authorEmail: '',\n })\n const [state, setState] = useState<'idle' | 'busy' | 'done' | 'error'>(\n 'idle',\n )\n const [eligibility, setEligibility] = useState<ReviewFormEligibility | null>(null)\n const [reviewToken, setReviewToken] = useState('')\n const [submitted, setSubmitted] = useState<{ pending: boolean } | null>(null)\n const [errorText, setErrorText] = useState('')\n\n useEffect(() => {\n if (!hostId) return\n let active = true\n void (async () => {\n const resolved =\n productIdProp || (await resolveProductIdFromSlug(hostId))\n if (!active || !resolved) return\n setProductId(resolved)\n const response = await fetch(\n `/api/commerce/reviews?hostId=${encodeURIComponent(hostId)}` +\n `&productId=${encodeURIComponent(resolved)}`,\n ).catch(() => null)\n if (!active || !response?.ok) return\n const payload = await response.json().catch(() => ({}))\n setReviews(payload?.reviews ?? [])\n setAggregate(payload?.aggregate ?? { count: 0, average: 0 })\n // May this visitor review, and would it be verified? (AGL-3736)\n const token = reviewTokenFromLocation(window.location.search)\n setReviewToken(token)\n const answer = await fetch(\n `/api/commerce/reviews?hostId=${encodeURIComponent(hostId)}` +\n `&productId=${encodeURIComponent(resolved)}&mode=eligibility` +\n (token ? `&review=${encodeURIComponent(token)}` : ''),\n { credentials: 'same-origin' },\n ).catch(() => null)\n if (!active || !answer?.ok) return\n setEligibility(await answer.json().catch(() => null))\n })()\n return () => {\n active = false\n }\n }, [hostId, productIdProp])\n\n const handleSubmit = async () => {\n if (!hostId || !productId || state === 'busy') return\n setState('busy')\n try {\n const response = await siteFetch('/api/commerce/reviews', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n hostId,\n productId,\n rating: form.rating,\n body: form.body,\n authorName: form.authorName,\n ...(eligibility?.needsEmail ? { authorEmail: form.authorEmail } : {}),\n ...(reviewToken ? { reviewToken } : {}),\n }),\n })\n const payload = await response.json().catch(() => ({}))\n if (response.ok) {\n setSubmitted({ pending: Boolean(payload?.pending) })\n setState('done')\n } else {\n setErrorText(String(payload?.error ?? ''))\n setState('error')\n }\n } catch {\n setState('error')\n }\n }\n\n if (!hostId) {\n return (\n <Box ref={ref} {...rest} sx={[EDITOR_HINT_SX, ...nodeSx]}>\n {'★★★★★ Product reviews render here'}\n </Box>\n )\n }\n\n return (\n <Box ref={ref} {...rest} sx={[{ display: 'flex', flexDirection: 'column', gap: 1.5 }, ...nodeSx]}>\n {/* No JSON-LD here any more (AGL-686). This block used to emit a\n free-standing `AggregateRating` node, which schema.org ignores —\n a rating has to be a PROPERTY of the Product it rates. The PDP's\n server-rendered Product node now carries `aggregateRating`,\n built from the same `readProductReviews` reader this block's API\n uses, so the structured data and the visible stars cannot\n disagree. It also removes the one call site that was primed to\n become stored XSS the moment a reviewer name went into it. */}\n <Typography variant=\"h6\">\n {heading || 'Reviews'}\n {aggregate.count > 0 ? ` (${aggregate.average} ★ · ${aggregate.count})` : ''}\n </Typography>\n {(reviews ?? []).map((review) => (\n <Box key={review.id}>\n <Box sx={{ display: 'flex', gap: 1, alignItems: 'center' }}>\n <Rating value={review.rating} size=\"small\" readOnly />\n <Typography variant=\"body2\" sx={{ fontWeight: 600 }}>\n {review.authorName}\n </Typography>\n {review.verified ? (\n <Chip label=\"Verified buyer\" size=\"small\" variant=\"outlined\" color=\"success\" />\n ) : null}\n </Box>\n <Typography variant=\"body2\" sx={{ mt: 0.5 }}>\n {review.body}\n </Typography>\n {review.reply ? (\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ display: 'block', pl: 2, mt: 0.5, borderLeft: 2, borderColor: 'divider' }}\n >\n {`Seller: ${review.reply}`}\n </Typography>\n ) : null}\n <Divider sx={{ mt: 1.5 }} />\n </Box>\n ))}\n {reviews && reviews.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No reviews yet — be the first.'}\n </Typography>\n ) : null}\n {eligibility?.tokenProblem && TOKEN_PROBLEM_TEXT[eligibility.tokenProblem] ? (\n <Alert severity=\"info\">{TOKEN_PROBLEM_TEXT[eligibility.tokenProblem]}</Alert>\n ) : null}\n {state === 'done' ? (\n <Alert severity=\"success\">\n {submitted?.pending\n ? 'Thanks — your review is awaiting moderation.'\n : 'Thanks — your review is posted.'}\n </Alert>\n ) : reviewFormMode(eligibility) === 'sign-in' ? (\n <Typography variant=\"body2\" color=\"text.secondary\" data-review-sign-in=\"\">\n {'Only customers who bought this can review. '}\n <Box component=\"a\" href={REVIEW_SIGN_IN_HREF} sx={{ color: 'primary.main' }}>\n {'Sign in'}\n </Box>\n </Typography>\n ) : reviewFormMode(eligibility) === 'none' ? null : (\n <Box sx={{ display: 'flex', flexDirection: 'column', gap: 1, maxWidth: 480 }}>\n <Typography variant=\"subtitle2\">{'Write a review'}</Typography>\n {eligibility?.verifiedVia ? (\n <Chip\n label=\"Your review will show as Verified buyer\"\n size=\"small\"\n variant=\"outlined\"\n color=\"success\"\n sx={{ alignSelf: 'flex-start' }}\n />\n ) : null}\n <Rating\n value={form.rating}\n onChange={(_event, value) =>\n setForm((prev) => ({ ...prev, rating: value ?? 5 }))\n }\n />\n <TextField\n placeholder=\"What did you think?\"\n value={form.body}\n onChange={(event) =>\n setForm((prev) => ({ ...prev, body: event.target.value }))\n }\n size=\"small\"\n multiline\n minRows={2}\n />\n <Box sx={{ display: 'flex', gap: 1 }}>\n <TextField\n placeholder=\"Name\"\n value={form.authorName}\n onChange={(event) =>\n setForm((prev) => ({ ...prev, authorName: event.target.value }))\n }\n size=\"small\"\n sx={{ flex: 1 }}\n />\n {eligibility?.needsEmail ? (\n <TextField\n placeholder=\"Email (never shown)\"\n type=\"email\"\n value={form.authorEmail}\n onChange={(event) =>\n setForm((prev) => ({ ...prev, authorEmail: event.target.value }))\n }\n size=\"small\"\n sx={{ flex: 1.4 }}\n />\n ) : null}\n </Box>\n {state === 'error' ? (\n <Alert severity=\"error\">\n {errorText || 'Could not submit — check the fields.'}\n </Alert>\n ) : null}\n <Button\n variant=\"outlined\"\n size=\"small\"\n disabled={\n state === 'busy' ||\n !form.body.trim() ||\n Boolean(eligibility?.needsEmail && !form.authorEmail.trim())\n }\n onClick={handleSubmit}\n sx={{ alignSelf: 'flex-start' }}\n >\n {'Submit review'}\n </Button>\n </Box>\n )}\n </Box>\n )\n },\n)\nSingleProductReviews.displayName = 'AglynSingleProductReviews'\n\nexport const schema: Aglyn.ComponentSchema<ProductReviewsProps> = {\n $id: ID,\n pluginId: BUNDLE_ID,\n displayName: 'Product reviews',\n description:\n 'Approved reviews with verified-buyer badges, and a form for customers ' +\n 'who bought the product to add one.',\n category: Aglyn.ComponentCategory.COMMERCE,\n icon: { path: mdiStarOutline.path, sx: { color: '#2e7d32' } },\n flags: { selfClosing: Aglyn.FEATURE_FLAG.ENABLED },\n attributes: [\n {\n name: 'productId',\n label: 'Product id',\n description:\n 'Whose reviews these are. Blank reads the product from the URL, ' +\n 'which is what you want on a product template; naming one pins the ' +\n 'block to that product wherever it is placed.',\n component: Aglyn.FieldComponentType.PRODUCT_SELECT,\n },\n {\n name: 'heading',\n label: 'Heading',\n description: 'Defaults to \"Reviews\" (\"What customers say\" store-wide).',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n // Default `product`, so a page that already places this block keeps it.\n name: 'scope',\n label: 'Show',\n description:\n 'One product’s reviews with a form, or the store’s latest approved ' +\n 'reviews across every product. Store-wide shows nothing until a ' +\n 'shopper’s review is approved.',\n component: Aglyn.FieldComponentType.SELECT,\n options: [\n { label: 'This product', value: 'product' },\n { label: 'Whole store', value: 'store' },\n ],\n },\n {\n name: 'maxItems',\n label: 'Max reviews',\n description: 'Store-wide: how many to show (default 6, at most 12).',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n type: 'number',\n },\n {\n name: 'headingLevel',\n label: 'Heading level',\n description: 'Store-wide: the heading’s level on the page (default H2).',\n component: Aglyn.FieldComponentType.SELECT,\n options: [\n { label: 'H2', value: '2' },\n { label: 'H3', value: '3' },\n { label: 'H4', value: '4' },\n ],\n },\n ],\n}\n\nexport const presets: Aglyn.PresetSchema[] = [\n {\n $id: generatePresetId(ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Product reviews',\n pluginId: BUNDLE_ID,\n description: 'Verified-buyer reviews with a submit form',\n category: Aglyn.ComponentCategory.COMMERCE,\n icon: { path: mdiStarOutline.path, sx: { color: '#2e7d32' } },\n data: {\n $id: null,\n componentId: ID,\n pluginId: BUNDLE_ID,\n props: {},\n },\n },\n]\n\nexport default ProductReviews\n"],"names":["Aglyn","mdiStarOutline","Alert","Box","Button","Chip","Divider","Rating","TextField","Typography","forwardRef","useEffect","useState","BUNDLE_ID","generatePresetId","ID","EDITOR_HINT_SX","p","border","borderColor","borderRadius","color","fontSize","fontFamily","REVIEW_SIGN_IN_HREF","reviewTokenFromLocation","search","token","URLSearchParams","get","test","reviewFormMode","eligibility","enabled","canReview","TOKEN_PROBLEM_TEXT","review_token_used","review_token_expired","review_token_invalid","resolveProductIdFromSlug","hostId","payload","match","window","location","pathname","response","fetch","encodeURIComponent","decodeURIComponent","catch","ok","json","String","product","id","ProductReviews","props","ref","scope","StoreReviews","SingleProductReviews","displayName","productId","_productId","heading","_scope","maxItems","headingLevel","rest","nodeSx","Array","isArray","useSite","setPayload","limit","Number","Math","min","floor","active","then","body","reviews","aggregate","count","average","undefined","sx","length","data-store-reviews-empty","display","asked","level","flexDirection","gap","alignItems","textAlign","variant","component","value","precision","readOnly","size","gridTemplateColumns","xs","sm","md","map","review","m","bgcolor","rating","flex","flexWrap","fontWeight","authorName","verified","label","productName","productSlug","href","textDecoration","productIdProp","_maxItems","_headingLevel","siteFetch","useSiteFetch","setProductId","setReviews","setAggregate","form","setForm","authorEmail","state","setState","setEligibility","reviewToken","setReviewToken","submitted","setSubmitted","errorText","setErrorText","resolved","answer","credentials","handleSubmit","method","headers","JSON","stringify","needsEmail","pending","Boolean","error","mt","reply","pl","borderLeft","tokenProblem","severity","data-review-sign-in","maxWidth","verifiedVia","alignSelf","onChange","_event","prev","placeholder","event","target","multiline","minRows","type","disabled","trim","onClick","schema","$id","pluginId","description","category","ComponentCategory","COMMERCE","icon","path","flags","selfClosing","FEATURE_FLAG","ENABLED","attributes","name","FieldComponentType","PRODUCT_SELECT","TEXT_FIELD","SELECT","options","presets","NodeType","PRESET","data","componentId"],"mappings":"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"}
@@ -424,8 +424,8 @@ export declare function renameProductOptions(product: Pick<HostProduct, 'options
424
424
  /**
425
425
  * Whether a variant has a price a sale can charge (AGL-2916).
426
426
  *
427
- * A product proposed by AI is created with every price left empty for the
428
- * merchant to set, so a stored variant can lack one. `Number(undefined)` is
427
+ * A product may be stored with a price left empty on purpose (an owner who
428
+ * clears it, or a writer asked for none), so a stored variant can lack one. `Number(undefined)` is
429
429
  * no price, and neither is `null`, which `Number` reads as zero: a sale door
430
430
  * that priced either would charge nothing, or fail at the processor.
431
431
  */
@@ -495,6 +495,15 @@ export declare function unpricedProductDraft(proposal: ProposedProduct, takenSlu
495
495
  * worked out again for that price. `null` leaves it unpriced on purpose.
496
496
  */
497
497
  export declare function defaultPricedProductDraft(proposal: ProposedProduct, takenSlugs: ReadonlySet<string>, nowMs: number, priceUsd?: number | null): UnpricedProductDraft | HostProduct;
498
+ /**
499
+ * The price a proposed product is created at (Zach, 2026-10-12): a price an
500
+ * AI proposal suggests for what the product is, when it is a real one — a
501
+ * finite amount above $0, at most `COMMERCE_MAX_PRICE_USD`, rounded to whole
502
+ * cents — else the default price (`COMMERCE_DEFAULT_PRICE_USD`). Never $0,
503
+ * negative or past the ceiling, whatever a proposal carries; the owner
504
+ * changes it like any price.
505
+ */
506
+ export declare function proposedProductPriceUsd(suggested: unknown): number;
498
507
  /** Variant whose option selections match exactly; undefined if none. */
499
508
  export declare function findVariant(product: Pick<HostProduct, 'variants'>, selections: Record<string, string>): ProductVariant | undefined;
500
509
  /** [min, max] price across variants; [0, 0] when there are none. */
@@ -363,8 +363,8 @@ export const COMMERCE_SLUG_MAX_LENGTH = 80;
363
363
  /**
364
364
  * Whether a variant has a price a sale can charge (AGL-2916).
365
365
  *
366
- * A product proposed by AI is created with every price left empty for the
367
- * merchant to set, so a stored variant can lack one. `Number(undefined)` is
366
+ * A product may be stored with a price left empty on purpose (an owner who
367
+ * clears it, or a writer asked for none), so a stored variant can lack one. `Number(undefined)` is
368
368
  * no price, and neither is `null`, which `Number` reads as zero: a sale door
369
369
  * that priced either would charge nothing, or fail at the processor.
370
370
  */ export function variantHasPrice(variant) {
@@ -502,6 +502,18 @@ export const COMMERCE_SLUG_MAX_LENGTH = 80;
502
502
  variants
503
503
  });
504
504
  }
505
+ /**
506
+ * The price a proposed product is created at (Zach, 2026-10-12): a price an
507
+ * AI proposal suggests for what the product is, when it is a real one — a
508
+ * finite amount above $0, at most `COMMERCE_MAX_PRICE_USD`, rounded to whole
509
+ * cents — else the default price (`COMMERCE_DEFAULT_PRICE_USD`). Never $0,
510
+ * negative or past the ceiling, whatever a proposal carries; the owner
511
+ * changes it like any price.
512
+ */ export function proposedProductPriceUsd(suggested) {
513
+ if (typeof suggested !== 'number' || !Number.isFinite(suggested)) return COMMERCE_DEFAULT_PRICE_USD;
514
+ const cents = Math.round(suggested * 100);
515
+ return cents > 0 && cents <= COMMERCE_MAX_PRICE_USD * 100 ? cents / 100 : COMMERCE_DEFAULT_PRICE_USD;
516
+ }
505
517
  /** Variant whose option selections match exactly; undefined if none. */ export function findVariant(product, selections) {
506
518
  var _product_variants;
507
519
  return (_product_variants = product.variants) == null ? void 0 : _product_variants.find((variant)=>{