@aglyn/plugins-commerce 1.0.0-beta.285 → 1.0.0-beta.288
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/README.md +1 -1
- package/package.json +13 -13
- package/src/lib/components/cart.js +25 -12
- package/src/lib/components/cart.js.map +1 -1
- package/src/lib/components/commerce-console-page.js +70 -13
- package/src/lib/components/commerce-console-page.js.map +1 -1
- package/src/lib/components/console/order-detail-dialog.component.js +2 -1
- package/src/lib/components/console/order-detail-dialog.component.js.map +1 -1
- package/src/lib/components/console/order-webhooks-card.component.js +1 -1
- package/src/lib/components/console/order-webhooks-card.component.js.map +1 -1
- package/src/lib/components/console/payment-methods-card.component.js +1 -1
- package/src/lib/components/console/payment-methods-card.component.js.map +1 -1
- package/src/lib/components/console/pos/pos-cart-panel.component.d.ts +5 -0
- package/src/lib/components/console/pos/pos-cart-panel.component.js +18 -12
- package/src/lib/components/console/pos/pos-cart-panel.component.js.map +1 -1
- package/src/lib/components/console/pos/pos-hardware-api.d.ts +76 -5
- package/src/lib/components/console/pos/pos-hardware-api.js +53 -11
- package/src/lib/components/console/pos/pos-hardware-api.js.map +1 -1
- package/src/lib/components/console/pos/pos-hardware-buyer.component.d.ts +18 -0
- package/src/lib/components/console/pos/pos-hardware-buyer.component.js +629 -0
- package/src/lib/components/console/pos/pos-hardware-buyer.component.js.map +1 -0
- package/src/lib/components/console/pos/pos-hardware-sale-api.d.ts +162 -0
- package/src/lib/components/console/pos/pos-hardware-sale-api.js +122 -0
- package/src/lib/components/console/pos/pos-hardware-sale-api.js.map +1 -0
- package/src/lib/components/console/pos/pos-hardware-sale-terms.component.d.ts +39 -0
- package/src/lib/components/console/pos/pos-hardware-sale-terms.component.js +251 -0
- package/src/lib/components/console/pos/pos-hardware-sale-terms.component.js.map +1 -0
- package/src/lib/components/console/pos/use-pos-display.js +50 -0
- package/src/lib/components/console/pos/use-pos-display.js.map +1 -1
- package/src/lib/components/console/pos-cellular-control.component.d.ts +41 -0
- package/src/lib/components/console/pos-cellular-control.component.js +211 -0
- package/src/lib/components/console/pos-cellular-control.component.js.map +1 -0
- package/src/lib/components/console/pos-devices-card.component.d.ts +9 -1
- package/src/lib/components/console/pos-devices-card.component.js +47 -15
- package/src/lib/components/console/pos-devices-card.component.js.map +1 -1
- package/src/lib/components/console/pos-display/pos-display-api.d.ts +6 -0
- package/src/lib/components/console/pos-display/pos-display-api.js +8 -1
- package/src/lib/components/console/pos-display/pos-display-api.js.map +1 -1
- package/src/lib/components/console/pos-display-page.component.d.ts +13 -0
- package/src/lib/components/console/pos-display-page.component.js +44 -2
- package/src/lib/components/console/pos-display-page.component.js.map +1 -1
- package/src/lib/components/console/pos-hardware-card.component.d.ts +12 -4
- package/src/lib/components/console/pos-hardware-card.component.js +157 -36
- package/src/lib/components/console/pos-hardware-card.component.js.map +1 -1
- package/src/lib/components/console/pos-hardware-orders-staff-page.component.js +360 -80
- package/src/lib/components/console/pos-hardware-orders-staff-page.component.js.map +1 -1
- package/src/lib/components/console/pos-hardware-terms-page.component.d.ts +34 -0
- package/src/lib/components/console/pos-hardware-terms-page.component.js +257 -0
- package/src/lib/components/console/pos-hardware-terms-page.component.js.map +1 -0
- package/src/lib/components/console/pos-hardware-terms-sign-page.component.d.ts +27 -0
- package/src/lib/components/console/pos-hardware-terms-sign-page.component.js +104 -0
- package/src/lib/components/console/pos-hardware-terms-sign-page.component.js.map +1 -0
- package/src/lib/components/console/pos-hardware-waiting-card.component.d.ts +24 -0
- package/src/lib/components/console/pos-hardware-waiting-card.component.js +205 -0
- package/src/lib/components/console/pos-hardware-waiting-card.component.js.map +1 -0
- package/src/lib/components/console/pos-ops/pos-check-in-dialog.component.js +297 -201
- package/src/lib/components/console/pos-ops/pos-check-in-dialog.component.js.map +1 -1
- package/src/lib/components/console/pos-ops/pos-check-ins-card.component.d.ts +3 -0
- package/src/lib/components/console/pos-ops/pos-check-ins-card.component.js +260 -92
- package/src/lib/components/console/pos-ops/pos-check-ins-card.component.js.map +1 -1
- package/src/lib/components/console/pos-ops/pos-operations-bar.component.d.ts +9 -0
- package/src/lib/components/console/pos-ops/pos-operations-bar.component.js +15 -6
- package/src/lib/components/console/pos-ops/pos-operations-bar.component.js.map +1 -1
- package/src/lib/components/console/pos-ops/pos-rain-check-dialog.component.d.ts +25 -0
- package/src/lib/components/console/pos-ops/pos-rain-check-dialog.component.js +136 -0
- package/src/lib/components/console/pos-ops/pos-rain-check-dialog.component.js.map +1 -0
- package/src/lib/components/console/pos-ops/pos-shift-panel.component.d.ts +6 -0
- package/src/lib/components/console/pos-ops/pos-shift-panel.component.js +48 -31
- package/src/lib/components/console/pos-ops/pos-shift-panel.component.js.map +1 -1
- package/src/lib/components/console/pos-page.component.d.ts +1 -1
- package/src/lib/components/console/pos-page.component.js +63 -12
- package/src/lib/components/console/pos-page.component.js.map +1 -1
- package/src/lib/components/console/pos-reader-splash-setting.component.js +1 -1
- package/src/lib/components/console/pos-reader-splash-setting.component.js.map +1 -1
- package/src/lib/components/console/product-check-in-fields.component.d.ts +4 -2
- package/src/lib/components/console/product-check-in-fields.component.js +60 -14
- package/src/lib/components/console/product-check-in-fields.component.js.map +1 -1
- package/src/lib/components/console/product-editor-dialog.component.js +44 -1
- package/src/lib/components/console/product-editor-dialog.component.js.map +1 -1
- package/src/lib/components/console/product-hardware-sku-field.component.d.ts +19 -0
- package/src/lib/components/console/product-hardware-sku-field.component.js +92 -0
- package/src/lib/components/console/product-hardware-sku-field.component.js.map +1 -0
- package/src/lib/components/console/product-kiosk-offer-field.component.d.ts +22 -0
- package/src/lib/components/console/product-kiosk-offer-field.component.js +94 -0
- package/src/lib/components/console/product-kiosk-offer-field.component.js.map +1 -0
- package/src/lib/components/console/product-register-fields.component.d.ts +4 -0
- package/src/lib/components/console/product-register-fields.component.js +31 -2
- package/src/lib/components/console/product-register-fields.component.js.map +1 -1
- package/src/lib/components/console/products-hub-card.component.js +11 -2
- package/src/lib/components/console/products-hub-card.component.js.map +1 -1
- package/src/lib/components/console/register-hardware-card.component.d.ts +11 -0
- package/src/lib/components/console/register-hardware-card.component.js +56 -27
- package/src/lib/components/console/register-hardware-card.component.js.map +1 -1
- package/src/lib/components/console/reservation-guests-dialog.component.d.ts +28 -0
- package/src/lib/components/console/reservation-guests-dialog.component.js +189 -0
- package/src/lib/components/console/reservation-guests-dialog.component.js.map +1 -0
- package/src/lib/components/console/reservations-card.component.d.ts +9 -0
- package/src/lib/components/console/reservations-card.component.js +158 -23
- package/src/lib/components/console/reservations-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.d.ts +1 -1
- package/src/lib/components/console/shipstation-card.component.js +3 -3
- package/src/lib/components/console/shipstation-card.component.js.map +1 -1
- package/src/lib/components/product-detail.d.ts +4 -0
- package/src/lib/components/product-detail.js +40 -5
- package/src/lib/components/product-detail.js.map +1 -1
- package/src/lib/components/product-reviews.js +28 -11
- package/src/lib/components/product-reviews.js.map +1 -1
- package/src/lib/components/reservation-widget.js +28 -5
- package/src/lib/components/reservation-widget.js.map +1 -1
- package/src/lib/components/use-day-picker.d.ts +25 -0
- package/src/lib/components/use-day-picker.js +57 -0
- package/src/lib/components/use-day-picker.js.map +1 -0
- package/src/lib/constants/operator-alerts.d.ts +55 -0
- package/src/lib/constants/operator-alerts.js +156 -1
- package/src/lib/constants/operator-alerts.js.map +1 -1
- package/src/lib/declarations.console-server.js +54 -1
- package/src/lib/declarations.console-server.js.map +1 -1
- package/src/lib/declarations.d.ts +4 -2
- package/src/lib/declarations.js +15 -4
- package/src/lib/declarations.js.map +1 -1
- package/src/lib/declarations.server.js +9 -0
- package/src/lib/declarations.server.js.map +1 -1
- package/src/lib/model/commerce-cart.d.ts +9 -1
- package/src/lib/model/commerce-cart.js +4 -3
- package/src/lib/model/commerce-cart.js.map +1 -1
- package/src/lib/model/commerce-check-in-product.d.ts +26 -0
- package/src/lib/model/commerce-check-in-product.js +64 -2
- package/src/lib/model/commerce-check-in-product.js.map +1 -1
- package/src/lib/model/commerce-check-in-record.d.ts +127 -0
- package/src/lib/model/commerce-check-in-record.js +36 -0
- package/src/lib/model/commerce-check-in-record.js.map +1 -0
- package/src/lib/model/commerce-check-in.d.ts +91 -87
- package/src/lib/model/commerce-check-in.js +186 -62
- package/src/lib/model/commerce-check-in.js.map +1 -1
- package/src/lib/model/commerce-kiosk-offer.d.ts +31 -0
- package/src/lib/model/commerce-kiosk-offer.js +35 -0
- package/src/lib/model/commerce-kiosk-offer.js.map +1 -0
- package/src/lib/model/commerce-orders.d.ts +76 -0
- package/src/lib/model/commerce-orders.js +6 -0
- package/src/lib/model/commerce-orders.js.map +1 -1
- package/src/lib/model/commerce-pos-day-report.d.ts +54 -1
- package/src/lib/model/commerce-pos-day-report.js +39 -2
- package/src/lib/model/commerce-pos-day-report.js.map +1 -1
- package/src/lib/model/commerce-pos-offline.d.ts +1 -1
- package/src/lib/model/commerce-pos-offline.js +6 -1
- package/src/lib/model/commerce-pos-offline.js.map +1 -1
- package/src/lib/model/commerce-pos-ops.js +3 -2
- package/src/lib/model/commerce-pos-ops.js.map +1 -1
- package/src/lib/model/commerce-pos.d.ts +36 -3
- package/src/lib/model/commerce-pos.js +14 -6
- package/src/lib/model/commerce-pos.js.map +1 -1
- package/src/lib/model/commerce-receipt.js +3 -2
- package/src/lib/model/commerce-receipt.js.map +1 -1
- package/src/lib/model/commerce-reservations.d.ts +69 -4
- package/src/lib/model/commerce-reservations.js +57 -5
- package/src/lib/model/commerce-reservations.js.map +1 -1
- package/src/lib/model/commerce.d.ts +66 -0
- package/src/lib/model/commerce.js +15 -0
- package/src/lib/model/commerce.js.map +1 -1
- package/src/lib/model/order-list-fields.d.ts +11 -0
- package/src/lib/model/order-list-fields.js +17 -1
- package/src/lib/model/order-list-fields.js.map +1 -1
- package/src/lib/model/own-store-tax-summary.d.ts +152 -0
- package/src/lib/model/own-store-tax-summary.js +288 -0
- package/src/lib/model/own-store-tax-summary.js.map +1 -0
- package/src/lib/model/plugin-permissions.d.ts +74 -0
- package/src/lib/model/plugin-permissions.js +224 -7
- package/src/lib/model/plugin-permissions.js.map +1 -1
- package/src/lib/model/pos-cellular.d.ts +223 -0
- package/src/lib/model/pos-cellular.js +293 -0
- package/src/lib/model/pos-cellular.js.map +1 -0
- package/src/lib/model/pos-hardware-in-person.d.ts +98 -0
- package/src/lib/model/pos-hardware-in-person.js +224 -0
- package/src/lib/model/pos-hardware-in-person.js.map +1 -0
- package/src/lib/model/pos-hardware-tax-summary.d.ts +166 -0
- package/src/lib/model/pos-hardware-tax-summary.js +225 -0
- package/src/lib/model/pos-hardware-tax-summary.js.map +1 -0
- package/src/lib/model/pos-hardware-terms.d.ts +74 -0
- package/src/lib/model/pos-hardware-terms.js +115 -0
- package/src/lib/model/pos-hardware-terms.js.map +1 -0
- package/src/lib/model/pos-hardware.d.ts +349 -7
- package/src/lib/model/pos-hardware.js +291 -20
- package/src/lib/model/pos-hardware.js.map +1 -1
- package/src/lib/model/product-write.d.ts +10 -0
- package/src/lib/model/product-write.js +4 -2
- package/src/lib/model/product-write.js.map +1 -1
- package/src/lib/model/workspace-site-permissions.d.ts +26 -0
- package/src/lib/model/workspace-site-permissions.js +81 -0
- package/src/lib/model/workspace-site-permissions.js.map +1 -0
- package/src/lib/plugin.js +16 -0
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/server/billing-webhook.js +25 -1
- package/src/lib/server/billing-webhook.js.map +1 -1
- package/src/lib/server/cancel-order.js +7 -5
- package/src/lib/server/cancel-order.js.map +1 -1
- package/src/lib/server/cart-checkout.js +20 -8
- package/src/lib/server/cart-checkout.js.map +1 -1
- package/src/lib/server/cart.js +29 -0
- package/src/lib/server/cart.js.map +1 -1
- package/src/lib/server/checkout.js +42 -13
- package/src/lib/server/checkout.js.map +1 -1
- package/src/lib/server/collection-membership.d.ts +4 -0
- package/src/lib/server/collection-membership.js +5 -10
- package/src/lib/server/collection-membership.js.map +1 -1
- package/src/lib/server/commerce-site-keys.d.ts +48 -0
- package/src/lib/server/commerce-site-keys.js +61 -0
- package/src/lib/server/commerce-site-keys.js.map +1 -0
- package/src/lib/server/draft-order.js +8 -5
- package/src/lib/server/draft-order.js.map +1 -1
- package/src/lib/server/fulfill-order.js +7 -5
- package/src/lib/server/fulfill-order.js.map +1 -1
- package/src/lib/server/gift-cards.js +8 -5
- package/src/lib/server/gift-cards.js.map +1 -1
- package/src/lib/server/local-fulfillment-status.js +9 -6
- package/src/lib/server/local-fulfillment-status.js.map +1 -1
- package/src/lib/server/member-post.js +6 -5
- package/src/lib/server/member-post.js.map +1 -1
- package/src/lib/server/membership-admin-password.d.ts +4 -2
- package/src/lib/server/membership-admin-password.js +10 -8
- package/src/lib/server/membership-admin-password.js.map +1 -1
- package/src/lib/server/membership-admin-remove.d.ts +7 -5
- package/src/lib/server/membership-admin-remove.js +9 -9
- package/src/lib/server/membership-admin-remove.js.map +1 -1
- package/src/lib/server/order-notifications.js +5 -2
- package/src/lib/server/order-notifications.js.map +1 -1
- package/src/lib/server/order-print.js +7 -5
- package/src/lib/server/order-print.js.map +1 -1
- package/src/lib/server/order-receipt-send.js +8 -5
- package/src/lib/server/order-receipt-send.js.map +1 -1
- package/src/lib/server/order-webhooks.js +8 -8
- package/src/lib/server/order-webhooks.js.map +1 -1
- package/src/lib/server/orders-shipping-prepare.js +9 -6
- package/src/lib/server/orders-shipping-prepare.js.map +1 -1
- package/src/lib/server/own-store-tax-return-source.d.ts +47 -0
- package/src/lib/server/own-store-tax-return-source.js +312 -0
- package/src/lib/server/own-store-tax-return-source.js.map +1 -0
- package/src/lib/server/payment-methods.js +13 -9
- package/src/lib/server/payment-methods.js.map +1 -1
- package/src/lib/server/pos-auth.d.ts +37 -6
- package/src/lib/server/pos-auth.js +40 -15
- package/src/lib/server/pos-auth.js.map +1 -1
- package/src/lib/server/pos-cellular-billing.d.ts +81 -0
- package/src/lib/server/pos-cellular-billing.js +711 -0
- package/src/lib/server/pos-cellular-billing.js.map +1 -0
- package/src/lib/server/pos-cellular-notify.d.ts +47 -0
- package/src/lib/server/pos-cellular-notify.js +150 -0
- package/src/lib/server/pos-cellular-notify.js.map +1 -0
- package/src/lib/server/pos-cellular.d.ts +88 -0
- package/src/lib/server/pos-cellular.js +339 -0
- package/src/lib/server/pos-cellular.js.map +1 -0
- package/src/lib/server/pos-check-in.d.ts +5 -1
- package/src/lib/server/pos-check-in.js +296 -28
- package/src/lib/server/pos-check-in.js.map +1 -1
- package/src/lib/server/pos-day-report.js +29 -3
- package/src/lib/server/pos-day-report.js.map +1 -1
- package/src/lib/server/pos-display.d.ts +16 -0
- package/src/lib/server/pos-display.js +129 -2
- package/src/lib/server/pos-display.js.map +1 -1
- package/src/lib/server/pos-hardware-admin.d.ts +10 -1
- package/src/lib/server/pos-hardware-admin.js +250 -84
- package/src/lib/server/pos-hardware-admin.js.map +1 -1
- package/src/lib/server/pos-hardware-attach.d.ts +105 -0
- package/src/lib/server/pos-hardware-attach.js +443 -0
- package/src/lib/server/pos-hardware-attach.js.map +1 -0
- package/src/lib/server/pos-hardware-buyer-lookup.d.ts +58 -0
- package/src/lib/server/pos-hardware-buyer-lookup.js +180 -0
- package/src/lib/server/pos-hardware-buyer-lookup.js.map +1 -0
- package/src/lib/server/pos-hardware-buyer-record.d.ts +29 -0
- package/src/lib/server/pos-hardware-buyer-record.js +86 -0
- package/src/lib/server/pos-hardware-buyer-record.js.map +1 -0
- package/src/lib/server/pos-hardware-cancel.d.ts +58 -0
- package/src/lib/server/pos-hardware-cancel.js +147 -0
- package/src/lib/server/pos-hardware-cancel.js.map +1 -0
- package/src/lib/server/pos-hardware-closure-notices.d.ts +27 -0
- package/src/lib/server/pos-hardware-closure-notices.js +85 -0
- package/src/lib/server/pos-hardware-closure-notices.js.map +1 -0
- package/src/lib/server/pos-hardware-closure.d.ts +60 -0
- package/src/lib/server/pos-hardware-closure.js +153 -0
- package/src/lib/server/pos-hardware-closure.js.map +1 -0
- package/src/lib/server/pos-hardware-in-person.d.ts +61 -0
- package/src/lib/server/pos-hardware-in-person.js +236 -0
- package/src/lib/server/pos-hardware-in-person.js.map +1 -0
- package/src/lib/server/pos-hardware-notify.d.ts +40 -2
- package/src/lib/server/pos-hardware-notify.js +195 -14
- package/src/lib/server/pos-hardware-notify.js.map +1 -1
- package/src/lib/server/pos-hardware-readers.d.ts +115 -0
- package/src/lib/server/pos-hardware-readers.js +430 -0
- package/src/lib/server/pos-hardware-readers.js.map +1 -0
- package/src/lib/server/pos-hardware-returns.d.ts +116 -0
- package/src/lib/server/pos-hardware-returns.js +502 -0
- package/src/lib/server/pos-hardware-returns.js.map +1 -0
- package/src/lib/server/pos-hardware-sale-fields.d.ts +28 -0
- package/src/lib/server/pos-hardware-sale-fields.js +44 -0
- package/src/lib/server/pos-hardware-sale-fields.js.map +1 -0
- package/src/lib/server/pos-hardware-sale.d.ts +54 -0
- package/src/lib/server/pos-hardware-sale.js +369 -0
- package/src/lib/server/pos-hardware-sale.js.map +1 -0
- package/src/lib/server/pos-hardware-tax-return-source.d.ts +99 -0
- package/src/lib/server/pos-hardware-tax-return-source.js +462 -0
- package/src/lib/server/pos-hardware-tax-return-source.js.map +1 -0
- package/src/lib/server/pos-hardware-terms.d.ts +64 -0
- package/src/lib/server/pos-hardware-terms.js +154 -0
- package/src/lib/server/pos-hardware-terms.js.map +1 -0
- package/src/lib/server/pos-hardware-user-eraser.d.ts +3 -0
- package/src/lib/server/pos-hardware-user-eraser.js +31 -7
- package/src/lib/server/pos-hardware-user-eraser.js.map +1 -1
- package/src/lib/server/pos-hardware-webhook.d.ts +5 -0
- package/src/lib/server/pos-hardware-webhook.js +51 -6
- package/src/lib/server/pos-hardware-webhook.js.map +1 -1
- package/src/lib/server/pos-hardware.d.ts +74 -1
- package/src/lib/server/pos-hardware.js +175 -10
- package/src/lib/server/pos-hardware.js.map +1 -1
- package/src/lib/server/pos-kiosk.d.ts +2 -0
- package/src/lib/server/pos-kiosk.js +17 -12
- package/src/lib/server/pos-kiosk.js.map +1 -1
- package/src/lib/server/pos-offline-card-order.d.ts +64 -0
- package/src/lib/server/pos-offline-card-order.js +102 -0
- package/src/lib/server/pos-offline-card-order.js.map +1 -0
- package/src/lib/server/pos-offline-card-reconcile.d.ts +67 -1
- package/src/lib/server/pos-offline-card-reconcile.js +215 -18
- package/src/lib/server/pos-offline-card-reconcile.js.map +1 -1
- package/src/lib/server/pos-offline-sync.d.ts +39 -0
- package/src/lib/server/pos-offline-sync.js +190 -55
- package/src/lib/server/pos-offline-sync.js.map +1 -1
- package/src/lib/server/pos-ops-gate.d.ts +28 -9
- package/src/lib/server/pos-ops-gate.js +50 -19
- package/src/lib/server/pos-ops-gate.js.map +1 -1
- package/src/lib/server/pos-ops-routes.js +24 -0
- package/src/lib/server/pos-ops-routes.js.map +1 -1
- package/src/lib/server/pos-order-refund-ids.d.ts +28 -0
- package/src/lib/server/pos-order-refund-ids.js +31 -0
- package/src/lib/server/pos-order-refund-ids.js.map +1 -0
- package/src/lib/server/pos-order.js +52 -49
- package/src/lib/server/pos-order.js.map +1 -1
- package/src/lib/server/pos-payment.js +1 -1
- package/src/lib/server/pos-payment.js.map +1 -1
- package/src/lib/server/pos-reader-splash-admin.js +30 -32
- package/src/lib/server/pos-reader-splash-admin.js.map +1 -1
- package/src/lib/server/pos-reader-splash-images-route.d.ts +17 -0
- package/src/lib/server/pos-reader-splash-images-route.js +113 -0
- package/src/lib/server/pos-reader-splash-images-route.js.map +1 -0
- package/src/lib/server/pos-reader-splash-images.d.ts +51 -0
- package/src/lib/server/pos-reader-splash-images.js +123 -0
- package/src/lib/server/pos-reader-splash-images.js.map +1 -0
- package/src/lib/server/pos-reader-splash.d.ts +68 -1
- package/src/lib/server/pos-reader-splash.js +182 -11
- package/src/lib/server/pos-reader-splash.js.map +1 -1
- package/src/lib/server/pos-readers.d.ts +29 -0
- package/src/lib/server/pos-readers.js +134 -35
- package/src/lib/server/pos-readers.js.map +1 -1
- package/src/lib/server/pos-refund-authority.d.ts +3 -1
- package/src/lib/server/pos-refund-authority.js +3 -2
- package/src/lib/server/pos-refund-authority.js.map +1 -1
- package/src/lib/server/pos-return.d.ts +10 -0
- package/src/lib/server/pos-return.js +29 -7
- package/src/lib/server/pos-return.js.map +1 -1
- package/src/lib/server/pos-sale.js +6 -1
- package/src/lib/server/pos-sale.js.map +1 -1
- package/src/lib/server/pos-shift.js +11 -0
- package/src/lib/server/pos-shift.js.map +1 -1
- package/src/lib/server/pos-staff-pin.js +29 -24
- package/src/lib/server/pos-staff-pin.js.map +1 -1
- package/src/lib/server/pos-terminal.d.ts +14 -4
- package/src/lib/server/pos-terminal.js +34 -38
- package/src/lib/server/pos-terminal.js.map +1 -1
- package/src/lib/server/printers.d.ts +7 -3
- package/src/lib/server/printers.js +18 -12
- package/src/lib/server/printers.js.map +1 -1
- package/src/lib/server/product-drafts.d.ts +11 -1
- package/src/lib/server/product-drafts.js +12 -8
- package/src/lib/server/product-drafts.js.map +1 -1
- package/src/lib/server/product.d.ts +15 -1
- package/src/lib/server/product.js +16 -3
- package/src/lib/server/product.js.map +1 -1
- package/src/lib/server/products-write.d.ts +26 -2
- package/src/lib/server/products-write.js +19 -7
- package/src/lib/server/products-write.js.map +1 -1
- package/src/lib/server/refund.js +55 -42
- package/src/lib/server/refund.js.map +1 -1
- package/src/lib/server/reservation-availability-query.d.ts +8 -0
- package/src/lib/server/reservation-availability-query.js +11 -1
- package/src/lib/server/reservation-availability-query.js.map +1 -1
- package/src/lib/server/reservation-availability.d.ts +1 -1
- package/src/lib/server/reservation-availability.js +7 -2
- package/src/lib/server/reservation-availability.js.map +1 -1
- package/src/lib/server/reservation-guests.d.ts +32 -0
- package/src/lib/server/reservation-guests.js +285 -0
- package/src/lib/server/reservation-guests.js.map +1 -0
- package/src/lib/server/reservation-walk-in.d.ts +11 -1
- package/src/lib/server/reservation-walk-in.js +56 -12
- package/src/lib/server/reservation-walk-in.js.map +1 -1
- package/src/lib/server/reserve.js +18 -5
- package/src/lib/server/reserve.js.map +1 -1
- package/src/lib/server/returns.js +9 -6
- package/src/lib/server/returns.js.map +1 -1
- package/src/lib/server/reviews.d.ts +22 -2
- package/src/lib/server/reviews.js +88 -20
- package/src/lib/server/reviews.js.map +1 -1
- package/src/lib/server/shipping-connectors.js +10 -11
- package/src/lib/server/shipping-connectors.js.map +1 -1
- package/src/lib/server/site-page-resolver.js +20 -3
- package/src/lib/server/site-page-resolver.js.map +1 -1
- package/src/lib/server/store-day.d.ts +33 -0
- package/src/lib/server/store-day.js +46 -0
- package/src/lib/server/store-day.js.map +1 -0
- package/src/lib/server/store-setup-ai-writer.d.ts +2 -1
- package/src/lib/server/store-setup-ai-writer.js +5 -6
- package/src/lib/server/store-setup-ai-writer.js.map +1 -1
- package/src/lib/server/storefront-tax.d.ts +13 -0
- package/src/lib/server/storefront-tax.js +8 -1
- package/src/lib/server/storefront-tax.js.map +1 -1
- package/src/lib/server/tax-return-source.d.ts +32 -3
- package/src/lib/server/tax-return-source.js +99 -20
- package/src/lib/server/tax-return-source.js.map +1 -1
- package/src/lib/server.js +100 -2
- package/src/lib/server.js.map +1 -1
- package/src/lib/testing/fake-firestore.js +58 -16
- package/src/lib/testing/fake-firestore.js.map +1 -1
- package/src/lib/testing/pos-ops-memory-firestore.js +18 -4
- package/src/lib/testing/pos-ops-memory-firestore.js.map +1 -1
- package/src/lib/testing/site-register-permissions.d.ts +29 -0
- package/src/lib/testing/site-register-permissions.js +38 -0
- package/src/lib/testing/site-register-permissions.js.map +1 -0
- package/src/lib/transfer/tracking.server.js +8 -5
- package/src/lib/transfer/tracking.server.js.map +1 -1
- package/src/lib/utils/add-to-cart.d.ts +2 -0
- package/src/lib/utils/add-to-cart.js +6 -3
- package/src/lib/utils/add-to-cart.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/components/product-reviews.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport * as Aglyn from '@aglyn/aglyn'\nimport { mdiStarOutline } from '@aglyn/shared-data-mdi'\nimport Alert from '@mui/material/Alert'\nimport Box from '@mui/material/Box'\nimport Button from '@mui/material/Button'\nimport Chip from '@mui/material/Chip'\nimport Divider from '@mui/material/Divider'\nimport Rating from '@mui/material/Rating'\nimport TextField from '@mui/material/TextField'\nimport Typography from '@mui/material/Typography'\nimport { forwardRef, useEffect, useState } from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport { generatePresetId } from '../utils/generate-preset-id'\n\n// Component ids are persisted in screen documents; never rename.\nexport const ID: Aglyn.ComponentId = 'product-reviews'\n\nexport interface ProductReviewsProps {\n /** Product id; blank resolves from the /products/{slug} page product. */\n productId?: string\n heading?: string\n /**\n * `product` (default): one product's reviews with the submit form.\n * `store`: the store's latest approved reviews across every product, with\n * the average and count, and no form — for a storefront home. In store\n * scope the block draws its own heading (`heading`, default \"What\n * customers say\") and draws NOTHING when there are no approved reviews,\n * so a page never shows an empty reviews band.\n */\n scope?: 'product' | 'store'\n /** Store scope: how many reviews to show (default 6, at most 12). */\n maxItems?: number\n /** Store scope: the heading's level, 2–4 (default 2). */\n headingLevel?: 2 | 3 | 4 | '2' | '3' | '4'\n}\n\ninterface StoreReviewView {\n id: string\n rating: number\n body: string\n authorName: string\n verified: boolean\n createdAtMs: number\n productName?: string\n productSlug?: string\n}\n\nconst EDITOR_HINT_SX = {\n p: 3,\n border: '1px dashed',\n borderColor: 'divider',\n borderRadius: 1,\n color: 'text.secondary',\n fontSize: 13,\n fontFamily: 'system-ui, sans-serif',\n} as const\n\ninterface ReviewView {\n id: string\n rating: number\n body: string\n authorName: string\n verified: boolean\n reply?: string | null\n createdAtMs: number\n}\n\n/** What `GET /api/commerce/reviews?mode=eligibility` answers (AGL-3736). */\nexport interface ReviewFormEligibility {\n enabled?: boolean\n policy?: 'verified' | 'anyone'\n signedIn?: boolean\n verifiedVia?: 'member' | 'token' | null\n canReview?: boolean\n needsEmail?: boolean\n tokenProblem?: string\n}\n\n/** Where a shopper signs in to their account on the store. */\nexport const REVIEW_SIGN_IN_HREF = '/account'\n\n/** The review link from the review-request email, if the page carries one. */\nexport function reviewTokenFromLocation(search: string): string {\n const token = new URLSearchParams(search).get('review') ?? ''\n return /^[A-Za-z0-9_-]{32}$/.test(token) ? token : ''\n}\n\n/**\n * Which form the block draws, from the server's answer. Pure, so the three\n * states are specified in one place:\n * - `form`: the visitor may review (verified or, under `anyone`, pending);\n * - `sign-in`: the store takes verified reviews only and this visitor is\n * not one — \"Only customers who bought this can review.\" and a sign-in\n * link (no form: a typed email verifies nothing);\n * - `none`: reviews are off on this plan, or the answer has not come yet.\n */\nexport function reviewFormMode(\n eligibility: ReviewFormEligibility | null,\n): 'form' | 'sign-in' | 'none' {\n if (!eligibility || eligibility.enabled === false) return 'none'\n if (eligibility.canReview) return 'form'\n return 'sign-in'\n}\n\nconst TOKEN_PROBLEM_TEXT: Record<string, string> = {\n review_token_used: 'This review link has already been used.',\n review_token_expired: 'This review link has expired.',\n review_token_invalid: 'This review link is not valid for this product.',\n}\n\nasync function resolveProductIdFromSlug(hostId: string): Promise<string> {\n const match = window.location.pathname.match(/\\/products\\/([^/?#]+)/)\n if (!match) return ''\n const response = await fetch(\n `/api/commerce/product?hostId=${encodeURIComponent(hostId)}` +\n `&slug=${encodeURIComponent(decodeURIComponent(match[1]))}`,\n ).catch(() => null)\n if (!response?.ok) return ''\n const payload = await response.json().catch(() => ({}))\n return String(payload?.product?.id ?? '')\n}\n\n/**\n * Product reviews block (AGL-324): approved reviews with verified-buyer\n * badges, seller replies, an aggregate summary (emitted as JSON-LD\n * AggregateRating), and a submit form. Since AGL-3736 the form asks the server\n * who the visitor is first: a verified buyer (signed in, or arriving with the\n * review-request email's link) gets the form and publishes at once; anyone\n * else sees \"Only customers who bought this can review\" and a sign-in link,\n * unless the store accepts reviews from anyone (held for moderation).\n */\nconst ProductReviews = forwardRef<HTMLDivElement, ProductReviewsProps>(\n (props, ref) => {\n if (props.scope === 'store') return <StoreReviews ref={ref} {...props} />\n return <SingleProductReviews ref={ref} {...props} />\n },\n)\nProductReviews.displayName = 'AglynProductReviews'\n\n/**\n * The store-wide reviews band (`scope: 'store'`): the store's latest APPROVED\n * reviews, read-only stars, the reviewer's first name and initial, a verified\n * badge, and the product reviewed. Real reviews or nothing: with none it\n * renders an empty, zero-height element — never sample reviews on a live page\n * (sample testimonials presented as customers' would be fake reviews). The\n * editor, which has no site, gets a dashed hint instead.\n */\nconst StoreReviews = forwardRef<HTMLDivElement, ProductReviewsProps>(\n (props, ref) => {\n const {\n productId: _productId,\n heading,\n scope: _scope,\n maxItems,\n headingLevel,\n ...rest\n } = props\n const nodeSx = Array.isArray(props['sx']) ? props['sx'] : [props['sx']]\n const { hostId } = Aglyn.useSite()\n const [payload, setPayload] = useState<{\n reviews: StoreReviewView[]\n aggregate: { count: number; average: number }\n } | null>(null)\n const limit = maxItems && Number(maxItems) > 0 ? Math.min(12, Math.floor(Number(maxItems))) : 6\n\n useEffect(() => {\n if (!hostId) return\n let active = true\n void fetch(\n `/api/commerce/reviews?hostId=${encodeURIComponent(hostId)}` +\n `&scope=store&limit=${limit}`,\n )\n .then((response) => (response.ok ? response.json() : null))\n .then((body) => {\n if (!active || !body) return\n setPayload({\n reviews: Array.isArray(body.reviews) ? body.reviews : [],\n aggregate: body.aggregate ?? { count: 0, average: 0 },\n })\n })\n .catch(() => undefined)\n return () => {\n active = false\n }\n }, [hostId, limit])\n\n if (!hostId) {\n return (\n <Box ref={ref} {...rest} sx={[EDITOR_HINT_SX, ...nodeSx]}>\n {'★★★★★ Customer reviews appear here once shoppers review your products'}\n </Box>\n )\n }\n // Loading, failed, or none approved: nothing at all — not the node's\n // padding, not a heading over an empty band.\n if (!payload || payload.reviews.length === 0) {\n return <Box ref={ref} data-store-reviews-empty=\"\" sx={{ display: 'none' }} />\n }\n // A select may store the level as a string; read it as a number.\n const asked = Number(headingLevel)\n const level = asked === 3 || asked === 4 ? asked : 2\n const { aggregate } = payload\n return (\n <Box\n ref={ref}\n {...rest}\n sx={[{ display: 'flex', flexDirection: 'column', gap: 3 }, ...nodeSx]}\n >\n <Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 1, textAlign: 'center' }}>\n <Typography variant={level === 2 ? 'h4' : level === 3 ? 'h5' : 'h6'} component={`h${level}`}>\n {heading || 'What customers say'}\n </Typography>\n {aggregate.count > 0 ? (\n <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>\n <Rating value={aggregate.average} precision={0.1} readOnly size=\"small\" />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`${aggregate.average} out of 5 · ${aggregate.count} ${aggregate.count === 1 ? 'review' : 'reviews'}`}\n </Typography>\n </Box>\n ) : null}\n </Box>\n <Box\n sx={{\n display: 'grid',\n gap: 2,\n gridTemplateColumns: { xs: '1fr', sm: 'repeat(2, 1fr)', md: 'repeat(3, 1fr)' },\n }}\n >\n {payload.reviews.map((review) => (\n <Box\n key={review.id}\n component=\"figure\"\n sx={{\n m: 0,\n p: 2.5,\n display: 'flex',\n flexDirection: 'column',\n gap: 1,\n border: 1,\n borderColor: 'divider',\n borderRadius: 2,\n bgcolor: 'background.paper',\n }}\n >\n <Rating value={review.rating} readOnly size=\"small\" />\n <Typography component=\"blockquote\" variant=\"body1\" sx={{ m: 0, flex: 1 }}>\n {review.body}\n </Typography>\n <Box component=\"figcaption\" sx={{ display: 'flex', flexWrap: 'wrap', gap: 1, alignItems: 'center' }}>\n <Typography variant=\"body2\" sx={{ fontWeight: 600 }}>\n {review.authorName}\n </Typography>\n {review.verified ? (\n <Chip label=\"Verified buyer\" size=\"small\" variant=\"outlined\" color=\"success\" />\n ) : null}\n </Box>\n {review.productName ? (\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n component={review.productSlug ? 'a' : 'span'}\n {...(review.productSlug ? { href: `/products/${review.productSlug}` } : {})}\n sx={{ textDecoration: 'none' }}\n >\n {review.productName}\n </Typography>\n ) : null}\n </Box>\n ))}\n </Box>\n </Box>\n )\n },\n)\nStoreReviews.displayName = 'AglynStoreReviews'\n\nconst SingleProductReviews = forwardRef<HTMLDivElement, ProductReviewsProps>(\n (props, ref) => {\n const {\n productId: productIdProp,\n heading,\n scope: _scope,\n maxItems: _maxItems,\n headingLevel: _headingLevel,\n ...rest\n } = props\n // Node styles ride the renderer-merged sx; recompose (stack.ts pattern).\n const nodeSx = Array.isArray(props['sx']) ? props['sx'] : [props['sx']]\n const { hostId } = Aglyn.useSite()\n const siteFetch = Aglyn.useSiteFetch()\n const [productId, setProductId] = useState(productIdProp ?? '')\n const [reviews, setReviews] = useState<ReviewView[] | null>(null)\n const [aggregate, setAggregate] = useState({ count: 0, average: 0 })\n const [form, setForm] = useState({\n rating: 5,\n body: '',\n authorName: '',\n authorEmail: '',\n })\n const [state, setState] = useState<'idle' | 'busy' | 'done' | 'error'>(\n 'idle',\n )\n const [eligibility, setEligibility] = useState<ReviewFormEligibility | null>(null)\n const [reviewToken, setReviewToken] = useState('')\n const [submitted, setSubmitted] = useState<{ pending: boolean } | null>(null)\n const [errorText, setErrorText] = useState('')\n\n useEffect(() => {\n if (!hostId) return\n let active = true\n void (async () => {\n const resolved =\n productIdProp || (await resolveProductIdFromSlug(hostId))\n if (!active || !resolved) return\n setProductId(resolved)\n const response = await fetch(\n `/api/commerce/reviews?hostId=${encodeURIComponent(hostId)}` +\n `&productId=${encodeURIComponent(resolved)}`,\n ).catch(() => null)\n if (!active || !response?.ok) return\n const payload = await response.json().catch(() => ({}))\n setReviews(payload?.reviews ?? [])\n setAggregate(payload?.aggregate ?? { count: 0, average: 0 })\n // May this visitor review, and would it be verified? (AGL-3736)\n const token = reviewTokenFromLocation(window.location.search)\n setReviewToken(token)\n const answer = await fetch(\n `/api/commerce/reviews?hostId=${encodeURIComponent(hostId)}` +\n `&productId=${encodeURIComponent(resolved)}&mode=eligibility` +\n (token ? `&review=${encodeURIComponent(token)}` : ''),\n { credentials: 'same-origin' },\n ).catch(() => null)\n if (!active || !answer?.ok) return\n setEligibility(await answer.json().catch(() => null))\n })()\n return () => {\n active = false\n }\n }, [hostId, productIdProp])\n\n const handleSubmit = async () => {\n if (!hostId || !productId || state === 'busy') return\n setState('busy')\n try {\n const response = await siteFetch('/api/commerce/reviews', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n hostId,\n productId,\n rating: form.rating,\n body: form.body,\n authorName: form.authorName,\n ...(eligibility?.needsEmail ? { authorEmail: form.authorEmail } : {}),\n ...(reviewToken ? { reviewToken } : {}),\n }),\n })\n const payload = await response.json().catch(() => ({}))\n if (response.ok) {\n setSubmitted({ pending: Boolean(payload?.pending) })\n setState('done')\n } else {\n setErrorText(String(payload?.error ?? ''))\n setState('error')\n }\n } catch {\n setState('error')\n }\n }\n\n if (!hostId) {\n return (\n <Box ref={ref} {...rest} sx={[EDITOR_HINT_SX, ...nodeSx]}>\n {'★★★★★ Product reviews render here'}\n </Box>\n )\n }\n\n return (\n <Box ref={ref} {...rest} sx={[{ display: 'flex', flexDirection: 'column', gap: 1.5 }, ...nodeSx]}>\n {/* No JSON-LD here any more (AGL-686). This block used to emit a\n free-standing `AggregateRating` node, which schema.org ignores —\n a rating has to be a PROPERTY of the Product it rates. The PDP's\n server-rendered Product node now carries `aggregateRating`,\n built from the same `readProductReviews` reader this block's API\n uses, so the structured data and the visible stars cannot\n disagree. It also removes the one call site that was primed to\n become stored XSS the moment a reviewer name went into it. */}\n <Typography variant=\"h6\">\n {heading || 'Reviews'}\n {aggregate.count > 0 ? ` (${aggregate.average} ★ · ${aggregate.count})` : ''}\n </Typography>\n {(reviews ?? []).map((review) => (\n <Box key={review.id}>\n <Box sx={{ display: 'flex', gap: 1, alignItems: 'center' }}>\n <Rating value={review.rating} size=\"small\" readOnly />\n <Typography variant=\"body2\" sx={{ fontWeight: 600 }}>\n {review.authorName}\n </Typography>\n {review.verified ? (\n <Chip label=\"Verified buyer\" size=\"small\" variant=\"outlined\" color=\"success\" />\n ) : null}\n </Box>\n <Typography variant=\"body2\" sx={{ mt: 0.5 }}>\n {review.body}\n </Typography>\n {review.reply ? (\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ display: 'block', pl: 2, mt: 0.5, borderLeft: 2, borderColor: 'divider' }}\n >\n {`Seller: ${review.reply}`}\n </Typography>\n ) : null}\n <Divider sx={{ mt: 1.5 }} />\n </Box>\n ))}\n {reviews && reviews.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No reviews yet — be the first.'}\n </Typography>\n ) : null}\n {eligibility?.tokenProblem && TOKEN_PROBLEM_TEXT[eligibility.tokenProblem] ? (\n <Alert severity=\"info\">{TOKEN_PROBLEM_TEXT[eligibility.tokenProblem]}</Alert>\n ) : null}\n {state === 'done' ? (\n <Alert severity=\"success\">\n {submitted?.pending\n ? 'Thanks — your review is awaiting moderation.'\n : 'Thanks — your review is posted.'}\n </Alert>\n ) : reviewFormMode(eligibility) === 'sign-in' ? (\n <Typography variant=\"body2\" color=\"text.secondary\" data-review-sign-in=\"\">\n {'Only customers who bought this can review. '}\n <Box component=\"a\" href={REVIEW_SIGN_IN_HREF} sx={{ color: 'primary.main' }}>\n {'Sign in'}\n </Box>\n </Typography>\n ) : reviewFormMode(eligibility) === 'none' ? null : (\n <Box sx={{ display: 'flex', flexDirection: 'column', gap: 1, maxWidth: 480 }}>\n <Typography variant=\"subtitle2\">{'Write a review'}</Typography>\n {eligibility?.verifiedVia ? (\n <Chip\n label=\"Your review will show as Verified buyer\"\n size=\"small\"\n variant=\"outlined\"\n color=\"success\"\n sx={{ alignSelf: 'flex-start' }}\n />\n ) : null}\n <Rating\n value={form.rating}\n onChange={(_event, value) =>\n setForm((prev) => ({ ...prev, rating: value ?? 5 }))\n }\n />\n <TextField\n placeholder=\"What did you think?\"\n value={form.body}\n onChange={(event) =>\n setForm((prev) => ({ ...prev, body: event.target.value }))\n }\n size=\"small\"\n multiline\n minRows={2}\n />\n <Box sx={{ display: 'flex', gap: 1 }}>\n <TextField\n placeholder=\"Name\"\n value={form.authorName}\n onChange={(event) =>\n setForm((prev) => ({ ...prev, authorName: event.target.value }))\n }\n size=\"small\"\n sx={{ flex: 1 }}\n />\n {eligibility?.needsEmail ? (\n <TextField\n placeholder=\"Email (never shown)\"\n type=\"email\"\n value={form.authorEmail}\n onChange={(event) =>\n setForm((prev) => ({ ...prev, authorEmail: event.target.value }))\n }\n size=\"small\"\n sx={{ flex: 1.4 }}\n />\n ) : null}\n </Box>\n {state === 'error' ? (\n <Alert severity=\"error\">\n {errorText || 'Could not submit — check the fields.'}\n </Alert>\n ) : null}\n <Button\n variant=\"outlined\"\n size=\"small\"\n disabled={\n state === 'busy' ||\n !form.body.trim() ||\n Boolean(eligibility?.needsEmail && !form.authorEmail.trim())\n }\n onClick={handleSubmit}\n sx={{ alignSelf: 'flex-start' }}\n >\n {'Submit review'}\n </Button>\n </Box>\n )}\n </Box>\n )\n },\n)\nSingleProductReviews.displayName = 'AglynSingleProductReviews'\n\nexport const schema: Aglyn.ComponentSchema<ProductReviewsProps> = {\n $id: ID,\n pluginId: BUNDLE_ID,\n displayName: 'Product reviews',\n description:\n 'Approved reviews with verified-buyer badges, and a form for customers ' +\n 'who bought the product to add one.',\n category: Aglyn.ComponentCategory.COMMERCE,\n icon: { path: mdiStarOutline.path, sx: { color: '#2e7d32' } },\n flags: { selfClosing: Aglyn.FEATURE_FLAG.ENABLED },\n attributes: [\n {\n name: 'productId',\n label: 'Product id',\n description:\n 'Whose reviews these are. Blank reads the product from the URL, ' +\n 'which is what you want on a product template; naming one pins the ' +\n 'block to that product wherever it is placed.',\n component: Aglyn.FieldComponentType.PRODUCT_SELECT,\n },\n {\n name: 'heading',\n label: 'Heading',\n description: 'Defaults to \"Reviews\" (\"What customers say\" store-wide).',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n // Default `product`, so a page that already places this block keeps it.\n name: 'scope',\n label: 'Show',\n description:\n 'One product’s reviews with a form, or the store’s latest approved ' +\n 'reviews across every product. Store-wide shows nothing until a ' +\n 'shopper’s review is approved.',\n component: Aglyn.FieldComponentType.SELECT,\n options: [\n { label: 'This product', value: 'product' },\n { label: 'Whole store', value: 'store' },\n ],\n },\n {\n name: 'maxItems',\n label: 'Max reviews',\n description: 'Store-wide: how many to show (default 6, at most 12).',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n type: 'number',\n },\n {\n name: 'headingLevel',\n label: 'Heading level',\n description: 'Store-wide: the heading’s level on the page (default H2).',\n component: Aglyn.FieldComponentType.SELECT,\n options: [\n { label: 'H2', value: '2' },\n { label: 'H3', value: '3' },\n { label: 'H4', value: '4' },\n ],\n },\n ],\n}\n\nexport const presets: Aglyn.PresetSchema[] = [\n {\n $id: generatePresetId(ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Product reviews',\n pluginId: BUNDLE_ID,\n description: 'Verified-buyer reviews with a submit form',\n category: Aglyn.ComponentCategory.COMMERCE,\n icon: { path: mdiStarOutline.path, sx: { color: '#2e7d32' } },\n data: {\n $id: null,\n componentId: ID,\n pluginId: BUNDLE_ID,\n props: {},\n },\n },\n]\n\nexport default ProductReviews\n"],"names":["Aglyn","mdiStarOutline","Alert","Box","Button","Chip","Divider","Rating","TextField","Typography","forwardRef","useEffect","useState","BUNDLE_ID","generatePresetId","ID","EDITOR_HINT_SX","p","border","borderColor","borderRadius","color","fontSize","fontFamily","REVIEW_SIGN_IN_HREF","reviewTokenFromLocation","search","token","URLSearchParams","get","test","reviewFormMode","eligibility","enabled","canReview","TOKEN_PROBLEM_TEXT","review_token_used","review_token_expired","review_token_invalid","resolveProductIdFromSlug","hostId","payload","match","window","location","pathname","response","fetch","encodeURIComponent","decodeURIComponent","catch","ok","json","String","product","id","ProductReviews","props","ref","scope","StoreReviews","SingleProductReviews","displayName","productId","_productId","heading","_scope","maxItems","headingLevel","rest","nodeSx","Array","isArray","useSite","setPayload","limit","Number","Math","min","floor","active","then","body","reviews","aggregate","count","average","undefined","sx","length","data-store-reviews-empty","display","asked","level","flexDirection","gap","alignItems","textAlign","variant","component","value","precision","readOnly","size","gridTemplateColumns","xs","sm","md","map","review","m","bgcolor","rating","flex","flexWrap","fontWeight","authorName","verified","label","productName","productSlug","href","textDecoration","productIdProp","_maxItems","_headingLevel","siteFetch","useSiteFetch","setProductId","setReviews","setAggregate","form","setForm","authorEmail","state","setState","setEligibility","reviewToken","setReviewToken","submitted","setSubmitted","errorText","setErrorText","resolved","answer","credentials","handleSubmit","method","headers","JSON","stringify","needsEmail","pending","Boolean","error","mt","reply","pl","borderLeft","tokenProblem","severity","data-review-sign-in","maxWidth","verifiedVia","alignSelf","onChange","_event","prev","placeholder","event","target","multiline","minRows","type","disabled","trim","onClick","schema","$id","pluginId","description","category","ComponentCategory","COMMERCE","icon","path","flags","selfClosing","FEATURE_FLAG","ENABLED","attributes","name","FieldComponentType","PRODUCT_SELECT","TEXT_FIELD","SELECT","options","presets","NodeType","PRESET","data","componentId"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/components/product-reviews.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport * as Aglyn from '@aglyn/aglyn'\nimport { mdiStarOutline } from '@aglyn/shared-data-mdi'\nimport Alert from '@mui/material/Alert'\nimport Box from '@mui/material/Box'\nimport Button from '@mui/material/Button'\nimport Chip from '@mui/material/Chip'\nimport Divider from '@mui/material/Divider'\nimport Rating from '@mui/material/Rating'\nimport TextField from '@mui/material/TextField'\nimport Typography from '@mui/material/Typography'\nimport { forwardRef, useEffect, useState } from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport { generatePresetId } from '../utils/generate-preset-id'\n\n// Component ids are persisted in screen documents; never rename.\nexport const ID: Aglyn.ComponentId = 'product-reviews'\n\nexport interface ProductReviewsProps {\n /** Product id; blank resolves from the /products/{slug} page product. */\n productId?: string\n heading?: string\n /**\n * `product` (default): one product's reviews with the submit form.\n * `store`: the store's latest approved reviews across every product, with\n * the average and count, and no form — for a storefront home. In store\n * scope the block draws its own heading (`heading`, default \"What\n * customers say\") and draws NOTHING when there are no approved reviews,\n * so a page never shows an empty reviews band.\n */\n scope?: 'product' | 'store'\n /** Store scope: how many reviews to show (default 6, at most 12). */\n maxItems?: number\n /** Store scope: the heading's level, 2–4 (default 2). */\n headingLevel?: 2 | 3 | 4 | '2' | '3' | '4'\n}\n\ninterface StoreReviewView {\n id: string\n rating: number\n body: string\n authorName: string\n verified: boolean\n createdAtMs: number\n productName?: string\n productSlug?: string\n /** Sold in person only, and the store shows its reviews (AGL-4060). */\n soldInStore?: boolean\n}\n\nconst EDITOR_HINT_SX = {\n p: 3,\n border: '1px dashed',\n borderColor: 'divider',\n borderRadius: 1,\n color: 'text.secondary',\n fontSize: 13,\n fontFamily: 'system-ui, sans-serif',\n} as const\n\ninterface ReviewView {\n id: string\n rating: number\n body: string\n authorName: string\n verified: boolean\n reply?: string | null\n createdAtMs: number\n}\n\n/** What `GET /api/commerce/reviews?mode=eligibility` answers (AGL-3736). */\nexport interface ReviewFormEligibility {\n enabled?: boolean\n policy?: 'verified' | 'anyone'\n signedIn?: boolean\n verifiedVia?: 'member' | 'token' | null\n canReview?: boolean\n needsEmail?: boolean\n tokenProblem?: string\n}\n\n/** Where a shopper signs in to their account on the store. */\nexport const REVIEW_SIGN_IN_HREF = '/account'\n\n/** The review link from the review-request email, if the page carries one. */\nexport function reviewTokenFromLocation(search: string): string {\n const token = new URLSearchParams(search).get('review') ?? ''\n return /^[A-Za-z0-9_-]{32}$/.test(token) ? token : ''\n}\n\n/**\n * Which form the block draws, from the server's answer. Pure, so the three\n * states are specified in one place:\n * - `form`: the visitor may review (verified or, under `anyone`, pending);\n * - `sign-in`: the store takes verified reviews only and this visitor is\n * not one — \"Only customers who bought this can review.\" and a sign-in\n * link (no form: a typed email verifies nothing);\n * - `none`: reviews are off on this plan, or the answer has not come yet.\n */\nexport function reviewFormMode(\n eligibility: ReviewFormEligibility | null,\n): 'form' | 'sign-in' | 'none' {\n if (!eligibility || eligibility.enabled === false) return 'none'\n if (eligibility.canReview) return 'form'\n return 'sign-in'\n}\n\nconst TOKEN_PROBLEM_TEXT: Record<string, string> = {\n review_token_used: 'This review link has already been used.',\n review_token_expired: 'This review link has expired.',\n review_token_invalid: 'This review link is not valid for this product.',\n}\n\nasync function resolveProductIdFromSlug(hostId: string): Promise<string> {\n const match = window.location.pathname.match(/\\/products\\/([^/?#]+)/)\n if (!match) return ''\n const response = await fetch(\n `/api/commerce/product?hostId=${encodeURIComponent(hostId)}` +\n `&slug=${encodeURIComponent(decodeURIComponent(match[1]))}`,\n ).catch(() => null)\n if (!response?.ok) return ''\n const payload = await response.json().catch(() => ({}))\n return String(payload?.product?.id ?? '')\n}\n\n/**\n * Product reviews block (AGL-324): approved reviews with verified-buyer\n * badges, seller replies, an aggregate summary (emitted as JSON-LD\n * AggregateRating), and a submit form. Since AGL-3736 the form asks the server\n * who the visitor is first: a verified buyer (signed in, or arriving with the\n * review-request email's link) gets the form and publishes at once; anyone\n * else sees \"Only customers who bought this can review\" and a sign-in link,\n * unless the store accepts reviews from anyone (held for moderation).\n */\nconst ProductReviews = forwardRef<HTMLDivElement, ProductReviewsProps>(\n (props, ref) => {\n if (props.scope === 'store') return <StoreReviews ref={ref} {...props} />\n return <SingleProductReviews ref={ref} {...props} />\n },\n)\nProductReviews.displayName = 'AglynProductReviews'\n\n/**\n * The store-wide reviews band (`scope: 'store'`): the store's latest APPROVED\n * reviews, read-only stars, the reviewer's first name and initial, a verified\n * badge, and the product reviewed — linked to its page, or, for a product\n * sold in person only, marked \"Sold in store\" and linked nowhere (AGL-4060).\n * Real reviews or nothing: with none it\n * renders an empty, zero-height element — never sample reviews on a live page\n * (sample testimonials presented as customers' would be fake reviews). The\n * editor, which has no site, gets a dashed hint instead.\n */\nconst StoreReviews = forwardRef<HTMLDivElement, ProductReviewsProps>(\n (props, ref) => {\n const {\n productId: _productId,\n heading,\n scope: _scope,\n maxItems,\n headingLevel,\n ...rest\n } = props\n const nodeSx = Array.isArray(props['sx']) ? props['sx'] : [props['sx']]\n const { hostId } = Aglyn.useSite()\n const [payload, setPayload] = useState<{\n reviews: StoreReviewView[]\n aggregate: { count: number; average: number }\n } | null>(null)\n const limit = maxItems && Number(maxItems) > 0 ? Math.min(12, Math.floor(Number(maxItems))) : 6\n\n useEffect(() => {\n if (!hostId) return\n let active = true\n void fetch(\n `/api/commerce/reviews?hostId=${encodeURIComponent(hostId)}` +\n `&scope=store&limit=${limit}`,\n )\n .then((response) => (response.ok ? response.json() : null))\n .then((body) => {\n if (!active || !body) return\n setPayload({\n reviews: Array.isArray(body.reviews) ? body.reviews : [],\n aggregate: body.aggregate ?? { count: 0, average: 0 },\n })\n })\n .catch(() => undefined)\n return () => {\n active = false\n }\n }, [hostId, limit])\n\n if (!hostId) {\n return (\n <Box ref={ref} {...rest} sx={[EDITOR_HINT_SX, ...nodeSx]}>\n {'★★★★★ Customer reviews appear here once shoppers review your products'}\n </Box>\n )\n }\n // Loading, failed, or none approved: nothing at all — not the node's\n // padding, not a heading over an empty band.\n if (!payload || payload.reviews.length === 0) {\n return <Box ref={ref} data-store-reviews-empty=\"\" sx={{ display: 'none' }} />\n }\n // A select may store the level as a string; read it as a number.\n const asked = Number(headingLevel)\n const level = asked === 3 || asked === 4 ? asked : 2\n const { aggregate } = payload\n return (\n <Box\n ref={ref}\n {...rest}\n sx={[{ display: 'flex', flexDirection: 'column', gap: 3 }, ...nodeSx]}\n >\n <Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 1, textAlign: 'center' }}>\n <Typography variant={level === 2 ? 'h4' : level === 3 ? 'h5' : 'h6'} component={`h${level}`}>\n {heading || 'What customers say'}\n </Typography>\n {aggregate.count > 0 ? (\n <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>\n <Rating value={aggregate.average} precision={0.1} readOnly size=\"small\" />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`${aggregate.average} out of 5 · ${aggregate.count} ${aggregate.count === 1 ? 'review' : 'reviews'}`}\n </Typography>\n </Box>\n ) : null}\n </Box>\n <Box\n sx={{\n display: 'grid',\n gap: 2,\n gridTemplateColumns: { xs: '1fr', sm: 'repeat(2, 1fr)', md: 'repeat(3, 1fr)' },\n }}\n >\n {payload.reviews.map((review) => (\n <Box\n key={review.id}\n component=\"figure\"\n sx={{\n m: 0,\n p: 2.5,\n display: 'flex',\n flexDirection: 'column',\n gap: 1,\n border: 1,\n borderColor: 'divider',\n borderRadius: 2,\n bgcolor: 'background.paper',\n }}\n >\n <Rating value={review.rating} readOnly size=\"small\" />\n <Typography component=\"blockquote\" variant=\"body1\" sx={{ m: 0, flex: 1 }}>\n {review.body}\n </Typography>\n <Box component=\"figcaption\" sx={{ display: 'flex', flexWrap: 'wrap', gap: 1, alignItems: 'center' }}>\n <Typography variant=\"body2\" sx={{ fontWeight: 600 }}>\n {review.authorName}\n </Typography>\n {review.verified ? (\n <Chip label=\"Verified buyer\" size=\"small\" variant=\"outlined\" color=\"success\" />\n ) : null}\n </Box>\n {review.productName || review.soldInStore ? (\n <Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1, alignItems: 'center' }}>\n {review.productName ? (\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n component={review.productSlug && !review.soldInStore ? 'a' : 'span'}\n {...(review.productSlug && !review.soldInStore ? { href: `/products/${review.productSlug}` } : {})}\n sx={{ textDecoration: 'none' }}\n >\n {review.productName}\n </Typography>\n ) : null}\n {review.soldInStore ? <Chip label=\"Sold in store\" size=\"small\" variant=\"outlined\" /> : null}\n </Box>\n ) : null}\n </Box>\n ))}\n </Box>\n </Box>\n )\n },\n)\nStoreReviews.displayName = 'AglynStoreReviews'\n\nconst SingleProductReviews = forwardRef<HTMLDivElement, ProductReviewsProps>(\n (props, ref) => {\n const {\n productId: productIdProp,\n heading,\n scope: _scope,\n maxItems: _maxItems,\n headingLevel: _headingLevel,\n ...rest\n } = props\n // Node styles ride the renderer-merged sx; recompose (stack.ts pattern).\n const nodeSx = Array.isArray(props['sx']) ? props['sx'] : [props['sx']]\n const { hostId } = Aglyn.useSite()\n const siteFetch = Aglyn.useSiteFetch()\n const [productId, setProductId] = useState(productIdProp ?? '')\n const [reviews, setReviews] = useState<ReviewView[] | null>(null)\n const [aggregate, setAggregate] = useState({ count: 0, average: 0 })\n const [form, setForm] = useState({\n rating: 5,\n body: '',\n authorName: '',\n authorEmail: '',\n })\n const [state, setState] = useState<'idle' | 'busy' | 'done' | 'error'>(\n 'idle',\n )\n const [eligibility, setEligibility] = useState<ReviewFormEligibility | null>(null)\n const [reviewToken, setReviewToken] = useState('')\n const [submitted, setSubmitted] = useState<{ pending: boolean } | null>(null)\n const [errorText, setErrorText] = useState('')\n\n useEffect(() => {\n if (!hostId) return\n let active = true\n void (async () => {\n const resolved =\n productIdProp || (await resolveProductIdFromSlug(hostId))\n if (!active || !resolved) return\n setProductId(resolved)\n const response = await fetch(\n `/api/commerce/reviews?hostId=${encodeURIComponent(hostId)}` +\n `&productId=${encodeURIComponent(resolved)}`,\n ).catch(() => null)\n if (!active || !response?.ok) return\n const payload = await response.json().catch(() => ({}))\n setReviews(payload?.reviews ?? [])\n setAggregate(payload?.aggregate ?? { count: 0, average: 0 })\n // May this visitor review, and would it be verified? (AGL-3736)\n const token = reviewTokenFromLocation(window.location.search)\n setReviewToken(token)\n const answer = await fetch(\n `/api/commerce/reviews?hostId=${encodeURIComponent(hostId)}` +\n `&productId=${encodeURIComponent(resolved)}&mode=eligibility` +\n (token ? `&review=${encodeURIComponent(token)}` : ''),\n { credentials: 'same-origin' },\n ).catch(() => null)\n if (!active || !answer?.ok) return\n setEligibility(await answer.json().catch(() => null))\n })()\n return () => {\n active = false\n }\n }, [hostId, productIdProp])\n\n const handleSubmit = async () => {\n if (!hostId || !productId || state === 'busy') return\n setState('busy')\n try {\n const response = await siteFetch('/api/commerce/reviews', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n hostId,\n productId,\n rating: form.rating,\n body: form.body,\n authorName: form.authorName,\n ...(eligibility?.needsEmail ? { authorEmail: form.authorEmail } : {}),\n ...(reviewToken ? { reviewToken } : {}),\n }),\n })\n const payload = await response.json().catch(() => ({}))\n if (response.ok) {\n setSubmitted({ pending: Boolean(payload?.pending) })\n setState('done')\n } else {\n setErrorText(String(payload?.error ?? ''))\n setState('error')\n }\n } catch {\n setState('error')\n }\n }\n\n if (!hostId) {\n return (\n <Box ref={ref} {...rest} sx={[EDITOR_HINT_SX, ...nodeSx]}>\n {'★★★★★ Product reviews render here'}\n </Box>\n )\n }\n\n return (\n <Box ref={ref} {...rest} sx={[{ display: 'flex', flexDirection: 'column', gap: 1.5 }, ...nodeSx]}>\n {/* No JSON-LD here any more (AGL-686). This block used to emit a\n free-standing `AggregateRating` node, which schema.org ignores —\n a rating has to be a PROPERTY of the Product it rates. The PDP's\n server-rendered Product node now carries `aggregateRating`,\n built from the same `readProductReviews` reader this block's API\n uses, so the structured data and the visible stars cannot\n disagree. It also removes the one call site that was primed to\n become stored XSS the moment a reviewer name went into it. */}\n <Typography variant=\"h6\">\n {heading || 'Reviews'}\n {aggregate.count > 0 ? ` (${aggregate.average} ★ · ${aggregate.count})` : ''}\n </Typography>\n {(reviews ?? []).map((review) => (\n <Box key={review.id}>\n <Box sx={{ display: 'flex', gap: 1, alignItems: 'center' }}>\n <Rating value={review.rating} size=\"small\" readOnly />\n <Typography variant=\"body2\" sx={{ fontWeight: 600 }}>\n {review.authorName}\n </Typography>\n {review.verified ? (\n <Chip label=\"Verified buyer\" size=\"small\" variant=\"outlined\" color=\"success\" />\n ) : null}\n </Box>\n <Typography variant=\"body2\" sx={{ mt: 0.5 }}>\n {review.body}\n </Typography>\n {review.reply ? (\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ display: 'block', pl: 2, mt: 0.5, borderLeft: 2, borderColor: 'divider' }}\n >\n {`Seller: ${review.reply}`}\n </Typography>\n ) : null}\n <Divider sx={{ mt: 1.5 }} />\n </Box>\n ))}\n {reviews && reviews.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No reviews yet — be the first.'}\n </Typography>\n ) : null}\n {eligibility?.tokenProblem && TOKEN_PROBLEM_TEXT[eligibility.tokenProblem] ? (\n <Alert severity=\"info\">{TOKEN_PROBLEM_TEXT[eligibility.tokenProblem]}</Alert>\n ) : null}\n {state === 'done' ? (\n <Alert severity=\"success\">\n {submitted?.pending\n ? 'Thanks — your review is awaiting moderation.'\n : 'Thanks — your review is posted.'}\n </Alert>\n ) : reviewFormMode(eligibility) === 'sign-in' ? (\n <Typography variant=\"body2\" color=\"text.secondary\" data-review-sign-in=\"\">\n {'Only customers who bought this can review. '}\n <Box component=\"a\" href={REVIEW_SIGN_IN_HREF} sx={{ color: 'primary.main' }}>\n {'Sign in'}\n </Box>\n </Typography>\n ) : reviewFormMode(eligibility) === 'none' ? null : (\n <Box sx={{ display: 'flex', flexDirection: 'column', gap: 1, maxWidth: 480 }}>\n <Typography variant=\"subtitle2\">{'Write a review'}</Typography>\n {eligibility?.verifiedVia ? (\n <Chip\n label=\"Your review will show as Verified buyer\"\n size=\"small\"\n variant=\"outlined\"\n color=\"success\"\n sx={{ alignSelf: 'flex-start' }}\n />\n ) : null}\n <Rating\n value={form.rating}\n onChange={(_event, value) =>\n setForm((prev) => ({ ...prev, rating: value ?? 5 }))\n }\n />\n <TextField\n placeholder=\"What did you think?\"\n value={form.body}\n onChange={(event) =>\n setForm((prev) => ({ ...prev, body: event.target.value }))\n }\n size=\"small\"\n multiline\n minRows={2}\n />\n <Box sx={{ display: 'flex', gap: 1 }}>\n <TextField\n placeholder=\"Name\"\n value={form.authorName}\n onChange={(event) =>\n setForm((prev) => ({ ...prev, authorName: event.target.value }))\n }\n size=\"small\"\n sx={{ flex: 1 }}\n />\n {eligibility?.needsEmail ? (\n <TextField\n placeholder=\"Email (never shown)\"\n type=\"email\"\n value={form.authorEmail}\n onChange={(event) =>\n setForm((prev) => ({ ...prev, authorEmail: event.target.value }))\n }\n size=\"small\"\n sx={{ flex: 1.4 }}\n />\n ) : null}\n </Box>\n {state === 'error' ? (\n <Alert severity=\"error\">\n {errorText || 'Could not submit — check the fields.'}\n </Alert>\n ) : null}\n <Button\n variant=\"outlined\"\n size=\"small\"\n disabled={\n state === 'busy' ||\n !form.body.trim() ||\n Boolean(eligibility?.needsEmail && !form.authorEmail.trim())\n }\n onClick={handleSubmit}\n sx={{ alignSelf: 'flex-start' }}\n >\n {'Submit review'}\n </Button>\n </Box>\n )}\n </Box>\n )\n },\n)\nSingleProductReviews.displayName = 'AglynSingleProductReviews'\n\nexport const schema: Aglyn.ComponentSchema<ProductReviewsProps> = {\n $id: ID,\n pluginId: BUNDLE_ID,\n displayName: 'Product reviews',\n description:\n 'Approved reviews with verified-buyer badges, and a form for customers ' +\n 'who bought the product to add one.',\n category: Aglyn.ComponentCategory.COMMERCE,\n icon: { path: mdiStarOutline.path, sx: { color: '#2e7d32' } },\n flags: { selfClosing: Aglyn.FEATURE_FLAG.ENABLED },\n attributes: [\n {\n name: 'productId',\n label: 'Product id',\n description:\n 'Whose reviews these are. Blank reads the product from the URL, ' +\n 'which is what you want on a product template; naming one pins the ' +\n 'block to that product wherever it is placed.',\n component: Aglyn.FieldComponentType.PRODUCT_SELECT,\n },\n {\n name: 'heading',\n label: 'Heading',\n description: 'Defaults to \"Reviews\" (\"What customers say\" store-wide).',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n // Default `product`, so a page that already places this block keeps it.\n name: 'scope',\n label: 'Show',\n description:\n 'One product’s reviews with a form, or the store’s latest approved ' +\n 'reviews across every product. Store-wide shows nothing until a ' +\n 'shopper’s review is approved.',\n component: Aglyn.FieldComponentType.SELECT,\n options: [\n { label: 'This product', value: 'product' },\n { label: 'Whole store', value: 'store' },\n ],\n },\n {\n name: 'maxItems',\n label: 'Max reviews',\n description: 'Store-wide: how many to show (default 6, at most 12).',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n type: 'number',\n },\n {\n name: 'headingLevel',\n label: 'Heading level',\n description: 'Store-wide: the heading’s level on the page (default H2).',\n component: Aglyn.FieldComponentType.SELECT,\n options: [\n { label: 'H2', value: '2' },\n { label: 'H3', value: '3' },\n { label: 'H4', value: '4' },\n ],\n },\n ],\n}\n\nexport const presets: Aglyn.PresetSchema[] = [\n {\n $id: generatePresetId(ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Product reviews',\n pluginId: BUNDLE_ID,\n description: 'Verified-buyer reviews with a submit form',\n category: Aglyn.ComponentCategory.COMMERCE,\n icon: { path: mdiStarOutline.path, sx: { color: '#2e7d32' } },\n data: {\n $id: null,\n componentId: ID,\n pluginId: BUNDLE_ID,\n props: {},\n },\n },\n]\n\nexport default ProductReviews\n"],"names":["Aglyn","mdiStarOutline","Alert","Box","Button","Chip","Divider","Rating","TextField","Typography","forwardRef","useEffect","useState","BUNDLE_ID","generatePresetId","ID","EDITOR_HINT_SX","p","border","borderColor","borderRadius","color","fontSize","fontFamily","REVIEW_SIGN_IN_HREF","reviewTokenFromLocation","search","token","URLSearchParams","get","test","reviewFormMode","eligibility","enabled","canReview","TOKEN_PROBLEM_TEXT","review_token_used","review_token_expired","review_token_invalid","resolveProductIdFromSlug","hostId","payload","match","window","location","pathname","response","fetch","encodeURIComponent","decodeURIComponent","catch","ok","json","String","product","id","ProductReviews","props","ref","scope","StoreReviews","SingleProductReviews","displayName","productId","_productId","heading","_scope","maxItems","headingLevel","rest","nodeSx","Array","isArray","useSite","setPayload","limit","Number","Math","min","floor","active","then","body","reviews","aggregate","count","average","undefined","sx","length","data-store-reviews-empty","display","asked","level","flexDirection","gap","alignItems","textAlign","variant","component","value","precision","readOnly","size","gridTemplateColumns","xs","sm","md","map","review","m","bgcolor","rating","flex","flexWrap","fontWeight","authorName","verified","label","productName","soldInStore","productSlug","href","textDecoration","productIdProp","_maxItems","_headingLevel","siteFetch","useSiteFetch","setProductId","setReviews","setAggregate","form","setForm","authorEmail","state","setState","setEligibility","reviewToken","setReviewToken","submitted","setSubmitted","errorText","setErrorText","resolved","answer","credentials","handleSubmit","method","headers","JSON","stringify","needsEmail","pending","Boolean","error","mt","reply","pl","borderLeft","tokenProblem","severity","data-review-sign-in","maxWidth","verifiedVia","alignSelf","onChange","_event","prev","placeholder","event","target","multiline","minRows","type","disabled","trim","onClick","schema","$id","pluginId","description","category","ComponentCategory","COMMERCE","icon","path","flags","selfClosing","FEATURE_FLAG","ENABLED","attributes","name","FieldComponentType","PRODUCT_SELECT","TEXT_FIELD","SELECT","options","presets","NodeType","PRESET","data","componentId"],"mappings":";;;AAAA;;;;;;;;;;;;;;;CAeC,GAED,YAAYA,WAAW,eAAc;AACrC,SAASC,cAAc,QAAQ,yBAAwB;AACvD,OAAOC,WAAW,sBAAqB;AACvC,OAAOC,SAAS,oBAAmB;AACnC,OAAOC,YAAY,uBAAsB;AACzC,OAAOC,UAAU,qBAAoB;AACrC,OAAOC,aAAa,wBAAuB;AAC3C,OAAOC,YAAY,uBAAsB;AACzC,OAAOC,eAAe,0BAAyB;AAC/C,OAAOC,gBAAgB,2BAA0B;AACjD,SAASC,UAAU,EAAEC,SAAS,EAAEC,QAAQ,QAAQ,QAAO;AACvD,SAASC,SAAS,QAAQ,gCAA4B;AACtD,SAASC,gBAAgB,QAAQ,iCAA6B;AAE9D,iEAAiE;AACjE,OAAO,MAAMC,KAAwB,kBAAiB;AAkCtD,MAAMC,iBAAiB;IACrBC,GAAG;IACHC,QAAQ;IACRC,aAAa;IACbC,cAAc;IACdC,OAAO;IACPC,UAAU;IACVC,YAAY;AACd;AAuBA,4DAA4D,GAC5D,OAAO,MAAMC,sBAAsB,WAAU;AAE7C,4EAA4E,GAC5E,OAAO,SAASC,wBAAwBC,MAAc;QACtC;IAAd,MAAMC,SAAQ,OAAA,IAAIC,gBAAgBF,QAAQG,GAAG,CAAC,qBAAhC,OAA6C;IAC3D,OAAO,sBAAsBC,IAAI,CAACH,SAASA,QAAQ;AACrD;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASI,eACdC,WAAyC;IAEzC,IAAI,CAACA,eAAeA,YAAYC,OAAO,KAAK,OAAO,OAAO;IAC1D,IAAID,YAAYE,SAAS,EAAE,OAAO;IAClC,OAAO;AACT;AAEA,MAAMC,qBAA6C;IACjDC,mBAAmB;IACnBC,sBAAsB;IACtBC,sBAAsB;AACxB;AAEA,eAAeC,yBAAyBC,MAAc;;QAStCC;IARd,MAAMC,QAAQC,OAAOC,QAAQ,CAACC,QAAQ,CAACH,KAAK,CAAC;IAC7C,IAAI,CAACA,OAAO,OAAO;IACnB,MAAMI,WAAW,MAAMC,MACrB,CAAC,6BAA6B,EAAEC,mBAAmBR,SAAS,GAC1D,CAAC,MAAM,EAAEQ,mBAAmBC,mBAAmBP,KAAK,CAAC,EAAE,IAAI,EAC7DQ,KAAK,CAAC,IAAM;IACd,IAAI,EAACJ,4BAAAA,SAAUK,EAAE,GAAE,OAAO;IAC1B,MAAMV,UAAU,MAAMK,SAASM,IAAI,GAAGF,KAAK,CAAC,IAAO,CAAA,CAAC,CAAA;IACpD,OAAOG,eAAOZ,4BAAAA,mBAAAA,QAASa,OAAO,qBAAhBb,iBAAkBc,EAAE,mBAAI;AACxC;AAEA;;;;;;;;CAQC,GACD,MAAMC,+BAAiB9C,WACrB,CAAC+C,OAAOC;IACN,IAAID,MAAME,KAAK,KAAK,SAAS,qBAAO,KAACC;QAAaF,KAAKA;OAASD;IAChE,qBAAO,KAACI;QAAqBH,KAAKA;OAASD;AAC7C;AAEFD,eAAeM,WAAW,GAAG;AAE7B;;;;;;;;;CASC,GACD,MAAMF,6BAAelD,WACnB,CAAC+C,OAAOC;IACN,MAAM,EACJK,WAAWC,UAAU,EACrBC,OAAO,EACPN,OAAOO,MAAM,EACbC,QAAQ,EACRC,YAAY,EAEb,GAAGX,OADCY,wCACDZ;;;;;;;IACJ,MAAMa,SAASC,MAAMC,OAAO,CAACf,KAAK,CAAC,KAAK,IAAIA,KAAK,CAAC,KAAK,GAAG;QAACA,KAAK,CAAC,KAAK;KAAC;IACvE,MAAM,EAAEjB,MAAM,EAAE,GAAGxC,MAAMyE,OAAO;IAChC,MAAM,CAAChC,SAASiC,WAAW,GAAG9D,SAGpB;IACV,MAAM+D,QAAQR,YAAYS,OAAOT,YAAY,IAAIU,KAAKC,GAAG,CAAC,IAAID,KAAKE,KAAK,CAACH,OAAOT,cAAc;IAE9FxD,UAAU;QACR,IAAI,CAAC6B,QAAQ;QACb,IAAIwC,SAAS;QACb,KAAKjC,MACH,CAAC,6BAA6B,EAAEC,mBAAmBR,SAAS,GAC1D,CAAC,mBAAmB,EAAEmC,OAAO,EAE9BM,IAAI,CAAC,CAACnC,WAAcA,SAASK,EAAE,GAAGL,SAASM,IAAI,KAAK,MACpD6B,IAAI,CAAC,CAACC;gBAIQA;YAHb,IAAI,CAACF,UAAU,CAACE,MAAM;YACtBR,WAAW;gBACTS,SAASZ,MAAMC,OAAO,CAACU,KAAKC,OAAO,IAAID,KAAKC,OAAO,GAAG,EAAE;gBACxDC,SAAS,GAAEF,kBAAAA,KAAKE,SAAS,YAAdF,kBAAkB;oBAAEG,OAAO;oBAAGC,SAAS;gBAAE;YACtD;QACF,GACCpC,KAAK,CAAC,IAAMqC;QACf,OAAO;YACLP,SAAS;QACX;IACF,GAAG;QAACxC;QAAQmC;KAAM;IAElB,IAAI,CAACnC,QAAQ;QACX,qBACE,KAACrC;YAAIuD,KAAKA;WAASW;YAAMmB,IAAI;gBAACxE;mBAAmBsD;aAAO;sBACrD;;IAGP;IACA,qEAAqE;IACrE,6CAA6C;IAC7C,IAAI,CAAC7B,WAAWA,QAAQ0C,OAAO,CAACM,MAAM,KAAK,GAAG;QAC5C,qBAAO,KAACtF;YAAIuD,KAAKA;YAAKgC,4BAAyB;YAAGF,IAAI;gBAAEG,SAAS;YAAO;;IAC1E;IACA,iEAAiE;IACjE,MAAMC,QAAQhB,OAAOR;IACrB,MAAMyB,QAAQD,UAAU,KAAKA,UAAU,IAAIA,QAAQ;IACnD,MAAM,EAAER,SAAS,EAAE,GAAG3C;IACtB,qBACE,MAACtC;QACCuD,KAAKA;OACDW;QACJmB,IAAI;YAAC;gBAAEG,SAAS;gBAAQG,eAAe;gBAAUC,KAAK;YAAE;eAAMzB;SAAO;;0BAErE,MAACnE;gBAAIqF,IAAI;oBAAEG,SAAS;oBAAQG,eAAe;oBAAUE,YAAY;oBAAUD,KAAK;oBAAGE,WAAW;gBAAS;;kCACrG,KAACxF;wBAAWyF,SAASL,UAAU,IAAI,OAAOA,UAAU,IAAI,OAAO;wBAAMM,WAAW,CAAC,CAAC,EAAEN,OAAO;kCACxF5B,WAAW;;oBAEbmB,UAAUC,KAAK,GAAG,kBACjB,MAAClF;wBAAIqF,IAAI;4BAAEG,SAAS;4BAAQK,YAAY;4BAAUD,KAAK;wBAAE;;0CACvD,KAACxF;gCAAO6F,OAAOhB,UAAUE,OAAO;gCAAEe,WAAW;gCAAKC,QAAQ;gCAACC,MAAK;;0CAChE,KAAC9F;gCAAWyF,SAAQ;gCAAQ7E,OAAM;0CAC/B,GAAG+D,UAAUE,OAAO,CAAC,YAAY,EAAEF,UAAUC,KAAK,CAAC,CAAC,EAAED,UAAUC,KAAK,KAAK,IAAI,WAAW,WAAW;;;yBAGvG;;;0BAEN,KAAClF;gBACCqF,IAAI;oBACFG,SAAS;oBACTI,KAAK;oBACLS,qBAAqB;wBAAEC,IAAI;wBAAOC,IAAI;wBAAkBC,IAAI;oBAAiB;gBAC/E;0BAEClE,QAAQ0C,OAAO,CAACyB,GAAG,CAAC,CAACC,uBACpB,MAAC1G;wBAECgG,WAAU;wBACVX,IAAI;4BACFsB,GAAG;4BACH7F,GAAG;4BACH0E,SAAS;4BACTG,eAAe;4BACfC,KAAK;4BACL7E,QAAQ;4BACRC,aAAa;4BACbC,cAAc;4BACd2F,SAAS;wBACX;;0CAEA,KAACxG;gCAAO6F,OAAOS,OAAOG,MAAM;gCAAEV,QAAQ;gCAACC,MAAK;;0CAC5C,KAAC9F;gCAAW0F,WAAU;gCAAaD,SAAQ;gCAAQV,IAAI;oCAAEsB,GAAG;oCAAGG,MAAM;gCAAE;0CACpEJ,OAAO3B,IAAI;;0CAEd,MAAC/E;gCAAIgG,WAAU;gCAAaX,IAAI;oCAAEG,SAAS;oCAAQuB,UAAU;oCAAQnB,KAAK;oCAAGC,YAAY;gCAAS;;kDAChG,KAACvF;wCAAWyF,SAAQ;wCAAQV,IAAI;4CAAE2B,YAAY;wCAAI;kDAC/CN,OAAOO,UAAU;;oCAEnBP,OAAOQ,QAAQ,iBACd,KAAChH;wCAAKiH,OAAM;wCAAiBf,MAAK;wCAAQL,SAAQ;wCAAW7E,OAAM;yCACjE;;;4BAELwF,OAAOU,WAAW,IAAIV,OAAOW,WAAW,iBACvC,MAACrH;gCAAIqF,IAAI;oCAAEG,SAAS;oCAAQuB,UAAU;oCAAQnB,KAAK;oCAAGC,YAAY;gCAAS;;oCACxEa,OAAOU,WAAW,iBACjB,KAAC9G;wCACCyF,SAAQ;wCACR7E,OAAM;wCACN8E,WAAWU,OAAOY,WAAW,IAAI,CAACZ,OAAOW,WAAW,GAAG,MAAM;uCACxDX,OAAOY,WAAW,IAAI,CAACZ,OAAOW,WAAW,GAAG;wCAAEE,MAAM,CAAC,UAAU,EAAEb,OAAOY,WAAW,EAAE;oCAAC,IAAI,CAAC;wCAChGjC,IAAI;4CAAEmC,gBAAgB;wCAAO;kDAE5Bd,OAAOU,WAAW;0CAEnB;oCACHV,OAAOW,WAAW,iBAAG,KAACnH;wCAAKiH,OAAM;wCAAgBf,MAAK;wCAAQL,SAAQ;yCAAgB;;iCAEvF;;uBAzCCW,OAAOtD,EAAE;;;;AA+C1B;AAEFK,aAAaE,WAAW,GAAG;AAE3B,MAAMD,qCAAuBnD,WAC3B,CAAC+C,OAAOC;IACN,MAAM,EACJK,WAAW6D,aAAa,EACxB3D,OAAO,EACPN,OAAOO,MAAM,EACbC,UAAU0D,SAAS,EACnBzD,cAAc0D,aAAa,EAE5B,GAAGrE,OADCY,wCACDZ;;;;;;;IACJ,yEAAyE;IACzE,MAAMa,SAASC,MAAMC,OAAO,CAACf,KAAK,CAAC,KAAK,IAAIA,KAAK,CAAC,KAAK,GAAG;QAACA,KAAK,CAAC,KAAK;KAAC;IACvE,MAAM,EAAEjB,MAAM,EAAE,GAAGxC,MAAMyE,OAAO;IAChC,MAAMsD,YAAY/H,MAAMgI,YAAY;IACpC,MAAM,CAACjE,WAAWkE,aAAa,GAAGrH,SAASgH,wBAAAA,gBAAiB;IAC5D,MAAM,CAACzC,SAAS+C,WAAW,GAAGtH,SAA8B;IAC5D,MAAM,CAACwE,WAAW+C,aAAa,GAAGvH,SAAS;QAAEyE,OAAO;QAAGC,SAAS;IAAE;IAClE,MAAM,CAAC8C,MAAMC,QAAQ,GAAGzH,SAAS;QAC/BoG,QAAQ;QACR9B,MAAM;QACNkC,YAAY;QACZkB,aAAa;IACf;IACA,MAAM,CAACC,OAAOC,SAAS,GAAG5H,SACxB;IAEF,MAAM,CAACoB,aAAayG,eAAe,GAAG7H,SAAuC;IAC7E,MAAM,CAAC8H,aAAaC,eAAe,GAAG/H,SAAS;IAC/C,MAAM,CAACgI,WAAWC,aAAa,GAAGjI,SAAsC;IACxE,MAAM,CAACkI,WAAWC,aAAa,GAAGnI,SAAS;IAE3CD,UAAU;QACR,IAAI,CAAC6B,QAAQ;QACb,IAAIwC,SAAS;QACb,KAAK,AAAC,CAAA;;YACJ,MAAMgE,WACJpB,iBAAkB,MAAMrF,yBAAyBC;YACnD,IAAI,CAACwC,UAAU,CAACgE,UAAU;YAC1Bf,aAAae;YACb,MAAMlG,WAAW,MAAMC,MACrB,CAAC,6BAA6B,EAAEC,mBAAmBR,SAAS,GAC1D,CAAC,WAAW,EAAEQ,mBAAmBgG,WAAW,EAC9C9F,KAAK,CAAC,IAAM;YACd,IAAI,CAAC8B,UAAU,EAAClC,4BAAAA,SAAUK,EAAE,GAAE;YAC9B,MAAMV,UAAU,MAAMK,SAASM,IAAI,GAAGF,KAAK,CAAC,IAAO,CAAA,CAAC,CAAA;YACpDgF,mBAAWzF,2BAAAA,QAAS0C,OAAO,mBAAI,EAAE;YACjCgD,sBAAa1F,2BAAAA,QAAS2C,SAAS,oBAAI;gBAAEC,OAAO;gBAAGC,SAAS;YAAE;YAC1D,gEAAgE;YAChE,MAAM3D,QAAQF,wBAAwBkB,OAAOC,QAAQ,CAAClB,MAAM;YAC5DiH,eAAehH;YACf,MAAMsH,SAAS,MAAMlG,MACnB,CAAC,6BAA6B,EAAEC,mBAAmBR,SAAS,GAC1D,CAAC,WAAW,EAAEQ,mBAAmBgG,UAAU,iBAAiB,CAAC,GAC5DrH,CAAAA,QAAQ,CAAC,QAAQ,EAAEqB,mBAAmBrB,QAAQ,GAAG,EAAC,GACrD;gBAAEuH,aAAa;YAAc,GAC7BhG,KAAK,CAAC,IAAM;YACd,IAAI,CAAC8B,UAAU,EAACiE,0BAAAA,OAAQ9F,EAAE,GAAE;YAC5BsF,eAAe,MAAMQ,OAAO7F,IAAI,GAAGF,KAAK,CAAC,IAAM;QACjD,CAAA;QACA,OAAO;YACL8B,SAAS;QACX;IACF,GAAG;QAACxC;QAAQoF;KAAc;IAE1B,MAAMuB,eAAe;QACnB,IAAI,CAAC3G,UAAU,CAACuB,aAAawE,UAAU,QAAQ;QAC/CC,SAAS;QACT,IAAI;YACF,MAAM1F,WAAW,MAAMiF,UAAU,yBAAyB;gBACxDqB,QAAQ;gBACRC,SAAS;oBAAE,gBAAgB;gBAAmB;gBAC9CnE,MAAMoE,KAAKC,SAAS,CAAC;oBACnB/G;oBACAuB;oBACAiD,QAAQoB,KAAKpB,MAAM;oBACnB9B,MAAMkD,KAAKlD,IAAI;oBACfkC,YAAYgB,KAAKhB,UAAU;mBACvBpF,CAAAA,+BAAAA,YAAawH,UAAU,IAAG;oBAAElB,aAAaF,KAAKE,WAAW;gBAAC,IAAI,CAAC,GAC/DI,cAAc;oBAAEA;gBAAY,IAAI,CAAC;YAEzC;YACA,MAAMjG,UAAU,MAAMK,SAASM,IAAI,GAAGF,KAAK,CAAC,IAAO,CAAA,CAAC,CAAA;YACpD,IAAIJ,SAASK,EAAE,EAAE;gBACf0F,aAAa;oBAAEY,SAASC,QAAQjH,2BAAAA,QAASgH,OAAO;gBAAE;gBAClDjB,SAAS;YACX,OAAO;;gBACLO,aAAa1F,eAAOZ,2BAAAA,QAASkH,KAAK,mBAAI;gBACtCnB,SAAS;YACX;QACF,EAAE,eAAM;YACNA,SAAS;QACX;IACF;IAEA,IAAI,CAAChG,QAAQ;QACX,qBACE,KAACrC;YAAIuD,KAAKA;WAASW;YAAMmB,IAAI;gBAACxE;mBAAmBsD;aAAO;sBACrD;;IAGP;IAEA,qBACE,MAACnE;QAAIuD,KAAKA;OAASW;QAAMmB,IAAI;YAAC;gBAAEG,SAAS;gBAAQG,eAAe;gBAAUC,KAAK;YAAI;eAAMzB;SAAO;;0BAS9F,MAAC7D;gBAAWyF,SAAQ;;oBACjBjC,WAAW;oBACXmB,UAAUC,KAAK,GAAG,IAAI,CAAC,EAAE,EAAED,UAAUE,OAAO,CAAC,KAAK,EAAEF,UAAUC,KAAK,CAAC,CAAC,CAAC,GAAG;;;YAE3E,CAACF,kBAAAA,UAAW,EAAE,EAAEyB,GAAG,CAAC,CAACC,uBACpB,MAAC1G;;sCACC,MAACA;4BAAIqF,IAAI;gCAAEG,SAAS;gCAAQI,KAAK;gCAAGC,YAAY;4BAAS;;8CACvD,KAACzF;oCAAO6F,OAAOS,OAAOG,MAAM;oCAAET,MAAK;oCAAQD,QAAQ;;8CACnD,KAAC7F;oCAAWyF,SAAQ;oCAAQV,IAAI;wCAAE2B,YAAY;oCAAI;8CAC/CN,OAAOO,UAAU;;gCAEnBP,OAAOQ,QAAQ,iBACd,KAAChH;oCAAKiH,OAAM;oCAAiBf,MAAK;oCAAQL,SAAQ;oCAAW7E,OAAM;qCACjE;;;sCAEN,KAACZ;4BAAWyF,SAAQ;4BAAQV,IAAI;gCAAEoE,IAAI;4BAAI;sCACvC/C,OAAO3B,IAAI;;wBAEb2B,OAAOgD,KAAK,iBACX,KAACpJ;4BACCyF,SAAQ;4BACR7E,OAAM;4BACNmE,IAAI;gCAAEG,SAAS;gCAASmE,IAAI;gCAAGF,IAAI;gCAAKG,YAAY;gCAAG5I,aAAa;4BAAU;sCAE7E,CAAC,QAAQ,EAAE0F,OAAOgD,KAAK,EAAE;6BAE1B;sCACJ,KAACvJ;4BAAQkF,IAAI;gCAAEoE,IAAI;4BAAI;;;mBAtBf/C,OAAOtD,EAAE;YAyBpB4B,WAAWA,QAAQM,MAAM,KAAK,kBAC7B,KAAChF;gBAAWyF,SAAQ;gBAAQ7E,OAAM;0BAC/B;iBAED;YACHW,CAAAA,+BAAAA,YAAagI,YAAY,KAAI7H,kBAAkB,CAACH,YAAYgI,YAAY,CAAC,iBACxE,KAAC9J;gBAAM+J,UAAS;0BAAQ9H,kBAAkB,CAACH,YAAYgI,YAAY,CAAC;iBAClE;YACHzB,UAAU,uBACT,KAACrI;gBAAM+J,UAAS;0BACbrB,CAAAA,6BAAAA,UAAWa,OAAO,IACf,iDACA;iBAEJ1H,eAAeC,iBAAiB,0BAClC,MAACvB;gBAAWyF,SAAQ;gBAAQ7E,OAAM;gBAAiB6I,uBAAoB;;oBACpE;kCACD,KAAC/J;wBAAIgG,WAAU;wBAAIuB,MAAMlG;wBAAqBgE,IAAI;4BAAEnE,OAAO;wBAAe;kCACvE;;;iBAGHU,eAAeC,iBAAiB,SAAS,qBAC3C,MAAC7B;gBAAIqF,IAAI;oBAAEG,SAAS;oBAAQG,eAAe;oBAAUC,KAAK;oBAAGoE,UAAU;gBAAI;;kCACzE,KAAC1J;wBAAWyF,SAAQ;kCAAa;;oBAChClE,CAAAA,+BAAAA,YAAaoI,WAAW,kBACvB,KAAC/J;wBACCiH,OAAM;wBACNf,MAAK;wBACLL,SAAQ;wBACR7E,OAAM;wBACNmE,IAAI;4BAAE6E,WAAW;wBAAa;yBAE9B;kCACJ,KAAC9J;wBACC6F,OAAOgC,KAAKpB,MAAM;wBAClBsD,UAAU,CAACC,QAAQnE,QACjBiC,QAAQ,CAACmC,OAAU,aAAKA;oCAAMxD,MAAM,EAAEZ,gBAAAA,QAAS;;;kCAGnD,KAAC5F;wBACCiK,aAAY;wBACZrE,OAAOgC,KAAKlD,IAAI;wBAChBoF,UAAU,CAACI,QACTrC,QAAQ,CAACmC,OAAU,aAAKA;oCAAMtF,MAAMwF,MAAMC,MAAM,CAACvE,KAAK;;wBAExDG,MAAK;wBACLqE,SAAS;wBACTC,SAAS;;kCAEX,MAAC1K;wBAAIqF,IAAI;4BAAEG,SAAS;4BAAQI,KAAK;wBAAE;;0CACjC,KAACvF;gCACCiK,aAAY;gCACZrE,OAAOgC,KAAKhB,UAAU;gCACtBkD,UAAU,CAACI,QACTrC,QAAQ,CAACmC,OAAU,aAAKA;4CAAMpD,YAAYsD,MAAMC,MAAM,CAACvE,KAAK;;gCAE9DG,MAAK;gCACLf,IAAI;oCAAEyB,MAAM;gCAAE;;4BAEfjF,CAAAA,+BAAAA,YAAawH,UAAU,kBACtB,KAAChJ;gCACCiK,aAAY;gCACZK,MAAK;gCACL1E,OAAOgC,KAAKE,WAAW;gCACvBgC,UAAU,CAACI,QACTrC,QAAQ,CAACmC,OAAU,aAAKA;4CAAMlC,aAAaoC,MAAMC,MAAM,CAACvE,KAAK;;gCAE/DG,MAAK;gCACLf,IAAI;oCAAEyB,MAAM;gCAAI;iCAEhB;;;oBAELsB,UAAU,wBACT,KAACrI;wBAAM+J,UAAS;kCACbnB,aAAa;yBAEd;kCACJ,KAAC1I;wBACC8F,SAAQ;wBACRK,MAAK;wBACLwE,UACExC,UAAU,UACV,CAACH,KAAKlD,IAAI,CAAC8F,IAAI,MACftB,QAAQ1H,CAAAA,+BAAAA,YAAawH,UAAU,KAAI,CAACpB,KAAKE,WAAW,CAAC0C,IAAI;wBAE3DC,SAAS9B;wBACT3D,IAAI;4BAAE6E,WAAW;wBAAa;kCAE7B;;;;;;AAMb;AAEFxG,qBAAqBC,WAAW,GAAG;AAEnC,OAAO,MAAMoH,SAAqD;IAChEC,KAAKpK;IACLqK,UAAUvK;IACViD,aAAa;IACbuH,aACE,2EACA;IACFC,UAAUtL,MAAMuL,iBAAiB,CAACC,QAAQ;IAC1CC,MAAM;QAAEC,MAAMzL,eAAeyL,IAAI;QAAElG,IAAI;YAAEnE,OAAO;QAAU;IAAE;IAC5DsK,OAAO;QAAEC,aAAa5L,MAAM6L,YAAY,CAACC,OAAO;IAAC;IACjDC,YAAY;QACV;YACEC,MAAM;YACN1E,OAAO;YACP+D,aACE,oEACA,uEACA;YACFlF,WAAWnG,MAAMiM,kBAAkB,CAACC,cAAc;QACpD;QACA;YACEF,MAAM;YACN1E,OAAO;YACP+D,aAAa;YACblF,WAAWnG,MAAMiM,kBAAkB,CAACE,UAAU;QAChD;QACA;YACE,wEAAwE;YACxEH,MAAM;YACN1E,OAAO;YACP+D,aACE,uEACA,oEACA;YACFlF,WAAWnG,MAAMiM,kBAAkB,CAACG,MAAM;YAC1CC,SAAS;gBACP;oBAAE/E,OAAO;oBAAgBlB,OAAO;gBAAU;gBAC1C;oBAAEkB,OAAO;oBAAelB,OAAO;gBAAQ;aACxC;QACH;QACA;YACE4F,MAAM;YACN1E,OAAO;YACP+D,aAAa;YACblF,WAAWnG,MAAMiM,kBAAkB,CAACE,UAAU;YAC9CrB,MAAM;QACR;QACA;YACEkB,MAAM;YACN1E,OAAO;YACP+D,aAAa;YACblF,WAAWnG,MAAMiM,kBAAkB,CAACG,MAAM;YAC1CC,SAAS;gBACP;oBAAE/E,OAAO;oBAAMlB,OAAO;gBAAI;gBAC1B;oBAAEkB,OAAO;oBAAMlB,OAAO;gBAAI;gBAC1B;oBAAEkB,OAAO;oBAAMlB,OAAO;gBAAI;aAC3B;QACH;KACD;AACH,EAAC;AAED,OAAO,MAAMkG,UAAgC;IAC3C;QACEnB,KAAKrK,iBAAiBC;QACtB+J,MAAM9K,MAAMuM,QAAQ,CAACC,MAAM;QAC3B1I,aAAa;QACbsH,UAAUvK;QACVwK,aAAa;QACbC,UAAUtL,MAAMuL,iBAAiB,CAACC,QAAQ;QAC1CC,MAAM;YAAEC,MAAMzL,eAAeyL,IAAI;YAAElG,IAAI;gBAAEnE,OAAO;YAAU;QAAE;QAC5DoL,MAAM;YACJtB,KAAK;YACLuB,aAAa3L;YACbqK,UAAUvK;YACV4C,OAAO,CAAC;QACV;IACF;CACD,CAAA;AAED,eAAeD,eAAc"}
|
|
@@ -54,6 +54,7 @@ const usd = (cents)=>`$${(cents / 100).toFixed(2)}`;
|
|
|
54
54
|
const [checkOut, setCheckOut] = useState('');
|
|
55
55
|
const [guestName, setGuestName] = useState('');
|
|
56
56
|
const [guestEmail, setGuestEmail] = useState('');
|
|
57
|
+
/** How many guests (AGL-4043), optional; '' sends none. */ const [guests, setGuests] = useState('');
|
|
57
58
|
const [status, setStatus] = useState('idle');
|
|
58
59
|
const [message, setMessage] = useState('');
|
|
59
60
|
useEffect(()=>{
|
|
@@ -96,7 +97,8 @@ const usd = (cents)=>`$${(cents / 100).toFixed(2)}`;
|
|
|
96
97
|
// `fewLeft`.
|
|
97
98
|
const unitsLeft = useMemo(()=>{
|
|
98
99
|
if (!availability || !checkInDayMs || !checkOutDayMs) return null;
|
|
99
|
-
return CommerceModel.fewestUnitsLeft(availability.fewLeft, checkInDayMs, checkOutDayMs
|
|
100
|
+
return CommerceModel.fewestUnitsLeft(availability.fewLeft, checkInDayMs, checkOutDayMs, // The store's own threshold for this resource; 0 never shows it.
|
|
101
|
+
CommerceModel.resourceFewLeftAt(availability.resource));
|
|
100
102
|
}, [
|
|
101
103
|
availability,
|
|
102
104
|
checkInDayMs,
|
|
@@ -118,8 +120,14 @@ const usd = (cents)=>`$${(cents / 100).toFixed(2)}`;
|
|
|
118
120
|
checkOutDayMs,
|
|
119
121
|
resourceId,
|
|
120
122
|
guestName,
|
|
121
|
-
guestEmail
|
|
123
|
+
guestEmail,
|
|
124
|
+
guests
|
|
122
125
|
]);
|
|
126
|
+
// At most what one unit sleeps; the server asks the same question.
|
|
127
|
+
const guestCeiling = availability ? CommerceModel.resourceGuestCeiling(availability.resource) : CommerceModel.MAX_RESERVATION_GUESTS;
|
|
128
|
+
const guestsProblem = CommerceModel.reservationGuestsProblem(guests.trim() === '' ? undefined : Number(guests), {
|
|
129
|
+
capacity: guestCeiling
|
|
130
|
+
});
|
|
123
131
|
const handleReserve = async ()=>{
|
|
124
132
|
if (!hostId || !resourceId || status === 'sending') return;
|
|
125
133
|
setStatus('sending');
|
|
@@ -137,14 +145,16 @@ const usd = (cents)=>`$${(cents / 100).toFixed(2)}`;
|
|
|
137
145
|
'Content-Type': 'application/json',
|
|
138
146
|
'Idempotency-Key': attemptKey.current
|
|
139
147
|
},
|
|
140
|
-
body: JSON.stringify({
|
|
148
|
+
body: JSON.stringify(_extends({
|
|
141
149
|
hostId,
|
|
142
150
|
resourceId,
|
|
143
151
|
checkInDayMs,
|
|
144
152
|
checkOutDayMs,
|
|
145
153
|
guestName,
|
|
146
154
|
guestEmail
|
|
147
|
-
})
|
|
155
|
+
}, guests.trim() && !guestsProblem ? {
|
|
156
|
+
guests: Number(guests)
|
|
157
|
+
} : {}))
|
|
148
158
|
});
|
|
149
159
|
const payload = await response.json().catch(()=>({}));
|
|
150
160
|
if (response.ok && (payload == null ? void 0 : payload.url)) {
|
|
@@ -362,6 +372,19 @@ const usd = (cents)=>`$${(cents / 100).toFixed(2)}`;
|
|
|
362
372
|
onChange: (event)=>setGuestEmail(event.target.value),
|
|
363
373
|
size: "small"
|
|
364
374
|
}),
|
|
375
|
+
/*#__PURE__*/ _jsx(TextField, {
|
|
376
|
+
label: "Guests (optional)",
|
|
377
|
+
value: guests,
|
|
378
|
+
onChange: (event)=>setGuests(event.target.value.replace(/\D/g, '')),
|
|
379
|
+
error: Boolean(guestsProblem),
|
|
380
|
+
helperText: guestsProblem != null ? guestsProblem : `Up to ${guestCeiling}`,
|
|
381
|
+
size: "small",
|
|
382
|
+
slotProps: {
|
|
383
|
+
htmlInput: {
|
|
384
|
+
inputMode: 'numeric'
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}),
|
|
365
388
|
status === 'error' ? /*#__PURE__*/ _jsx(Alert, {
|
|
366
389
|
severity: "error",
|
|
367
390
|
children: message || 'Reservation is unavailable right now.'
|
|
@@ -369,7 +392,7 @@ const usd = (cents)=>`$${(cents / 100).toFixed(2)}`;
|
|
|
369
392
|
/*#__PURE__*/ _jsx(Button, {
|
|
370
393
|
variant: "contained",
|
|
371
394
|
color: "primary",
|
|
372
|
-
disabled: status === 'sending' || conflict || !quote || Boolean(quote.problem) || !guestEmail.trim(),
|
|
395
|
+
disabled: status === 'sending' || conflict || !quote || Boolean(quote.problem) || Boolean(guestsProblem) || !guestEmail.trim(),
|
|
373
396
|
onClick: handleReserve,
|
|
374
397
|
children: status === 'sending' ? 'Redirecting…' : reserveLabel || 'Reserve now'
|
|
375
398
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/components/reservation-widget.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport * as Aglyn from '@aglyn/aglyn'\nimport {\n buildBeginCheckoutParams,\n trackEventBeforeNavigation,\n} from '@aglyn/aglyn/app-utils/analytics-events'\nimport * as CommerceModel from '../model'\nimport { mdiCalendarCheckOutline } from '@aglyn/shared-data-mdi'\nimport Alert from '@mui/material/Alert'\nimport Box from '@mui/material/Box'\nimport Button from '@mui/material/Button'\nimport Divider from '@mui/material/Divider'\nimport TextField from '@mui/material/TextField'\nimport Typography from '@mui/material/Typography'\nimport {\n forwardRef,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport { generatePresetId } from '../utils/generate-preset-id'\n\n// Component ids are persisted in screen documents; never rename.\nexport const ID: Aglyn.ComponentId = 'reservation-widget'\n\nexport interface ReservationWidgetProps {\n /** Resource id from the console Resources card. */\n resourceId?: string\n reserveLabel?: string\n}\n\ninterface Availability {\n resource: CommerceModel.HostResource\n /**\n * Closed ranges. On a several-unit resource (AGL-4044) only the nights with\n * no unit left, so the one-unit overlap check below is the right one for\n * both: the endpoint sends no `units`, and the resource reads as one.\n */\n unavailable: Array<{ fromDayMs: number; toDayMs: number }>\n /** Nights with only a few units left; several-unit resources only. */\n fewLeft?: Array<{ dayMs: number; unitsLeft: number }>\n}\n\nconst usd = (cents: number) => `$${(cents / 100).toFixed(2)}`\n\n/**\n * Reservation widget (AGL-310): date-range picker with a live quote\n * (nights × seasonal rates + deposit due today) and a reserve flow that\n * charges the deposit through the merchant's account. The server\n * re-quotes and re-checks availability — the widget is display-only.\n */\nconst ReservationWidget = forwardRef<HTMLDivElement, ReservationWidgetProps>(\n (props, ref) => {\n const { resourceId, reserveLabel, ...rest } = props\n // Node styles ride the renderer-merged sx; recompose (stack.ts pattern).\n const nodeSx = Array.isArray(props['sx']) ? props['sx'] : [props['sx']]\n const { hostId } = Aglyn.useSite()\n const { suppressNavigation } = useContext(Aglyn.ScreenLinkContext)\n const siteFetch = Aglyn.useSiteFetch()\n const [availability, setAvailability] = useState<Availability | null>(null)\n const [checkIn, setCheckIn] = useState('')\n const [checkOut, setCheckOut] = useState('')\n const [guestName, setGuestName] = useState('')\n const [guestEmail, setGuestEmail] = useState('')\n const [status, setStatus] = useState<'idle' | 'sending' | 'error'>('idle')\n const [message, setMessage] = useState('')\n\n useEffect(() => {\n if (!hostId || !resourceId) return\n let active = true\n void fetch(\n `/api/commerce/reservation-availability?hostId=${encodeURIComponent(hostId)}` +\n `&resourceId=${encodeURIComponent(resourceId)}`,\n )\n .then((response) => (response.ok ? response.json() : null))\n .then((payload) => {\n if (active && payload) setAvailability(payload)\n })\n .catch(() => undefined)\n return () => {\n active = false\n }\n }, [hostId, resourceId])\n\n const checkInDayMs = checkIn ? Date.parse(`${checkIn}T00:00:00Z`) : 0\n const checkOutDayMs = checkOut ? Date.parse(`${checkOut}T00:00:00Z`) : 0\n\n const quote = useMemo(() => {\n if (!availability || !checkInDayMs || !checkOutDayMs) return null\n return CommerceModel.computeReservationQuote(\n availability.resource,\n checkInDayMs,\n checkOutDayMs,\n )\n }, [availability, checkInDayMs, checkOutDayMs])\n\n const conflict = useMemo(() => {\n if (!availability || !checkInDayMs || !checkOutDayMs) return false\n return !CommerceModel.isRangeAvailable(\n availability.resource,\n availability.unavailable.map((range) => ({\n checkInDayMs: range.fromDayMs,\n checkOutDayMs: range.toDayMs,\n status: 'confirmed' as const,\n })),\n checkInDayMs,\n checkOutDayMs,\n )\n }, [availability, checkInDayMs, checkOutDayMs])\n\n // \"Only 2 left\" (AGL-4044): the fewest units free on any night of the\n // stay, when that is a few. Never on a one-unit resource, which sends no\n // `fewLeft`.\n const unitsLeft = useMemo(() => {\n if (!availability || !checkInDayMs || !checkOutDayMs) return null\n return CommerceModel.fewestUnitsLeft(\n availability.fewLeft,\n checkInDayMs,\n checkOutDayMs,\n )\n }, [availability, checkInDayMs, checkOutDayMs])\n\n /**\n * Idempotency key for ONE reservation attempt (AGL-1697).\n *\n * Minted lazily on the first Reserve click, retired whenever the stay\n * changes shape — different dates, resource or guest are a different\n * attempt. A retry of THIS attempt presents the same key, so the server\n * replays the original session URL instead of telling the guest their own\n * pending hold \"just sold out\".\n */\n const attemptKey = useRef('')\n useEffect(() => {\n attemptKey.current = ''\n }, [checkInDayMs, checkOutDayMs, resourceId, guestName, guestEmail])\n\n const handleReserve = async () => {\n if (!hostId || !resourceId || status === 'sending') return\n setStatus('sending')\n setMessage('')\n if (!attemptKey.current) {\n attemptKey.current =\n globalThis.crypto?.randomUUID?.() ??\n `${Date.now()}-${Math.random().toString(36).slice(2)}`\n }\n try {\n const response = await siteFetch('/api/commerce/reserve', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Idempotency-Key': attemptKey.current,\n },\n body: JSON.stringify({\n hostId,\n resourceId,\n checkInDayMs,\n checkOutDayMs,\n guestName,\n guestEmail,\n }),\n })\n const payload = await response.json().catch(() => ({}))\n if (response.ok && payload?.url) {\n // The stay's `begin_checkout`. This widget charges a real deposit\n // through the merchant's Stripe account and reported nothing at all,\n // so a lodging site saw traffic and then silence — GA4's ecommerce\n // reports read that as a 100% abandonment rate on every resource\n // they list, which is the same hole `purchase` closed for products.\n //\n // After the server accepted the hold and minted a session, never on\n // the click: the dates can lose a race and answer 409, and counting\n // those would report checkouts that Stripe never saw.\n //\n // `value` is what is being charged TODAY — `reserve.ts` bills\n // `depositCents || totalCents` — not the value of the stay. Reporting\n // the whole stay against a deposit payment would make the merchant's\n // checkout value disagree with the `purchase` that follows it, and\n // the gap would look like abandoned upsell rather than a deposit.\n //\n // Ex-tax, matching `buildStorefrontPurchaseParams`: the lodging tax\n // rides the session as a second line and is money held for a taxing\n // authority, not the merchant's revenue.\n const chargedCents = quote\n ? quote.depositCents || quote.totalCents\n : 0\n if (availability && chargedCents > 0) {\n await trackEventBeforeNavigation(\n 'begin_checkout',\n buildBeginCheckoutParams({\n value: chargedCents / 100,\n items: [\n {\n item_id: resourceId,\n item_name: availability.resource.name,\n price: chargedCents / 100,\n quantity: 1,\n },\n ],\n }),\n )\n }\n window.location.assign(payload.url)\n return\n }\n setMessage(String(payload?.error ?? ''))\n setStatus('error')\n } catch {\n setStatus('error')\n }\n }\n\n if (!hostId) {\n return (\n <Box\n ref={ref}\n {...rest}\n sx={[\n {\n p: 3,\n border: '1px dashed',\n borderColor: 'divider',\n borderRadius: 1,\n color: 'text.secondary',\n fontSize: 13,\n fontFamily: 'system-ui, sans-serif',\n },\n ...nodeSx,\n ]}\n >\n {'Reservation widget — dates, quote, and reserve render here'}\n </Box>\n )\n }\n if (!resourceId) {\n // The instruction is for the author, so only editing surfaces draw it;\n // a published page renders the bare element.\n if (!suppressNavigation) return <Box ref={ref} {...rest} />\n return (\n <Box ref={ref} {...rest} sx={[{ p: 2 }, ...nodeSx]}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Set a resource id on this block (console → Resources).'}\n </Typography>\n </Box>\n )\n }\n\n return (\n <Box\n ref={ref}\n {...rest}\n sx={[\n {\n display: 'flex',\n flexDirection: 'column',\n gap: 1.5,\n maxWidth: 420,\n p: 2,\n border: 1,\n borderColor: 'divider',\n borderRadius: 1,\n },\n ...nodeSx,\n ]}\n >\n {availability ? (\n <>\n <Typography variant=\"h6\">{availability.resource.name}</Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`From $${availability.resource.nightlyRateUsd}/night` +\n (availability.resource.minNights\n ? ` · ${availability.resource.minNights}-night minimum`\n : '')}\n </Typography>\n </>\n ) : null}\n <Box sx={{ display: 'flex', gap: 1 }}>\n <TextField\n label=\"Check-in\"\n type=\"date\"\n value={checkIn}\n onChange={(event) => setCheckIn(event.target.value)}\n size=\"small\"\n fullWidth\n slotProps={{ inputLabel: { shrink: true } }}\n />\n <TextField\n label=\"Check-out\"\n type=\"date\"\n value={checkOut}\n onChange={(event) => setCheckOut(event.target.value)}\n size=\"small\"\n fullWidth\n slotProps={{ inputLabel: { shrink: true } }}\n />\n </Box>\n {conflict ? (\n <Alert severity=\"warning\">{'Those dates are unavailable.'}</Alert>\n ) : quote?.problem ? (\n <Alert severity=\"info\">{quote.problem}</Alert>\n ) : quote ? (\n <>\n <Divider />\n <Box sx={{ display: 'flex', justifyContent: 'space-between' }}>\n <Typography variant=\"body2\">\n {`${quote.nights} night${quote.nights === 1 ? '' : 's'}`}\n </Typography>\n <Typography variant=\"body2\">\n {usd(quote.subtotalCents)}\n </Typography>\n </Box>\n {quote.depositCents < quote.totalCents ? (\n <Box sx={{ display: 'flex', justifyContent: 'space-between' }}>\n <Typography variant=\"body2\" sx={{ fontWeight: 600 }}>\n {'Due today (deposit)'}\n </Typography>\n <Typography variant=\"body2\" sx={{ fontWeight: 600 }}>\n {usd(quote.depositCents)}\n </Typography>\n </Box>\n ) : null}\n {unitsLeft !== null ? (\n <Typography variant=\"body2\" color=\"warning.main\">\n {`Only ${unitsLeft} left`}\n </Typography>\n ) : null}\n </>\n ) : null}\n <TextField\n label=\"Your name\"\n value={guestName}\n onChange={(event) => setGuestName(event.target.value)}\n size=\"small\"\n />\n <TextField\n label=\"Email\"\n type=\"email\"\n value={guestEmail}\n onChange={(event) => setGuestEmail(event.target.value)}\n size=\"small\"\n />\n {status === 'error' ? (\n <Alert severity=\"error\">\n {message || 'Reservation is unavailable right now.'}\n </Alert>\n ) : null}\n <Button\n variant=\"contained\"\n color=\"primary\"\n disabled={\n status === 'sending' ||\n conflict ||\n !quote ||\n Boolean(quote.problem) ||\n !guestEmail.trim()\n }\n onClick={handleReserve}\n >\n {status === 'sending'\n ? 'Redirecting…'\n : reserveLabel || 'Reserve now'}\n </Button>\n </Box>\n )\n },\n)\nReservationWidget.displayName = 'AglynReservationWidget'\n\nexport const schema: Aglyn.ComponentSchema<ReservationWidgetProps> = {\n $id: ID,\n pluginId: BUNDLE_ID,\n displayName: 'Reservation widget',\n description:\n 'A date-range booking with a live quote and a deposit taken up front.',\n category: Aglyn.ComponentCategory.COMMERCE,\n icon: { path: mdiCalendarCheckOutline.path, sx: { color: '#2e7d32' } },\n flags: { selfClosing: Aglyn.FEATURE_FLAG.ENABLED },\n attributes: [\n {\n name: 'resourceId',\n label: 'Resource id',\n description: 'From the Resources card on the Products page.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'reserveLabel',\n label: 'Button label',\n description: 'Defaults to \"Reserve now\".',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n ],\n}\n\nexport const presets: Aglyn.PresetSchema[] = [\n {\n $id: generatePresetId(ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Reservation widget',\n pluginId: BUNDLE_ID,\n description: 'Date-range stay booking with live quote + deposit',\n category: Aglyn.ComponentCategory.COMMERCE,\n icon: { path: mdiCalendarCheckOutline.path, sx: { color: '#2e7d32' } },\n data: {\n $id: null,\n componentId: ID,\n pluginId: BUNDLE_ID,\n props: {},\n },\n },\n]\n\nexport default ReservationWidget\n"],"names":["Aglyn","buildBeginCheckoutParams","trackEventBeforeNavigation","CommerceModel","mdiCalendarCheckOutline","Alert","Box","Button","Divider","TextField","Typography","forwardRef","useContext","useEffect","useMemo","useRef","useState","BUNDLE_ID","generatePresetId","ID","usd","cents","toFixed","ReservationWidget","props","ref","resourceId","reserveLabel","rest","nodeSx","Array","isArray","hostId","useSite","suppressNavigation","ScreenLinkContext","siteFetch","useSiteFetch","availability","setAvailability","checkIn","setCheckIn","checkOut","setCheckOut","guestName","setGuestName","guestEmail","setGuestEmail","status","setStatus","message","setMessage","active","fetch","encodeURIComponent","then","response","ok","json","payload","catch","undefined","checkInDayMs","Date","parse","checkOutDayMs","quote","computeReservationQuote","resource","conflict","isRangeAvailable","unavailable","map","range","fromDayMs","toDayMs","unitsLeft","fewestUnitsLeft","fewLeft","attemptKey","current","handleReserve","globalThis","crypto","randomUUID","now","Math","random","toString","slice","method","headers","body","JSON","stringify","url","chargedCents","depositCents","totalCents","value","items","item_id","item_name","name","price","quantity","window","location","assign","String","error","sx","p","border","borderColor","borderRadius","color","fontSize","fontFamily","variant","display","flexDirection","gap","maxWidth","nightlyRateUsd","minNights","label","type","onChange","event","target","size","fullWidth","slotProps","inputLabel","shrink","severity","problem","justifyContent","nights","subtotalCents","fontWeight","disabled","Boolean","trim","onClick","displayName","schema","$id","pluginId","description","category","ComponentCategory","COMMERCE","icon","path","flags","selfClosing","FEATURE_FLAG","ENABLED","attributes","component","FieldComponentType","TEXT_FIELD","presets","NodeType","PRESET","data","componentId"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/components/reservation-widget.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport * as Aglyn from '@aglyn/aglyn'\nimport {\n buildBeginCheckoutParams,\n trackEventBeforeNavigation,\n} from '@aglyn/aglyn/app-utils/analytics-events'\nimport * as CommerceModel from '../model'\nimport { mdiCalendarCheckOutline } from '@aglyn/shared-data-mdi'\nimport Alert from '@mui/material/Alert'\nimport Box from '@mui/material/Box'\nimport Button from '@mui/material/Button'\nimport Divider from '@mui/material/Divider'\nimport TextField from '@mui/material/TextField'\nimport Typography from '@mui/material/Typography'\nimport {\n forwardRef,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport { generatePresetId } from '../utils/generate-preset-id'\n\n// Component ids are persisted in screen documents; never rename.\nexport const ID: Aglyn.ComponentId = 'reservation-widget'\n\nexport interface ReservationWidgetProps {\n /** Resource id from the console Resources card. */\n resourceId?: string\n reserveLabel?: string\n}\n\ninterface Availability {\n resource: CommerceModel.HostResource\n /**\n * Closed ranges. On a several-unit resource (AGL-4044) only the nights with\n * no unit left, so the one-unit overlap check below is the right one for\n * both: the endpoint sends no `units`, and the resource reads as one.\n */\n unavailable: Array<{ fromDayMs: number; toDayMs: number }>\n /** Nights with only a few units left; several-unit resources only. */\n fewLeft?: Array<{ dayMs: number; unitsLeft: number }>\n}\n\nconst usd = (cents: number) => `$${(cents / 100).toFixed(2)}`\n\n/**\n * Reservation widget (AGL-310): date-range picker with a live quote\n * (nights × seasonal rates + deposit due today) and a reserve flow that\n * charges the deposit through the merchant's account. The server\n * re-quotes and re-checks availability — the widget is display-only.\n */\nconst ReservationWidget = forwardRef<HTMLDivElement, ReservationWidgetProps>(\n (props, ref) => {\n const { resourceId, reserveLabel, ...rest } = props\n // Node styles ride the renderer-merged sx; recompose (stack.ts pattern).\n const nodeSx = Array.isArray(props['sx']) ? props['sx'] : [props['sx']]\n const { hostId } = Aglyn.useSite()\n const { suppressNavigation } = useContext(Aglyn.ScreenLinkContext)\n const siteFetch = Aglyn.useSiteFetch()\n const [availability, setAvailability] = useState<Availability | null>(null)\n const [checkIn, setCheckIn] = useState('')\n const [checkOut, setCheckOut] = useState('')\n const [guestName, setGuestName] = useState('')\n const [guestEmail, setGuestEmail] = useState('')\n /** How many guests (AGL-4043), optional; '' sends none. */\n const [guests, setGuests] = useState('')\n const [status, setStatus] = useState<'idle' | 'sending' | 'error'>('idle')\n const [message, setMessage] = useState('')\n\n useEffect(() => {\n if (!hostId || !resourceId) return\n let active = true\n void fetch(\n `/api/commerce/reservation-availability?hostId=${encodeURIComponent(hostId)}` +\n `&resourceId=${encodeURIComponent(resourceId)}`,\n )\n .then((response) => (response.ok ? response.json() : null))\n .then((payload) => {\n if (active && payload) setAvailability(payload)\n })\n .catch(() => undefined)\n return () => {\n active = false\n }\n }, [hostId, resourceId])\n\n const checkInDayMs = checkIn ? Date.parse(`${checkIn}T00:00:00Z`) : 0\n const checkOutDayMs = checkOut ? Date.parse(`${checkOut}T00:00:00Z`) : 0\n\n const quote = useMemo(() => {\n if (!availability || !checkInDayMs || !checkOutDayMs) return null\n return CommerceModel.computeReservationQuote(\n availability.resource,\n checkInDayMs,\n checkOutDayMs,\n )\n }, [availability, checkInDayMs, checkOutDayMs])\n\n const conflict = useMemo(() => {\n if (!availability || !checkInDayMs || !checkOutDayMs) return false\n return !CommerceModel.isRangeAvailable(\n availability.resource,\n availability.unavailable.map((range) => ({\n checkInDayMs: range.fromDayMs,\n checkOutDayMs: range.toDayMs,\n status: 'confirmed' as const,\n })),\n checkInDayMs,\n checkOutDayMs,\n )\n }, [availability, checkInDayMs, checkOutDayMs])\n\n // \"Only 2 left\" (AGL-4044): the fewest units free on any night of the\n // stay, when that is a few. Never on a one-unit resource, which sends no\n // `fewLeft`.\n const unitsLeft = useMemo(() => {\n if (!availability || !checkInDayMs || !checkOutDayMs) return null\n return CommerceModel.fewestUnitsLeft(\n availability.fewLeft,\n checkInDayMs,\n checkOutDayMs,\n // The store's own threshold for this resource; 0 never shows it.\n CommerceModel.resourceFewLeftAt(availability.resource),\n )\n }, [availability, checkInDayMs, checkOutDayMs])\n\n /**\n * Idempotency key for ONE reservation attempt (AGL-1697).\n *\n * Minted lazily on the first Reserve click, retired whenever the stay\n * changes shape — different dates, resource or guest are a different\n * attempt. A retry of THIS attempt presents the same key, so the server\n * replays the original session URL instead of telling the guest their own\n * pending hold \"just sold out\".\n */\n const attemptKey = useRef('')\n useEffect(() => {\n attemptKey.current = ''\n }, [checkInDayMs, checkOutDayMs, resourceId, guestName, guestEmail, guests])\n\n // At most what one unit sleeps; the server asks the same question.\n const guestCeiling = availability ? CommerceModel.resourceGuestCeiling(availability.resource) : CommerceModel.MAX_RESERVATION_GUESTS\n const guestsProblem = CommerceModel.reservationGuestsProblem(guests.trim() === '' ? undefined : Number(guests), {\n capacity: guestCeiling,\n })\n\n const handleReserve = async () => {\n if (!hostId || !resourceId || status === 'sending') return\n setStatus('sending')\n setMessage('')\n if (!attemptKey.current) {\n attemptKey.current =\n globalThis.crypto?.randomUUID?.() ??\n `${Date.now()}-${Math.random().toString(36).slice(2)}`\n }\n try {\n const response = await siteFetch('/api/commerce/reserve', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Idempotency-Key': attemptKey.current,\n },\n body: JSON.stringify({\n hostId,\n resourceId,\n checkInDayMs,\n checkOutDayMs,\n guestName,\n guestEmail,\n ...(guests.trim() && !guestsProblem ? { guests: Number(guests) } : {}),\n }),\n })\n const payload = await response.json().catch(() => ({}))\n if (response.ok && payload?.url) {\n // The stay's `begin_checkout`. This widget charges a real deposit\n // through the merchant's Stripe account and reported nothing at all,\n // so a lodging site saw traffic and then silence — GA4's ecommerce\n // reports read that as a 100% abandonment rate on every resource\n // they list, which is the same hole `purchase` closed for products.\n //\n // After the server accepted the hold and minted a session, never on\n // the click: the dates can lose a race and answer 409, and counting\n // those would report checkouts that Stripe never saw.\n //\n // `value` is what is being charged TODAY — `reserve.ts` bills\n // `depositCents || totalCents` — not the value of the stay. Reporting\n // the whole stay against a deposit payment would make the merchant's\n // checkout value disagree with the `purchase` that follows it, and\n // the gap would look like abandoned upsell rather than a deposit.\n //\n // Ex-tax, matching `buildStorefrontPurchaseParams`: the lodging tax\n // rides the session as a second line and is money held for a taxing\n // authority, not the merchant's revenue.\n const chargedCents = quote\n ? quote.depositCents || quote.totalCents\n : 0\n if (availability && chargedCents > 0) {\n await trackEventBeforeNavigation(\n 'begin_checkout',\n buildBeginCheckoutParams({\n value: chargedCents / 100,\n items: [\n {\n item_id: resourceId,\n item_name: availability.resource.name,\n price: chargedCents / 100,\n quantity: 1,\n },\n ],\n }),\n )\n }\n window.location.assign(payload.url)\n return\n }\n setMessage(String(payload?.error ?? ''))\n setStatus('error')\n } catch {\n setStatus('error')\n }\n }\n\n if (!hostId) {\n return (\n <Box\n ref={ref}\n {...rest}\n sx={[\n {\n p: 3,\n border: '1px dashed',\n borderColor: 'divider',\n borderRadius: 1,\n color: 'text.secondary',\n fontSize: 13,\n fontFamily: 'system-ui, sans-serif',\n },\n ...nodeSx,\n ]}\n >\n {'Reservation widget — dates, quote, and reserve render here'}\n </Box>\n )\n }\n if (!resourceId) {\n // The instruction is for the author, so only editing surfaces draw it;\n // a published page renders the bare element.\n if (!suppressNavigation) return <Box ref={ref} {...rest} />\n return (\n <Box ref={ref} {...rest} sx={[{ p: 2 }, ...nodeSx]}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Set a resource id on this block (console → Resources).'}\n </Typography>\n </Box>\n )\n }\n\n return (\n <Box\n ref={ref}\n {...rest}\n sx={[\n {\n display: 'flex',\n flexDirection: 'column',\n gap: 1.5,\n maxWidth: 420,\n p: 2,\n border: 1,\n borderColor: 'divider',\n borderRadius: 1,\n },\n ...nodeSx,\n ]}\n >\n {availability ? (\n <>\n <Typography variant=\"h6\">{availability.resource.name}</Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`From $${availability.resource.nightlyRateUsd}/night` +\n (availability.resource.minNights\n ? ` · ${availability.resource.minNights}-night minimum`\n : '')}\n </Typography>\n </>\n ) : null}\n <Box sx={{ display: 'flex', gap: 1 }}>\n <TextField\n label=\"Check-in\"\n type=\"date\"\n value={checkIn}\n onChange={(event) => setCheckIn(event.target.value)}\n size=\"small\"\n fullWidth\n slotProps={{ inputLabel: { shrink: true } }}\n />\n <TextField\n label=\"Check-out\"\n type=\"date\"\n value={checkOut}\n onChange={(event) => setCheckOut(event.target.value)}\n size=\"small\"\n fullWidth\n slotProps={{ inputLabel: { shrink: true } }}\n />\n </Box>\n {conflict ? (\n <Alert severity=\"warning\">{'Those dates are unavailable.'}</Alert>\n ) : quote?.problem ? (\n <Alert severity=\"info\">{quote.problem}</Alert>\n ) : quote ? (\n <>\n <Divider />\n <Box sx={{ display: 'flex', justifyContent: 'space-between' }}>\n <Typography variant=\"body2\">\n {`${quote.nights} night${quote.nights === 1 ? '' : 's'}`}\n </Typography>\n <Typography variant=\"body2\">\n {usd(quote.subtotalCents)}\n </Typography>\n </Box>\n {quote.depositCents < quote.totalCents ? (\n <Box sx={{ display: 'flex', justifyContent: 'space-between' }}>\n <Typography variant=\"body2\" sx={{ fontWeight: 600 }}>\n {'Due today (deposit)'}\n </Typography>\n <Typography variant=\"body2\" sx={{ fontWeight: 600 }}>\n {usd(quote.depositCents)}\n </Typography>\n </Box>\n ) : null}\n {unitsLeft !== null ? (\n <Typography variant=\"body2\" color=\"warning.main\">\n {`Only ${unitsLeft} left`}\n </Typography>\n ) : null}\n </>\n ) : null}\n <TextField\n label=\"Your name\"\n value={guestName}\n onChange={(event) => setGuestName(event.target.value)}\n size=\"small\"\n />\n <TextField\n label=\"Email\"\n type=\"email\"\n value={guestEmail}\n onChange={(event) => setGuestEmail(event.target.value)}\n size=\"small\"\n />\n <TextField\n label=\"Guests (optional)\"\n value={guests}\n onChange={(event) => setGuests(event.target.value.replace(/\\D/g, ''))}\n error={Boolean(guestsProblem)}\n helperText={guestsProblem ?? `Up to ${guestCeiling}`}\n size=\"small\"\n slotProps={{ htmlInput: { inputMode: 'numeric' } }}\n />\n {status === 'error' ? (\n <Alert severity=\"error\">\n {message || 'Reservation is unavailable right now.'}\n </Alert>\n ) : null}\n <Button\n variant=\"contained\"\n color=\"primary\"\n disabled={\n status === 'sending' ||\n conflict ||\n !quote ||\n Boolean(quote.problem) ||\n Boolean(guestsProblem) ||\n !guestEmail.trim()\n }\n onClick={handleReserve}\n >\n {status === 'sending'\n ? 'Redirecting…'\n : reserveLabel || 'Reserve now'}\n </Button>\n </Box>\n )\n },\n)\nReservationWidget.displayName = 'AglynReservationWidget'\n\nexport const schema: Aglyn.ComponentSchema<ReservationWidgetProps> = {\n $id: ID,\n pluginId: BUNDLE_ID,\n displayName: 'Reservation widget',\n description:\n 'A date-range booking with a live quote and a deposit taken up front.',\n category: Aglyn.ComponentCategory.COMMERCE,\n icon: { path: mdiCalendarCheckOutline.path, sx: { color: '#2e7d32' } },\n flags: { selfClosing: Aglyn.FEATURE_FLAG.ENABLED },\n attributes: [\n {\n name: 'resourceId',\n label: 'Resource id',\n description: 'From the Resources card on the Products page.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'reserveLabel',\n label: 'Button label',\n description: 'Defaults to \"Reserve now\".',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n ],\n}\n\nexport const presets: Aglyn.PresetSchema[] = [\n {\n $id: generatePresetId(ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Reservation widget',\n pluginId: BUNDLE_ID,\n description: 'Date-range stay booking with live quote + deposit',\n category: Aglyn.ComponentCategory.COMMERCE,\n icon: { path: mdiCalendarCheckOutline.path, sx: { color: '#2e7d32' } },\n data: {\n $id: null,\n componentId: ID,\n pluginId: BUNDLE_ID,\n props: {},\n },\n },\n]\n\nexport default ReservationWidget\n"],"names":["Aglyn","buildBeginCheckoutParams","trackEventBeforeNavigation","CommerceModel","mdiCalendarCheckOutline","Alert","Box","Button","Divider","TextField","Typography","forwardRef","useContext","useEffect","useMemo","useRef","useState","BUNDLE_ID","generatePresetId","ID","usd","cents","toFixed","ReservationWidget","props","ref","resourceId","reserveLabel","rest","nodeSx","Array","isArray","hostId","useSite","suppressNavigation","ScreenLinkContext","siteFetch","useSiteFetch","availability","setAvailability","checkIn","setCheckIn","checkOut","setCheckOut","guestName","setGuestName","guestEmail","setGuestEmail","guests","setGuests","status","setStatus","message","setMessage","active","fetch","encodeURIComponent","then","response","ok","json","payload","catch","undefined","checkInDayMs","Date","parse","checkOutDayMs","quote","computeReservationQuote","resource","conflict","isRangeAvailable","unavailable","map","range","fromDayMs","toDayMs","unitsLeft","fewestUnitsLeft","fewLeft","resourceFewLeftAt","attemptKey","current","guestCeiling","resourceGuestCeiling","MAX_RESERVATION_GUESTS","guestsProblem","reservationGuestsProblem","trim","Number","capacity","handleReserve","globalThis","crypto","randomUUID","now","Math","random","toString","slice","method","headers","body","JSON","stringify","url","chargedCents","depositCents","totalCents","value","items","item_id","item_name","name","price","quantity","window","location","assign","String","error","sx","p","border","borderColor","borderRadius","color","fontSize","fontFamily","variant","display","flexDirection","gap","maxWidth","nightlyRateUsd","minNights","label","type","onChange","event","target","size","fullWidth","slotProps","inputLabel","shrink","severity","problem","justifyContent","nights","subtotalCents","fontWeight","replace","Boolean","helperText","htmlInput","inputMode","disabled","onClick","displayName","schema","$id","pluginId","description","category","ComponentCategory","COMMERCE","icon","path","flags","selfClosing","FEATURE_FLAG","ENABLED","attributes","component","FieldComponentType","TEXT_FIELD","presets","NodeType","PRESET","data","componentId"],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface UseDayPickerProps {
|
|
2
|
+
/** The chosen day, `YYYY-MM-DD`, or '' for none. */
|
|
3
|
+
value: string;
|
|
4
|
+
onChange: (dayKey: string) => void;
|
|
5
|
+
/** Today on the store's calendar: the first day it offers. */
|
|
6
|
+
todayKey: string;
|
|
7
|
+
label?: string;
|
|
8
|
+
helperText?: string;
|
|
9
|
+
/** Days after `todayKey` only (a rain check moves a pass to a later day). */
|
|
10
|
+
afterKey?: string;
|
|
11
|
+
size?: 'small' | 'medium';
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The day a pass is for (AGL-4043): the MUI X date picker the form builder's
|
|
16
|
+
* date field uses, offering today on the store's calendar up to two years
|
|
17
|
+
* ahead and never a day that has passed. Its own chunk, loaded only where a
|
|
18
|
+
* product asks for a day, so a product page without one never downloads the
|
|
19
|
+
* picker. Clearing it is "the day you buy".
|
|
20
|
+
*/
|
|
21
|
+
export declare function UseDayPicker(props: UseDayPickerProps): import("react").JSX.Element;
|
|
22
|
+
export declare namespace UseDayPicker {
|
|
23
|
+
var displayName: string;
|
|
24
|
+
}
|
|
25
|
+
export default UseDayPicker;
|
|
@@ -0,0 +1,57 @@
|
|
|
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
|
+
*/ 'use client';
|
|
17
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
18
|
+
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
|
|
19
|
+
import { DatePicker } from "@mui/x-date-pickers/DatePicker";
|
|
20
|
+
import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
|
|
21
|
+
import dayjs from "dayjs";
|
|
22
|
+
import { addCheckInDays, CHECK_IN_USE_DAY_MAX_AHEAD_DAYS, isCheckInDayKey } from "../model/commerce-check-in-product.js";
|
|
23
|
+
const DAY_FORMAT = 'YYYY-MM-DD';
|
|
24
|
+
/**
|
|
25
|
+
* The day a pass is for (AGL-4043): the MUI X date picker the form builder's
|
|
26
|
+
* date field uses, offering today on the store's calendar up to two years
|
|
27
|
+
* ahead and never a day that has passed. Its own chunk, loaded only where a
|
|
28
|
+
* product asks for a day, so a product page without one never downloads the
|
|
29
|
+
* picker. Clearing it is "the day you buy".
|
|
30
|
+
*/ export function UseDayPicker(props) {
|
|
31
|
+
const { value, onChange, todayKey, label, helperText, afterKey, size, disabled } = props;
|
|
32
|
+
const first = afterKey && afterKey >= todayKey ? addCheckInDays(afterKey, 1) : todayKey;
|
|
33
|
+
return /*#__PURE__*/ _jsx(LocalizationProvider, {
|
|
34
|
+
dateAdapter: AdapterDayjs,
|
|
35
|
+
children: /*#__PURE__*/ _jsx(DatePicker, {
|
|
36
|
+
label: label != null ? label : 'Day you will use it',
|
|
37
|
+
value: isCheckInDayKey(value) ? dayjs(value) : null,
|
|
38
|
+
onChange: (picked)=>onChange((picked == null ? void 0 : picked.isValid()) ? picked.format(DAY_FORMAT) : ''),
|
|
39
|
+
minDate: dayjs(first),
|
|
40
|
+
maxDate: dayjs(addCheckInDays(todayKey, CHECK_IN_USE_DAY_MAX_AHEAD_DAYS)),
|
|
41
|
+
disabled: disabled,
|
|
42
|
+
slotProps: {
|
|
43
|
+
textField: {
|
|
44
|
+
size: size != null ? size : 'small',
|
|
45
|
+
helperText
|
|
46
|
+
},
|
|
47
|
+
field: {
|
|
48
|
+
clearable: true
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
UseDayPicker.displayName = 'UseDayPicker';
|
|
55
|
+
export default UseDayPicker;
|
|
56
|
+
|
|
57
|
+
//# sourceMappingURL=use-day-picker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/components/use-day-picker.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'\nimport { DatePicker } from '@mui/x-date-pickers/DatePicker'\nimport { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'\nimport dayjs from 'dayjs'\nimport {\n addCheckInDays,\n CHECK_IN_USE_DAY_MAX_AHEAD_DAYS,\n isCheckInDayKey,\n} from '../model/commerce-check-in-product'\n\nconst DAY_FORMAT = 'YYYY-MM-DD'\n\nexport interface UseDayPickerProps {\n /** The chosen day, `YYYY-MM-DD`, or '' for none. */\n value: string\n onChange: (dayKey: string) => void\n /** Today on the store's calendar: the first day it offers. */\n todayKey: string\n label?: string\n helperText?: string\n /** Days after `todayKey` only (a rain check moves a pass to a later day). */\n afterKey?: string\n size?: 'small' | 'medium'\n disabled?: boolean\n}\n\n/**\n * The day a pass is for (AGL-4043): the MUI X date picker the form builder's\n * date field uses, offering today on the store's calendar up to two years\n * ahead and never a day that has passed. Its own chunk, loaded only where a\n * product asks for a day, so a product page without one never downloads the\n * picker. Clearing it is \"the day you buy\".\n */\nexport function UseDayPicker(props: UseDayPickerProps) {\n const { value, onChange, todayKey, label, helperText, afterKey, size, disabled } = props\n const first = afterKey && afterKey >= todayKey ? addCheckInDays(afterKey, 1) : todayKey\n return (\n <LocalizationProvider dateAdapter={AdapterDayjs}>\n <DatePicker\n label={label ?? 'Day you will use it'}\n value={isCheckInDayKey(value) ? dayjs(value) : null}\n onChange={(picked) => onChange(picked?.isValid() ? picked.format(DAY_FORMAT) : '')}\n minDate={dayjs(first)}\n maxDate={dayjs(addCheckInDays(todayKey, CHECK_IN_USE_DAY_MAX_AHEAD_DAYS))}\n disabled={disabled}\n slotProps={{\n textField: { size: size ?? 'small', helperText },\n field: { clearable: true },\n }}\n />\n </LocalizationProvider>\n )\n}\n\nUseDayPicker.displayName = 'UseDayPicker'\n\nexport default UseDayPicker\n"],"names":["AdapterDayjs","DatePicker","LocalizationProvider","dayjs","addCheckInDays","CHECK_IN_USE_DAY_MAX_AHEAD_DAYS","isCheckInDayKey","DAY_FORMAT","UseDayPicker","props","value","onChange","todayKey","label","helperText","afterKey","size","disabled","first","dateAdapter","picked","isValid","format","minDate","maxDate","slotProps","textField","field","clearable","displayName"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;AAEA,SAASA,YAAY,QAAQ,mCAAkC;AAC/D,SAASC,UAAU,QAAQ,iCAAgC;AAC3D,SAASC,oBAAoB,QAAQ,2CAA0C;AAC/E,OAAOC,WAAW,QAAO;AACzB,SACEC,cAAc,EACdC,+BAA+B,EAC/BC,eAAe,QACV,wCAAoC;AAE3C,MAAMC,aAAa;AAgBnB;;;;;;CAMC,GACD,OAAO,SAASC,aAAaC,KAAwB;IACnD,MAAM,EAAEC,KAAK,EAAEC,QAAQ,EAAEC,QAAQ,EAAEC,KAAK,EAAEC,UAAU,EAAEC,QAAQ,EAAEC,IAAI,EAAEC,QAAQ,EAAE,GAAGR;IACnF,MAAMS,QAAQH,YAAYA,YAAYH,WAAWR,eAAeW,UAAU,KAAKH;IAC/E,qBACE,KAACV;QAAqBiB,aAAanB;kBACjC,cAAA,KAACC;YACCY,KAAK,EAAEA,gBAAAA,QAAS;YAChBH,OAAOJ,gBAAgBI,SAASP,MAAMO,SAAS;YAC/CC,UAAU,CAACS,SAAWT,SAASS,CAAAA,0BAAAA,OAAQC,OAAO,MAAKD,OAAOE,MAAM,CAACf,cAAc;YAC/EgB,SAASpB,MAAMe;YACfM,SAASrB,MAAMC,eAAeQ,UAAUP;YACxCY,UAAUA;YACVQ,WAAW;gBACTC,WAAW;oBAAEV,IAAI,EAAEA,eAAAA,OAAQ;oBAASF;gBAAW;gBAC/Ca,OAAO;oBAAEC,WAAW;gBAAK;YAC3B;;;AAIR;AAEApB,aAAaqB,WAAW,GAAG;AAE3B,eAAerB,aAAY"}
|
|
@@ -25,4 +25,59 @@ export declare const COMMERCE_FEE_REPRICE_REFUSED: OperatorAlertDefinition;
|
|
|
25
25
|
* every paid order is work waiting on staff until it is marked ordered.
|
|
26
26
|
*/
|
|
27
27
|
export declare const COMMERCE_POS_HARDWARE_ORDER: OperatorAlertDefinition;
|
|
28
|
+
/**
|
|
29
|
+
* A card taken offline on the Aglyn POS app's reader reached Stripe, but the
|
|
30
|
+
* sale it belongs to never synced (AGL-4039). Raised by the hourly sweep once
|
|
31
|
+
* the intent has waited past `POS_OFFLINE_CARD_UNSYNCED_ALERT_MS`, while there
|
|
32
|
+
* is still time to sync it before Stripe's authorization lapses.
|
|
33
|
+
*/
|
|
34
|
+
export declare const COMMERCE_POS_OFFLINE_CARD_UNSYNCED: OperatorAlertDefinition;
|
|
35
|
+
/**
|
|
36
|
+
* A card taken offline was made out to a Stripe account that is not the
|
|
37
|
+
* store's (AGL-4039): a stale offline kit, or a register that was tampered
|
|
38
|
+
* with. The sync never captures it and records the order unpaid.
|
|
39
|
+
*/
|
|
40
|
+
export declare const COMMERCE_POS_OFFLINE_CARD_WRONG_ACCOUNT: OperatorAlertDefinition;
|
|
41
|
+
/**
|
|
42
|
+
* A hardware order already placed with Stripe was canceled and refunded
|
|
43
|
+
* because its workspace was deleted or the account that placed it closed
|
|
44
|
+
* (AGL-4038). The merchant has their money back; the platform has paid Stripe
|
|
45
|
+
* for hardware that is still on its way unless someone cancels it there.
|
|
46
|
+
*/
|
|
47
|
+
export declare const COMMERCE_POS_HARDWARE_CANCELED_AFTER_ORDERING: OperatorAlertDefinition;
|
|
48
|
+
/**
|
|
49
|
+
* Stripe refused the refund of an unshipped hardware order while its
|
|
50
|
+
* workspace or its buyer's account was closing (AGL-4038). The closure went
|
|
51
|
+
* ahead, as decided, so the order may be gone from the staff page with the
|
|
52
|
+
* merchant's money still held.
|
|
53
|
+
*/
|
|
54
|
+
export declare const COMMERCE_POS_HARDWARE_REFUND_FAILED: OperatorAlertDefinition;
|
|
55
|
+
/**
|
|
56
|
+
* A buyer asked to return a card reader they bought in person at the
|
|
57
|
+
* platform's own store (AGL-4038), from their own purchase history. Staff
|
|
58
|
+
* refund it from Staff → Hardware orders, or decline it there.
|
|
59
|
+
*/
|
|
60
|
+
export declare const COMMERCE_POS_HARDWARE_RETURN_REQUESTED: OperatorAlertDefinition;
|
|
61
|
+
/**
|
|
62
|
+
* A workspace's monthly 4G charge for its Stripe Reader S710s did not
|
|
63
|
+
* collect (AGL-4038): the card declined or asked for authentication (the
|
|
64
|
+
* invoice is left open on their Billing page, which nothing retries), or
|
|
65
|
+
* the workspace has no Stripe customer to bill. Stripe still bills the
|
|
66
|
+
* platform for those readers' cellular service.
|
|
67
|
+
*/
|
|
68
|
+
export declare const COMMERCE_POS_CELLULAR_CHARGE_FAILED: OperatorAlertDefinition;
|
|
69
|
+
/**
|
|
70
|
+
* 4G could not be turned off at Stripe for a store that is gone (AGL-4038):
|
|
71
|
+
* its workspace was deleted, or the store was, while its S710s had 4G. The
|
|
72
|
+
* readers stay on the platform's Stripe account with cellular on, which
|
|
73
|
+
* Stripe keeps billing the platform for, and nobody is charged for it.
|
|
74
|
+
*/
|
|
75
|
+
export declare const COMMERCE_POS_CELLULAR_TURN_OFF_FAILED: OperatorAlertDefinition;
|
|
76
|
+
/**
|
|
77
|
+
* A workspace's monthly 4G charge stayed unpaid past the grace period
|
|
78
|
+
* (AGL-4038), so 4G was turned off at Stripe for every one of its stores and
|
|
79
|
+
* the members who manage its billing were emailed. Paying does not turn it
|
|
80
|
+
* back on; the merchant does, once it is paid.
|
|
81
|
+
*/
|
|
82
|
+
export declare const COMMERCE_POS_CELLULAR_TURNED_OFF_UNPAID: OperatorAlertDefinition;
|
|
28
83
|
export declare const COMMERCE_OPERATOR_ALERTS: readonly OperatorAlertDefinition[];
|