@aglyn/plugins-commerce 1.0.0-beta.234 → 1.0.0-beta.235
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +13 -13
- package/src/lib/components/cart.js +3 -2
- package/src/lib/components/cart.js.map +1 -1
- package/src/lib/components/console/commerce-analytics-card.component.js +3 -6
- package/src/lib/components/console/commerce-analytics-card.component.js.map +1 -1
- package/src/lib/components/console/commerce-glance-card.component.js +9 -3
- package/src/lib/components/console/commerce-glance-card.component.js.map +1 -1
- package/src/lib/components/console/customer-notifications-card.component.js +3 -1
- package/src/lib/components/console/customer-notifications-card.component.js.map +1 -1
- package/src/lib/components/console/discounts-card.component.js +2 -3
- package/src/lib/components/console/discounts-card.component.js.map +1 -1
- package/src/lib/components/console/host-coupons-card.component.js +3 -4
- package/src/lib/components/console/host-coupons-card.component.js.map +1 -1
- package/src/lib/components/console/local-delivery-settings-card.component.js +2 -2
- package/src/lib/components/console/local-delivery-settings-card.component.js.map +1 -1
- package/src/lib/components/console/locations-card.component.js +1 -1
- package/src/lib/components/console/locations-card.component.js.map +1 -1
- package/src/lib/components/console/member-posts-card.component.js +2 -2
- package/src/lib/components/console/member-posts-card.component.js.map +1 -1
- package/src/lib/components/console/order-detail-dialog.component.js +35 -30
- package/src/lib/components/console/order-detail-dialog.component.js.map +1 -1
- package/src/lib/components/console/order-fulfillment-panel.component.d.ts +11 -1
- package/src/lib/components/console/order-fulfillment-panel.component.js +75 -4
- package/src/lib/components/console/order-fulfillment-panel.component.js.map +1 -1
- package/src/lib/components/console/order-zones.d.ts +45 -1
- package/src/lib/components/console/order-zones.js +8 -0
- package/src/lib/components/console/order-zones.js.map +1 -1
- package/src/lib/components/console/pickup-delivery-queue-card.component.js +15 -1
- package/src/lib/components/console/pickup-delivery-queue-card.component.js.map +1 -1
- package/src/lib/components/console/pos-page.component.js +3 -2
- package/src/lib/components/console/pos-page.component.js.map +1 -1
- package/src/lib/components/console/product-editor-dialog.component.d.ts +2 -0
- package/src/lib/components/console/product-editor-dialog.component.js +30 -12
- package/src/lib/components/console/product-editor-dialog.component.js.map +1 -1
- package/src/lib/components/console/products-hub-card.component.js +13 -4
- package/src/lib/components/console/products-hub-card.component.js.map +1 -1
- package/src/lib/components/console/products-hub-zone.component.d.ts +2 -1
- package/src/lib/components/console/products-hub-zone.component.js +1 -1
- package/src/lib/components/console/products-hub-zone.component.js.map +1 -1
- package/src/lib/components/console/reviews-moderation-card.component.js +1 -2
- package/src/lib/components/console/reviews-moderation-card.component.js.map +1 -1
- package/src/lib/components/console/shipping-settings-card.component.js +2 -2
- package/src/lib/components/console/shipping-settings-card.component.js.map +1 -1
- package/src/lib/components/console/shippingeasy-card.component.js +1 -1
- package/src/lib/components/console/shippingeasy-card.component.js.map +1 -1
- package/src/lib/components/console/shipstation-card.component.js +1 -2
- package/src/lib/components/console/shipstation-card.component.js.map +1 -1
- package/src/lib/components/console/store-settings-card.component.js +3 -1
- package/src/lib/components/console/store-settings-card.component.js.map +1 -1
- package/src/lib/components/console/tax-settings-card.component.js +4 -1
- package/src/lib/components/console/tax-settings-card.component.js.map +1 -1
- package/src/lib/components/order-status.js +24 -1
- package/src/lib/components/order-status.js.map +1 -1
- package/src/lib/components/product-detail.js +30 -16
- package/src/lib/components/product-detail.js.map +1 -1
- package/src/lib/components/product-grid.d.ts +7 -0
- package/src/lib/components/product-grid.js +50 -7
- package/src/lib/components/product-grid.js.map +1 -1
- package/src/lib/components/related-products.js +1 -1
- package/src/lib/components/related-products.js.map +1 -1
- package/src/lib/components/wishlist.js +1 -1
- package/src/lib/components/wishlist.js.map +1 -1
- package/src/lib/declarations.server.js +11 -0
- package/src/lib/declarations.server.js.map +1 -1
- package/src/lib/model/commerce-orders.d.ts +8 -0
- package/src/lib/model/commerce-orders.js.map +1 -1
- package/src/lib/model/commerce.d.ts +21 -0
- package/src/lib/model/commerce.js +39 -1
- package/src/lib/model/commerce.js.map +1 -1
- package/src/lib/model/index.d.ts +1 -0
- package/src/lib/model/index.js +1 -0
- package/src/lib/model/index.js.map +1 -1
- package/src/lib/model/order-annotations.d.ts +54 -0
- package/src/lib/model/order-annotations.js +76 -0
- package/src/lib/model/order-annotations.js.map +1 -0
- package/src/lib/model/order-events.d.ts +2 -0
- package/src/lib/model/order-events.js.map +1 -1
- package/src/lib/model/order-fulfillment.d.ts +29 -0
- package/src/lib/model/order-fulfillment.js +63 -0
- package/src/lib/model/order-fulfillment.js.map +1 -1
- package/src/lib/model/order-local-fulfillment.d.ts +36 -0
- package/src/lib/model/order-local-fulfillment.js +19 -1
- package/src/lib/model/order-local-fulfillment.js.map +1 -1
- package/src/lib/model/order-status-view.d.ts +11 -0
- package/src/lib/model/order-status-view.js +20 -3
- package/src/lib/model/order-status-view.js.map +1 -1
- package/src/lib/model/product-write.js +8 -4
- package/src/lib/model/product-write.js.map +1 -1
- package/src/lib/server/api-v1/openapi.js +1 -0
- package/src/lib/server/api-v1/openapi.js.map +1 -1
- package/src/lib/server/api-v1/order-view.d.ts +1 -0
- package/src/lib/server/api-v1/order-view.js +10 -0
- package/src/lib/server/api-v1/order-view.js.map +1 -1
- package/src/lib/server/cart-checkout.js +9 -1
- package/src/lib/server/cart-checkout.js.map +1 -1
- package/src/lib/server/catalog.d.ts +6 -0
- package/src/lib/server/catalog.js +3 -1
- package/src/lib/server/catalog.js.map +1 -1
- package/src/lib/server/checkout.js +11 -1
- package/src/lib/server/checkout.js.map +1 -1
- package/src/lib/server/fulfill-order.d.ts +12 -0
- package/src/lib/server/fulfill-order.js +13 -0
- package/src/lib/server/fulfill-order.js.map +1 -1
- package/src/lib/server/local-delivery-records.d.ts +30 -0
- package/src/lib/server/local-delivery-records.js +284 -0
- package/src/lib/server/local-delivery-records.js.map +1 -0
- package/src/lib/server/order-annotations.d.ts +28 -0
- package/src/lib/server/order-annotations.js +143 -0
- package/src/lib/server/order-annotations.js.map +1 -0
- package/src/lib/server/order-events.js +2 -1
- package/src/lib/server/order-events.js.map +1 -1
- package/src/lib/server/order-notifications.js +39 -8
- package/src/lib/server/order-notifications.js.map +1 -1
- package/src/lib/server/pos-payment.js +9 -3
- package/src/lib/server/pos-payment.js.map +1 -1
- package/src/lib/server/product-catalog.js +6 -2
- package/src/lib/server/product-catalog.js.map +1 -1
- package/src/lib/server/product-drafts.d.ts +36 -9
- package/src/lib/server/product-drafts.js +24 -5
- package/src/lib/server/product-drafts.js.map +1 -1
- package/src/lib/server/product.d.ts +6 -1
- package/src/lib/server/product.js +1 -1
- package/src/lib/server/product.js.map +1 -1
- package/src/lib/server/products-write.d.ts +29 -0
- package/src/lib/server/products-write.js +7 -7
- package/src/lib/server/products-write.js.map +1 -1
- package/src/lib/server/shipment-records.js +2 -0
- package/src/lib/server/shipment-records.js.map +1 -1
- package/src/lib/server/site-page-resolver.d.ts +13 -0
- package/src/lib/server/site-page-resolver.js +138 -1
- package/src/lib/server/site-page-resolver.js.map +1 -1
- package/src/lib/server.js +5 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/utils/use-storefront-purchase-event.js +8 -2
- package/src/lib/utils/use-storefront-purchase-event.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/shipment-records.ts"],"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 type {\n PluginShipFromAddress,\n PluginShippableRecord,\n PluginShipmentLine,\n PluginShipmentRecords,\n PluginTrackingOutcome,\n} from '@aglyn/aglyn/plugin-manager/plugin-shipment-records'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin'\nimport * as CommerceModel from '../model'\nimport { recordOrderShipment } from './fulfill-order'\n\n/**\n * THIS PLUGIN'S ORDERS, AS A SHIPPER SEES THEM (AGL-3612): commerce's\n * implementation of core's `core.shipment-records`, registered from the\n * server declarations so it is there wherever a label is bought or a\n * tracking webhook lands.\n *\n * A label buyer never reads `hosts/{id}/orders` itself. It asks here, and\n * every write it makes is this plugin's own: a shipment through\n * `recordOrderShipment` — the transition rule, the quantity check, the\n * timeline and the buyer's shipped email — and a delivery through the same\n * function's `delivered` door, which sends the delivered email.\n */\n\nconst ordersOf = (hostId: string) =>\n firebaseAdmin.app().firestore().collection('hosts').doc(hostId).collection('orders')\n\n/** The statuses a new shipment may be recorded from. */\nconst SHIPPABLE = new Set(['paid', 'partially_fulfilled'])\n\nfunction toPluginAddress(\n address: CommerceModel.OrderAddress | undefined,\n): PluginShippableRecord['shipTo'] {\n const country = String(address?.country ?? '').trim().toUpperCase()\n if (!/^[A-Z]{2}$/.test(country)) return undefined\n return {\n country,\n ...(address?.name ? { name: address.name } : {}),\n ...(address?.line1 ? { line1: address.line1 } : {}),\n ...(address?.line2 ? { line2: address.line2 } : {}),\n ...(address?.city ? { city: address.city } : {}),\n ...(address?.state ? { state: address.state } : {}),\n ...(address?.postalCode ? { postalCode: address.postalCode } : {}),\n ...(address?.phone ? { phone: address.phone } : {}),\n }\n}\n\n/** The order as a shippable record, with each line's weight and customs facts. */\nexport async function readShippableOrder(\n hostId: string,\n orderId: string,\n): Promise<PluginShippableRecord | null> {\n if (!hostId || !orderId || /^__.*__$/.test(orderId)) return null\n const snapshot = await ordersOf(hostId).doc(orderId).get()\n if (!snapshot.exists) return null\n const order = CommerceModel.liftLegacyOrder((snapshot.data() ?? {}) as never)\n const states = CommerceModel.orderLineFulfillmentStates(order)\n const productIds = [...new Set((order.lineItems ?? []).map((line) => line.productId).filter(Boolean))]\n const products = new Map<string, CommerceModel.HostProduct>()\n await Promise.all(\n productIds.map(async (productId) => {\n const product = await firebaseAdmin\n .app()\n .firestore()\n .collection('hosts')\n .doc(hostId)\n .collection('products')\n .doc(productId)\n .get()\n .catch(() => null)\n if (product?.exists) {\n products.set(productId, CommerceModel.liftLegacyProduct(product.data() as never))\n }\n }),\n )\n const lines: PluginShipmentLine[] = []\n ;(order.lineItems ?? []).forEach((line, index) => {\n const state = states[index]\n if (!state?.requiresShipping) return\n const product = products.get(line.productId)\n const variant = product?.variants.find((one) => one.id === line.variantId) ?? product?.variants[0]\n const facts = product?.shipping\n const dims = CommerceModel.productParcelDimensions(product)\n lines.push({\n lineIndex: index,\n name: [line.name, line.variantLabel].filter(Boolean).join(' — ') || 'Item',\n ...(line.sku ? { sku: line.sku } : {}),\n quantity: state.quantity,\n quantityUnshipped: state.remainingQuantity,\n unitValueCents: Math.max(0, Math.round(Number(line.unitAmountCents ?? 0))),\n ...(Number(variant?.weightGrams) > 0 ? { weightGrams: Number(variant?.weightGrams) } : {}),\n ...(dims ?? {}),\n ...(facts?.hsCode ? { hsCode: facts.hsCode } : {}),\n ...(facts?.originCountry ? { originCountry: facts.originCountry } : {}),\n })\n })\n return {\n hostId,\n recordId: orderId,\n displayRef: order.number ? `#${order.number}` : `#${orderId.slice(0, 8)}`,\n status: order.status,\n shippable: SHIPPABLE.has(order.status),\n currency: 'usd',\n ...(toPluginAddress(order.shippingAddress)\n ? {\n shipTo: {\n ...toPluginAddress(order.shippingAddress),\n ...(!order.shippingAddress?.name && order.customerName ? { name: order.customerName } : {}),\n } as NonNullable<PluginShippableRecord['shipTo']>,\n }\n : {}),\n ...(order.customerEmail ? { customerEmail: order.customerEmail } : {}),\n lines,\n shipments: (order.fulfillments ?? [])\n .filter((fulfillment) => CommerceModel.fulfillmentIsActive(fulfillment))\n .map((fulfillment) => ({\n id: fulfillment.id,\n ...(fulfillment.carrier ? { carrier: fulfillment.carrier } : {}),\n ...(fulfillment.trackingNumber ? { trackingNumber: fulfillment.trackingNumber } : {}),\n ...(fulfillment.trackingUrl ? { trackingUrl: fulfillment.trackingUrl } : {}),\n ...(fulfillment.labelRef ? { labelRef: fulfillment.labelRef } : {}),\n lineIndexes: fulfillment.lineItemIds ?? [],\n atMs: fulfillment.atMs,\n })),\n ...(order.createdAtMs ? { createdAtMs: order.createdAtMs } : {}),\n testMode: CommerceModel.orderIsTestMode({\n checkoutSessionId: order.checkoutSessionId,\n livemode: (order as unknown as { livemode?: unknown }).livemode,\n $id: orderId,\n }),\n }\n}\n\n/**\n * Records what the carrier says on the shipment it is about, once, and\n * moves the order to delivered when every parcel of a fully shipped order\n * has arrived — through `recordOrderShipment`, so the buyer's delivered email\n * is this plugin's to send.\n */\nexport async function recordShipmentTracking(update: {\n hostId: string\n recordId: string\n trackingNumber: string\n status: string\n detail?: string\n atMs: number\n}): Promise<PluginTrackingOutcome> {\n const orderRef = ordersOf(update.hostId).doc(update.recordId)\n const result = await firebaseAdmin\n .app()\n .firestore()\n .runTransaction(async (transaction) => {\n const snapshot = await transaction.get(orderRef)\n if (!snapshot.exists) return { outcome: 'no_such_record' as const }\n const order = CommerceModel.liftLegacyOrder((snapshot.data() ?? {}) as never)\n const fulfillments = [...(order.fulfillments ?? [])]\n const index = fulfillments.findIndex(\n (fulfillment) =>\n CommerceModel.fulfillmentIsActive(fulfillment) &&\n String(fulfillment.trackingNumber ?? '').trim().toUpperCase() ===\n update.trackingNumber.trim().toUpperCase(),\n )\n if (index < 0) return { outcome: 'no_such_shipment' as const }\n if (fulfillments[index].trackingStatus === update.status) {\n return { outcome: 'unchanged' as const, order, fulfillments }\n }\n fulfillments[index] = {\n ...fulfillments[index],\n trackingStatus: update.status,\n trackingStatusAtMs: update.atMs,\n }\n transaction.update(orderRef, {\n fulfillments,\n timeline: CommerceModel.appendOrderEvent(\n order,\n 'tracking',\n [CommerceModel.trackingStatusLabel(update.status), update.detail].filter(Boolean).join(': ').slice(0, 300),\n update.atMs,\n ),\n })\n return { outcome: 'recorded' as const, order, fulfillments }\n })\n if (result.outcome === 'no_such_record' || result.outcome === 'no_such_shipment') {\n return { outcome: result.outcome }\n }\n const everyDelivered = result.fulfillments\n .filter((fulfillment) => CommerceModel.fulfillmentIsActive(fulfillment) && fulfillment.trackingNumber)\n .every((fulfillment) => fulfillment.trackingStatus === 'delivered')\n if (update.status === 'delivered' && everyDelivered && result.order.status === 'fulfilled') {\n await recordOrderShipment({ hostId: update.hostId, orderId: update.recordId, to: 'delivered' })\n }\n return { outcome: result.outcome }\n}\n\n/** The site's locations that have a postal address complete enough to ship from. */\nexport async function readShipFromAddresses(hostId: string): Promise<PluginShipFromAddress[]> {\n const snapshot = await firebaseAdmin\n .app()\n .firestore()\n .collection('hosts')\n .doc(hostId)\n .collection('locations')\n .limit(50)\n .get()\n const places: PluginShipFromAddress[] = []\n for (const doc of snapshot.docs) {\n const location = doc.data() as CommerceModel.InventoryLocation\n const address = CommerceModel.normalizePostalAddress(location.postalAddress)\n if (!address?.country || !address.line1 || !address.city || !address.postalCode) continue\n places.push({\n id: doc.id,\n name: String(location.name ?? 'Location'),\n address: { ...address, country: address.country, name: String(location.name ?? '') },\n })\n }\n return places.sort((a, b) => a.name.localeCompare(b.name))\n}\n\nexport const commerceShipmentRecords: PluginShipmentRecords = {\n read: readShippableOrder,\n async recordShipment(write) {\n const outcome = await recordOrderShipment({\n hostId: write.hostId,\n orderId: write.recordId,\n to: 'fulfilled',\n carrier: write.carrier,\n trackingNumber: write.trackingNumber,\n ...(write.lines?.length\n ? { lineItems: write.lines.map((line) => ({ lineItemId: line.lineIndex, quantity: line.quantity })) }\n : {}),\n ...(write.trackingUrl ? { trackingUrl: write.trackingUrl } : {}),\n ...(write.labelUrl ? { labelUrl: write.labelUrl } : {}),\n ...(write.labelRef ? { labelRef: write.labelRef, idempotencyKey: `label:${write.labelRef}` } : {}),\n })\n switch (outcome.outcome) {\n case 'recorded':\n return { outcome: 'recorded', shipmentId: outcome.fulfillment?.id ?? '' }\n case 'already':\n return { outcome: 'already', ...(outcome.fulfillment ? { shipmentId: outcome.fulfillment.id } : {}) }\n case 'no_such_order':\n return { outcome: 'no_such_record' }\n case 'blocked':\n return { outcome: 'blocked', from: outcome.from }\n default:\n return { outcome: 'blocked', from: 'invalid', reason: outcome.message }\n }\n },\n recordTracking: recordShipmentTracking,\n shipFromAddresses: readShipFromAddresses,\n}\n"],"names":["firebaseAdmin","CommerceModel","recordOrderShipment","ordersOf","hostId","app","firestore","collection","doc","SHIPPABLE","Set","toPluginAddress","address","country","String","trim","toUpperCase","test","undefined","name","line1","line2","city","state","postalCode","phone","readShippableOrder","orderId","snapshot","order","get","exists","liftLegacyOrder","data","states","orderLineFulfillmentStates","productIds","lineItems","map","line","productId","filter","Boolean","products","Map","Promise","all","product","catch","set","liftLegacyProduct","lines","forEach","index","requiresShipping","variant","variants","find","one","id","variantId","facts","shipping","dims","productParcelDimensions","push","lineIndex","variantLabel","join","sku","quantity","quantityUnshipped","remainingQuantity","unitValueCents","Math","max","round","Number","unitAmountCents","weightGrams","hsCode","originCountry","recordId","displayRef","number","slice","status","shippable","has","currency","shippingAddress","shipTo","customerName","customerEmail","shipments","fulfillments","fulfillment","fulfillmentIsActive","carrier","trackingNumber","trackingUrl","labelRef","lineIndexes","lineItemIds","atMs","createdAtMs","testMode","orderIsTestMode","checkoutSessionId","livemode","$id","recordShipmentTracking","update","orderRef","result","runTransaction","transaction","outcome","findIndex","trackingStatus","trackingStatusAtMs","timeline","appendOrderEvent","trackingStatusLabel","detail","everyDelivered","every","to","readShipFromAddresses","limit","places","docs","location","normalizePostalAddress","postalAddress","sort","a","b","localeCompare","commerceShipmentRecords","read","recordShipment","write","length","lineItemId","labelUrl","idempotencyKey","shipmentId","from","reason","message","recordTracking","shipFromAddresses"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/shipment-records.ts"],"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 type {\n PluginShipFromAddress,\n PluginShippableRecord,\n PluginShipmentLine,\n PluginShipmentRecords,\n PluginTrackingOutcome,\n} from '@aglyn/aglyn/plugin-manager/plugin-shipment-records'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin'\nimport * as CommerceModel from '../model'\nimport { recordOrderShipment } from './fulfill-order'\n\n/**\n * THIS PLUGIN'S ORDERS, AS A SHIPPER SEES THEM (AGL-3612): commerce's\n * implementation of core's `core.shipment-records`, registered from the\n * server declarations so it is there wherever a label is bought or a\n * tracking webhook lands.\n *\n * A label buyer never reads `hosts/{id}/orders` itself. It asks here, and\n * every write it makes is this plugin's own: a shipment through\n * `recordOrderShipment` — the transition rule, the quantity check, the\n * timeline and the buyer's shipped email — and a delivery through the same\n * function's `delivered` door, which sends the delivered email.\n */\n\nconst ordersOf = (hostId: string) =>\n firebaseAdmin.app().firestore().collection('hosts').doc(hostId).collection('orders')\n\n/** The statuses a new shipment may be recorded from. */\nconst SHIPPABLE = new Set(['paid', 'partially_fulfilled'])\n\nfunction toPluginAddress(\n address: CommerceModel.OrderAddress | undefined,\n): PluginShippableRecord['shipTo'] {\n const country = String(address?.country ?? '').trim().toUpperCase()\n if (!/^[A-Z]{2}$/.test(country)) return undefined\n return {\n country,\n ...(address?.name ? { name: address.name } : {}),\n ...(address?.line1 ? { line1: address.line1 } : {}),\n ...(address?.line2 ? { line2: address.line2 } : {}),\n ...(address?.city ? { city: address.city } : {}),\n ...(address?.state ? { state: address.state } : {}),\n ...(address?.postalCode ? { postalCode: address.postalCode } : {}),\n ...(address?.phone ? { phone: address.phone } : {}),\n }\n}\n\n/** The order as a shippable record, with each line's weight and customs facts. */\nexport async function readShippableOrder(\n hostId: string,\n orderId: string,\n): Promise<PluginShippableRecord | null> {\n if (!hostId || !orderId || /^__.*__$/.test(orderId)) return null\n const snapshot = await ordersOf(hostId).doc(orderId).get()\n if (!snapshot.exists) return null\n const order = CommerceModel.liftLegacyOrder((snapshot.data() ?? {}) as never)\n const states = CommerceModel.orderLineFulfillmentStates(order)\n const productIds = [...new Set((order.lineItems ?? []).map((line) => line.productId).filter(Boolean))]\n const products = new Map<string, CommerceModel.HostProduct>()\n await Promise.all(\n productIds.map(async (productId) => {\n const product = await firebaseAdmin\n .app()\n .firestore()\n .collection('hosts')\n .doc(hostId)\n .collection('products')\n .doc(productId)\n .get()\n .catch(() => null)\n if (product?.exists) {\n products.set(productId, CommerceModel.liftLegacyProduct(product.data() as never))\n }\n }),\n )\n const lines: PluginShipmentLine[] = []\n ;(order.lineItems ?? []).forEach((line, index) => {\n const state = states[index]\n if (!state?.requiresShipping) return\n const product = products.get(line.productId)\n const variant = product?.variants.find((one) => one.id === line.variantId) ?? product?.variants[0]\n const facts = product?.shipping\n const dims = CommerceModel.productParcelDimensions(product)\n lines.push({\n lineIndex: index,\n name: [line.name, line.variantLabel].filter(Boolean).join(' — ') || 'Item',\n ...(line.sku ? { sku: line.sku } : {}),\n quantity: state.quantity,\n quantityUnshipped: state.remainingQuantity,\n unitValueCents: Math.max(0, Math.round(Number(line.unitAmountCents ?? 0))),\n ...(Number(variant?.weightGrams) > 0 ? { weightGrams: Number(variant?.weightGrams) } : {}),\n ...(dims ?? {}),\n ...(facts?.hsCode ? { hsCode: facts.hsCode } : {}),\n ...(facts?.originCountry ? { originCountry: facts.originCountry } : {}),\n })\n })\n return {\n hostId,\n recordId: orderId,\n displayRef: order.number ? `#${order.number}` : `#${orderId.slice(0, 8)}`,\n status: order.status,\n shippable: SHIPPABLE.has(order.status),\n currency: 'usd',\n ...(toPluginAddress(order.shippingAddress)\n ? {\n shipTo: {\n ...toPluginAddress(order.shippingAddress),\n ...(!order.shippingAddress?.name && order.customerName ? { name: order.customerName } : {}),\n } as NonNullable<PluginShippableRecord['shipTo']>,\n }\n : {}),\n ...(order.customerEmail ? { customerEmail: order.customerEmail } : {}),\n lines,\n shipments: (order.fulfillments ?? [])\n .filter((fulfillment) => CommerceModel.fulfillmentIsActive(fulfillment))\n .map((fulfillment) => ({\n id: fulfillment.id,\n ...(fulfillment.carrier ? { carrier: fulfillment.carrier } : {}),\n ...(fulfillment.trackingNumber ? { trackingNumber: fulfillment.trackingNumber } : {}),\n ...(fulfillment.trackingUrl ? { trackingUrl: fulfillment.trackingUrl } : {}),\n ...(fulfillment.labelRef ? { labelRef: fulfillment.labelRef } : {}),\n lineIndexes: fulfillment.lineItemIds ?? [],\n atMs: fulfillment.atMs,\n })),\n ...(order.createdAtMs ? { createdAtMs: order.createdAtMs } : {}),\n testMode: CommerceModel.orderIsTestMode({\n checkoutSessionId: order.checkoutSessionId,\n livemode: (order as unknown as { livemode?: unknown }).livemode,\n $id: orderId,\n }),\n }\n}\n\n/**\n * Records what the carrier says on the shipment it is about, once, and\n * moves the order to delivered when every parcel of a fully shipped order\n * has arrived — through `recordOrderShipment`, so the buyer's delivered email\n * is this plugin's to send.\n */\nexport async function recordShipmentTracking(update: {\n hostId: string\n recordId: string\n trackingNumber: string\n status: string\n detail?: string\n atMs: number\n}): Promise<PluginTrackingOutcome> {\n const orderRef = ordersOf(update.hostId).doc(update.recordId)\n const result = await firebaseAdmin\n .app()\n .firestore()\n .runTransaction(async (transaction) => {\n const snapshot = await transaction.get(orderRef)\n if (!snapshot.exists) return { outcome: 'no_such_record' as const }\n const order = CommerceModel.liftLegacyOrder((snapshot.data() ?? {}) as never)\n const fulfillments = [...(order.fulfillments ?? [])]\n const index = fulfillments.findIndex(\n (fulfillment) =>\n CommerceModel.fulfillmentIsActive(fulfillment) &&\n String(fulfillment.trackingNumber ?? '').trim().toUpperCase() ===\n update.trackingNumber.trim().toUpperCase(),\n )\n if (index < 0) return { outcome: 'no_such_shipment' as const }\n if (fulfillments[index].trackingStatus === update.status) {\n return { outcome: 'unchanged' as const, order, fulfillments }\n }\n fulfillments[index] = {\n ...fulfillments[index],\n trackingStatus: update.status,\n trackingStatusAtMs: update.atMs,\n }\n transaction.update(orderRef, {\n fulfillments,\n timeline: CommerceModel.appendOrderEvent(\n order,\n 'tracking',\n [CommerceModel.trackingStatusLabel(update.status), update.detail].filter(Boolean).join(': ').slice(0, 300),\n update.atMs,\n ),\n })\n return { outcome: 'recorded' as const, order, fulfillments }\n })\n if (result.outcome === 'no_such_record' || result.outcome === 'no_such_shipment') {\n return { outcome: result.outcome }\n }\n const everyDelivered = result.fulfillments\n .filter((fulfillment) => CommerceModel.fulfillmentIsActive(fulfillment) && fulfillment.trackingNumber)\n .every((fulfillment) => fulfillment.trackingStatus === 'delivered')\n if (update.status === 'delivered' && everyDelivered && result.order.status === 'fulfilled') {\n await recordOrderShipment({ hostId: update.hostId, orderId: update.recordId, to: 'delivered' })\n }\n return { outcome: result.outcome }\n}\n\n/** The site's locations that have a postal address complete enough to ship from. */\nexport async function readShipFromAddresses(hostId: string): Promise<PluginShipFromAddress[]> {\n const snapshot = await firebaseAdmin\n .app()\n .firestore()\n .collection('hosts')\n .doc(hostId)\n .collection('locations')\n .limit(50)\n .get()\n const places: PluginShipFromAddress[] = []\n for (const doc of snapshot.docs) {\n const location = doc.data() as CommerceModel.InventoryLocation\n const address = CommerceModel.normalizePostalAddress(location.postalAddress)\n if (!address?.country || !address.line1 || !address.city || !address.postalCode) continue\n places.push({\n id: doc.id,\n name: String(location.name ?? 'Location'),\n address: { ...address, country: address.country, name: String(location.name ?? '') },\n })\n }\n return places.sort((a, b) => a.name.localeCompare(b.name))\n}\n\nexport const commerceShipmentRecords: PluginShipmentRecords = {\n read: readShippableOrder,\n async recordShipment(write) {\n const outcome = await recordOrderShipment({\n hostId: write.hostId,\n orderId: write.recordId,\n to: 'fulfilled',\n carrier: write.carrier,\n trackingNumber: write.trackingNumber,\n ...(write.lines?.length\n ? { lineItems: write.lines.map((line) => ({ lineItemId: line.lineIndex, quantity: line.quantity })) }\n : {}),\n ...(write.trackingUrl ? { trackingUrl: write.trackingUrl } : {}),\n ...(write.labelUrl ? { labelUrl: write.labelUrl } : {}),\n ...(write.labelRef ? { labelRef: write.labelRef, idempotencyKey: `label:${write.labelRef}` } : {}),\n ...(write.labelCostCents ? { labelCostCents: write.labelCostCents } : {}),\n })\n switch (outcome.outcome) {\n case 'recorded':\n return { outcome: 'recorded', shipmentId: outcome.fulfillment?.id ?? '' }\n case 'already':\n return { outcome: 'already', ...(outcome.fulfillment ? { shipmentId: outcome.fulfillment.id } : {}) }\n case 'no_such_order':\n return { outcome: 'no_such_record' }\n case 'blocked':\n return { outcome: 'blocked', from: outcome.from }\n default:\n return { outcome: 'blocked', from: 'invalid', reason: outcome.message }\n }\n },\n recordTracking: recordShipmentTracking,\n shipFromAddresses: readShipFromAddresses,\n}\n"],"names":["firebaseAdmin","CommerceModel","recordOrderShipment","ordersOf","hostId","app","firestore","collection","doc","SHIPPABLE","Set","toPluginAddress","address","country","String","trim","toUpperCase","test","undefined","name","line1","line2","city","state","postalCode","phone","readShippableOrder","orderId","snapshot","order","get","exists","liftLegacyOrder","data","states","orderLineFulfillmentStates","productIds","lineItems","map","line","productId","filter","Boolean","products","Map","Promise","all","product","catch","set","liftLegacyProduct","lines","forEach","index","requiresShipping","variant","variants","find","one","id","variantId","facts","shipping","dims","productParcelDimensions","push","lineIndex","variantLabel","join","sku","quantity","quantityUnshipped","remainingQuantity","unitValueCents","Math","max","round","Number","unitAmountCents","weightGrams","hsCode","originCountry","recordId","displayRef","number","slice","status","shippable","has","currency","shippingAddress","shipTo","customerName","customerEmail","shipments","fulfillments","fulfillment","fulfillmentIsActive","carrier","trackingNumber","trackingUrl","labelRef","lineIndexes","lineItemIds","atMs","createdAtMs","testMode","orderIsTestMode","checkoutSessionId","livemode","$id","recordShipmentTracking","update","orderRef","result","runTransaction","transaction","outcome","findIndex","trackingStatus","trackingStatusAtMs","timeline","appendOrderEvent","trackingStatusLabel","detail","everyDelivered","every","to","readShipFromAddresses","limit","places","docs","location","normalizePostalAddress","postalAddress","sort","a","b","localeCompare","commerceShipmentRecords","read","recordShipment","write","length","lineItemId","labelUrl","idempotencyKey","labelCostCents","shipmentId","from","reason","message","recordTracking","shipFromAddresses"],"mappings":"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"}
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import type { SitePageResolver } from '@aglyn/aglyn/server';
|
|
18
18
|
import * as Aglyn from '@aglyn/aglyn/server';
|
|
19
|
+
import * as CommerceModel from '../model';
|
|
19
20
|
/**
|
|
20
21
|
* Commerce page resolver (AGL-292/298/418), relocated verbatim from the
|
|
21
22
|
* tenant loader: /products/{slug} renders the host's designated PDP
|
|
@@ -25,6 +26,18 @@ import * as Aglyn from '@aglyn/aglyn/server';
|
|
|
25
26
|
* undefined for everything else so the loader falls through.
|
|
26
27
|
*/
|
|
27
28
|
export declare const commerceSitePageResolver: SitePageResolver;
|
|
29
|
+
/**
|
|
30
|
+
* What `{{product.price}}` says: the price, "From" the lowest where variants
|
|
31
|
+
* differ, or "Price coming soon" for a product listed before any variant has
|
|
32
|
+
* one (AGL-3676) — never "$0".
|
|
33
|
+
*/
|
|
34
|
+
export declare function productPriceText(product: Pick<CommerceModel.HostProduct, 'variants'>, minPrice: number, maxPrice: number): string;
|
|
35
|
+
/**
|
|
36
|
+
* The store's built-in product page (AGL-3676), root first, for the layout's
|
|
37
|
+
* slot: the product block for the routed slug and the related products under
|
|
38
|
+
* it — the two blocks a product template is made of.
|
|
39
|
+
*/
|
|
40
|
+
export declare function buildProductPageNodes(slug: string): Record<string, Aglyn.AglynNodeSchema>;
|
|
28
41
|
/** The order-status page's node tree, root first, for the layout's slot. */
|
|
29
42
|
export declare function buildOrderStatusNodes(): Record<string, Aglyn.AglynNodeSchema>;
|
|
30
43
|
/** The return form's node tree, root first, for the layout's slot. */
|
|
@@ -111,7 +111,7 @@ import { readProductReviews } from "./reviews.js";
|
|
|
111
111
|
tokens: {
|
|
112
112
|
'product.name': product.name,
|
|
113
113
|
'product.description': (_product_description = product.description) != null ? _product_description : '',
|
|
114
|
-
'product.price': minPrice
|
|
114
|
+
'product.price': productPriceText(product, minPrice, maxPrice),
|
|
115
115
|
'product.image': (_ref = (_ref1 = (_product_mediaUrls = product.mediaUrls) == null ? void 0 : _product_mediaUrls[0]) != null ? _ref1 : product.imageUrl) != null ? _ref : '',
|
|
116
116
|
'product.slug': product.slug
|
|
117
117
|
},
|
|
@@ -172,6 +172,14 @@ import { readProductReviews } from "./reviews.js";
|
|
|
172
172
|
}
|
|
173
173
|
}
|
|
174
174
|
}
|
|
175
|
+
// No product template designated, or none that renders: the store's
|
|
176
|
+
// built-in product page (AGL-3676), in the site's own header and footer,
|
|
177
|
+
// rather than a 404 behind every card the product grid links. A store a
|
|
178
|
+
// guided start built has no template of its own, and its grid links each
|
|
179
|
+
// product here.
|
|
180
|
+
if (productRaw && !productRaw.deletedAt && productRaw.status === 'active') {
|
|
181
|
+
return composeBuiltInProductPage(hostId, host, productSnapshot.docs[0].id, CommerceModel.liftLegacyProduct(productRaw));
|
|
182
|
+
}
|
|
175
183
|
}
|
|
176
184
|
// Commerce collection routes (AGL-298): /collections/{slug} renders
|
|
177
185
|
// the designated collection template with collection tokens; the
|
|
@@ -259,6 +267,135 @@ import { readProductReviews } from "./reviews.js";
|
|
|
259
267
|
}
|
|
260
268
|
return undefined;
|
|
261
269
|
};
|
|
270
|
+
/**
|
|
271
|
+
* What `{{product.price}}` says: the price, "From" the lowest where variants
|
|
272
|
+
* differ, or "Price coming soon" for a product listed before any variant has
|
|
273
|
+
* one (AGL-3676) — never "$0".
|
|
274
|
+
*/ export function productPriceText(product, minPrice, maxPrice) {
|
|
275
|
+
var _product_variants;
|
|
276
|
+
if (!((_product_variants = product.variants) != null ? _product_variants : []).some((variant)=>CommerceModel.variantHasPrice(variant))) {
|
|
277
|
+
return 'Price coming soon';
|
|
278
|
+
}
|
|
279
|
+
return minPrice === maxPrice ? `$${minPrice}` : `From $${minPrice}`;
|
|
280
|
+
}
|
|
281
|
+
const PRODUCT_PAGE_NODE = 'pdp__detail';
|
|
282
|
+
const PRODUCT_PAGE_RELATED_NODE = 'pdp__related';
|
|
283
|
+
/**
|
|
284
|
+
* The store's built-in product page (AGL-3676), root first, for the layout's
|
|
285
|
+
* slot: the product block for the routed slug and the related products under
|
|
286
|
+
* it — the two blocks a product template is made of.
|
|
287
|
+
*/ export function buildProductPageNodes(slug) {
|
|
288
|
+
return {
|
|
289
|
+
[Aglyn.NODE_ROOT_ID]: {
|
|
290
|
+
$id: Aglyn.NODE_ROOT_ID,
|
|
291
|
+
componentId: 'div',
|
|
292
|
+
nodes: [
|
|
293
|
+
'pdp__container'
|
|
294
|
+
]
|
|
295
|
+
},
|
|
296
|
+
pdp__container: {
|
|
297
|
+
$id: 'pdp__container',
|
|
298
|
+
parentId: Aglyn.NODE_ROOT_ID,
|
|
299
|
+
componentId: 'muiContainer',
|
|
300
|
+
pluginId: 'mui',
|
|
301
|
+
props: {
|
|
302
|
+
maxWidth: 'lg',
|
|
303
|
+
sx: {
|
|
304
|
+
paddingTop: 6,
|
|
305
|
+
paddingBottom: 10
|
|
306
|
+
}
|
|
307
|
+
},
|
|
308
|
+
nodes: [
|
|
309
|
+
PRODUCT_PAGE_NODE,
|
|
310
|
+
PRODUCT_PAGE_RELATED_NODE
|
|
311
|
+
]
|
|
312
|
+
},
|
|
313
|
+
[PRODUCT_PAGE_NODE]: {
|
|
314
|
+
$id: PRODUCT_PAGE_NODE,
|
|
315
|
+
parentId: 'pdp__container',
|
|
316
|
+
componentId: 'product-detail',
|
|
317
|
+
pluginId: 'commerce',
|
|
318
|
+
props: {
|
|
319
|
+
slug
|
|
320
|
+
}
|
|
321
|
+
},
|
|
322
|
+
[PRODUCT_PAGE_RELATED_NODE]: {
|
|
323
|
+
$id: PRODUCT_PAGE_RELATED_NODE,
|
|
324
|
+
parentId: 'pdp__container',
|
|
325
|
+
componentId: 'related-products',
|
|
326
|
+
pluginId: 'commerce',
|
|
327
|
+
props: {
|
|
328
|
+
heading: 'You may also like',
|
|
329
|
+
maxItems: 4,
|
|
330
|
+
sx: {
|
|
331
|
+
marginTop: 8
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
async function composeBuiltInProductPage(hostId, host, productId, product) {
|
|
338
|
+
try {
|
|
339
|
+
var _ref, _ref1, _ref2, _ref3;
|
|
340
|
+
var _product_mediaUrls, _host_seo, _product_seo, _product_seo1;
|
|
341
|
+
const layoutId = await resolveBuiltInPageLayoutId({
|
|
342
|
+
hostId,
|
|
343
|
+
host: host
|
|
344
|
+
});
|
|
345
|
+
const productReviews = await readProductReviews(hostId, productId).catch((error)=>{
|
|
346
|
+
console.error('product review aggregate failed', error);
|
|
347
|
+
return {
|
|
348
|
+
reviews: [],
|
|
349
|
+
aggregate: {
|
|
350
|
+
count: 0,
|
|
351
|
+
average: 0
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
});
|
|
355
|
+
const card = collectSocialImageFacts([
|
|
356
|
+
(_ref = (_product_mediaUrls = product.mediaUrls) == null ? void 0 : _product_mediaUrls[0]) != null ? _ref : product.imageUrl,
|
|
357
|
+
host == null ? void 0 : (_host_seo = host.seo) == null ? void 0 : _host_seo.image
|
|
358
|
+
]);
|
|
359
|
+
const nodes = await composeNodesWithChrome({
|
|
360
|
+
hostId,
|
|
361
|
+
layoutId,
|
|
362
|
+
screenNodes: buildProductPageNodes(product.slug),
|
|
363
|
+
socialImages: card.socialImages,
|
|
364
|
+
host: host
|
|
365
|
+
});
|
|
366
|
+
if (!nodes) return undefined;
|
|
367
|
+
return {
|
|
368
|
+
props: JSON.parse(JSON.stringify(_extends({
|
|
369
|
+
// Seeded, so the product renders server-side and the head writes
|
|
370
|
+
// its Product structured data, as on a template page (AGL-659).
|
|
371
|
+
pageData: {
|
|
372
|
+
commerce: _extends({
|
|
373
|
+
product: toPublicProductDetail(productId, product)
|
|
374
|
+
}, productReviews.aggregate.count ? {
|
|
375
|
+
reviews: productReviews
|
|
376
|
+
} : {})
|
|
377
|
+
},
|
|
378
|
+
data: {
|
|
379
|
+
host,
|
|
380
|
+
screen: {
|
|
381
|
+
data: {
|
|
382
|
+
displayName: product.name,
|
|
383
|
+
seo: {
|
|
384
|
+
title: (_ref1 = (_product_seo = product.seo) == null ? void 0 : _product_seo.title) != null ? _ref1 : undefined,
|
|
385
|
+
description: (_ref2 = (_ref3 = (_product_seo1 = product.seo) == null ? void 0 : _product_seo1.description) != null ? _ref3 : product.description) != null ? _ref2 : undefined
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
},
|
|
390
|
+
nodes
|
|
391
|
+
}, card.collected()))),
|
|
392
|
+
revalidate: 60
|
|
393
|
+
};
|
|
394
|
+
} catch (error) {
|
|
395
|
+
console.error('built-in product page composition failed', error);
|
|
396
|
+
return undefined;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
262
399
|
const ORDER_STATUS_NODE = 'order-status__block';
|
|
263
400
|
/** The order-status page's node tree, root first, for the layout's slot. */ export function buildOrderStatusNodes() {
|
|
264
401
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/site-page-resolver.ts"],"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 type { SitePageResolver } from '@aglyn/aglyn/server'\nimport { hostCollectionKind } from '@aglyn/aglyn/server'\nimport composeScreenNodes, {\n composeNodesWithChrome,\n} from '@aglyn/tenant-runtime/compose-screen-nodes'\nimport { resolveBuiltInPageLayoutId } from '@aglyn/tenant-runtime/built-in-page-layout'\nimport * as Aglyn from '@aglyn/aglyn/server'\nimport { ORDER_STATUS_PATH } from './order-status-token'\nimport { ORDER_STATUS_COMPONENT_ID } from '../constants/order-status'\nimport { RETURN_REQUEST_COMPONENT_ID, RETURN_REQUEST_PATH } from '../constants/return-request'\nimport getScreen from '@aglyn/tenant-runtime/get-screen'\nimport { collectSocialImageFacts } from '@aglyn/tenant-runtime/social-image-facts'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin'\nimport * as CommerceModel from '../model'\nimport { toPublicProductDetail } from './product'\nimport { readProductReviews } from './reviews'\n\n/**\n * Commerce page resolver (AGL-292/298/418), relocated verbatim from the\n * tenant loader: /products/{slug} renders the host's designated PDP\n * template with product tokens, /collections/{slug} the collection\n * template — both compose server-side and return complete page props.\n * Runs only for paths that didn't match a published screen; returns\n * undefined for everything else so the loader falls through.\n */\nexport const commerceSitePageResolver: SitePageResolver = async ({\n hostId,\n host,\n path,\n}) => {\n const hostRes = { host }\n // Product detail routes (AGL-292): /products/{slug} renders the\n // host's designated PDP template screen (settings/store\n // `pdpScreenId`, AGL-295) with product tokens; the product-detail\n // block hydrates variants client-side from the same slug. Same\n // mechanism as entry-template screens below.\n const pdpSegments = path.split('/').filter(Boolean)\n // The guest order-status page (AGL-3610), at /order-status — reached only\n // when the site has no page of its own there. The site's header and footer\n // around the order-status block, which reads the signed `?o=&t=` link after\n // hydration, so this page holds no order and is safe to cache. UNLISTED, so\n // the head says noindex: it is a private page, not a landing page.\n if (pdpSegments.length === 1 && pdpSegments[0] === ORDER_STATUS_PATH) {\n return composeOrderStatusPage(hostId, host)\n }\n // The buyer's return form (AGL-3611), at /order-return, under the same\n // terms: no page of the site's own there, the order read after hydration\n // from the link's `?o=&t=` (or the member's session), UNLISTED.\n if (pdpSegments.length === 1 && pdpSegments[0] === RETURN_REQUEST_PATH) {\n return composeReturnRequestPage(hostId, host)\n }\n if (pdpSegments.length === 2 && pdpSegments[0] === 'products') {\n const adminFirestore = firebaseAdmin.app().firestore()\n const hostDocRef = adminFirestore.collection('hosts').doc(hostId)\n const [storeSettings, productSnapshot] = await Promise.all([\n hostDocRef.collection('settings').doc('store').get(),\n hostDocRef\n .collection('products')\n .where('slug', '==', pdpSegments[1])\n .limit(1)\n .get(),\n ])\n const pdpScreenId = storeSettings.get('pdpScreenId')\n const productRaw = productSnapshot.docs[0]?.data() as any\n if (\n pdpScreenId &&\n productRaw &&\n !productRaw.deletedAt &&\n productRaw.status === 'active'\n ) {\n const product = CommerceModel.liftLegacyProduct(productRaw)\n const templateRes = await getScreen({\n hostId,\n screenId: pdpScreenId,\n // A PDP template renders against a routed PRODUCT, never at an address\n // of its own, so it is served here and 404s everywhere else (AGL-1400).\n allowTemplate: true,\n })\n if (templateRes.screen) {\n const [minPrice, maxPrice] = CommerceModel.productPriceRange(product)\n // Best-effort: a reviews read that fails costs the page its rating\n // snippet, not the page.\n const productReviews = await readProductReviews(\n hostId,\n productSnapshot.docs[0].id,\n ).catch((error) => {\n console.error('product review aggregate failed', error)\n return { reviews: [], aggregate: { count: 0, average: 0 } }\n })\n // The head shares this page as the template's image, then the site\n // default, so their documents are read in the template's own batch\n // (AGL-2850).\n const card = collectSocialImageFacts([\n (templateRes.screen as any).seo?.image,\n hostRes.host?.seo?.image,\n ])\n const templateNodes = await composeScreenNodes({\n hostId,\n screenId: pdpScreenId,\n screen: templateRes.screen,\n socialImages: card.socialImages,\n // The template's host variables, and its layout's, fill in from\n // this site as they do on every other page (AGL-2883).\n host: hostRes.host,\n tokens: {\n 'product.name': product.name,\n 'product.description': product.description ?? '',\n 'product.price':\n minPrice === maxPrice\n ? `$${minPrice}`\n : `From $${minPrice}`,\n 'product.image':\n product.mediaUrls?.[0] ?? product.imageUrl ?? '',\n 'product.slug': product.slug,\n },\n // Named as the product template it is if the page review holds it\n // (AGL-3374); without it the route has no page.\n page: {\n template: {\n role: 'entry',\n route: '/products/:slug',\n collectionName: 'Products',\n entryPath: `/products/${product.slug}`,\n fallback: 'not-found',\n },\n },\n })\n if (templateNodes) {\n return {\n props: JSON.parse(\n JSON.stringify({\n // The PDP block used to fetch this in an effect, so the\n // server rendered a skeleton and crawlers saw a product page\n // with no product in it (AGL-659). The product is already\n // loaded here — hand it down so it renders server-side.\n pageData: {\n commerce: {\n product: toPublicProductDetail(\n productSnapshot.docs[0].id,\n product,\n ),\n // Rating aggregate (AGL-686): needed HERE because\n // `aggregateRating` belongs nested inside the Product\n // structured data, and that is emitted server-side.\n // The reviews block used to publish a free-standing\n // AggregateRating node, which schema.org ignores.\n ...(productReviews.aggregate.count\n ? { reviews: productReviews }\n : {}),\n },\n },\n data: {\n host: hostRes.host,\n screen: {\n data: {\n ...templateRes.screen,\n // The product NAMES this page; the template's own name\n // (\"Product detail\") describes none of the products it\n // renders. Kept distinct from the SEO title because the\n // head treats an authored title as verbatim and a name\n // as the side the site title joins (AGL-1341) — so a\n // product with its own SEO title gets exactly that, and\n // one without gets \"Blue Widget – Acme Store\".\n displayName: product.name,\n seo: {\n ...((templateRes.screen as any).seo ?? {}),\n // Only when the PRODUCT authored one: inheriting the\n // template screen's title would give every product\n // page in the store the same `<title>`.\n title: product.seo?.title ?? undefined,\n description:\n product.seo?.description ??\n product.description ??\n undefined,\n },\n },\n },\n },\n nodes: templateNodes,\n ...card.collected(),\n }),\n ),\n revalidate: 60,\n }\n }\n }\n }\n }\n // Commerce collection routes (AGL-298): /collections/{slug} renders\n // the designated collection template with collection tokens; the\n // product-grid block derives the same slug from the URL.\n if (pdpSegments.length === 2 && pdpSegments[0] === 'collections') {\n const adminFirestore = firebaseAdmin.app().firestore()\n const hostDocRef = adminFirestore.collection('hosts').doc(hostId)\n const [storeSettings, collectionSnapshot] = await Promise.all([\n hostDocRef.collection('settings').doc('store').get(),\n hostDocRef\n .collection('collections')\n .where('slug', '==', pdpSegments[1])\n .limit(5)\n .get(),\n ])\n const collectionScreenId = storeSettings.get('collectionScreenId')\n // Content collections share this path and a slug is only unique within a\n // kind (AGL-954), so take the first CATALOG match rather than the first\n // doc — otherwise a blog with the same slug renders as a product page.\n const shopCollection = collectionSnapshot.docs\n .find((docSnapshot) => hostCollectionKind(docSnapshot.data()) === 'catalog')\n ?.data() as CommerceModel.HostCollection | undefined\n if (collectionScreenId && shopCollection) {\n const templateRes = await getScreen({\n hostId,\n screenId: collectionScreenId,\n allowTemplate: true,\n })\n if (templateRes.screen) {\n // As on the PDP above: the template's image, then the site default\n // (AGL-2850).\n const card = collectSocialImageFacts([\n (templateRes.screen as any).seo?.image,\n hostRes.host?.seo?.image,\n ])\n const templateNodes = await composeScreenNodes({\n hostId,\n screenId: collectionScreenId,\n screen: templateRes.screen,\n socialImages: card.socialImages,\n // As on the PDP above (AGL-2883).\n host: hostRes.host,\n tokens: {\n 'collection.name': shopCollection.name,\n 'collection.description': shopCollection.description ?? '',\n 'collection.image': shopCollection.imageUrl ?? '',\n 'collection.slug': shopCollection.slug,\n },\n // As on the PDP above (AGL-3374).\n page: {\n template: {\n role: 'entry',\n route: '/collections/:slug',\n collectionName: 'Store collections',\n entryPath: `/collections/${shopCollection.slug}`,\n fallback: 'not-found',\n },\n },\n })\n if (templateNodes) {\n return {\n props: JSON.parse(\n JSON.stringify({\n data: {\n host: hostRes.host,\n screen: {\n data: {\n ...templateRes.screen,\n // As on the PDP above: the catalog collection names the\n // page, and a name takes the site title after it while\n // an authored SEO title would not (AGL-1341). A shop\n // collection has no SEO title field of its own, so the\n // template's must not stand in for one.\n displayName: shopCollection.name,\n seo: {\n ...((templateRes.screen as any).seo ?? {}),\n title: undefined,\n description:\n shopCollection.description ?? undefined,\n },\n },\n },\n },\n nodes: templateNodes,\n ...card.collected(),\n }),\n ),\n revalidate: 60,\n }\n }\n }\n }\n }\n return undefined\n}\n\nconst ORDER_STATUS_NODE = 'order-status__block'\n\n/** The order-status page's node tree, root first, for the layout's slot. */\nexport function buildOrderStatusNodes(): Record<string, Aglyn.AglynNodeSchema> {\n return {\n [Aglyn.NODE_ROOT_ID]: {\n $id: Aglyn.NODE_ROOT_ID,\n componentId: 'div',\n nodes: ['order-status__container'],\n } as Aglyn.AglynNodeSchema,\n 'order-status__container': {\n $id: 'order-status__container',\n parentId: Aglyn.NODE_ROOT_ID,\n componentId: 'muiContainer',\n pluginId: 'mui',\n props: { maxWidth: 'md', sx: { paddingTop: 6, paddingBottom: 8 } },\n nodes: [ORDER_STATUS_NODE],\n } as Aglyn.AglynNodeSchema,\n [ORDER_STATUS_NODE]: {\n $id: ORDER_STATUS_NODE,\n parentId: 'order-status__container',\n componentId: ORDER_STATUS_COMPONENT_ID,\n pluginId: 'commerce',\n props: {},\n } as Aglyn.AglynNodeSchema,\n }\n}\n\nasync function composeOrderStatusPage(hostId: string, host: unknown) {\n try {\n const layoutId = await resolveBuiltInPageLayoutId({ hostId, host: host as never })\n const nodes = await composeNodesWithChrome({\n hostId,\n layoutId,\n screenNodes: buildOrderStatusNodes(),\n host: host as Aglyn.HostTokenSource,\n })\n if (!nodes) return undefined\n return {\n props: JSON.parse(\n JSON.stringify({\n data: {\n host,\n screen: {\n data: {\n displayName: 'Order status',\n visibility: Aglyn.HostScreenVisibility.UNLISTED,\n },\n },\n },\n nodes,\n }),\n ),\n revalidate: 3600,\n }\n } catch (error) {\n console.error('order status page composition failed', error)\n return undefined\n }\n}\n\nconst RETURN_REQUEST_NODE = 'ret__block'\n\n/** The return form's node tree, root first, for the layout's slot. */\nexport function buildReturnRequestNodes(): Record<string, Aglyn.AglynNodeSchema> {\n return {\n [Aglyn.NODE_ROOT_ID]: {\n $id: Aglyn.NODE_ROOT_ID,\n componentId: 'div',\n nodes: ['ret__container'],\n } as Aglyn.AglynNodeSchema,\n ret__container: {\n $id: 'ret__container',\n parentId: Aglyn.NODE_ROOT_ID,\n componentId: 'muiContainer',\n pluginId: 'mui',\n props: { maxWidth: 'md', sx: { paddingTop: 6, paddingBottom: 8 } },\n nodes: [RETURN_REQUEST_NODE],\n } as Aglyn.AglynNodeSchema,\n [RETURN_REQUEST_NODE]: {\n $id: RETURN_REQUEST_NODE,\n parentId: 'ret__container',\n componentId: RETURN_REQUEST_COMPONENT_ID,\n pluginId: 'commerce',\n props: {},\n } as Aglyn.AglynNodeSchema,\n }\n}\n\nasync function composeReturnRequestPage(hostId: string, host: unknown) {\n try {\n const layoutId = await resolveBuiltInPageLayoutId({ hostId, host: host as never })\n const nodes = await composeNodesWithChrome({\n hostId,\n layoutId,\n screenNodes: buildReturnRequestNodes(),\n host: host as Aglyn.HostTokenSource,\n })\n if (!nodes) return undefined\n return {\n props: JSON.parse(\n JSON.stringify({\n data: {\n host,\n screen: {\n data: {\n displayName: 'Request a return',\n visibility: Aglyn.HostScreenVisibility.UNLISTED,\n },\n },\n },\n nodes,\n }),\n ),\n revalidate: 3600,\n }\n } catch (error) {\n console.error('return request page composition failed', error)\n return undefined\n }\n}\n"],"names":["hostCollectionKind","composeScreenNodes","composeNodesWithChrome","resolveBuiltInPageLayoutId","Aglyn","ORDER_STATUS_PATH","ORDER_STATUS_COMPONENT_ID","RETURN_REQUEST_COMPONENT_ID","RETURN_REQUEST_PATH","getScreen","collectSocialImageFacts","firebaseAdmin","CommerceModel","toPublicProductDetail","readProductReviews","commerceSitePageResolver","hostId","host","path","hostRes","pdpSegments","split","filter","Boolean","length","composeOrderStatusPage","composeReturnRequestPage","productSnapshot","adminFirestore","app","firestore","hostDocRef","collection","doc","storeSettings","Promise","all","get","where","limit","pdpScreenId","productRaw","docs","data","deletedAt","status","product","liftLegacyProduct","templateRes","screenId","allowTemplate","screen","minPrice","maxPrice","productPriceRange","productReviews","id","catch","error","console","reviews","aggregate","count","average","card","seo","image","templateNodes","socialImages","tokens","name","description","mediaUrls","imageUrl","slug","page","template","role","route","collectionName","entryPath","fallback","props","JSON","parse","stringify","pageData","commerce","displayName","title","undefined","nodes","collected","revalidate","collectionSnapshot","collectionScreenId","shopCollection","find","docSnapshot","ORDER_STATUS_NODE","buildOrderStatusNodes","NODE_ROOT_ID","$id","componentId","parentId","pluginId","maxWidth","sx","paddingTop","paddingBottom","layoutId","screenNodes","visibility","HostScreenVisibility","UNLISTED","RETURN_REQUEST_NODE","buildReturnRequestNodes","ret__container"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/site-page-resolver.ts"],"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 type { SitePageResolver } from '@aglyn/aglyn/server'\nimport { hostCollectionKind } from '@aglyn/aglyn/server'\nimport composeScreenNodes, {\n composeNodesWithChrome,\n} from '@aglyn/tenant-runtime/compose-screen-nodes'\nimport { resolveBuiltInPageLayoutId } from '@aglyn/tenant-runtime/built-in-page-layout'\nimport * as Aglyn from '@aglyn/aglyn/server'\nimport { ORDER_STATUS_PATH } from './order-status-token'\nimport { ORDER_STATUS_COMPONENT_ID } from '../constants/order-status'\nimport { RETURN_REQUEST_COMPONENT_ID, RETURN_REQUEST_PATH } from '../constants/return-request'\nimport getScreen from '@aglyn/tenant-runtime/get-screen'\nimport { collectSocialImageFacts } from '@aglyn/tenant-runtime/social-image-facts'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin'\nimport * as CommerceModel from '../model'\nimport { toPublicProductDetail } from './product'\nimport { readProductReviews } from './reviews'\n\n/**\n * Commerce page resolver (AGL-292/298/418), relocated verbatim from the\n * tenant loader: /products/{slug} renders the host's designated PDP\n * template with product tokens, /collections/{slug} the collection\n * template — both compose server-side and return complete page props.\n * Runs only for paths that didn't match a published screen; returns\n * undefined for everything else so the loader falls through.\n */\nexport const commerceSitePageResolver: SitePageResolver = async ({\n hostId,\n host,\n path,\n}) => {\n const hostRes = { host }\n // Product detail routes (AGL-292): /products/{slug} renders the\n // host's designated PDP template screen (settings/store\n // `pdpScreenId`, AGL-295) with product tokens; the product-detail\n // block hydrates variants client-side from the same slug. Same\n // mechanism as entry-template screens below.\n const pdpSegments = path.split('/').filter(Boolean)\n // The guest order-status page (AGL-3610), at /order-status — reached only\n // when the site has no page of its own there. The site's header and footer\n // around the order-status block, which reads the signed `?o=&t=` link after\n // hydration, so this page holds no order and is safe to cache. UNLISTED, so\n // the head says noindex: it is a private page, not a landing page.\n if (pdpSegments.length === 1 && pdpSegments[0] === ORDER_STATUS_PATH) {\n return composeOrderStatusPage(hostId, host)\n }\n // The buyer's return form (AGL-3611), at /order-return, under the same\n // terms: no page of the site's own there, the order read after hydration\n // from the link's `?o=&t=` (or the member's session), UNLISTED.\n if (pdpSegments.length === 1 && pdpSegments[0] === RETURN_REQUEST_PATH) {\n return composeReturnRequestPage(hostId, host)\n }\n if (pdpSegments.length === 2 && pdpSegments[0] === 'products') {\n const adminFirestore = firebaseAdmin.app().firestore()\n const hostDocRef = adminFirestore.collection('hosts').doc(hostId)\n const [storeSettings, productSnapshot] = await Promise.all([\n hostDocRef.collection('settings').doc('store').get(),\n hostDocRef\n .collection('products')\n .where('slug', '==', pdpSegments[1])\n .limit(1)\n .get(),\n ])\n const pdpScreenId = storeSettings.get('pdpScreenId')\n const productRaw = productSnapshot.docs[0]?.data() as any\n if (\n pdpScreenId &&\n productRaw &&\n !productRaw.deletedAt &&\n productRaw.status === 'active'\n ) {\n const product = CommerceModel.liftLegacyProduct(productRaw)\n const templateRes = await getScreen({\n hostId,\n screenId: pdpScreenId,\n // A PDP template renders against a routed PRODUCT, never at an address\n // of its own, so it is served here and 404s everywhere else (AGL-1400).\n allowTemplate: true,\n })\n if (templateRes.screen) {\n const [minPrice, maxPrice] = CommerceModel.productPriceRange(product)\n // Best-effort: a reviews read that fails costs the page its rating\n // snippet, not the page.\n const productReviews = await readProductReviews(\n hostId,\n productSnapshot.docs[0].id,\n ).catch((error) => {\n console.error('product review aggregate failed', error)\n return { reviews: [], aggregate: { count: 0, average: 0 } }\n })\n // The head shares this page as the template's image, then the site\n // default, so their documents are read in the template's own batch\n // (AGL-2850).\n const card = collectSocialImageFacts([\n (templateRes.screen as any).seo?.image,\n hostRes.host?.seo?.image,\n ])\n const templateNodes = await composeScreenNodes({\n hostId,\n screenId: pdpScreenId,\n screen: templateRes.screen,\n socialImages: card.socialImages,\n // The template's host variables, and its layout's, fill in from\n // this site as they do on every other page (AGL-2883).\n host: hostRes.host,\n tokens: {\n 'product.name': product.name,\n 'product.description': product.description ?? '',\n 'product.price': productPriceText(product, minPrice, maxPrice),\n 'product.image':\n product.mediaUrls?.[0] ?? product.imageUrl ?? '',\n 'product.slug': product.slug,\n },\n // Named as the product template it is if the page review holds it\n // (AGL-3374); without it the route has no page.\n page: {\n template: {\n role: 'entry',\n route: '/products/:slug',\n collectionName: 'Products',\n entryPath: `/products/${product.slug}`,\n fallback: 'not-found',\n },\n },\n })\n if (templateNodes) {\n return {\n props: JSON.parse(\n JSON.stringify({\n // The PDP block used to fetch this in an effect, so the\n // server rendered a skeleton and crawlers saw a product page\n // with no product in it (AGL-659). The product is already\n // loaded here — hand it down so it renders server-side.\n pageData: {\n commerce: {\n product: toPublicProductDetail(\n productSnapshot.docs[0].id,\n product,\n ),\n // Rating aggregate (AGL-686): needed HERE because\n // `aggregateRating` belongs nested inside the Product\n // structured data, and that is emitted server-side.\n // The reviews block used to publish a free-standing\n // AggregateRating node, which schema.org ignores.\n ...(productReviews.aggregate.count\n ? { reviews: productReviews }\n : {}),\n },\n },\n data: {\n host: hostRes.host,\n screen: {\n data: {\n ...templateRes.screen,\n // The product NAMES this page; the template's own name\n // (\"Product detail\") describes none of the products it\n // renders. Kept distinct from the SEO title because the\n // head treats an authored title as verbatim and a name\n // as the side the site title joins (AGL-1341) — so a\n // product with its own SEO title gets exactly that, and\n // one without gets \"Blue Widget – Acme Store\".\n displayName: product.name,\n seo: {\n ...((templateRes.screen as any).seo ?? {}),\n // Only when the PRODUCT authored one: inheriting the\n // template screen's title would give every product\n // page in the store the same `<title>`.\n title: product.seo?.title ?? undefined,\n description:\n product.seo?.description ??\n product.description ??\n undefined,\n },\n },\n },\n },\n nodes: templateNodes,\n ...card.collected(),\n }),\n ),\n revalidate: 60,\n }\n }\n }\n }\n // No product template designated, or none that renders: the store's\n // built-in product page (AGL-3676), in the site's own header and footer,\n // rather than a 404 behind every card the product grid links. A store a\n // guided start built has no template of its own, and its grid links each\n // product here.\n if (productRaw && !productRaw.deletedAt && productRaw.status === 'active') {\n return composeBuiltInProductPage(\n hostId,\n host,\n productSnapshot.docs[0].id,\n CommerceModel.liftLegacyProduct(productRaw),\n )\n }\n }\n // Commerce collection routes (AGL-298): /collections/{slug} renders\n // the designated collection template with collection tokens; the\n // product-grid block derives the same slug from the URL.\n if (pdpSegments.length === 2 && pdpSegments[0] === 'collections') {\n const adminFirestore = firebaseAdmin.app().firestore()\n const hostDocRef = adminFirestore.collection('hosts').doc(hostId)\n const [storeSettings, collectionSnapshot] = await Promise.all([\n hostDocRef.collection('settings').doc('store').get(),\n hostDocRef\n .collection('collections')\n .where('slug', '==', pdpSegments[1])\n .limit(5)\n .get(),\n ])\n const collectionScreenId = storeSettings.get('collectionScreenId')\n // Content collections share this path and a slug is only unique within a\n // kind (AGL-954), so take the first CATALOG match rather than the first\n // doc — otherwise a blog with the same slug renders as a product page.\n const shopCollection = collectionSnapshot.docs\n .find((docSnapshot) => hostCollectionKind(docSnapshot.data()) === 'catalog')\n ?.data() as CommerceModel.HostCollection | undefined\n if (collectionScreenId && shopCollection) {\n const templateRes = await getScreen({\n hostId,\n screenId: collectionScreenId,\n allowTemplate: true,\n })\n if (templateRes.screen) {\n // As on the PDP above: the template's image, then the site default\n // (AGL-2850).\n const card = collectSocialImageFacts([\n (templateRes.screen as any).seo?.image,\n hostRes.host?.seo?.image,\n ])\n const templateNodes = await composeScreenNodes({\n hostId,\n screenId: collectionScreenId,\n screen: templateRes.screen,\n socialImages: card.socialImages,\n // As on the PDP above (AGL-2883).\n host: hostRes.host,\n tokens: {\n 'collection.name': shopCollection.name,\n 'collection.description': shopCollection.description ?? '',\n 'collection.image': shopCollection.imageUrl ?? '',\n 'collection.slug': shopCollection.slug,\n },\n // As on the PDP above (AGL-3374).\n page: {\n template: {\n role: 'entry',\n route: '/collections/:slug',\n collectionName: 'Store collections',\n entryPath: `/collections/${shopCollection.slug}`,\n fallback: 'not-found',\n },\n },\n })\n if (templateNodes) {\n return {\n props: JSON.parse(\n JSON.stringify({\n data: {\n host: hostRes.host,\n screen: {\n data: {\n ...templateRes.screen,\n // As on the PDP above: the catalog collection names the\n // page, and a name takes the site title after it while\n // an authored SEO title would not (AGL-1341). A shop\n // collection has no SEO title field of its own, so the\n // template's must not stand in for one.\n displayName: shopCollection.name,\n seo: {\n ...((templateRes.screen as any).seo ?? {}),\n title: undefined,\n description:\n shopCollection.description ?? undefined,\n },\n },\n },\n },\n nodes: templateNodes,\n ...card.collected(),\n }),\n ),\n revalidate: 60,\n }\n }\n }\n }\n }\n return undefined\n}\n\n/**\n * What `{{product.price}}` says: the price, \"From\" the lowest where variants\n * differ, or \"Price coming soon\" for a product listed before any variant has\n * one (AGL-3676) — never \"$0\".\n */\nexport function productPriceText(\n product: Pick<CommerceModel.HostProduct, 'variants'>,\n minPrice: number,\n maxPrice: number,\n): string {\n if (!(product.variants ?? []).some((variant) => CommerceModel.variantHasPrice(variant))) {\n return 'Price coming soon'\n }\n return minPrice === maxPrice ? `$${minPrice}` : `From $${minPrice}`\n}\n\nconst PRODUCT_PAGE_NODE = 'pdp__detail'\nconst PRODUCT_PAGE_RELATED_NODE = 'pdp__related'\n\n/**\n * The store's built-in product page (AGL-3676), root first, for the layout's\n * slot: the product block for the routed slug and the related products under\n * it — the two blocks a product template is made of.\n */\nexport function buildProductPageNodes(slug: string): Record<string, Aglyn.AglynNodeSchema> {\n return {\n [Aglyn.NODE_ROOT_ID]: {\n $id: Aglyn.NODE_ROOT_ID,\n componentId: 'div',\n nodes: ['pdp__container'],\n } as Aglyn.AglynNodeSchema,\n pdp__container: {\n $id: 'pdp__container',\n parentId: Aglyn.NODE_ROOT_ID,\n componentId: 'muiContainer',\n pluginId: 'mui',\n props: { maxWidth: 'lg', sx: { paddingTop: 6, paddingBottom: 10 } },\n nodes: [PRODUCT_PAGE_NODE, PRODUCT_PAGE_RELATED_NODE],\n } as Aglyn.AglynNodeSchema,\n [PRODUCT_PAGE_NODE]: {\n $id: PRODUCT_PAGE_NODE,\n parentId: 'pdp__container',\n componentId: 'product-detail',\n pluginId: 'commerce',\n props: { slug },\n } as Aglyn.AglynNodeSchema,\n [PRODUCT_PAGE_RELATED_NODE]: {\n $id: PRODUCT_PAGE_RELATED_NODE,\n parentId: 'pdp__container',\n componentId: 'related-products',\n pluginId: 'commerce',\n props: { heading: 'You may also like', maxItems: 4, sx: { marginTop: 8 } },\n } as Aglyn.AglynNodeSchema,\n }\n}\n\nasync function composeBuiltInProductPage(\n hostId: string,\n host: unknown,\n productId: string,\n product: ReturnType<typeof CommerceModel.liftLegacyProduct>,\n) {\n try {\n const layoutId = await resolveBuiltInPageLayoutId({ hostId, host: host as never })\n const productReviews = await readProductReviews(hostId, productId).catch((error) => {\n console.error('product review aggregate failed', error)\n return { reviews: [], aggregate: { count: 0, average: 0 } }\n })\n const card = collectSocialImageFacts([\n product.mediaUrls?.[0] ?? product.imageUrl,\n (host as { seo?: { image?: string } } | null)?.seo?.image,\n ])\n const nodes = await composeNodesWithChrome({\n hostId,\n layoutId,\n screenNodes: buildProductPageNodes(product.slug),\n socialImages: card.socialImages,\n host: host as Aglyn.HostTokenSource,\n })\n if (!nodes) return undefined\n return {\n props: JSON.parse(\n JSON.stringify({\n // Seeded, so the product renders server-side and the head writes\n // its Product structured data, as on a template page (AGL-659).\n pageData: {\n commerce: {\n product: toPublicProductDetail(productId, product),\n ...(productReviews.aggregate.count ? { reviews: productReviews } : {}),\n },\n },\n data: {\n host,\n screen: {\n data: {\n displayName: product.name,\n seo: {\n title: product.seo?.title ?? undefined,\n description: product.seo?.description ?? product.description ?? undefined,\n },\n },\n },\n },\n nodes,\n ...card.collected(),\n }),\n ),\n revalidate: 60,\n }\n } catch (error) {\n console.error('built-in product page composition failed', error)\n return undefined\n }\n}\n\nconst ORDER_STATUS_NODE = 'order-status__block'\n\n/** The order-status page's node tree, root first, for the layout's slot. */\nexport function buildOrderStatusNodes(): Record<string, Aglyn.AglynNodeSchema> {\n return {\n [Aglyn.NODE_ROOT_ID]: {\n $id: Aglyn.NODE_ROOT_ID,\n componentId: 'div',\n nodes: ['order-status__container'],\n } as Aglyn.AglynNodeSchema,\n 'order-status__container': {\n $id: 'order-status__container',\n parentId: Aglyn.NODE_ROOT_ID,\n componentId: 'muiContainer',\n pluginId: 'mui',\n props: { maxWidth: 'md', sx: { paddingTop: 6, paddingBottom: 8 } },\n nodes: [ORDER_STATUS_NODE],\n } as Aglyn.AglynNodeSchema,\n [ORDER_STATUS_NODE]: {\n $id: ORDER_STATUS_NODE,\n parentId: 'order-status__container',\n componentId: ORDER_STATUS_COMPONENT_ID,\n pluginId: 'commerce',\n props: {},\n } as Aglyn.AglynNodeSchema,\n }\n}\n\nasync function composeOrderStatusPage(hostId: string, host: unknown) {\n try {\n const layoutId = await resolveBuiltInPageLayoutId({ hostId, host: host as never })\n const nodes = await composeNodesWithChrome({\n hostId,\n layoutId,\n screenNodes: buildOrderStatusNodes(),\n host: host as Aglyn.HostTokenSource,\n })\n if (!nodes) return undefined\n return {\n props: JSON.parse(\n JSON.stringify({\n data: {\n host,\n screen: {\n data: {\n displayName: 'Order status',\n visibility: Aglyn.HostScreenVisibility.UNLISTED,\n },\n },\n },\n nodes,\n }),\n ),\n revalidate: 3600,\n }\n } catch (error) {\n console.error('order status page composition failed', error)\n return undefined\n }\n}\n\nconst RETURN_REQUEST_NODE = 'ret__block'\n\n/** The return form's node tree, root first, for the layout's slot. */\nexport function buildReturnRequestNodes(): Record<string, Aglyn.AglynNodeSchema> {\n return {\n [Aglyn.NODE_ROOT_ID]: {\n $id: Aglyn.NODE_ROOT_ID,\n componentId: 'div',\n nodes: ['ret__container'],\n } as Aglyn.AglynNodeSchema,\n ret__container: {\n $id: 'ret__container',\n parentId: Aglyn.NODE_ROOT_ID,\n componentId: 'muiContainer',\n pluginId: 'mui',\n props: { maxWidth: 'md', sx: { paddingTop: 6, paddingBottom: 8 } },\n nodes: [RETURN_REQUEST_NODE],\n } as Aglyn.AglynNodeSchema,\n [RETURN_REQUEST_NODE]: {\n $id: RETURN_REQUEST_NODE,\n parentId: 'ret__container',\n componentId: RETURN_REQUEST_COMPONENT_ID,\n pluginId: 'commerce',\n props: {},\n } as Aglyn.AglynNodeSchema,\n }\n}\n\nasync function composeReturnRequestPage(hostId: string, host: unknown) {\n try {\n const layoutId = await resolveBuiltInPageLayoutId({ hostId, host: host as never })\n const nodes = await composeNodesWithChrome({\n hostId,\n layoutId,\n screenNodes: buildReturnRequestNodes(),\n host: host as Aglyn.HostTokenSource,\n })\n if (!nodes) return undefined\n return {\n props: JSON.parse(\n JSON.stringify({\n data: {\n host,\n screen: {\n data: {\n displayName: 'Request a return',\n visibility: Aglyn.HostScreenVisibility.UNLISTED,\n },\n },\n },\n nodes,\n }),\n ),\n revalidate: 3600,\n }\n } catch (error) {\n console.error('return request page composition failed', error)\n return undefined\n }\n}\n"],"names":["hostCollectionKind","composeScreenNodes","composeNodesWithChrome","resolveBuiltInPageLayoutId","Aglyn","ORDER_STATUS_PATH","ORDER_STATUS_COMPONENT_ID","RETURN_REQUEST_COMPONENT_ID","RETURN_REQUEST_PATH","getScreen","collectSocialImageFacts","firebaseAdmin","CommerceModel","toPublicProductDetail","readProductReviews","commerceSitePageResolver","hostId","host","path","hostRes","pdpSegments","split","filter","Boolean","length","composeOrderStatusPage","composeReturnRequestPage","productSnapshot","adminFirestore","app","firestore","hostDocRef","collection","doc","storeSettings","Promise","all","get","where","limit","pdpScreenId","productRaw","docs","data","deletedAt","status","product","liftLegacyProduct","templateRes","screenId","allowTemplate","screen","minPrice","maxPrice","productPriceRange","productReviews","id","catch","error","console","reviews","aggregate","count","average","card","seo","image","templateNodes","socialImages","tokens","name","description","productPriceText","mediaUrls","imageUrl","slug","page","template","role","route","collectionName","entryPath","fallback","props","JSON","parse","stringify","pageData","commerce","displayName","title","undefined","nodes","collected","revalidate","composeBuiltInProductPage","collectionSnapshot","collectionScreenId","shopCollection","find","docSnapshot","variants","some","variant","variantHasPrice","PRODUCT_PAGE_NODE","PRODUCT_PAGE_RELATED_NODE","buildProductPageNodes","NODE_ROOT_ID","$id","componentId","pdp__container","parentId","pluginId","maxWidth","sx","paddingTop","paddingBottom","heading","maxItems","marginTop","productId","layoutId","screenNodes","ORDER_STATUS_NODE","buildOrderStatusNodes","visibility","HostScreenVisibility","UNLISTED","RETURN_REQUEST_NODE","buildReturnRequestNodes","ret__container"],"mappings":"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"}
|
package/src/lib/server.js
CHANGED
|
@@ -396,6 +396,11 @@ registerPluginJob({
|
|
|
396
396
|
// on first call.
|
|
397
397
|
registerPluginApiRoute('commerce/products/save', async (req, res)=>(await import("./server/products-write.js")).productSaveHandler(req, res));
|
|
398
398
|
registerPluginApiRoute('commerce/products/stock', async (req, res)=>(await import("./server/products-write.js")).productStockHandler(req, res));
|
|
399
|
+
// The order dialog's note and restock answer for the native apps
|
|
400
|
+
// (AGL-3651, AGL-3652): the console's own timeline computation, in one
|
|
401
|
+
// transaction on the stored order. Loaded on first call.
|
|
402
|
+
registerPluginApiRoute('commerce/order-note', async (req, res)=>(await import("./server/order-annotations.js")).orderNoteHandler(req, res));
|
|
403
|
+
registerPluginApiRoute('commerce/order-restock-answer', async (req, res)=>(await import("./server/order-annotations.js")).orderRestockAnswerHandler(req, res));
|
|
399
404
|
// Issue / void store credit (AGL-2226). Server-side because the host
|
|
400
405
|
// catch-all in the Firestore rules would otherwise let a client write
|
|
401
406
|
// its own `balanceCents`, which checkout applies as amount-off.
|