@aglyn/plugins-commerce 1.0.0-beta.143
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/LICENSE +201 -0
- package/README.md +10 -0
- package/package.json +57 -0
- package/src/index.d.ts +38 -0
- package/src/index.js +38 -0
- package/src/index.js.map +1 -0
- package/src/lib/components/account.d.ts +31 -0
- package/src/lib/components/account.js +645 -0
- package/src/lib/components/account.js.map +1 -0
- package/src/lib/components/cart.d.ts +38 -0
- package/src/lib/components/cart.js +851 -0
- package/src/lib/components/cart.js.map +1 -0
- package/src/lib/components/commerce-console-page.d.ts +32 -0
- package/src/lib/components/commerce-console-page.js +296 -0
- package/src/lib/components/commerce-console-page.js.map +1 -0
- package/src/lib/components/commerce-console-sections.d.ts +34 -0
- package/src/lib/components/commerce-console-sections.js +62 -0
- package/src/lib/components/commerce-console-sections.js.map +1 -0
- package/src/lib/components/console/catalog-organization-card.component.d.ts +14 -0
- package/src/lib/components/console/catalog-organization-card.component.js +1087 -0
- package/src/lib/components/console/catalog-organization-card.component.js.map +1 -0
- package/src/lib/components/console/commerce-analytics-card.component.d.ts +27 -0
- package/src/lib/components/console/commerce-analytics-card.component.js +310 -0
- package/src/lib/components/console/commerce-analytics-card.component.js.map +1 -0
- package/src/lib/components/console/commerce-glance-card.component.d.ts +20 -0
- package/src/lib/components/console/commerce-glance-card.component.js +340 -0
- package/src/lib/components/console/commerce-glance-card.component.js.map +1 -0
- package/src/lib/components/console/commerce-stat-tile.component.d.ts +37 -0
- package/src/lib/components/console/commerce-stat-tile.component.js +72 -0
- package/src/lib/components/console/commerce-stat-tile.component.js.map +1 -0
- package/src/lib/components/console/discounts-card.component.d.ts +14 -0
- package/src/lib/components/console/discounts-card.component.js +443 -0
- package/src/lib/components/console/discounts-card.component.js.map +1 -0
- package/src/lib/components/console/entitlement-gate.component.d.ts +76 -0
- package/src/lib/components/console/entitlement-gate.component.js +115 -0
- package/src/lib/components/console/entitlement-gate.component.js.map +1 -0
- package/src/lib/components/console/gift-cards-card.component.d.ts +29 -0
- package/src/lib/components/console/gift-cards-card.component.js +379 -0
- package/src/lib/components/console/gift-cards-card.component.js.map +1 -0
- package/src/lib/components/console/host-coupons-card.component.d.ts +14 -0
- package/src/lib/components/console/host-coupons-card.component.js +252 -0
- package/src/lib/components/console/host-coupons-card.component.js.map +1 -0
- package/src/lib/components/console/host-orders-card.component.d.ts +13 -0
- package/src/lib/components/console/host-orders-card.component.js +861 -0
- package/src/lib/components/console/host-orders-card.component.js.map +1 -0
- package/src/lib/components/console/locations-card.component.d.ts +14 -0
- package/src/lib/components/console/locations-card.component.js +256 -0
- package/src/lib/components/console/locations-card.component.js.map +1 -0
- package/src/lib/components/console/member-posts-card.component.d.ts +8 -0
- package/src/lib/components/console/member-posts-card.component.js +283 -0
- package/src/lib/components/console/member-posts-card.component.js.map +1 -0
- package/src/lib/components/console/newest-site-users-card.component.d.ts +36 -0
- package/src/lib/components/console/newest-site-users-card.component.js +126 -0
- package/src/lib/components/console/newest-site-users-card.component.js.map +1 -0
- package/src/lib/components/console/order-detail-dialog.component.d.ts +28 -0
- package/src/lib/components/console/order-detail-dialog.component.js +976 -0
- package/src/lib/components/console/order-detail-dialog.component.js.map +1 -0
- package/src/lib/components/console/paid-media.d.ts +127 -0
- package/src/lib/components/console/paid-media.js +397 -0
- package/src/lib/components/console/paid-media.js.map +1 -0
- package/src/lib/components/console/payments-settings-card.component.d.ts +13 -0
- package/src/lib/components/console/payments-settings-card.component.js +295 -0
- package/src/lib/components/console/payments-settings-card.component.js.map +1 -0
- package/src/lib/components/console/pos-page.component.d.ts +13 -0
- package/src/lib/components/console/pos-page.component.js +916 -0
- package/src/lib/components/console/pos-page.component.js.map +1 -0
- package/src/lib/components/console/product-editor-dialog.component.d.ts +32 -0
- package/src/lib/components/console/product-editor-dialog.component.js +1374 -0
- package/src/lib/components/console/product-editor-dialog.component.js.map +1 -0
- package/src/lib/components/console/products-hub-card.component.d.ts +16 -0
- package/src/lib/components/console/products-hub-card.component.js +1280 -0
- package/src/lib/components/console/products-hub-card.component.js.map +1 -0
- package/src/lib/components/console/products-hub-zone.component.d.ts +50 -0
- package/src/lib/components/console/products-hub-zone.component.js +200 -0
- package/src/lib/components/console/products-hub-zone.component.js.map +1 -0
- package/src/lib/components/console/recovery-queue-card.component.d.ts +39 -0
- package/src/lib/components/console/recovery-queue-card.component.js +262 -0
- package/src/lib/components/console/recovery-queue-card.component.js.map +1 -0
- package/src/lib/components/console/registers-card.component.d.ts +21 -0
- package/src/lib/components/console/registers-card.component.js +296 -0
- package/src/lib/components/console/registers-card.component.js.map +1 -0
- package/src/lib/components/console/reservations-card.component.d.ts +14 -0
- package/src/lib/components/console/reservations-card.component.js +650 -0
- package/src/lib/components/console/reservations-card.component.js.map +1 -0
- package/src/lib/components/console/reviews-moderation-card.component.d.ts +8 -0
- package/src/lib/components/console/reviews-moderation-card.component.js +235 -0
- package/src/lib/components/console/reviews-moderation-card.component.js.map +1 -0
- package/src/lib/components/console/shipping-settings-card.component.d.ts +38 -0
- package/src/lib/components/console/shipping-settings-card.component.js +560 -0
- package/src/lib/components/console/shipping-settings-card.component.js.map +1 -0
- package/src/lib/components/console/stock-movements-card.component.d.ts +31 -0
- package/src/lib/components/console/stock-movements-card.component.js +333 -0
- package/src/lib/components/console/stock-movements-card.component.js.map +1 -0
- package/src/lib/components/console/store-settings-card.component.d.ts +14 -0
- package/src/lib/components/console/store-settings-card.component.js +300 -0
- package/src/lib/components/console/store-settings-card.component.js.map +1 -0
- package/src/lib/components/console/storefront-tax-summary-card.component.d.ts +73 -0
- package/src/lib/components/console/storefront-tax-summary-card.component.js +408 -0
- package/src/lib/components/console/storefront-tax-summary-card.component.js.map +1 -0
- package/src/lib/components/console/suppliers-card.component.d.ts +8 -0
- package/src/lib/components/console/suppliers-card.component.js +328 -0
- package/src/lib/components/console/suppliers-card.component.js.map +1 -0
- package/src/lib/components/console/tax-settings-card.component.d.ts +14 -0
- package/src/lib/components/console/tax-settings-card.component.js +473 -0
- package/src/lib/components/console/tax-settings-card.component.js.map +1 -0
- package/src/lib/components/gate.d.ts +39 -0
- package/src/lib/components/gate.js +202 -0
- package/src/lib/components/gate.js.map +1 -0
- package/src/lib/components/gated-video.d.ts +40 -0
- package/src/lib/components/gated-video.js +366 -0
- package/src/lib/components/gated-video.js.map +1 -0
- package/src/lib/components/member-feed.d.ts +32 -0
- package/src/lib/components/member-feed.js +198 -0
- package/src/lib/components/member-feed.js.map +1 -0
- package/src/lib/components/member-recovery.d.ts +35 -0
- package/src/lib/components/member-recovery.js +313 -0
- package/src/lib/components/member-recovery.js.map +1 -0
- package/src/lib/components/member-signin.d.ts +35 -0
- package/src/lib/components/member-signin.js +257 -0
- package/src/lib/components/member-signin.js.map +1 -0
- package/src/lib/components/member-signup.d.ts +34 -0
- package/src/lib/components/member-signup.js +287 -0
- package/src/lib/components/member-signup.js.map +1 -0
- package/src/lib/components/newsletter-signup.d.ts +39 -0
- package/src/lib/components/newsletter-signup.js +229 -0
- package/src/lib/components/newsletter-signup.js.map +1 -0
- package/src/lib/components/product-detail.d.ts +38 -0
- package/src/lib/components/product-detail.js +1022 -0
- package/src/lib/components/product-detail.js.map +1 -0
- package/src/lib/components/product-grid.d.ts +67 -0
- package/src/lib/components/product-grid.js +909 -0
- package/src/lib/components/product-grid.js.map +1 -0
- package/src/lib/components/product-reviews.d.ts +32 -0
- package/src/lib/components/product-reviews.js +349 -0
- package/src/lib/components/product-reviews.js.map +1 -0
- package/src/lib/components/related-products.d.ts +33 -0
- package/src/lib/components/related-products.js +232 -0
- package/src/lib/components/related-products.js.map +1 -0
- package/src/lib/components/reservation-widget.d.ts +33 -0
- package/src/lib/components/reservation-widget.js +418 -0
- package/src/lib/components/reservation-widget.js.map +1 -0
- package/src/lib/components/storefront-payment-element-fallback.d.ts +18 -0
- package/src/lib/components/storefront-payment-element-fallback.js +51 -0
- package/src/lib/components/storefront-payment-element-fallback.js.map +1 -0
- package/src/lib/components/storefront-payment-element.d.ts +78 -0
- package/src/lib/components/storefront-payment-element.js +156 -0
- package/src/lib/components/storefront-payment-element.js.map +1 -0
- package/src/lib/components/wishlist.d.ts +46 -0
- package/src/lib/components/wishlist.js +304 -0
- package/src/lib/components/wishlist.js.map +1 -0
- package/src/lib/constants/bundle-common.d.ts +18 -0
- package/src/lib/constants/bundle-common.js +18 -0
- package/src/lib/constants/bundle-common.js.map +1 -0
- package/src/lib/constants/product-filters.d.ts +35 -0
- package/src/lib/constants/product-filters.js +109 -0
- package/src/lib/constants/product-filters.js.map +1 -0
- package/src/lib/model/commerce-cart.d.ts +52 -0
- package/src/lib/model/commerce-cart.js +102 -0
- package/src/lib/model/commerce-cart.js.map +1 -0
- package/src/lib/model/commerce-connect-transfer.d.ts +243 -0
- package/src/lib/model/commerce-connect-transfer.js +231 -0
- package/src/lib/model/commerce-connect-transfer.js.map +1 -0
- package/src/lib/model/commerce-discounts.d.ts +164 -0
- package/src/lib/model/commerce-discounts.js +231 -0
- package/src/lib/model/commerce-discounts.js.map +1 -0
- package/src/lib/model/commerce-dispute.d.ts +124 -0
- package/src/lib/model/commerce-dispute.js +225 -0
- package/src/lib/model/commerce-dispute.js.map +1 -0
- package/src/lib/model/commerce-gift-cards.d.ts +107 -0
- package/src/lib/model/commerce-gift-cards.js +121 -0
- package/src/lib/model/commerce-gift-cards.js.map +1 -0
- package/src/lib/model/commerce-io.d.ts +36 -0
- package/src/lib/model/commerce-io.js +245 -0
- package/src/lib/model/commerce-io.js.map +1 -0
- package/src/lib/model/commerce-orders.d.ts +1040 -0
- package/src/lib/model/commerce-orders.js +1022 -0
- package/src/lib/model/commerce-orders.js.map +1 -0
- package/src/lib/model/commerce-promotions.d.ts +188 -0
- package/src/lib/model/commerce-promotions.js +199 -0
- package/src/lib/model/commerce-promotions.js.map +1 -0
- package/src/lib/model/commerce-reservations.d.ts +175 -0
- package/src/lib/model/commerce-reservations.js +157 -0
- package/src/lib/model/commerce-reservations.js.map +1 -0
- package/src/lib/model/commerce-shipping.d.ts +316 -0
- package/src/lib/model/commerce-shipping.js +484 -0
- package/src/lib/model/commerce-shipping.js.map +1 -0
- package/src/lib/model/commerce-stock-holds.d.ts +199 -0
- package/src/lib/model/commerce-stock-holds.js +190 -0
- package/src/lib/model/commerce-stock-holds.js.map +1 -0
- package/src/lib/model/commerce-subscription-status.d.ts +67 -0
- package/src/lib/model/commerce-subscription-status.js +76 -0
- package/src/lib/model/commerce-subscription-status.js.map +1 -0
- package/src/lib/model/commerce-tax-decision.d.ts +222 -0
- package/src/lib/model/commerce-tax-decision.js +165 -0
- package/src/lib/model/commerce-tax-decision.js.map +1 -0
- package/src/lib/model/commerce-tax.d.ts +176 -0
- package/src/lib/model/commerce-tax.js +92 -0
- package/src/lib/model/commerce-tax.js.map +1 -0
- package/src/lib/model/commerce.d.ts +551 -0
- package/src/lib/model/commerce.js +739 -0
- package/src/lib/model/commerce.js.map +1 -0
- package/src/lib/model/index.d.ts +40 -0
- package/src/lib/model/index.js +40 -0
- package/src/lib/model/index.js.map +1 -0
- package/src/lib/model/order-figures.d.ts +48 -0
- package/src/lib/model/order-figures.js +74 -0
- package/src/lib/model/order-figures.js.map +1 -0
- package/src/lib/model/plugin-permissions.d.ts +23 -0
- package/src/lib/model/plugin-permissions.js +34 -0
- package/src/lib/model/plugin-permissions.js.map +1 -0
- package/src/lib/model/purchase-analytics.d.ts +186 -0
- package/src/lib/model/purchase-analytics.js +216 -0
- package/src/lib/model/purchase-analytics.js.map +1 -0
- package/src/lib/plugin-config.d.ts +62 -0
- package/src/lib/plugin-config.js +78 -0
- package/src/lib/plugin-config.js.map +1 -0
- package/src/lib/plugin.d.ts +28 -0
- package/src/lib/plugin.js +147 -0
- package/src/lib/plugin.js.map +1 -0
- package/src/lib/server/billing-webhook.d.ts +26 -0
- package/src/lib/server/billing-webhook.js +4011 -0
- package/src/lib/server/billing-webhook.js.map +1 -0
- package/src/lib/server/cancel-order.d.ts +87 -0
- package/src/lib/server/cancel-order.js +368 -0
- package/src/lib/server/cancel-order.js.map +1 -0
- package/src/lib/server/cart-checkout.d.ts +25 -0
- package/src/lib/server/cart-checkout.js +976 -0
- package/src/lib/server/cart-checkout.js.map +1 -0
- package/src/lib/server/cart-cookie.d.ts +32 -0
- package/src/lib/server/cart-cookie.js +77 -0
- package/src/lib/server/cart-cookie.js.map +1 -0
- package/src/lib/server/cart.d.ts +34 -0
- package/src/lib/server/cart.js +181 -0
- package/src/lib/server/cart.js.map +1 -0
- package/src/lib/server/catalog.d.ts +127 -0
- package/src/lib/server/catalog.js +277 -0
- package/src/lib/server/catalog.js.map +1 -0
- package/src/lib/server/checkout.d.ts +36 -0
- package/src/lib/server/checkout.js +915 -0
- package/src/lib/server/checkout.js.map +1 -0
- package/src/lib/server/connect.d.ts +26 -0
- package/src/lib/server/connect.js +238 -0
- package/src/lib/server/connect.js.map +1 -0
- package/src/lib/server/contact-refund.d.ts +117 -0
- package/src/lib/server/contact-refund.js +196 -0
- package/src/lib/server/contact-refund.js.map +1 -0
- package/src/lib/server/download.d.ts +39 -0
- package/src/lib/server/download.js +206 -0
- package/src/lib/server/download.js.map +1 -0
- package/src/lib/server/draft-order.d.ts +26 -0
- package/src/lib/server/draft-order.js +706 -0
- package/src/lib/server/draft-order.js.map +1 -0
- package/src/lib/server/feed.d.ts +24 -0
- package/src/lib/server/feed.js +59 -0
- package/src/lib/server/feed.js.map +1 -0
- package/src/lib/server/fulfill-order.d.ts +97 -0
- package/src/lib/server/fulfill-order.js +235 -0
- package/src/lib/server/fulfill-order.js.map +1 -0
- package/src/lib/server/gate.d.ts +36 -0
- package/src/lib/server/gate.js +112 -0
- package/src/lib/server/gate.js.map +1 -0
- package/src/lib/server/gift-cards.d.ts +42 -0
- package/src/lib/server/gift-cards.js +219 -0
- package/src/lib/server/gift-cards.js.map +1 -0
- package/src/lib/server/low-stock.d.ts +49 -0
- package/src/lib/server/low-stock.js +60 -0
- package/src/lib/server/low-stock.js.map +1 -0
- package/src/lib/server/manual-tax-rate.d.ts +82 -0
- package/src/lib/server/manual-tax-rate.js +113 -0
- package/src/lib/server/manual-tax-rate.js.map +1 -0
- package/src/lib/server/member-feed.d.ts +24 -0
- package/src/lib/server/member-feed.js +72 -0
- package/src/lib/server/member-feed.js.map +1 -0
- package/src/lib/server/member-name-search.d.ts +37 -0
- package/src/lib/server/member-name-search.js +42 -0
- package/src/lib/server/member-name-search.js.map +1 -0
- package/src/lib/server/member-post.d.ts +23 -0
- package/src/lib/server/member-post.js +166 -0
- package/src/lib/server/member-post.js.map +1 -0
- package/src/lib/server/membership-account.d.ts +37 -0
- package/src/lib/server/membership-account.js +148 -0
- package/src/lib/server/membership-account.js.map +1 -0
- package/src/lib/server/membership-admin-password.d.ts +33 -0
- package/src/lib/server/membership-admin-password.js +202 -0
- package/src/lib/server/membership-admin-password.js.map +1 -0
- package/src/lib/server/membership-content.d.ts +23 -0
- package/src/lib/server/membership-content.js +105 -0
- package/src/lib/server/membership-content.js.map +1 -0
- package/src/lib/server/membership-login.d.ts +19 -0
- package/src/lib/server/membership-login.js +137 -0
- package/src/lib/server/membership-login.js.map +1 -0
- package/src/lib/server/membership-logout.d.ts +19 -0
- package/src/lib/server/membership-logout.js +35 -0
- package/src/lib/server/membership-logout.js.map +1 -0
- package/src/lib/server/membership-recover.d.ts +60 -0
- package/src/lib/server/membership-recover.js +244 -0
- package/src/lib/server/membership-recover.js.map +1 -0
- package/src/lib/server/membership-register.d.ts +22 -0
- package/src/lib/server/membership-register.js +241 -0
- package/src/lib/server/membership-register.js.map +1 -0
- package/src/lib/server/membership-reset.d.ts +26 -0
- package/src/lib/server/membership-reset.js +88 -0
- package/src/lib/server/membership-reset.js.map +1 -0
- package/src/lib/server/membership-wishlist.d.ts +23 -0
- package/src/lib/server/membership-wishlist.js +89 -0
- package/src/lib/server/membership-wishlist.js.map +1 -0
- package/src/lib/server/membership.d.ts +96 -0
- package/src/lib/server/membership.js +222 -0
- package/src/lib/server/membership.js.map +1 -0
- package/src/lib/server/native-checkout.d.ts +170 -0
- package/src/lib/server/native-checkout.js +193 -0
- package/src/lib/server/native-checkout.js.map +1 -0
- package/src/lib/server/newsletter.d.ts +23 -0
- package/src/lib/server/newsletter.js +323 -0
- package/src/lib/server/newsletter.js.map +1 -0
- package/src/lib/server/notify-restock.d.ts +22 -0
- package/src/lib/server/notify-restock.js +84 -0
- package/src/lib/server/notify-restock.js.map +1 -0
- package/src/lib/server/order-analytics.d.ts +134 -0
- package/src/lib/server/order-analytics.js +263 -0
- package/src/lib/server/order-analytics.js.map +1 -0
- package/src/lib/server/order-figures.d.ts +33 -0
- package/src/lib/server/order-figures.js +220 -0
- package/src/lib/server/order-figures.js.map +1 -0
- package/src/lib/server/paid-media-delivery.d.ts +173 -0
- package/src/lib/server/paid-media-delivery.js +212 -0
- package/src/lib/server/paid-media-delivery.js.map +1 -0
- package/src/lib/server/pos-order.d.ts +54 -0
- package/src/lib/server/pos-order.js +1137 -0
- package/src/lib/server/pos-order.js.map +1 -0
- package/src/lib/server/process-abandoned.d.ts +54 -0
- package/src/lib/server/process-abandoned.js +222 -0
- package/src/lib/server/process-abandoned.js.map +1 -0
- package/src/lib/server/process-restock.d.ts +42 -0
- package/src/lib/server/process-restock.js +219 -0
- package/src/lib/server/process-restock.js.map +1 -0
- package/src/lib/server/product-card.d.ts +29 -0
- package/src/lib/server/product-card.js +54 -0
- package/src/lib/server/product-card.js.map +1 -0
- package/src/lib/server/product.d.ts +70 -0
- package/src/lib/server/product.js +105 -0
- package/src/lib/server/product.js.map +1 -0
- package/src/lib/server/promotion-hold.d.ts +144 -0
- package/src/lib/server/promotion-hold.js +174 -0
- package/src/lib/server/promotion-hold.js.map +1 -0
- package/src/lib/server/reconcile-stock.d.ts +106 -0
- package/src/lib/server/reconcile-stock.js +349 -0
- package/src/lib/server/reconcile-stock.js.map +1 -0
- package/src/lib/server/refund.d.ts +45 -0
- package/src/lib/server/refund.js +607 -0
- package/src/lib/server/refund.js.map +1 -0
- package/src/lib/server/related.d.ts +24 -0
- package/src/lib/server/related.js +104 -0
- package/src/lib/server/related.js.map +1 -0
- package/src/lib/server/reservation-availability.d.ts +23 -0
- package/src/lib/server/reservation-availability.js +111 -0
- package/src/lib/server/reservation-availability.js.map +1 -0
- package/src/lib/server/reserve-stock.d.ts +156 -0
- package/src/lib/server/reserve-stock.js +146 -0
- package/src/lib/server/reserve-stock.js.map +1 -0
- package/src/lib/server/reserve.d.ts +25 -0
- package/src/lib/server/reserve.js +426 -0
- package/src/lib/server/reserve.js.map +1 -0
- package/src/lib/server/restock-flag.d.ts +202 -0
- package/src/lib/server/restock-flag.js +449 -0
- package/src/lib/server/restock-flag.js.map +1 -0
- package/src/lib/server/reviews.d.ts +51 -0
- package/src/lib/server/reviews.js +132 -0
- package/src/lib/server/reviews.js.map +1 -0
- package/src/lib/server/site-page-enricher.d.ts +42 -0
- package/src/lib/server/site-page-enricher.js +149 -0
- package/src/lib/server/site-page-enricher.js.map +1 -0
- package/src/lib/server/site-page-resolver.d.ts +26 -0
- package/src/lib/server/site-page-resolver.js +223 -0
- package/src/lib/server/site-page-resolver.js.map +1 -0
- package/src/lib/server/stock-hold.d.ts +127 -0
- package/src/lib/server/stock-hold.js +258 -0
- package/src/lib/server/stock-hold.js.map +1 -0
- package/src/lib/server/storefront-tax-record.d.ts +22 -0
- package/src/lib/server/storefront-tax-record.js +218 -0
- package/src/lib/server/storefront-tax-record.js.map +1 -0
- package/src/lib/server/storefront-tax.d.ts +197 -0
- package/src/lib/server/storefront-tax.js +245 -0
- package/src/lib/server/storefront-tax.js.map +1 -0
- package/src/lib/server/stream.d.ts +25 -0
- package/src/lib/server/stream.js +191 -0
- package/src/lib/server/stream.js.map +1 -0
- package/src/lib/server/subscription-portal.d.ts +24 -0
- package/src/lib/server/subscription-portal.js +91 -0
- package/src/lib/server/subscription-portal.js.map +1 -0
- package/src/lib/server/supplier-outbox.d.ts +190 -0
- package/src/lib/server/supplier-outbox.js +342 -0
- package/src/lib/server/supplier-outbox.js.map +1 -0
- package/src/lib/server/supplier-update.d.ts +23 -0
- package/src/lib/server/supplier-update.js +289 -0
- package/src/lib/server/supplier-update.js.map +1 -0
- package/src/lib/server/tax-profile.d.ts +29 -0
- package/src/lib/server/tax-profile.js +40 -0
- package/src/lib/server/tax-profile.js.map +1 -0
- package/src/lib/server.d.ts +32 -0
- package/src/lib/server.js +311 -0
- package/src/lib/server.js.map +1 -0
- package/src/lib/site.d.ts +27 -0
- package/src/lib/site.js +134 -0
- package/src/lib/site.js.map +1 -0
- package/src/lib/utils/escape-html.d.ts +41 -0
- package/src/lib/utils/escape-html.js +41 -0
- package/src/lib/utils/escape-html.js.map +1 -0
- package/src/lib/utils/generate-preset-id.d.ts +19 -0
- package/src/lib/utils/generate-preset-id.js +25 -0
- package/src/lib/utils/generate-preset-id.js.map +1 -0
- package/src/lib/utils/member-continue.d.ts +45 -0
- package/src/lib/utils/member-continue.js +49 -0
- package/src/lib/utils/member-continue.js.map +1 -0
- package/src/lib/utils/use-storefront-purchase-event.d.ts +60 -0
- package/src/lib/utils/use-storefront-purchase-event.js +142 -0
- package/src/lib/utils/use-storefront-purchase-event.js.map +1 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ import * as Aglyn from "@aglyn/aglyn/server";
|
|
17
|
+
import * as CommerceModel from "../model/index.js";
|
|
18
|
+
import { firebaseAdmin, getOrgForHost } from "@aglyn/tenant-data-admin";
|
|
19
|
+
/**
|
|
20
|
+
* Approved reviews for a product, newest first, plus their aggregate.
|
|
21
|
+
*
|
|
22
|
+
* Extracted from the handler (AGL-686) so the site-page resolver can nest
|
|
23
|
+
* `aggregateRating` inside the server-rendered `Product` node. Schema.org
|
|
24
|
+
* wants it as a PROPERTY of the product; the reviews block was emitting a
|
|
25
|
+
* free-standing `AggregateRating` node alongside it, which is orphaned and
|
|
26
|
+
* ignored. One reader means the rating in the structured data and the rating
|
|
27
|
+
* on the page cannot disagree.
|
|
28
|
+
*/ export async function readProductReviews(hostId, productId) {
|
|
29
|
+
const hostRef = firebaseAdmin.app().firestore().collection('hosts').doc(hostId);
|
|
30
|
+
const reviewsSnapshot = await hostRef.collection('reviews').where('productId', '==', productId).where('status', '==', 'approved').limit(100).get();
|
|
31
|
+
const reviews = reviewsSnapshot.docs.map((docSnapshot)=>{
|
|
32
|
+
var _docSnapshot_get, _docSnapshot_get1, _docSnapshot_get2, _docSnapshot_get3, _docSnapshot_get4;
|
|
33
|
+
return {
|
|
34
|
+
id: docSnapshot.id,
|
|
35
|
+
rating: Number((_docSnapshot_get = docSnapshot.get('rating')) != null ? _docSnapshot_get : 0),
|
|
36
|
+
body: String((_docSnapshot_get1 = docSnapshot.get('body')) != null ? _docSnapshot_get1 : ''),
|
|
37
|
+
authorName: String((_docSnapshot_get2 = docSnapshot.get('authorName')) != null ? _docSnapshot_get2 : 'Anonymous'),
|
|
38
|
+
verified: Boolean(docSnapshot.get('verified')),
|
|
39
|
+
reply: (_docSnapshot_get3 = docSnapshot.get('reply')) != null ? _docSnapshot_get3 : null,
|
|
40
|
+
createdAtMs: Number((_docSnapshot_get4 = docSnapshot.get('createdAtMs')) != null ? _docSnapshot_get4 : 0)
|
|
41
|
+
};
|
|
42
|
+
}).sort((a, b)=>b.createdAtMs - a.createdAtMs);
|
|
43
|
+
const count = reviews.length;
|
|
44
|
+
const average = count ? reviews.reduce((sum, review)=>sum + review.rating, 0) / count : 0;
|
|
45
|
+
return {
|
|
46
|
+
reviews,
|
|
47
|
+
aggregate: {
|
|
48
|
+
count,
|
|
49
|
+
average: Math.round(average * 10) / 10
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Product reviews (AGL-324). GET returns approved reviews + aggregate;
|
|
55
|
+
* POST submits into the moderation queue, marking `verified` when the
|
|
56
|
+
* email has a paid order containing the product. Pro-plan gated.
|
|
57
|
+
*/ export const reviewsHandler = async (req, res)=>{
|
|
58
|
+
var _ref, _ref1;
|
|
59
|
+
var _req_body, _req_body1;
|
|
60
|
+
const isPost = req.method === 'POST';
|
|
61
|
+
const hostId = String((_ref = isPost ? (_req_body = req.body) == null ? void 0 : _req_body.hostId : req.query.hostId) != null ? _ref : '');
|
|
62
|
+
const productId = String((_ref1 = isPost ? (_req_body1 = req.body) == null ? void 0 : _req_body1.productId : req.query.productId) != null ? _ref1 : '');
|
|
63
|
+
if (!hostId || !productId) {
|
|
64
|
+
return res.status(400).json({
|
|
65
|
+
error: 'Missing hostId or productId'
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
const firestore = firebaseAdmin.app().firestore();
|
|
70
|
+
const hostRef = firestore.collection('hosts').doc(hostId);
|
|
71
|
+
if (isPost) {
|
|
72
|
+
var _ref2, _ref3, _ref4, _ref5;
|
|
73
|
+
var _req_body2, _req_body3, _req_body4, _req_body5;
|
|
74
|
+
const org = await getOrgForHost(hostId);
|
|
75
|
+
if (!Aglyn.checkEntitlement(org == null ? void 0 : org.org, 'productReviews')) {
|
|
76
|
+
return res.status(403).json({
|
|
77
|
+
error: 'Reviews are not enabled'
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
const rating = Math.min(5, Math.max(1, Math.round(Number((_ref2 = (_req_body2 = req.body) == null ? void 0 : _req_body2.rating) != null ? _ref2 : 0))));
|
|
81
|
+
const body = String((_ref3 = (_req_body3 = req.body) == null ? void 0 : _req_body3.body) != null ? _ref3 : '').trim().slice(0, 2000);
|
|
82
|
+
const authorName = String((_ref4 = (_req_body4 = req.body) == null ? void 0 : _req_body4.authorName) != null ? _ref4 : '').trim().slice(0, 80);
|
|
83
|
+
const authorEmail = String((_ref5 = (_req_body5 = req.body) == null ? void 0 : _req_body5.authorEmail) != null ? _ref5 : '').trim().toLowerCase().slice(0, 120);
|
|
84
|
+
if (!rating || !body || !authorEmail) {
|
|
85
|
+
return res.status(400).json({
|
|
86
|
+
error: 'Rating, review, and email required'
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
// Verified buyer: a paid order with the product under this email.
|
|
90
|
+
let verified = false;
|
|
91
|
+
const orders = await hostRef.collection('orders').where('customerEmail', '==', authorEmail).limit(25).get();
|
|
92
|
+
for (const docSnapshot of orders.docs){
|
|
93
|
+
const order = CommerceModel.liftLegacyOrder(docSnapshot.data());
|
|
94
|
+
// Through the shared entitlement test (AGL-2454). "Verified buyer" on a
|
|
95
|
+
// purchase that was refunded line by line is a badge for goods the
|
|
96
|
+
// buyer no longer has — and the inline status literal this replaced
|
|
97
|
+
// only moved on a FULL refund.
|
|
98
|
+
if (CommerceModel.orderEntitlesProduct(order, productId)) {
|
|
99
|
+
verified = true;
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
await hostRef.collection('reviews').add({
|
|
104
|
+
productId,
|
|
105
|
+
rating,
|
|
106
|
+
body,
|
|
107
|
+
authorName: authorName || 'Anonymous',
|
|
108
|
+
authorEmail,
|
|
109
|
+
verified,
|
|
110
|
+
status: 'pending',
|
|
111
|
+
createdAtMs: Date.now()
|
|
112
|
+
});
|
|
113
|
+
return res.status(200).json({
|
|
114
|
+
ok: true,
|
|
115
|
+
pending: true
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
const { reviews, aggregate } = await readProductReviews(hostId, productId);
|
|
119
|
+
res.setHeader('Cache-Control', 'public, s-maxage=120, stale-while-revalidate=300');
|
|
120
|
+
return res.status(200).json({
|
|
121
|
+
reviews,
|
|
122
|
+
aggregate
|
|
123
|
+
});
|
|
124
|
+
} catch (error) {
|
|
125
|
+
console.error(error);
|
|
126
|
+
return res.status(500).json({
|
|
127
|
+
error: 'Reviews unavailable'
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
//# sourceMappingURL=reviews.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/reviews.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 { PluginApiHandler } from '@aglyn/aglyn/server'\nimport * as Aglyn from '@aglyn/aglyn/server'\nimport * as CommerceModel from '../model'\nimport { firebaseAdmin, getOrgForHost } from '@aglyn/tenant-data-admin'\n\nexport interface PublicProductReview {\n id: string\n rating: number\n body: string\n authorName: string\n verified: boolean\n reply: string | null\n createdAtMs: number\n}\n\nexport interface ProductReviewAggregate {\n count: number\n /** Mean rating, one decimal. */\n average: number\n}\n\n/**\n * Approved reviews for a product, newest first, plus their aggregate.\n *\n * Extracted from the handler (AGL-686) so the site-page resolver can nest\n * `aggregateRating` inside the server-rendered `Product` node. Schema.org\n * wants it as a PROPERTY of the product; the reviews block was emitting a\n * free-standing `AggregateRating` node alongside it, which is orphaned and\n * ignored. One reader means the rating in the structured data and the rating\n * on the page cannot disagree.\n */\nexport async function readProductReviews(\n hostId: string,\n productId: string,\n): Promise<{\n reviews: PublicProductReview[]\n aggregate: ProductReviewAggregate\n}> {\n const hostRef = firebaseAdmin\n .app()\n .firestore()\n .collection('hosts')\n .doc(hostId)\n const reviewsSnapshot = await hostRef\n .collection('reviews')\n .where('productId', '==', productId)\n .where('status', '==', 'approved')\n .limit(100)\n .get()\n const reviews = reviewsSnapshot.docs\n .map((docSnapshot) => ({\n id: docSnapshot.id,\n rating: Number(docSnapshot.get('rating') ?? 0),\n body: String(docSnapshot.get('body') ?? ''),\n authorName: String(docSnapshot.get('authorName') ?? 'Anonymous'),\n verified: Boolean(docSnapshot.get('verified')),\n reply: (docSnapshot.get('reply') as string | undefined) ?? null,\n createdAtMs: Number(docSnapshot.get('createdAtMs') ?? 0),\n }))\n .sort((a, b) => b.createdAtMs - a.createdAtMs)\n const count = reviews.length\n const average = count\n ? reviews.reduce((sum, review) => sum + review.rating, 0) / count\n : 0\n return {\n reviews,\n aggregate: { count, average: Math.round(average * 10) / 10 },\n }\n}\n\n/**\n * Product reviews (AGL-324). GET returns approved reviews + aggregate;\n * POST submits into the moderation queue, marking `verified` when the\n * email has a paid order containing the product. Pro-plan gated.\n */\nexport const reviewsHandler: PluginApiHandler = async (req, res) => {\n const isPost = req.method === 'POST'\n const hostId = String((isPost ? req.body?.hostId : req.query.hostId) ?? '')\n const productId = String(\n (isPost ? req.body?.productId : req.query.productId) ?? '',\n )\n if (!hostId || !productId) {\n return res.status(400).json({ error: 'Missing hostId or productId' })\n }\n try {\n const firestore = firebaseAdmin.app().firestore()\n const hostRef = firestore.collection('hosts').doc(hostId)\n\n if (isPost) {\n const org = await getOrgForHost(hostId)\n if (!Aglyn.checkEntitlement(org?.org as any, 'productReviews')) {\n return res.status(403).json({ error: 'Reviews are not enabled' })\n }\n const rating = Math.min(5, Math.max(1, Math.round(Number(req.body?.rating ?? 0))))\n const body = String(req.body?.body ?? '').trim().slice(0, 2000)\n const authorName = String(req.body?.authorName ?? '').trim().slice(0, 80)\n const authorEmail = String(req.body?.authorEmail ?? '')\n .trim()\n .toLowerCase()\n .slice(0, 120)\n if (!rating || !body || !authorEmail) {\n return res.status(400).json({ error: 'Rating, review, and email required' })\n }\n // Verified buyer: a paid order with the product under this email.\n let verified = false\n const orders = await hostRef\n .collection('orders')\n .where('customerEmail', '==', authorEmail)\n .limit(25)\n .get()\n for (const docSnapshot of orders.docs) {\n const order = CommerceModel.liftLegacyOrder(docSnapshot.data() as any)\n // Through the shared entitlement test (AGL-2454). \"Verified buyer\" on a\n // purchase that was refunded line by line is a badge for goods the\n // buyer no longer has — and the inline status literal this replaced\n // only moved on a FULL refund.\n if (CommerceModel.orderEntitlesProduct(order, productId)) {\n verified = true\n break\n }\n }\n await hostRef.collection('reviews').add({\n productId,\n rating,\n body,\n authorName: authorName || 'Anonymous',\n authorEmail,\n verified,\n status: 'pending',\n createdAtMs: Date.now(),\n })\n return res.status(200).json({ ok: true, pending: true })\n }\n\n const { reviews, aggregate } = await readProductReviews(hostId, productId)\n res.setHeader(\n 'Cache-Control',\n 'public, s-maxage=120, stale-while-revalidate=300',\n )\n return res.status(200).json({ reviews, aggregate })\n } catch (error) {\n console.error(error)\n return res.status(500).json({ error: 'Reviews unavailable' })\n }\n}\n"],"names":["Aglyn","CommerceModel","firebaseAdmin","getOrgForHost","readProductReviews","hostId","productId","hostRef","app","firestore","collection","doc","reviewsSnapshot","where","limit","get","reviews","docs","map","docSnapshot","id","rating","Number","body","String","authorName","verified","Boolean","reply","createdAtMs","sort","a","b","count","length","average","reduce","sum","review","aggregate","Math","round","reviewsHandler","req","res","isPost","method","query","status","json","error","org","checkEntitlement","min","max","trim","slice","authorEmail","toLowerCase","orders","order","liftLegacyOrder","data","orderEntitlesProduct","add","Date","now","ok","pending","setHeader","console"],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import type { SitePageEnricher } from '@aglyn/aglyn/server';
|
|
18
|
+
/**
|
|
19
|
+
* Seeds each product grid's FIRST, UNFILTERED page (AGL-659).
|
|
20
|
+
*
|
|
21
|
+
* `/products` shipped 11KB of HTML with zero words in it: the grid fetches
|
|
22
|
+
* its catalog in a `useEffect`, so the server rendered skeleton cards and
|
|
23
|
+
* crawlers indexed an empty shell. The fix is to run the same query on the
|
|
24
|
+
* server and hand the result down through `pageData`.
|
|
25
|
+
*
|
|
26
|
+
* Three things this deliberately does NOT do:
|
|
27
|
+
*
|
|
28
|
+
* - It does not seed anything the visitor can change. Search text, category
|
|
29
|
+
* chips, type chips and the price slider all start empty on mount, so a
|
|
30
|
+
* props-derived query is exactly what the grid's own first fetch would
|
|
31
|
+
* send. Seed a filtered page and SSR would disagree with hydration.
|
|
32
|
+
* - It does not seed later pages. Load more still goes to the API.
|
|
33
|
+
* - It does not share one slice between grids. Seeds are keyed by node id,
|
|
34
|
+
* because a collection-scoped grid and an all-products grid on the same
|
|
35
|
+
* page must not receive each other's items — wrong products would get
|
|
36
|
+
* indexed and visibly swap on hydrate.
|
|
37
|
+
*
|
|
38
|
+
* Failures are swallowed by the enricher runner, and the grid falls back to
|
|
39
|
+
* fetching. A catalog read that errors must not take a page down.
|
|
40
|
+
*/
|
|
41
|
+
export declare const commerceSitePageEnricher: SitePageEnricher;
|
|
42
|
+
export default commerceSitePageEnricher;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
17
|
+
import { createCatalogReadScope, queryPublicCatalog } from "./catalog.js";
|
|
18
|
+
/** Component id of the block this enricher seeds; persisted, never renamed. */ const PRODUCT_GRID_ID = 'product-grid';
|
|
19
|
+
/**
|
|
20
|
+
* Grids seeded per page. A page with a dozen grids would turn one render
|
|
21
|
+
* into a dozen catalog reads, so the walk stops after this many — the rest
|
|
22
|
+
* fetch client-side exactly as they did before, which is a slower first
|
|
23
|
+
* paint rather than a broken page.
|
|
24
|
+
*/ const MAX_SEEDED_GRIDS = 4;
|
|
25
|
+
/**
|
|
26
|
+
* Every product-grid node on the page.
|
|
27
|
+
*
|
|
28
|
+
* `composeScreenNodes` returns `Record<string, node>` — a DENORMALIZED FLAT
|
|
29
|
+
* MAP keyed by node id, whose children are id STRINGS in `nodes`. It does not
|
|
30
|
+
* return a tree, and there is no `children` array anywhere in it.
|
|
31
|
+
*
|
|
32
|
+
* The first version of this walked `children` recursively and therefore
|
|
33
|
+
* matched nothing at all, on any page. It shipped green because the unit
|
|
34
|
+
* tests fed it a nested fixture — the fixture encoded the assumption instead
|
|
35
|
+
* of the actual contract, so the tests only ever proved the walker was
|
|
36
|
+
* self-consistent. Iterating the map's values is both correct and simpler;
|
|
37
|
+
* the flat shape means no recursion is needed.
|
|
38
|
+
*
|
|
39
|
+
* Sorted by id so that the MAX_SEEDED_GRIDS cap takes a deterministic subset
|
|
40
|
+
* rather than whichever ones the map happened to enumerate first.
|
|
41
|
+
*/ function collectGridNodes(nodes) {
|
|
42
|
+
if (!nodes || typeof nodes !== 'object') return [];
|
|
43
|
+
const found = [];
|
|
44
|
+
for (const value of Object.values(nodes)){
|
|
45
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) continue;
|
|
46
|
+
const shaped = value;
|
|
47
|
+
if (shaped.componentId === PRODUCT_GRID_ID && shaped.$id) {
|
|
48
|
+
found.push(shaped);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return found.sort((a, b)=>String(a.$id).localeCompare(String(b.$id)));
|
|
52
|
+
}
|
|
53
|
+
/** Positive integer from a props value that may arrive as a string. */ function positiveInt(value) {
|
|
54
|
+
const parsed = Number(value);
|
|
55
|
+
if (!Number.isFinite(parsed) || parsed <= 0) return undefined;
|
|
56
|
+
return Math.floor(parsed);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The collection slug a grid on a `/collections/{slug}` page follows when
|
|
60
|
+
* it has no pinned collection — the server-side twin of the grid's
|
|
61
|
+
* `collectionSlugFromLocation()`, which reads `window.location`.
|
|
62
|
+
*/ function collectionSlugFromPath(path) {
|
|
63
|
+
const match = path.match(/\/collections\/([^/?#]+)/);
|
|
64
|
+
return match ? decodeURIComponent(match[1]) : '';
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Seeds each product grid's FIRST, UNFILTERED page (AGL-659).
|
|
68
|
+
*
|
|
69
|
+
* `/products` shipped 11KB of HTML with zero words in it: the grid fetches
|
|
70
|
+
* its catalog in a `useEffect`, so the server rendered skeleton cards and
|
|
71
|
+
* crawlers indexed an empty shell. The fix is to run the same query on the
|
|
72
|
+
* server and hand the result down through `pageData`.
|
|
73
|
+
*
|
|
74
|
+
* Three things this deliberately does NOT do:
|
|
75
|
+
*
|
|
76
|
+
* - It does not seed anything the visitor can change. Search text, category
|
|
77
|
+
* chips, type chips and the price slider all start empty on mount, so a
|
|
78
|
+
* props-derived query is exactly what the grid's own first fetch would
|
|
79
|
+
* send. Seed a filtered page and SSR would disagree with hydration.
|
|
80
|
+
* - It does not seed later pages. Load more still goes to the API.
|
|
81
|
+
* - It does not share one slice between grids. Seeds are keyed by node id,
|
|
82
|
+
* because a collection-scoped grid and an all-products grid on the same
|
|
83
|
+
* page must not receive each other's items — wrong products would get
|
|
84
|
+
* indexed and visibly swap on hydrate.
|
|
85
|
+
*
|
|
86
|
+
* Failures are swallowed by the enricher runner, and the grid falls back to
|
|
87
|
+
* fetching. A catalog read that errors must not take a page down.
|
|
88
|
+
*/ export const commerceSitePageEnricher = async ({ hostId, path, nodes })=>{
|
|
89
|
+
const grids = collectGridNodes(nodes).slice(0, MAX_SEEDED_GRIDS);
|
|
90
|
+
if (!grids.length) return undefined;
|
|
91
|
+
const seeds = {};
|
|
92
|
+
// One scope for this render, so four grids on a page cost one catalog read
|
|
93
|
+
// instead of four identical ones. Each grid still filters and sorts that
|
|
94
|
+
// snapshot for itself — the seeds stay keyed by node id.
|
|
95
|
+
const reads = createCatalogReadScope();
|
|
96
|
+
await Promise.all(grids.map(async (node)=>{
|
|
97
|
+
var _ref, _node_resolvedProps, _props_source;
|
|
98
|
+
const props = (_ref = (_node_resolvedProps = node.resolvedProps) != null ? _node_resolvedProps : node.props) != null ? _ref : {};
|
|
99
|
+
const source = (_props_source = props.source) != null ? _props_source : 'all';
|
|
100
|
+
// Ids first (rename-safe, AGL-343), slugs as the legacy path — the
|
|
101
|
+
// same precedence the grid applies when it builds its own params.
|
|
102
|
+
const collectionSlug = source === 'collection' ? props.collectionSlug || collectionSlugFromPath(path) : props.collectionSlug;
|
|
103
|
+
const pageLimit = positiveInt(props.pageSize);
|
|
104
|
+
const limit = pageLimit != null ? pageLimit : positiveInt(props.maxItems);
|
|
105
|
+
try {
|
|
106
|
+
const result = await queryPublicCatalog(_extends({
|
|
107
|
+
hostId
|
|
108
|
+
}, source === 'collection' ? props.collectionId ? {
|
|
109
|
+
collectionId: props.collectionId
|
|
110
|
+
} : collectionSlug ? {
|
|
111
|
+
collectionSlug
|
|
112
|
+
} : {} : {}, source === 'category' ? props.categoryId ? {
|
|
113
|
+
categoryId: props.categoryId
|
|
114
|
+
} : props.categorySlug ? {
|
|
115
|
+
categorySlug: props.categorySlug
|
|
116
|
+
} : {} : {}, source === 'tag' && props.tag ? {
|
|
117
|
+
tag: props.tag
|
|
118
|
+
} : {}, props.sort ? {
|
|
119
|
+
sort: props.sort
|
|
120
|
+
} : {}, limit ? {
|
|
121
|
+
limit
|
|
122
|
+
} : {}, {
|
|
123
|
+
// Facets ride along only when a control needs them, matching the
|
|
124
|
+
// grid's `facets=1` condition on its first request.
|
|
125
|
+
facets: Boolean(props.showCategories || props.showPriceFilter),
|
|
126
|
+
reads
|
|
127
|
+
}));
|
|
128
|
+
seeds[node.$id] = result;
|
|
129
|
+
} catch (error) {
|
|
130
|
+
// One grid failing leaves the others seeded; this one fetches.
|
|
131
|
+
console.error('commerce grid seed failed', node.$id, error);
|
|
132
|
+
}
|
|
133
|
+
}));
|
|
134
|
+
if (!Object.keys(seeds).length) return undefined;
|
|
135
|
+
return {
|
|
136
|
+
// Merged shallowly into the page props, so this key is the whole
|
|
137
|
+
// `pageData` for a screen page. Screens never reach a page resolver
|
|
138
|
+
// (the loader returns a resolver's answer before enrichers run), so
|
|
139
|
+
// there is no resolver-written pageData here to preserve.
|
|
140
|
+
pageData: {
|
|
141
|
+
commerce: {
|
|
142
|
+
grids: seeds
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
};
|
|
147
|
+
export default commerceSitePageEnricher;
|
|
148
|
+
|
|
149
|
+
//# sourceMappingURL=site-page-enricher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/site-page-enricher.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 { SitePageEnricher } from '@aglyn/aglyn/server'\nimport {\n createCatalogReadScope,\n queryPublicCatalog,\n type PublicCatalogResult,\n} from './catalog'\n\n/** Component id of the block this enricher seeds; persisted, never renamed. */\nconst PRODUCT_GRID_ID = 'product-grid'\n\n/**\n * Grids seeded per page. A page with a dozen grids would turn one render\n * into a dozen catalog reads, so the walk stops after this many — the rest\n * fetch client-side exactly as they did before, which is a slower first\n * paint rather than a broken page.\n */\nconst MAX_SEEDED_GRIDS = 4\n\ninterface GridProps {\n source?: 'all' | 'collection' | 'category' | 'tag'\n collectionId?: string\n collectionSlug?: string\n categoryId?: string\n categorySlug?: string\n tag?: string\n sort?: string\n maxItems?: number | string\n pageSize?: number | string\n showCategories?: boolean\n showPriceFilter?: boolean\n}\n\ninterface ComposedNode {\n $id?: string\n componentId?: string\n props?: Record<string, unknown>\n resolvedProps?: Record<string, unknown>\n /** Child node IDS — denormalized nodes do not nest (see below). */\n nodes?: string[]\n}\n\n/**\n * Every product-grid node on the page.\n *\n * `composeScreenNodes` returns `Record<string, node>` — a DENORMALIZED FLAT\n * MAP keyed by node id, whose children are id STRINGS in `nodes`. It does not\n * return a tree, and there is no `children` array anywhere in it.\n *\n * The first version of this walked `children` recursively and therefore\n * matched nothing at all, on any page. It shipped green because the unit\n * tests fed it a nested fixture — the fixture encoded the assumption instead\n * of the actual contract, so the tests only ever proved the walker was\n * self-consistent. Iterating the map's values is both correct and simpler;\n * the flat shape means no recursion is needed.\n *\n * Sorted by id so that the MAX_SEEDED_GRIDS cap takes a deterministic subset\n * rather than whichever ones the map happened to enumerate first.\n */\nfunction collectGridNodes(nodes: unknown): ComposedNode[] {\n if (!nodes || typeof nodes !== 'object') return []\n const found: ComposedNode[] = []\n for (const value of Object.values(nodes as Record<string, unknown>)) {\n if (!value || typeof value !== 'object' || Array.isArray(value)) continue\n const shaped = value as ComposedNode\n if (shaped.componentId === PRODUCT_GRID_ID && shaped.$id) {\n found.push(shaped)\n }\n }\n return found.sort((a, b) => String(a.$id).localeCompare(String(b.$id)))\n}\n\n/** Positive integer from a props value that may arrive as a string. */\nfunction positiveInt(value: unknown): number | undefined {\n const parsed = Number(value)\n if (!Number.isFinite(parsed) || parsed <= 0) return undefined\n return Math.floor(parsed)\n}\n\n/**\n * The collection slug a grid on a `/collections/{slug}` page follows when\n * it has no pinned collection — the server-side twin of the grid's\n * `collectionSlugFromLocation()`, which reads `window.location`.\n */\nfunction collectionSlugFromPath(path: string): string {\n const match = path.match(/\\/collections\\/([^/?#]+)/)\n return match ? decodeURIComponent(match[1]) : ''\n}\n\n/**\n * Seeds each product grid's FIRST, UNFILTERED page (AGL-659).\n *\n * `/products` shipped 11KB of HTML with zero words in it: the grid fetches\n * its catalog in a `useEffect`, so the server rendered skeleton cards and\n * crawlers indexed an empty shell. The fix is to run the same query on the\n * server and hand the result down through `pageData`.\n *\n * Three things this deliberately does NOT do:\n *\n * - It does not seed anything the visitor can change. Search text, category\n * chips, type chips and the price slider all start empty on mount, so a\n * props-derived query is exactly what the grid's own first fetch would\n * send. Seed a filtered page and SSR would disagree with hydration.\n * - It does not seed later pages. Load more still goes to the API.\n * - It does not share one slice between grids. Seeds are keyed by node id,\n * because a collection-scoped grid and an all-products grid on the same\n * page must not receive each other's items — wrong products would get\n * indexed and visibly swap on hydrate.\n *\n * Failures are swallowed by the enricher runner, and the grid falls back to\n * fetching. A catalog read that errors must not take a page down.\n */\nexport const commerceSitePageEnricher: SitePageEnricher = async ({\n hostId,\n path,\n nodes,\n}) => {\n const grids = collectGridNodes(nodes).slice(0, MAX_SEEDED_GRIDS)\n if (!grids.length) return undefined\n\n const seeds: Record<string, PublicCatalogResult> = {}\n // One scope for this render, so four grids on a page cost one catalog read\n // instead of four identical ones. Each grid still filters and sorts that\n // snapshot for itself — the seeds stay keyed by node id.\n const reads = createCatalogReadScope()\n await Promise.all(\n grids.map(async (node) => {\n const props = (node.resolvedProps ??\n node.props ??\n {}) as unknown as GridProps\n const source = props.source ?? 'all'\n // Ids first (rename-safe, AGL-343), slugs as the legacy path — the\n // same precedence the grid applies when it builds its own params.\n const collectionSlug =\n source === 'collection'\n ? props.collectionSlug || collectionSlugFromPath(path)\n : props.collectionSlug\n const pageLimit = positiveInt(props.pageSize)\n const limit = pageLimit ?? positiveInt(props.maxItems)\n\n try {\n const result = await queryPublicCatalog({\n hostId,\n ...(source === 'collection'\n ? props.collectionId\n ? { collectionId: props.collectionId }\n : collectionSlug\n ? { collectionSlug }\n : {}\n : {}),\n ...(source === 'category'\n ? props.categoryId\n ? { categoryId: props.categoryId }\n : props.categorySlug\n ? { categorySlug: props.categorySlug }\n : {}\n : {}),\n ...(source === 'tag' && props.tag ? { tag: props.tag } : {}),\n ...(props.sort ? { sort: props.sort } : {}),\n ...(limit ? { limit } : {}),\n // Facets ride along only when a control needs them, matching the\n // grid's `facets=1` condition on its first request.\n facets: Boolean(props.showCategories || props.showPriceFilter),\n reads,\n })\n seeds[node.$id as string] = result\n } catch (error) {\n // One grid failing leaves the others seeded; this one fetches.\n console.error('commerce grid seed failed', node.$id, error)\n }\n }),\n )\n\n if (!Object.keys(seeds).length) return undefined\n\n return {\n // Merged shallowly into the page props, so this key is the whole\n // `pageData` for a screen page. Screens never reach a page resolver\n // (the loader returns a resolver's answer before enrichers run), so\n // there is no resolver-written pageData here to preserve.\n pageData: { commerce: { grids: seeds } },\n }\n}\n\nexport default commerceSitePageEnricher\n"],"names":["createCatalogReadScope","queryPublicCatalog","PRODUCT_GRID_ID","MAX_SEEDED_GRIDS","collectGridNodes","nodes","found","value","Object","values","Array","isArray","shaped","componentId","$id","push","sort","a","b","String","localeCompare","positiveInt","parsed","Number","isFinite","undefined","Math","floor","collectionSlugFromPath","path","match","decodeURIComponent","commerceSitePageEnricher","hostId","grids","slice","length","seeds","reads","Promise","all","map","node","props","resolvedProps","source","collectionSlug","pageLimit","pageSize","limit","maxItems","result","collectionId","categoryId","categorySlug","tag","facets","Boolean","showCategories","showPriceFilter","error","console","keys","pageData","commerce"],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import type { SitePageResolver } from '@aglyn/aglyn/server';
|
|
18
|
+
/**
|
|
19
|
+
* Commerce page resolver (AGL-292/298/418), relocated verbatim from the
|
|
20
|
+
* tenant loader: /products/{slug} renders the host's designated PDP
|
|
21
|
+
* template with product tokens, /collections/{slug} the collection
|
|
22
|
+
* template — both compose server-side and return complete page props.
|
|
23
|
+
* Runs only for paths that didn't match a published screen; returns
|
|
24
|
+
* undefined for everything else so the loader falls through.
|
|
25
|
+
*/
|
|
26
|
+
export declare const commerceSitePageResolver: SitePageResolver;
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
17
|
+
import { hostCollectionKind } from "@aglyn/aglyn/server";
|
|
18
|
+
import composeScreenNodes from "@aglyn/tenant-runtime/compose-screen-nodes";
|
|
19
|
+
import getScreen from "@aglyn/tenant-runtime/get-screen";
|
|
20
|
+
import { collectSocialImageFacts } from "@aglyn/tenant-runtime/social-image-facts";
|
|
21
|
+
import { firebaseAdmin } from "@aglyn/tenant-data-admin";
|
|
22
|
+
import * as CommerceModel from "../model/index.js";
|
|
23
|
+
import { toPublicProductDetail } from "./product.js";
|
|
24
|
+
import { readProductReviews } from "./reviews.js";
|
|
25
|
+
/**
|
|
26
|
+
* Commerce page resolver (AGL-292/298/418), relocated verbatim from the
|
|
27
|
+
* tenant loader: /products/{slug} renders the host's designated PDP
|
|
28
|
+
* template with product tokens, /collections/{slug} the collection
|
|
29
|
+
* template — both compose server-side and return complete page props.
|
|
30
|
+
* Runs only for paths that didn't match a published screen; returns
|
|
31
|
+
* undefined for everything else so the loader falls through.
|
|
32
|
+
*/ export const commerceSitePageResolver = async ({ hostId, host, path })=>{
|
|
33
|
+
const hostRes = {
|
|
34
|
+
host
|
|
35
|
+
};
|
|
36
|
+
// Product detail routes (AGL-292): /products/{slug} renders the
|
|
37
|
+
// host's designated PDP template screen (settings/store
|
|
38
|
+
// `pdpScreenId`, AGL-295) with product tokens; the product-detail
|
|
39
|
+
// block hydrates variants client-side from the same slug. Same
|
|
40
|
+
// mechanism as entry-template screens below.
|
|
41
|
+
const pdpSegments = path.split('/').filter(Boolean);
|
|
42
|
+
if (pdpSegments.length === 2 && pdpSegments[0] === 'products') {
|
|
43
|
+
var _productSnapshot_docs_;
|
|
44
|
+
const adminFirestore = firebaseAdmin.app().firestore();
|
|
45
|
+
const hostDocRef = adminFirestore.collection('hosts').doc(hostId);
|
|
46
|
+
const [storeSettings, productSnapshot] = await Promise.all([
|
|
47
|
+
hostDocRef.collection('settings').doc('store').get(),
|
|
48
|
+
hostDocRef.collection('products').where('slug', '==', pdpSegments[1]).limit(1).get()
|
|
49
|
+
]);
|
|
50
|
+
const pdpScreenId = storeSettings.get('pdpScreenId');
|
|
51
|
+
const productRaw = (_productSnapshot_docs_ = productSnapshot.docs[0]) == null ? void 0 : _productSnapshot_docs_.data();
|
|
52
|
+
if (pdpScreenId && productRaw && !productRaw.deletedAt && productRaw.status === 'active') {
|
|
53
|
+
const product = CommerceModel.liftLegacyProduct(productRaw);
|
|
54
|
+
const templateRes = await getScreen({
|
|
55
|
+
hostId,
|
|
56
|
+
screenId: pdpScreenId,
|
|
57
|
+
// A PDP template renders against a routed PRODUCT, never at an address
|
|
58
|
+
// of its own, so it is served here and 404s everywhere else (AGL-1400).
|
|
59
|
+
allowTemplate: true
|
|
60
|
+
});
|
|
61
|
+
if (templateRes.screen) {
|
|
62
|
+
var _product_description, _ref, _ref1;
|
|
63
|
+
var _templateRes_screen_seo, _hostRes_host_seo, _hostRes_host, _product_mediaUrls;
|
|
64
|
+
const [minPrice, maxPrice] = CommerceModel.productPriceRange(product);
|
|
65
|
+
// Best-effort: a reviews read that fails costs the page its rating
|
|
66
|
+
// snippet, not the page.
|
|
67
|
+
const productReviews = await readProductReviews(hostId, productSnapshot.docs[0].id).catch((error)=>{
|
|
68
|
+
console.error('product review aggregate failed', error);
|
|
69
|
+
return {
|
|
70
|
+
reviews: [],
|
|
71
|
+
aggregate: {
|
|
72
|
+
count: 0,
|
|
73
|
+
average: 0
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
// The head shares this page as the template's image, then the site
|
|
78
|
+
// default, so their documents are read in the template's own batch
|
|
79
|
+
// (AGL-2850).
|
|
80
|
+
const card = collectSocialImageFacts([
|
|
81
|
+
(_templateRes_screen_seo = templateRes.screen.seo) == null ? void 0 : _templateRes_screen_seo.image,
|
|
82
|
+
(_hostRes_host = hostRes.host) == null ? void 0 : (_hostRes_host_seo = _hostRes_host.seo) == null ? void 0 : _hostRes_host_seo.image
|
|
83
|
+
]);
|
|
84
|
+
const templateNodes = await composeScreenNodes({
|
|
85
|
+
hostId,
|
|
86
|
+
screenId: pdpScreenId,
|
|
87
|
+
screen: templateRes.screen,
|
|
88
|
+
socialImages: card.socialImages,
|
|
89
|
+
// The template's host variables, and its layout's, fill in from
|
|
90
|
+
// this site as they do on every other page (AGL-2883).
|
|
91
|
+
host: hostRes.host,
|
|
92
|
+
tokens: {
|
|
93
|
+
'product.name': product.name,
|
|
94
|
+
'product.description': (_product_description = product.description) != null ? _product_description : '',
|
|
95
|
+
'product.price': minPrice === maxPrice ? `$${minPrice}` : `From $${minPrice}`,
|
|
96
|
+
'product.image': (_ref = (_ref1 = (_product_mediaUrls = product.mediaUrls) == null ? void 0 : _product_mediaUrls[0]) != null ? _ref1 : product.imageUrl) != null ? _ref : '',
|
|
97
|
+
'product.slug': product.slug
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
if (templateNodes) {
|
|
101
|
+
var _templateRes_screen_seo1, _ref2, _ref3, _ref4;
|
|
102
|
+
var _product_seo, _product_seo1;
|
|
103
|
+
return {
|
|
104
|
+
props: JSON.parse(JSON.stringify(_extends({
|
|
105
|
+
// The PDP block used to fetch this in an effect, so the
|
|
106
|
+
// server rendered a skeleton and crawlers saw a product page
|
|
107
|
+
// with no product in it (AGL-659). The product is already
|
|
108
|
+
// loaded here — hand it down so it renders server-side.
|
|
109
|
+
pageData: {
|
|
110
|
+
commerce: _extends({
|
|
111
|
+
product: toPublicProductDetail(productSnapshot.docs[0].id, product)
|
|
112
|
+
}, productReviews.aggregate.count ? {
|
|
113
|
+
reviews: productReviews
|
|
114
|
+
} : {})
|
|
115
|
+
},
|
|
116
|
+
data: {
|
|
117
|
+
host: hostRes.host,
|
|
118
|
+
screen: {
|
|
119
|
+
data: _extends({}, templateRes.screen, {
|
|
120
|
+
// The product NAMES this page; the template's own name
|
|
121
|
+
// ("Product detail") describes none of the products it
|
|
122
|
+
// renders. Kept distinct from the SEO title because the
|
|
123
|
+
// head treats an authored title as verbatim and a name
|
|
124
|
+
// as the side the site title joins (AGL-1341) — so a
|
|
125
|
+
// product with its own SEO title gets exactly that, and
|
|
126
|
+
// one without gets "Blue Widget – Acme Store".
|
|
127
|
+
displayName: product.name,
|
|
128
|
+
seo: _extends({}, (_templateRes_screen_seo1 = templateRes.screen.seo) != null ? _templateRes_screen_seo1 : {}, {
|
|
129
|
+
// Only when the PRODUCT authored one: inheriting the
|
|
130
|
+
// template screen's title would give every product
|
|
131
|
+
// page in the store the same `<title>`.
|
|
132
|
+
title: (_ref2 = (_product_seo = product.seo) == null ? void 0 : _product_seo.title) != null ? _ref2 : undefined,
|
|
133
|
+
description: (_ref3 = (_ref4 = (_product_seo1 = product.seo) == null ? void 0 : _product_seo1.description) != null ? _ref4 : product.description) != null ? _ref3 : undefined
|
|
134
|
+
})
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
nodes: templateNodes
|
|
139
|
+
}, card.collected()))),
|
|
140
|
+
revalidate: 60
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// Commerce collection routes (AGL-298): /collections/{slug} renders
|
|
147
|
+
// the designated collection template with collection tokens; the
|
|
148
|
+
// product-grid block derives the same slug from the URL.
|
|
149
|
+
if (pdpSegments.length === 2 && pdpSegments[0] === 'collections') {
|
|
150
|
+
var _collectionSnapshot_docs_find;
|
|
151
|
+
const adminFirestore = firebaseAdmin.app().firestore();
|
|
152
|
+
const hostDocRef = adminFirestore.collection('hosts').doc(hostId);
|
|
153
|
+
const [storeSettings, collectionSnapshot] = await Promise.all([
|
|
154
|
+
hostDocRef.collection('settings').doc('store').get(),
|
|
155
|
+
hostDocRef.collection('collections').where('slug', '==', pdpSegments[1]).limit(5).get()
|
|
156
|
+
]);
|
|
157
|
+
const collectionScreenId = storeSettings.get('collectionScreenId');
|
|
158
|
+
// Content collections share this path and a slug is only unique within a
|
|
159
|
+
// kind (AGL-954), so take the first CATALOG match rather than the first
|
|
160
|
+
// doc — otherwise a blog with the same slug renders as a product page.
|
|
161
|
+
const shopCollection = (_collectionSnapshot_docs_find = collectionSnapshot.docs.find((docSnapshot)=>hostCollectionKind(docSnapshot.data()) === 'catalog')) == null ? void 0 : _collectionSnapshot_docs_find.data();
|
|
162
|
+
if (collectionScreenId && shopCollection) {
|
|
163
|
+
const templateRes = await getScreen({
|
|
164
|
+
hostId,
|
|
165
|
+
screenId: collectionScreenId,
|
|
166
|
+
allowTemplate: true
|
|
167
|
+
});
|
|
168
|
+
if (templateRes.screen) {
|
|
169
|
+
var _shopCollection_description, _shopCollection_imageUrl;
|
|
170
|
+
var _templateRes_screen_seo2, _hostRes_host_seo1, _hostRes_host1;
|
|
171
|
+
// As on the PDP above: the template's image, then the site default
|
|
172
|
+
// (AGL-2850).
|
|
173
|
+
const card = collectSocialImageFacts([
|
|
174
|
+
(_templateRes_screen_seo2 = templateRes.screen.seo) == null ? void 0 : _templateRes_screen_seo2.image,
|
|
175
|
+
(_hostRes_host1 = hostRes.host) == null ? void 0 : (_hostRes_host_seo1 = _hostRes_host1.seo) == null ? void 0 : _hostRes_host_seo1.image
|
|
176
|
+
]);
|
|
177
|
+
const templateNodes = await composeScreenNodes({
|
|
178
|
+
hostId,
|
|
179
|
+
screenId: collectionScreenId,
|
|
180
|
+
screen: templateRes.screen,
|
|
181
|
+
socialImages: card.socialImages,
|
|
182
|
+
// As on the PDP above (AGL-2883).
|
|
183
|
+
host: hostRes.host,
|
|
184
|
+
tokens: {
|
|
185
|
+
'collection.name': shopCollection.name,
|
|
186
|
+
'collection.description': (_shopCollection_description = shopCollection.description) != null ? _shopCollection_description : '',
|
|
187
|
+
'collection.image': (_shopCollection_imageUrl = shopCollection.imageUrl) != null ? _shopCollection_imageUrl : '',
|
|
188
|
+
'collection.slug': shopCollection.slug
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
if (templateNodes) {
|
|
192
|
+
var _templateRes_screen_seo3, _shopCollection_description1;
|
|
193
|
+
return {
|
|
194
|
+
props: JSON.parse(JSON.stringify(_extends({
|
|
195
|
+
data: {
|
|
196
|
+
host: hostRes.host,
|
|
197
|
+
screen: {
|
|
198
|
+
data: _extends({}, templateRes.screen, {
|
|
199
|
+
// As on the PDP above: the catalog collection names the
|
|
200
|
+
// page, and a name takes the site title after it while
|
|
201
|
+
// an authored SEO title would not (AGL-1341). A shop
|
|
202
|
+
// collection has no SEO title field of its own, so the
|
|
203
|
+
// template's must not stand in for one.
|
|
204
|
+
displayName: shopCollection.name,
|
|
205
|
+
seo: _extends({}, (_templateRes_screen_seo3 = templateRes.screen.seo) != null ? _templateRes_screen_seo3 : {}, {
|
|
206
|
+
title: undefined,
|
|
207
|
+
description: (_shopCollection_description1 = shopCollection.description) != null ? _shopCollection_description1 : undefined
|
|
208
|
+
})
|
|
209
|
+
})
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
nodes: templateNodes
|
|
213
|
+
}, card.collected()))),
|
|
214
|
+
revalidate: 60
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return undefined;
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
//# sourceMappingURL=site-page-resolver.js.map
|