@aglyn/plugins-commerce 1.0.0-beta.143
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +10 -0
- package/package.json +57 -0
- package/src/index.d.ts +38 -0
- package/src/index.js +38 -0
- package/src/index.js.map +1 -0
- package/src/lib/components/account.d.ts +31 -0
- package/src/lib/components/account.js +645 -0
- package/src/lib/components/account.js.map +1 -0
- package/src/lib/components/cart.d.ts +38 -0
- package/src/lib/components/cart.js +851 -0
- package/src/lib/components/cart.js.map +1 -0
- package/src/lib/components/commerce-console-page.d.ts +32 -0
- package/src/lib/components/commerce-console-page.js +296 -0
- package/src/lib/components/commerce-console-page.js.map +1 -0
- package/src/lib/components/commerce-console-sections.d.ts +34 -0
- package/src/lib/components/commerce-console-sections.js +62 -0
- package/src/lib/components/commerce-console-sections.js.map +1 -0
- package/src/lib/components/console/catalog-organization-card.component.d.ts +14 -0
- package/src/lib/components/console/catalog-organization-card.component.js +1087 -0
- package/src/lib/components/console/catalog-organization-card.component.js.map +1 -0
- package/src/lib/components/console/commerce-analytics-card.component.d.ts +27 -0
- package/src/lib/components/console/commerce-analytics-card.component.js +310 -0
- package/src/lib/components/console/commerce-analytics-card.component.js.map +1 -0
- package/src/lib/components/console/commerce-glance-card.component.d.ts +20 -0
- package/src/lib/components/console/commerce-glance-card.component.js +340 -0
- package/src/lib/components/console/commerce-glance-card.component.js.map +1 -0
- package/src/lib/components/console/commerce-stat-tile.component.d.ts +37 -0
- package/src/lib/components/console/commerce-stat-tile.component.js +72 -0
- package/src/lib/components/console/commerce-stat-tile.component.js.map +1 -0
- package/src/lib/components/console/discounts-card.component.d.ts +14 -0
- package/src/lib/components/console/discounts-card.component.js +443 -0
- package/src/lib/components/console/discounts-card.component.js.map +1 -0
- package/src/lib/components/console/entitlement-gate.component.d.ts +76 -0
- package/src/lib/components/console/entitlement-gate.component.js +115 -0
- package/src/lib/components/console/entitlement-gate.component.js.map +1 -0
- package/src/lib/components/console/gift-cards-card.component.d.ts +29 -0
- package/src/lib/components/console/gift-cards-card.component.js +379 -0
- package/src/lib/components/console/gift-cards-card.component.js.map +1 -0
- package/src/lib/components/console/host-coupons-card.component.d.ts +14 -0
- package/src/lib/components/console/host-coupons-card.component.js +252 -0
- package/src/lib/components/console/host-coupons-card.component.js.map +1 -0
- package/src/lib/components/console/host-orders-card.component.d.ts +13 -0
- package/src/lib/components/console/host-orders-card.component.js +861 -0
- package/src/lib/components/console/host-orders-card.component.js.map +1 -0
- package/src/lib/components/console/locations-card.component.d.ts +14 -0
- package/src/lib/components/console/locations-card.component.js +256 -0
- package/src/lib/components/console/locations-card.component.js.map +1 -0
- package/src/lib/components/console/member-posts-card.component.d.ts +8 -0
- package/src/lib/components/console/member-posts-card.component.js +283 -0
- package/src/lib/components/console/member-posts-card.component.js.map +1 -0
- package/src/lib/components/console/newest-site-users-card.component.d.ts +36 -0
- package/src/lib/components/console/newest-site-users-card.component.js +126 -0
- package/src/lib/components/console/newest-site-users-card.component.js.map +1 -0
- package/src/lib/components/console/order-detail-dialog.component.d.ts +28 -0
- package/src/lib/components/console/order-detail-dialog.component.js +976 -0
- package/src/lib/components/console/order-detail-dialog.component.js.map +1 -0
- package/src/lib/components/console/paid-media.d.ts +127 -0
- package/src/lib/components/console/paid-media.js +397 -0
- package/src/lib/components/console/paid-media.js.map +1 -0
- package/src/lib/components/console/payments-settings-card.component.d.ts +13 -0
- package/src/lib/components/console/payments-settings-card.component.js +295 -0
- package/src/lib/components/console/payments-settings-card.component.js.map +1 -0
- package/src/lib/components/console/pos-page.component.d.ts +13 -0
- package/src/lib/components/console/pos-page.component.js +916 -0
- package/src/lib/components/console/pos-page.component.js.map +1 -0
- package/src/lib/components/console/product-editor-dialog.component.d.ts +32 -0
- package/src/lib/components/console/product-editor-dialog.component.js +1374 -0
- package/src/lib/components/console/product-editor-dialog.component.js.map +1 -0
- package/src/lib/components/console/products-hub-card.component.d.ts +16 -0
- package/src/lib/components/console/products-hub-card.component.js +1280 -0
- package/src/lib/components/console/products-hub-card.component.js.map +1 -0
- package/src/lib/components/console/products-hub-zone.component.d.ts +50 -0
- package/src/lib/components/console/products-hub-zone.component.js +200 -0
- package/src/lib/components/console/products-hub-zone.component.js.map +1 -0
- package/src/lib/components/console/recovery-queue-card.component.d.ts +39 -0
- package/src/lib/components/console/recovery-queue-card.component.js +262 -0
- package/src/lib/components/console/recovery-queue-card.component.js.map +1 -0
- package/src/lib/components/console/registers-card.component.d.ts +21 -0
- package/src/lib/components/console/registers-card.component.js +296 -0
- package/src/lib/components/console/registers-card.component.js.map +1 -0
- package/src/lib/components/console/reservations-card.component.d.ts +14 -0
- package/src/lib/components/console/reservations-card.component.js +650 -0
- package/src/lib/components/console/reservations-card.component.js.map +1 -0
- package/src/lib/components/console/reviews-moderation-card.component.d.ts +8 -0
- package/src/lib/components/console/reviews-moderation-card.component.js +235 -0
- package/src/lib/components/console/reviews-moderation-card.component.js.map +1 -0
- package/src/lib/components/console/shipping-settings-card.component.d.ts +38 -0
- package/src/lib/components/console/shipping-settings-card.component.js +560 -0
- package/src/lib/components/console/shipping-settings-card.component.js.map +1 -0
- package/src/lib/components/console/stock-movements-card.component.d.ts +31 -0
- package/src/lib/components/console/stock-movements-card.component.js +333 -0
- package/src/lib/components/console/stock-movements-card.component.js.map +1 -0
- package/src/lib/components/console/store-settings-card.component.d.ts +14 -0
- package/src/lib/components/console/store-settings-card.component.js +300 -0
- package/src/lib/components/console/store-settings-card.component.js.map +1 -0
- package/src/lib/components/console/storefront-tax-summary-card.component.d.ts +73 -0
- package/src/lib/components/console/storefront-tax-summary-card.component.js +408 -0
- package/src/lib/components/console/storefront-tax-summary-card.component.js.map +1 -0
- package/src/lib/components/console/suppliers-card.component.d.ts +8 -0
- package/src/lib/components/console/suppliers-card.component.js +328 -0
- package/src/lib/components/console/suppliers-card.component.js.map +1 -0
- package/src/lib/components/console/tax-settings-card.component.d.ts +14 -0
- package/src/lib/components/console/tax-settings-card.component.js +473 -0
- package/src/lib/components/console/tax-settings-card.component.js.map +1 -0
- package/src/lib/components/gate.d.ts +39 -0
- package/src/lib/components/gate.js +202 -0
- package/src/lib/components/gate.js.map +1 -0
- package/src/lib/components/gated-video.d.ts +40 -0
- package/src/lib/components/gated-video.js +366 -0
- package/src/lib/components/gated-video.js.map +1 -0
- package/src/lib/components/member-feed.d.ts +32 -0
- package/src/lib/components/member-feed.js +198 -0
- package/src/lib/components/member-feed.js.map +1 -0
- package/src/lib/components/member-recovery.d.ts +35 -0
- package/src/lib/components/member-recovery.js +313 -0
- package/src/lib/components/member-recovery.js.map +1 -0
- package/src/lib/components/member-signin.d.ts +35 -0
- package/src/lib/components/member-signin.js +257 -0
- package/src/lib/components/member-signin.js.map +1 -0
- package/src/lib/components/member-signup.d.ts +34 -0
- package/src/lib/components/member-signup.js +287 -0
- package/src/lib/components/member-signup.js.map +1 -0
- package/src/lib/components/newsletter-signup.d.ts +39 -0
- package/src/lib/components/newsletter-signup.js +229 -0
- package/src/lib/components/newsletter-signup.js.map +1 -0
- package/src/lib/components/product-detail.d.ts +38 -0
- package/src/lib/components/product-detail.js +1022 -0
- package/src/lib/components/product-detail.js.map +1 -0
- package/src/lib/components/product-grid.d.ts +67 -0
- package/src/lib/components/product-grid.js +909 -0
- package/src/lib/components/product-grid.js.map +1 -0
- package/src/lib/components/product-reviews.d.ts +32 -0
- package/src/lib/components/product-reviews.js +349 -0
- package/src/lib/components/product-reviews.js.map +1 -0
- package/src/lib/components/related-products.d.ts +33 -0
- package/src/lib/components/related-products.js +232 -0
- package/src/lib/components/related-products.js.map +1 -0
- package/src/lib/components/reservation-widget.d.ts +33 -0
- package/src/lib/components/reservation-widget.js +418 -0
- package/src/lib/components/reservation-widget.js.map +1 -0
- package/src/lib/components/storefront-payment-element-fallback.d.ts +18 -0
- package/src/lib/components/storefront-payment-element-fallback.js +51 -0
- package/src/lib/components/storefront-payment-element-fallback.js.map +1 -0
- package/src/lib/components/storefront-payment-element.d.ts +78 -0
- package/src/lib/components/storefront-payment-element.js +156 -0
- package/src/lib/components/storefront-payment-element.js.map +1 -0
- package/src/lib/components/wishlist.d.ts +46 -0
- package/src/lib/components/wishlist.js +304 -0
- package/src/lib/components/wishlist.js.map +1 -0
- package/src/lib/constants/bundle-common.d.ts +18 -0
- package/src/lib/constants/bundle-common.js +18 -0
- package/src/lib/constants/bundle-common.js.map +1 -0
- package/src/lib/constants/product-filters.d.ts +35 -0
- package/src/lib/constants/product-filters.js +109 -0
- package/src/lib/constants/product-filters.js.map +1 -0
- package/src/lib/model/commerce-cart.d.ts +52 -0
- package/src/lib/model/commerce-cart.js +102 -0
- package/src/lib/model/commerce-cart.js.map +1 -0
- package/src/lib/model/commerce-connect-transfer.d.ts +243 -0
- package/src/lib/model/commerce-connect-transfer.js +231 -0
- package/src/lib/model/commerce-connect-transfer.js.map +1 -0
- package/src/lib/model/commerce-discounts.d.ts +164 -0
- package/src/lib/model/commerce-discounts.js +231 -0
- package/src/lib/model/commerce-discounts.js.map +1 -0
- package/src/lib/model/commerce-dispute.d.ts +124 -0
- package/src/lib/model/commerce-dispute.js +225 -0
- package/src/lib/model/commerce-dispute.js.map +1 -0
- package/src/lib/model/commerce-gift-cards.d.ts +107 -0
- package/src/lib/model/commerce-gift-cards.js +121 -0
- package/src/lib/model/commerce-gift-cards.js.map +1 -0
- package/src/lib/model/commerce-io.d.ts +36 -0
- package/src/lib/model/commerce-io.js +245 -0
- package/src/lib/model/commerce-io.js.map +1 -0
- package/src/lib/model/commerce-orders.d.ts +1040 -0
- package/src/lib/model/commerce-orders.js +1022 -0
- package/src/lib/model/commerce-orders.js.map +1 -0
- package/src/lib/model/commerce-promotions.d.ts +188 -0
- package/src/lib/model/commerce-promotions.js +199 -0
- package/src/lib/model/commerce-promotions.js.map +1 -0
- package/src/lib/model/commerce-reservations.d.ts +175 -0
- package/src/lib/model/commerce-reservations.js +157 -0
- package/src/lib/model/commerce-reservations.js.map +1 -0
- package/src/lib/model/commerce-shipping.d.ts +316 -0
- package/src/lib/model/commerce-shipping.js +484 -0
- package/src/lib/model/commerce-shipping.js.map +1 -0
- package/src/lib/model/commerce-stock-holds.d.ts +199 -0
- package/src/lib/model/commerce-stock-holds.js +190 -0
- package/src/lib/model/commerce-stock-holds.js.map +1 -0
- package/src/lib/model/commerce-subscription-status.d.ts +67 -0
- package/src/lib/model/commerce-subscription-status.js +76 -0
- package/src/lib/model/commerce-subscription-status.js.map +1 -0
- package/src/lib/model/commerce-tax-decision.d.ts +222 -0
- package/src/lib/model/commerce-tax-decision.js +165 -0
- package/src/lib/model/commerce-tax-decision.js.map +1 -0
- package/src/lib/model/commerce-tax.d.ts +176 -0
- package/src/lib/model/commerce-tax.js +92 -0
- package/src/lib/model/commerce-tax.js.map +1 -0
- package/src/lib/model/commerce.d.ts +551 -0
- package/src/lib/model/commerce.js +739 -0
- package/src/lib/model/commerce.js.map +1 -0
- package/src/lib/model/index.d.ts +40 -0
- package/src/lib/model/index.js +40 -0
- package/src/lib/model/index.js.map +1 -0
- package/src/lib/model/order-figures.d.ts +48 -0
- package/src/lib/model/order-figures.js +74 -0
- package/src/lib/model/order-figures.js.map +1 -0
- package/src/lib/model/plugin-permissions.d.ts +23 -0
- package/src/lib/model/plugin-permissions.js +34 -0
- package/src/lib/model/plugin-permissions.js.map +1 -0
- package/src/lib/model/purchase-analytics.d.ts +186 -0
- package/src/lib/model/purchase-analytics.js +216 -0
- package/src/lib/model/purchase-analytics.js.map +1 -0
- package/src/lib/plugin-config.d.ts +62 -0
- package/src/lib/plugin-config.js +78 -0
- package/src/lib/plugin-config.js.map +1 -0
- package/src/lib/plugin.d.ts +28 -0
- package/src/lib/plugin.js +147 -0
- package/src/lib/plugin.js.map +1 -0
- package/src/lib/server/billing-webhook.d.ts +26 -0
- package/src/lib/server/billing-webhook.js +4011 -0
- package/src/lib/server/billing-webhook.js.map +1 -0
- package/src/lib/server/cancel-order.d.ts +87 -0
- package/src/lib/server/cancel-order.js +368 -0
- package/src/lib/server/cancel-order.js.map +1 -0
- package/src/lib/server/cart-checkout.d.ts +25 -0
- package/src/lib/server/cart-checkout.js +976 -0
- package/src/lib/server/cart-checkout.js.map +1 -0
- package/src/lib/server/cart-cookie.d.ts +32 -0
- package/src/lib/server/cart-cookie.js +77 -0
- package/src/lib/server/cart-cookie.js.map +1 -0
- package/src/lib/server/cart.d.ts +34 -0
- package/src/lib/server/cart.js +181 -0
- package/src/lib/server/cart.js.map +1 -0
- package/src/lib/server/catalog.d.ts +127 -0
- package/src/lib/server/catalog.js +277 -0
- package/src/lib/server/catalog.js.map +1 -0
- package/src/lib/server/checkout.d.ts +36 -0
- package/src/lib/server/checkout.js +915 -0
- package/src/lib/server/checkout.js.map +1 -0
- package/src/lib/server/connect.d.ts +26 -0
- package/src/lib/server/connect.js +238 -0
- package/src/lib/server/connect.js.map +1 -0
- package/src/lib/server/contact-refund.d.ts +117 -0
- package/src/lib/server/contact-refund.js +196 -0
- package/src/lib/server/contact-refund.js.map +1 -0
- package/src/lib/server/download.d.ts +39 -0
- package/src/lib/server/download.js +206 -0
- package/src/lib/server/download.js.map +1 -0
- package/src/lib/server/draft-order.d.ts +26 -0
- package/src/lib/server/draft-order.js +706 -0
- package/src/lib/server/draft-order.js.map +1 -0
- package/src/lib/server/feed.d.ts +24 -0
- package/src/lib/server/feed.js +59 -0
- package/src/lib/server/feed.js.map +1 -0
- package/src/lib/server/fulfill-order.d.ts +97 -0
- package/src/lib/server/fulfill-order.js +235 -0
- package/src/lib/server/fulfill-order.js.map +1 -0
- package/src/lib/server/gate.d.ts +36 -0
- package/src/lib/server/gate.js +112 -0
- package/src/lib/server/gate.js.map +1 -0
- package/src/lib/server/gift-cards.d.ts +42 -0
- package/src/lib/server/gift-cards.js +219 -0
- package/src/lib/server/gift-cards.js.map +1 -0
- package/src/lib/server/low-stock.d.ts +49 -0
- package/src/lib/server/low-stock.js +60 -0
- package/src/lib/server/low-stock.js.map +1 -0
- package/src/lib/server/manual-tax-rate.d.ts +82 -0
- package/src/lib/server/manual-tax-rate.js +113 -0
- package/src/lib/server/manual-tax-rate.js.map +1 -0
- package/src/lib/server/member-feed.d.ts +24 -0
- package/src/lib/server/member-feed.js +72 -0
- package/src/lib/server/member-feed.js.map +1 -0
- package/src/lib/server/member-name-search.d.ts +37 -0
- package/src/lib/server/member-name-search.js +42 -0
- package/src/lib/server/member-name-search.js.map +1 -0
- package/src/lib/server/member-post.d.ts +23 -0
- package/src/lib/server/member-post.js +166 -0
- package/src/lib/server/member-post.js.map +1 -0
- package/src/lib/server/membership-account.d.ts +37 -0
- package/src/lib/server/membership-account.js +148 -0
- package/src/lib/server/membership-account.js.map +1 -0
- package/src/lib/server/membership-admin-password.d.ts +33 -0
- package/src/lib/server/membership-admin-password.js +202 -0
- package/src/lib/server/membership-admin-password.js.map +1 -0
- package/src/lib/server/membership-content.d.ts +23 -0
- package/src/lib/server/membership-content.js +105 -0
- package/src/lib/server/membership-content.js.map +1 -0
- package/src/lib/server/membership-login.d.ts +19 -0
- package/src/lib/server/membership-login.js +137 -0
- package/src/lib/server/membership-login.js.map +1 -0
- package/src/lib/server/membership-logout.d.ts +19 -0
- package/src/lib/server/membership-logout.js +35 -0
- package/src/lib/server/membership-logout.js.map +1 -0
- package/src/lib/server/membership-recover.d.ts +60 -0
- package/src/lib/server/membership-recover.js +244 -0
- package/src/lib/server/membership-recover.js.map +1 -0
- package/src/lib/server/membership-register.d.ts +22 -0
- package/src/lib/server/membership-register.js +241 -0
- package/src/lib/server/membership-register.js.map +1 -0
- package/src/lib/server/membership-reset.d.ts +26 -0
- package/src/lib/server/membership-reset.js +88 -0
- package/src/lib/server/membership-reset.js.map +1 -0
- package/src/lib/server/membership-wishlist.d.ts +23 -0
- package/src/lib/server/membership-wishlist.js +89 -0
- package/src/lib/server/membership-wishlist.js.map +1 -0
- package/src/lib/server/membership.d.ts +96 -0
- package/src/lib/server/membership.js +222 -0
- package/src/lib/server/membership.js.map +1 -0
- package/src/lib/server/native-checkout.d.ts +170 -0
- package/src/lib/server/native-checkout.js +193 -0
- package/src/lib/server/native-checkout.js.map +1 -0
- package/src/lib/server/newsletter.d.ts +23 -0
- package/src/lib/server/newsletter.js +323 -0
- package/src/lib/server/newsletter.js.map +1 -0
- package/src/lib/server/notify-restock.d.ts +22 -0
- package/src/lib/server/notify-restock.js +84 -0
- package/src/lib/server/notify-restock.js.map +1 -0
- package/src/lib/server/order-analytics.d.ts +134 -0
- package/src/lib/server/order-analytics.js +263 -0
- package/src/lib/server/order-analytics.js.map +1 -0
- package/src/lib/server/order-figures.d.ts +33 -0
- package/src/lib/server/order-figures.js +220 -0
- package/src/lib/server/order-figures.js.map +1 -0
- package/src/lib/server/paid-media-delivery.d.ts +173 -0
- package/src/lib/server/paid-media-delivery.js +212 -0
- package/src/lib/server/paid-media-delivery.js.map +1 -0
- package/src/lib/server/pos-order.d.ts +54 -0
- package/src/lib/server/pos-order.js +1137 -0
- package/src/lib/server/pos-order.js.map +1 -0
- package/src/lib/server/process-abandoned.d.ts +54 -0
- package/src/lib/server/process-abandoned.js +222 -0
- package/src/lib/server/process-abandoned.js.map +1 -0
- package/src/lib/server/process-restock.d.ts +42 -0
- package/src/lib/server/process-restock.js +219 -0
- package/src/lib/server/process-restock.js.map +1 -0
- package/src/lib/server/product-card.d.ts +29 -0
- package/src/lib/server/product-card.js +54 -0
- package/src/lib/server/product-card.js.map +1 -0
- package/src/lib/server/product.d.ts +70 -0
- package/src/lib/server/product.js +105 -0
- package/src/lib/server/product.js.map +1 -0
- package/src/lib/server/promotion-hold.d.ts +144 -0
- package/src/lib/server/promotion-hold.js +174 -0
- package/src/lib/server/promotion-hold.js.map +1 -0
- package/src/lib/server/reconcile-stock.d.ts +106 -0
- package/src/lib/server/reconcile-stock.js +349 -0
- package/src/lib/server/reconcile-stock.js.map +1 -0
- package/src/lib/server/refund.d.ts +45 -0
- package/src/lib/server/refund.js +607 -0
- package/src/lib/server/refund.js.map +1 -0
- package/src/lib/server/related.d.ts +24 -0
- package/src/lib/server/related.js +104 -0
- package/src/lib/server/related.js.map +1 -0
- package/src/lib/server/reservation-availability.d.ts +23 -0
- package/src/lib/server/reservation-availability.js +111 -0
- package/src/lib/server/reservation-availability.js.map +1 -0
- package/src/lib/server/reserve-stock.d.ts +156 -0
- package/src/lib/server/reserve-stock.js +146 -0
- package/src/lib/server/reserve-stock.js.map +1 -0
- package/src/lib/server/reserve.d.ts +25 -0
- package/src/lib/server/reserve.js +426 -0
- package/src/lib/server/reserve.js.map +1 -0
- package/src/lib/server/restock-flag.d.ts +202 -0
- package/src/lib/server/restock-flag.js +449 -0
- package/src/lib/server/restock-flag.js.map +1 -0
- package/src/lib/server/reviews.d.ts +51 -0
- package/src/lib/server/reviews.js +132 -0
- package/src/lib/server/reviews.js.map +1 -0
- package/src/lib/server/site-page-enricher.d.ts +42 -0
- package/src/lib/server/site-page-enricher.js +149 -0
- package/src/lib/server/site-page-enricher.js.map +1 -0
- package/src/lib/server/site-page-resolver.d.ts +26 -0
- package/src/lib/server/site-page-resolver.js +223 -0
- package/src/lib/server/site-page-resolver.js.map +1 -0
- package/src/lib/server/stock-hold.d.ts +127 -0
- package/src/lib/server/stock-hold.js +258 -0
- package/src/lib/server/stock-hold.js.map +1 -0
- package/src/lib/server/storefront-tax-record.d.ts +22 -0
- package/src/lib/server/storefront-tax-record.js +218 -0
- package/src/lib/server/storefront-tax-record.js.map +1 -0
- package/src/lib/server/storefront-tax.d.ts +197 -0
- package/src/lib/server/storefront-tax.js +245 -0
- package/src/lib/server/storefront-tax.js.map +1 -0
- package/src/lib/server/stream.d.ts +25 -0
- package/src/lib/server/stream.js +191 -0
- package/src/lib/server/stream.js.map +1 -0
- package/src/lib/server/subscription-portal.d.ts +24 -0
- package/src/lib/server/subscription-portal.js +91 -0
- package/src/lib/server/subscription-portal.js.map +1 -0
- package/src/lib/server/supplier-outbox.d.ts +190 -0
- package/src/lib/server/supplier-outbox.js +342 -0
- package/src/lib/server/supplier-outbox.js.map +1 -0
- package/src/lib/server/supplier-update.d.ts +23 -0
- package/src/lib/server/supplier-update.js +289 -0
- package/src/lib/server/supplier-update.js.map +1 -0
- package/src/lib/server/tax-profile.d.ts +29 -0
- package/src/lib/server/tax-profile.js +40 -0
- package/src/lib/server/tax-profile.js.map +1 -0
- package/src/lib/server.d.ts +32 -0
- package/src/lib/server.js +311 -0
- package/src/lib/server.js.map +1 -0
- package/src/lib/site.d.ts +27 -0
- package/src/lib/site.js +134 -0
- package/src/lib/site.js.map +1 -0
- package/src/lib/utils/escape-html.d.ts +41 -0
- package/src/lib/utils/escape-html.js +41 -0
- package/src/lib/utils/escape-html.js.map +1 -0
- package/src/lib/utils/generate-preset-id.d.ts +19 -0
- package/src/lib/utils/generate-preset-id.js +25 -0
- package/src/lib/utils/generate-preset-id.js.map +1 -0
- package/src/lib/utils/member-continue.d.ts +45 -0
- package/src/lib/utils/member-continue.js +49 -0
- package/src/lib/utils/member-continue.js.map +1 -0
- package/src/lib/utils/use-storefront-purchase-event.d.ts +60 -0
- package/src/lib/utils/use-storefront-purchase-event.js +142 -0
- package/src/lib/utils/use-storefront-purchase-event.js.map +1 -0
|
@@ -0,0 +1,1280 @@
|
|
|
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 { _ as _extends } from "@swc/helpers/_/_extends";
|
|
18
|
+
import { _ as _object_without_properties_loose } from "@swc/helpers/_/_object_without_properties_loose";
|
|
19
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
20
|
+
import * as Aglyn from "@aglyn/aglyn";
|
|
21
|
+
import * as CommerceModel from "../../model/index.js";
|
|
22
|
+
import { PRODUCT_LIST_FILTER_FIELDS } from "../../constants/product-filters.js";
|
|
23
|
+
import { CardDisplay, useConfirmationContext } from "@aglyn/shared-ui-jsx";
|
|
24
|
+
import { ListPagination } from "@aglyn/shared-ui-jsx/components/list-pagination.component";
|
|
25
|
+
import QuotaReadoutComponent from "@aglyn/shared-ui-jsx/components/quota-readout.component";
|
|
26
|
+
import { TABLE_PAGE_SIZE_DEFAULT } from "@aglyn/shared-ui-jsx/const/table-pagination";
|
|
27
|
+
import { ScrollTable } from "@aglyn/shared-ui-jsx/components/scroll-table.component";
|
|
28
|
+
import { useSnackbar } from "@aglyn/shared-ui-snackstack";
|
|
29
|
+
import { Timestamp } from "@aglyn/shared-util-timestamp";
|
|
30
|
+
import { Alert, Box, Button, Chip, Dialog, DialogActions, DialogContent, DialogTitle, MenuItem, Stack, TableBody, TableCell, TableHead, TableRow, TextField, Typography } from "@mui/material";
|
|
31
|
+
import { addDoc, collection, doc, documentId, getCountFromServer, limit, orderBy, query, updateDoc, where } from "firebase/firestore";
|
|
32
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
33
|
+
import { useFirestore } from "@aglyn/tenant-feature-instance";
|
|
34
|
+
import { listFilterConstraints, useFirestoreCollection, writeGuardedBySeed } from "@aglyn/tenant-feature-instance";
|
|
35
|
+
import { useHostResourceApi } from "@aglyn/tenant-feature-instance";
|
|
36
|
+
import { useOrgPlan } from "@aglyn/tenant-feature-instance";
|
|
37
|
+
import ProductEditorDialog from "./product-editor-dialog.component.js";
|
|
38
|
+
import ProductsHubZone from "./products-hub-zone.component.js";
|
|
39
|
+
import { pluginDocsHelp } from "@aglyn/aglyn";
|
|
40
|
+
import { useConsoleWidgetSlot } from "@aglyn/aglyn/app-utils/console-widget-slot-context";
|
|
41
|
+
/**
|
|
42
|
+
* How many catalog documents the table's listener holds.
|
|
43
|
+
*
|
|
44
|
+
* A CEILING, not a page size: a 25,000-product catalog does not belong in a
|
|
45
|
+
* table, and three other readers on this card — the CSV export, the importer's
|
|
46
|
+
* duplicate-slug check and the reserved-stock clock — need the window whole.
|
|
47
|
+
* The footer pages what this holds; the query is what bounds it.
|
|
48
|
+
*/ const CATALOG_CEILING = 500;
|
|
49
|
+
/**
|
|
50
|
+
* How many of ONE product's license keys the dialog reads.
|
|
51
|
+
*
|
|
52
|
+
* Per product, not per store: the query carries the `productId` equality, so
|
|
53
|
+
* this bounds a single pool rather than the site's whole key collection.
|
|
54
|
+
*/ const KEY_POOL_CEILING = 500;
|
|
55
|
+
const STATUS_COLOR = {
|
|
56
|
+
active: 'success',
|
|
57
|
+
draft: 'warning',
|
|
58
|
+
archived: 'default'
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Products hub v1 (AGL-279): the catalog manager replacing the Commerce
|
|
62
|
+
* Starter card — search + status filter over `hosts/{hostId}/products`,
|
|
63
|
+
* full editor dialog, duplicate, archive/activate, soft delete (past
|
|
64
|
+
* order rows keep resolving). Product cap (`productsPerHost`, AGL-278)
|
|
65
|
+
* gated here on create/duplicate/import (AGL-471); server-side
|
|
66
|
+
* enforcement of the client-write path rides AGL-473.
|
|
67
|
+
*/ export function ProductsHubCard(props) {
|
|
68
|
+
var _ref, _ref1, _ref2, _ref3, _ref4, _ref5, _ref6, _ref7, _ref8, _ref9, _ref10, _ref11, _ref12;
|
|
69
|
+
var _importing_parsed, _importing_parsed1, _importing_parsed2;
|
|
70
|
+
const { hostId } = props;
|
|
71
|
+
const firestore = useFirestore();
|
|
72
|
+
const { enqueueSnackbar } = useSnackbar();
|
|
73
|
+
// Product cap (AGL-471): per-plan `productsPerHost`, same pattern as
|
|
74
|
+
// locations. Console-side gate; server enforcement rides AGL-473.
|
|
75
|
+
const createHostResource = useHostResourceApi();
|
|
76
|
+
const { org, ready: planReady } = useOrgPlan(hostId);
|
|
77
|
+
const { confirm } = useConfirmationContext();
|
|
78
|
+
const [search, setSearch] = useState('');
|
|
79
|
+
/*
|
|
80
|
+
* WHICH field the search box searches (AGL-2501).
|
|
81
|
+
*
|
|
82
|
+
* The box used to compare four fields at once — name, slug, tag, SKU — over
|
|
83
|
+
* the rows the listener had already fetched. Server-side that is not one
|
|
84
|
+
* query: Firestore allows a single `array-contains` per query and cannot OR
|
|
85
|
+
* across fields, so four fields at once means four queries and a merge, and
|
|
86
|
+
* a merged result cannot be paged or capped coherently.
|
|
87
|
+
*
|
|
88
|
+
* Naming the field is the honest trade. It buys a search that reaches the
|
|
89
|
+
* WHOLE catalog instead of an arbitrary five hundred rows of it, which is
|
|
90
|
+
* the case the old box got wrong — and a merchant looking for a SKU knows
|
|
91
|
+
* they are looking for a SKU.
|
|
92
|
+
*/ const [searchField, setSearchField] = useState('name');
|
|
93
|
+
const [statusFilter, setStatusFilter] = useState('all');
|
|
94
|
+
const [editing, setEditing] = useState(null);
|
|
95
|
+
const [creating, setCreating] = useState(false);
|
|
96
|
+
const [adjusting, setAdjusting] = useState(null);
|
|
97
|
+
const [importing, setImporting] = useState(null);
|
|
98
|
+
const [keysFor, setKeysFor] = useState(null);
|
|
99
|
+
const [keysText, setKeysText] = useState('');
|
|
100
|
+
/**
|
|
101
|
+
* What the import zone set for the import in the dialog, and what the last
|
|
102
|
+
* import created with those options (AGL-2916), which the hub's zone hands
|
|
103
|
+
* to its widgets.
|
|
104
|
+
*/ const [importOptions, setImportOptions] = useState({});
|
|
105
|
+
const [lastImport, setLastImport] = useState(null);
|
|
106
|
+
const WidgetSlot = useConsoleWidgetSlot();
|
|
107
|
+
const { data: productDocs, status: productsStatus, /**
|
|
108
|
+
* The product rows the stock dialog is seeded from are unconfirmed by
|
|
109
|
+
* the server (AGL-1358). A stock adjustment does not write the delta —
|
|
110
|
+
* it recomputes the WHOLE `variants` array from the seeded product and
|
|
111
|
+
* replaces it, so a cached seed silently reverts every sale, return and
|
|
112
|
+
* adjustment the server has recorded since that snapshot, on every
|
|
113
|
+
* variant and every location.
|
|
114
|
+
*/ fromCache: productsFromCache } = useFirestoreCollection(()=>{
|
|
115
|
+
/*
|
|
116
|
+
* FILTERED BY THE QUERY, and ordered rather than merely capped
|
|
117
|
+
* (AGL-2501, AGL-2292).
|
|
118
|
+
*
|
|
119
|
+
* `limit(500)` with no `orderBy` returns documents in ID order, and
|
|
120
|
+
* products are created at `createResourceUid()` — so that was a
|
|
121
|
+
* pseudo-random SAMPLE of five hundred, which the client `.sort()` by
|
|
122
|
+
* name below dressed up as a reliable alphabetical page. The search then
|
|
123
|
+
* ran over that sample, so a product on the wrong side of the cap
|
|
124
|
+
* answered "no products match": the one answer a search must never get
|
|
125
|
+
* wrong, on the list a merchant uses to find one item in their catalog.
|
|
126
|
+
*
|
|
127
|
+
* The cap STAYS — a 25,000-product catalog does not belong in a table,
|
|
128
|
+
* and the head-count has been a server aggregate since AGL-1716. What
|
|
129
|
+
* changes is that a filter now reaches the whole collection BEFORE the
|
|
130
|
+
* cap applies.
|
|
131
|
+
*
|
|
132
|
+
* ONE request, because Firestore allows one `array-contains` and the
|
|
133
|
+
* shared translator builds one predicate. Whichever control is set alone
|
|
134
|
+
* is answered entirely by the server; with BOTH set the name reaches the
|
|
135
|
+
* whole catalog and the status narrows those matches client-side below,
|
|
136
|
+
* which is complete unless a single name matches more than five hundred
|
|
137
|
+
* products.
|
|
138
|
+
*
|
|
139
|
+
* ⚠️ The default ordering is by DOCUMENT ID, not by `nameLower`.
|
|
140
|
+
* Ordering by a denormalized key drops every document that lacks it, and
|
|
141
|
+
* an unfiltered list must not be able to hide a product — a catalog
|
|
142
|
+
* imported before the search keys existed, or written by a path that
|
|
143
|
+
* forgot them, would simply stop appearing. Under a name filter the
|
|
144
|
+
* ordering does move to `nameLower`, and there it is safe: a document
|
|
145
|
+
* without the key has no `nameTokens` either, so the `array-contains`
|
|
146
|
+
* has already excluded it.
|
|
147
|
+
*/ const constraints = listFilterConstraints(PRODUCT_LIST_FILTER_FIELDS, search.trim() ? {
|
|
148
|
+
field: searchField,
|
|
149
|
+
op: 'contains',
|
|
150
|
+
value: search.trim()
|
|
151
|
+
} : statusFilter !== 'all' ? {
|
|
152
|
+
field: 'status',
|
|
153
|
+
op: 'equals',
|
|
154
|
+
value: statusFilter
|
|
155
|
+
} : null);
|
|
156
|
+
return query(collection(firestore, 'hosts', hostId, 'products'), ...constraints != null ? constraints : [
|
|
157
|
+
orderBy(documentId())
|
|
158
|
+
], /*
|
|
159
|
+
* `CATALOG_CEILING + 1` is a PROBE (AGL-2501). One document past the
|
|
160
|
+
* ceiling turns "this catalog is larger than the table" into a fact.
|
|
161
|
+
* The probe row is dropped below and never rendered, exported or
|
|
162
|
+
* counted.
|
|
163
|
+
*/ limit(CATALOG_CEILING + 1));
|
|
164
|
+
}, [
|
|
165
|
+
firestore,
|
|
166
|
+
hostId,
|
|
167
|
+
search,
|
|
168
|
+
searchField,
|
|
169
|
+
statusFilter
|
|
170
|
+
], {
|
|
171
|
+
idField: '$id'
|
|
172
|
+
});
|
|
173
|
+
/*
|
|
174
|
+
* License key pool (AGL-308) for the open dialog's product — asked FOR that
|
|
175
|
+
* product, and ordered (AGL-2501).
|
|
176
|
+
*
|
|
177
|
+
* This read `limit(500)` over the site's whole `licenseKeys` collection with
|
|
178
|
+
* no `orderBy`, then filtered by `productId` in the browser. Firestore
|
|
179
|
+
* answers an unordered limit in DOCUMENT-ID order, so on a store past five
|
|
180
|
+
* hundred keys the window was an arbitrary five hundred taken across every
|
|
181
|
+
* product — and the "N available" line below counted what happened to be in
|
|
182
|
+
* it. A product whose keys all hashed high showed a pool of zero while the
|
|
183
|
+
* storefront went on delivering them.
|
|
184
|
+
*
|
|
185
|
+
* The equality moves into the query, so the window is this product's keys
|
|
186
|
+
* rather than the store's. `orderBy(documentId())` orders on the document
|
|
187
|
+
* NAME, which cannot be absent — every candidate FIELD here (`createdAtMs`,
|
|
188
|
+
* `assignedAtMs`, `revokedAtMs`) is either optional by design or absent on
|
|
189
|
+
* exactly the keys the counts are about. An equality plus an ordering on
|
|
190
|
+
* `__name__` is served by the automatic single-field index, so this adds no
|
|
191
|
+
* composite for anyone to deploy.
|
|
192
|
+
*/ const { data: keyDocs } = useFirestoreCollection(()=>keysFor ? query(collection(firestore, 'hosts', hostId, 'licenseKeys'), where('productId', '==', keysFor.$id), orderBy(documentId()), // One past the ceiling, so a pool larger than the window is a
|
|
193
|
+
// fact rather than a guess from `length === 500`.
|
|
194
|
+
limit(KEY_POOL_CEILING + 1)) : null, [
|
|
195
|
+
firestore,
|
|
196
|
+
hostId,
|
|
197
|
+
keysFor == null ? void 0 : keysFor.$id
|
|
198
|
+
], {
|
|
199
|
+
idField: '$id'
|
|
200
|
+
});
|
|
201
|
+
/** The key pool is larger than the window, so the counts below are partial. */ const keyPoolTruncated = ((_ref = keyDocs == null ? void 0 : keyDocs.length) != null ? _ref : 0) > KEY_POOL_CEILING;
|
|
202
|
+
// Locations (AGL-286): the stock dialog buckets deltas when they exist.
|
|
203
|
+
const { data: locationDocs } = useFirestoreCollection(()=>query(collection(firestore, 'hosts', hostId, 'locations'), limit(25)), [
|
|
204
|
+
firestore,
|
|
205
|
+
hostId
|
|
206
|
+
], {
|
|
207
|
+
idField: '$id'
|
|
208
|
+
});
|
|
209
|
+
/** The read went past the ceiling, so the catalog is larger than the window. */ const catalogTruncated = ((_ref1 = productDocs == null ? void 0 : productDocs.length) != null ? _ref1 : 0) > CATALOG_CEILING;
|
|
210
|
+
const products = useMemo(()=>{
|
|
211
|
+
/*
|
|
212
|
+
* The SEARCH is gone from here — it is the query's job now, and running it
|
|
213
|
+
* twice would be worse than redundant. A server `contains` matches a word
|
|
214
|
+
* PREFIX ("cof" finds "Coffee"), while the old `includes` matched a raw
|
|
215
|
+
* substring, so a two-word search would have been sent to the server as
|
|
216
|
+
* its first word and then dropped again here by a whole-string compare
|
|
217
|
+
* that the matching row never satisfies: rows found, then hidden.
|
|
218
|
+
*
|
|
219
|
+
* The status narrowing stays, and only earns its keep when a name search
|
|
220
|
+
* is also active — that is the one combination the single predicate above
|
|
221
|
+
* cannot express, and here it runs over rows the server already matched by
|
|
222
|
+
* name across the whole catalog rather than over an arbitrary window.
|
|
223
|
+
*
|
|
224
|
+
* Soft-deleted products are still dropped here rather than in the query:
|
|
225
|
+
* `deletedAt` is absent on a live product, and Firestore cannot ask for
|
|
226
|
+
* documents that LACK a field.
|
|
227
|
+
*/ return (productDocs != null ? productDocs : []).slice(0, CATALOG_CEILING).filter((product)=>!product.deletedAt).map((product)=>_extends({}, CommerceModel.liftLegacyProduct(product), {
|
|
228
|
+
$id: product.$id
|
|
229
|
+
})).filter((product)=>statusFilter === 'all' || product.status === statusFilter).sort((a, b)=>a.name.localeCompare(b.name));
|
|
230
|
+
}, [
|
|
231
|
+
productDocs,
|
|
232
|
+
statusFilter
|
|
233
|
+
]);
|
|
234
|
+
/*==========================================
|
|
235
|
+
* THE TABLE PAGES, and the READ deliberately does not (AGL-2501).
|
|
236
|
+
*
|
|
237
|
+
* The card rendered every row of a five-hundred-document window in one wall
|
|
238
|
+
* with no footer under it — the shape this sweep is about. What it must not
|
|
239
|
+
* become is a server-paged query, because three other things in this file
|
|
240
|
+
* read the same window and every one of them needs it whole:
|
|
241
|
+
*
|
|
242
|
+
* * `handleExport` writes the CSV from these rows. A ten-row page would
|
|
243
|
+
* silently export ten products under a filename that claims the catalog.
|
|
244
|
+
* * the CSV importer builds `existingSlugs` from them to refuse a
|
|
245
|
+
* duplicate slug. Narrowed to a page, it would stop seeing the clash and
|
|
246
|
+
* create the duplicate it exists to prevent.
|
|
247
|
+
* * the reserved-stock clock arms off them.
|
|
248
|
+
*
|
|
249
|
+
* The window is also already correct: `orderBy(documentId())` unfiltered,
|
|
250
|
+
* `nameLower` under a name filter, and the filters themselves reach the
|
|
251
|
+
* whole collection through the query. So the fix here is the CONTROL, not
|
|
252
|
+
* the read — and the head-count beside it has been a server aggregate since
|
|
253
|
+
* AGL-1716.
|
|
254
|
+
*=========================================*/ const [page, setPage] = useState(0);
|
|
255
|
+
const [pageSize, setPageSize] = useState(TABLE_PAGE_SIZE_DEFAULT);
|
|
256
|
+
// A filter narrows the list under the reader's feet, and page four of the
|
|
257
|
+
// unfiltered catalog is not a position in the filtered one.
|
|
258
|
+
useEffect(()=>setPage(0), [
|
|
259
|
+
search,
|
|
260
|
+
searchField,
|
|
261
|
+
statusFilter
|
|
262
|
+
]);
|
|
263
|
+
const visibleProducts = useMemo(()=>products.slice(page * pageSize, page * pageSize + pageSize), [
|
|
264
|
+
products,
|
|
265
|
+
page,
|
|
266
|
+
pageSize
|
|
267
|
+
]);
|
|
268
|
+
/**
|
|
269
|
+
* A CLOCK, because a hold lapses without anybody writing anything
|
|
270
|
+
* (AGL-2356).
|
|
271
|
+
*
|
|
272
|
+
* The Firestore listener re-renders on document changes, and a reservation
|
|
273
|
+
* expiring is not one — `expiresAtMs` simply passes. Without a tick the
|
|
274
|
+
* "reserved" caption below would keep naming a hold that lapsed twenty
|
|
275
|
+
* minutes ago, which is worse than not showing it at all: the merchant would
|
|
276
|
+
* be told stock is spoken for while the storefront happily sells it.
|
|
277
|
+
*
|
|
278
|
+
* Only runs while something is actually held, so an ordinary catalog costs
|
|
279
|
+
* no timer. A NEW hold arrives as a document change, which re-renders, which
|
|
280
|
+
* re-arms this.
|
|
281
|
+
*/ const [nowMs, setNowMs] = useState(()=>Date.now());
|
|
282
|
+
const anyHeld = useMemo(()=>products.some((product)=>CommerceModel.heldProductUnits(product, nowMs) > 0), [
|
|
283
|
+
products,
|
|
284
|
+
nowMs
|
|
285
|
+
]);
|
|
286
|
+
useEffect(()=>{
|
|
287
|
+
if (!anyHeld) return undefined;
|
|
288
|
+
const timer = setInterval(()=>setNowMs(Date.now()), 60000);
|
|
289
|
+
return ()=>clearInterval(timer);
|
|
290
|
+
}, [
|
|
291
|
+
anyHeld
|
|
292
|
+
]);
|
|
293
|
+
/**
|
|
294
|
+
* The catalog HEAD-COUNT is a server aggregate, not the length of the
|
|
295
|
+
* capped listener (AGL-1716, the AGL-1706 shape).
|
|
296
|
+
*
|
|
297
|
+
* The listener is `limit(500)` — correctly; a 25,000-product catalog does
|
|
298
|
+
* not belong in a table. What it must not do is answer "how many products
|
|
299
|
+
* does this site have", and it did: the length saturated at 500 and was
|
|
300
|
+
* handed to `checkQuota(org, 'productsPerHost', …)` and to the batch check
|
|
301
|
+
* the CSV importer runs. The bands are 2,500 / 10,000 / 25,000 above the
|
|
302
|
+
* window, so on Pro and up the check compared 500 against thousands and
|
|
303
|
+
* could never refuse — the card offered headroom and `api/hosts/resources`
|
|
304
|
+
* then refused the create, which is the AGL-1716 shape exactly.
|
|
305
|
+
*
|
|
306
|
+
* The aggregate is deliberately UNFILTERED, which also closes a second,
|
|
307
|
+
* quieter disagreement: `api/hosts/resources` enforces this quota with a
|
|
308
|
+
* plain `collection('products').count()`, and `softDeletes` there governs
|
|
309
|
+
* only the flat per-host cap on webhooks — so the server has always
|
|
310
|
+
* counted soft-deleted products toward `productsPerHost` while this card
|
|
311
|
+
* excluded them. The card now asks the enforcing route's question, in the
|
|
312
|
+
* enforcing route's terms.
|
|
313
|
+
*
|
|
314
|
+
* THE LIST KEEPS ITS CAP, and the filtered `products` view above still
|
|
315
|
+
* drives the table, the export and the slug set. A one-shot goes stale
|
|
316
|
+
* where a listener refreshed for free, so the count is re-read after any
|
|
317
|
+
* mutation that moves it.
|
|
318
|
+
*
|
|
319
|
+
* No counting RULE moves: `checkQuota` is untouched and `report-usage`
|
|
320
|
+
* meters contacts, storage and API requests — never the catalog.
|
|
321
|
+
*/ const [productCountEpoch, setProductCountEpoch] = useState(0);
|
|
322
|
+
const [serverProductCount, setServerProductCount] = useState(null);
|
|
323
|
+
useEffect(()=>{
|
|
324
|
+
let active = true;
|
|
325
|
+
void getCountFromServer(collection(firestore, 'hosts', hostId, 'products')).then((snapshot)=>{
|
|
326
|
+
if (active) setServerProductCount(snapshot.data().count);
|
|
327
|
+
}).catch(()=>{
|
|
328
|
+
// Falls back to the live-row count below — a LOWER bound, and this
|
|
329
|
+
// card's prior behaviour. Deliberately not 0: `checkQuota` answers
|
|
330
|
+
// from whatever it is handed, and 0 used is a confident wrong
|
|
331
|
+
// number in the flattering direction.
|
|
332
|
+
});
|
|
333
|
+
return ()=>{
|
|
334
|
+
active = false;
|
|
335
|
+
};
|
|
336
|
+
}, [
|
|
337
|
+
firestore,
|
|
338
|
+
hostId,
|
|
339
|
+
productCountEpoch
|
|
340
|
+
]);
|
|
341
|
+
// Cap against ALL live products, not the filtered view (AGL-471). This is
|
|
342
|
+
// the fallback now: pending or denied, it can only UNDERSTATE, never
|
|
343
|
+
// overstate, so nothing it gates fires on a count larger than the truth.
|
|
344
|
+
const loadedProductCount = useMemo(()=>(productDocs != null ? productDocs : []).filter((product)=>!product.deletedAt).length, [
|
|
345
|
+
productDocs
|
|
346
|
+
]);
|
|
347
|
+
const productCount = serverProductCount != null ? serverProductCount : loadedProductCount;
|
|
348
|
+
// Gate only once the org doc has loaded: an unresolved org reads as the
|
|
349
|
+
// free tier's 0-product cap, which swallowed every Add/Duplicate click.
|
|
350
|
+
// The resources API (AGL-473) stays the authoritative cap on create.
|
|
351
|
+
//
|
|
352
|
+
// `planReady` rather than `org` truthiness (AGL-1064): a host with no
|
|
353
|
+
// owning org never produces one, and waiting on the value alone would
|
|
354
|
+
// hold this open forever. The old fallback allowed UNLIMITED during the
|
|
355
|
+
// window — safe only because the API re-checks; the controls now disable
|
|
356
|
+
// instead, which does not lean on that backstop.
|
|
357
|
+
// NULL means "not known yet", which a boolean cannot say — the old
|
|
358
|
+
// fallback object claimed UNLIMITED and every consumer believed it.
|
|
359
|
+
const productQuota = useMemo(()=>planReady ? Aglyn.checkQuota(org, 'productsPerHost', productCount) : null, [
|
|
360
|
+
org,
|
|
361
|
+
planReady,
|
|
362
|
+
productCount
|
|
363
|
+
]);
|
|
364
|
+
const handleDuplicate = useCallback((product)=>async ()=>{
|
|
365
|
+
// Plan unknown — the control is disabled; this guards the race.
|
|
366
|
+
if (!productQuota) return;
|
|
367
|
+
if (!productQuota.allowed) {
|
|
368
|
+
return void enqueueSnackbar(`Your plan includes ${productQuota.limit} products — upgrade for more`, {
|
|
369
|
+
variant: 'info',
|
|
370
|
+
persist: false
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
const { $id: _sourceId } = product, copy = _object_without_properties_loose(product, [
|
|
374
|
+
"$id"
|
|
375
|
+
]);
|
|
376
|
+
try {
|
|
377
|
+
// Duplicate is a create — rides the quota-enforcing API (AGL-473).
|
|
378
|
+
await createHostResource({
|
|
379
|
+
hostId,
|
|
380
|
+
resource: 'product',
|
|
381
|
+
data: _extends({}, copy, {
|
|
382
|
+
name: `${product.name} (copy)`,
|
|
383
|
+
slug: CommerceModel.commerceSlug(`${product.slug}-copy`),
|
|
384
|
+
status: 'draft',
|
|
385
|
+
createdAtMs: Date.now(),
|
|
386
|
+
updatedAtMs: Date.now()
|
|
387
|
+
})
|
|
388
|
+
});
|
|
389
|
+
// A create moves the count and the aggregate is a one-shot — the
|
|
390
|
+
// listener refreshes the ROWS for free, the count has to be asked
|
|
391
|
+
// again or the cap drifts stale for the rest of the session.
|
|
392
|
+
setProductCountEpoch((epoch)=>epoch + 1);
|
|
393
|
+
enqueueSnackbar('Product duplicated as draft', {
|
|
394
|
+
variant: 'success',
|
|
395
|
+
persist: false
|
|
396
|
+
});
|
|
397
|
+
} catch (error) {
|
|
398
|
+
var _ref;
|
|
399
|
+
enqueueSnackbar((_ref = error == null ? void 0 : error.message) != null ? _ref : 'Could not duplicate product', {
|
|
400
|
+
variant: 'warning',
|
|
401
|
+
persist: false
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
}, [
|
|
405
|
+
hostId,
|
|
406
|
+
createHostResource,
|
|
407
|
+
enqueueSnackbar,
|
|
408
|
+
productQuota
|
|
409
|
+
]);
|
|
410
|
+
const handleStatus = useCallback((product, status)=>async ()=>{
|
|
411
|
+
// A product with a variant nobody has priced (AGL-2916) stays off the
|
|
412
|
+
// storefront until the editor has a price for each.
|
|
413
|
+
if (status === 'active' && CommerceModel.productPriceMissing(product)) {
|
|
414
|
+
return void enqueueSnackbar(`Set a price for every variant of ${product.name} before activating it.`, {
|
|
415
|
+
variant: 'info',
|
|
416
|
+
persist: false
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
await updateDoc(doc(firestore, 'hosts', hostId, 'products', product.$id), {
|
|
420
|
+
status,
|
|
421
|
+
updatedAtMs: Date.now(),
|
|
422
|
+
updatedAt: Timestamp.now()
|
|
423
|
+
});
|
|
424
|
+
}, [
|
|
425
|
+
firestore,
|
|
426
|
+
hostId,
|
|
427
|
+
enqueueSnackbar
|
|
428
|
+
]);
|
|
429
|
+
const handleDelete = useCallback((product)=>async ()=>{
|
|
430
|
+
const confirmed = await confirm({
|
|
431
|
+
title: 'Delete this product?',
|
|
432
|
+
description: `"${product.name}" stops being purchasable; blocks referencing ` + 'it show a checkout error until repointed.',
|
|
433
|
+
confirmationText: 'Delete',
|
|
434
|
+
confirmationButtonProps: {
|
|
435
|
+
color: 'error'
|
|
436
|
+
}
|
|
437
|
+
}).then(()=>true).catch(()=>false);
|
|
438
|
+
if (!confirmed) return;
|
|
439
|
+
await updateDoc(doc(firestore, 'hosts', hostId, 'products', product.$id), {
|
|
440
|
+
deletedAt: Timestamp.now()
|
|
441
|
+
});
|
|
442
|
+
enqueueSnackbar('Product deleted', {
|
|
443
|
+
variant: 'success',
|
|
444
|
+
persist: false
|
|
445
|
+
});
|
|
446
|
+
}, [
|
|
447
|
+
confirm,
|
|
448
|
+
firestore,
|
|
449
|
+
hostId,
|
|
450
|
+
enqueueSnackbar
|
|
451
|
+
]);
|
|
452
|
+
// CSV import/export (AGL-282): Shopify-dialect columns, dry-run first.
|
|
453
|
+
const handleExport = useCallback(()=>{
|
|
454
|
+
const csv = CommerceModel.productsToCsv(products);
|
|
455
|
+
const blob = new Blob([
|
|
456
|
+
csv
|
|
457
|
+
], {
|
|
458
|
+
type: 'text/csv'
|
|
459
|
+
});
|
|
460
|
+
const anchor = document.createElement('a');
|
|
461
|
+
anchor.href = URL.createObjectURL(blob);
|
|
462
|
+
anchor.download = `products-${hostId}.csv`;
|
|
463
|
+
anchor.click();
|
|
464
|
+
URL.revokeObjectURL(anchor.href);
|
|
465
|
+
}, [
|
|
466
|
+
products,
|
|
467
|
+
hostId
|
|
468
|
+
]);
|
|
469
|
+
const handleImportApply = useCallback(async ()=>{
|
|
470
|
+
const parsed = importing == null ? void 0 : importing.parsed;
|
|
471
|
+
if (!parsed || parsed.products.length === 0) return;
|
|
472
|
+
// Batch-aware cap (AGL-471): the whole import must fit the plan.
|
|
473
|
+
// Not startable until the plan is known (AGL-1064) — the old `org ?`
|
|
474
|
+
// guard let an import begin against an unknown cap and leaned on the
|
|
475
|
+
// API rejecting it partway through, leaving a half-imported catalog.
|
|
476
|
+
if (!planReady) return;
|
|
477
|
+
const batchQuota = Aglyn.checkQuota(org, 'productsPerHost', productCount + parsed.products.length - 1);
|
|
478
|
+
if (!batchQuota.allowed) {
|
|
479
|
+
return void enqueueSnackbar(`This import needs ${parsed.products.length} product slots — your ` + `plan allows ${batchQuota.limit}. See Billing to upgrade.`, {
|
|
480
|
+
variant: 'info',
|
|
481
|
+
persist: false
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
const existingSlugs = new Set(products.map((product)=>product.slug));
|
|
485
|
+
const created = [];
|
|
486
|
+
try {
|
|
487
|
+
// Each create rides the quota-enforcing API (AGL-473); the batch cap
|
|
488
|
+
// above short-circuits before we start, so this loop stays bounded.
|
|
489
|
+
for (const product of parsed.products){
|
|
490
|
+
var _ref, _ref1;
|
|
491
|
+
var _product_variants_, _product_mediaUrls;
|
|
492
|
+
let slug = product.slug;
|
|
493
|
+
while(existingSlugs.has(slug))slug = `${product.slug}-${Date.now() % 1000}`;
|
|
494
|
+
existingSlugs.add(slug);
|
|
495
|
+
const { id } = await createHostResource({
|
|
496
|
+
hostId,
|
|
497
|
+
resource: 'product',
|
|
498
|
+
data: _extends({}, product, CommerceModel.productSearchFields(product), {
|
|
499
|
+
slug,
|
|
500
|
+
priceUsd: (_ref = (_product_variants_ = product.variants[0]) == null ? void 0 : _product_variants_.priceUsd) != null ? _ref : 0,
|
|
501
|
+
inventory: CommerceModel.productInventory(product),
|
|
502
|
+
imageUrl: (_ref1 = (_product_mediaUrls = product.mediaUrls) == null ? void 0 : _product_mediaUrls[0]) != null ? _ref1 : null,
|
|
503
|
+
createdAtMs: Date.now(),
|
|
504
|
+
updatedAtMs: Date.now()
|
|
505
|
+
})
|
|
506
|
+
});
|
|
507
|
+
created.push(id);
|
|
508
|
+
}
|
|
509
|
+
} catch (error) {
|
|
510
|
+
var _ref2;
|
|
511
|
+
return void enqueueSnackbar((_ref2 = error == null ? void 0 : error.message) != null ? _ref2 : 'Import failed', {
|
|
512
|
+
variant: 'warning',
|
|
513
|
+
persist: false
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
setLastImport({
|
|
517
|
+
key: `${Date.now().toString(36)}-${created.length}`,
|
|
518
|
+
productIds: created,
|
|
519
|
+
options: importOptions
|
|
520
|
+
});
|
|
521
|
+
setImportOptions({});
|
|
522
|
+
setImporting(null);
|
|
523
|
+
setProductCountEpoch((epoch)=>epoch + 1);
|
|
524
|
+
enqueueSnackbar(`Imported ${parsed.products.length} products`, {
|
|
525
|
+
variant: 'success',
|
|
526
|
+
persist: false
|
|
527
|
+
});
|
|
528
|
+
}, [
|
|
529
|
+
importing,
|
|
530
|
+
products,
|
|
531
|
+
hostId,
|
|
532
|
+
createHostResource,
|
|
533
|
+
enqueueSnackbar,
|
|
534
|
+
org,
|
|
535
|
+
planReady,
|
|
536
|
+
productCount,
|
|
537
|
+
importOptions
|
|
538
|
+
]);
|
|
539
|
+
const handleAdjustSave = useCallback(async ()=>{
|
|
540
|
+
if (!adjusting) return;
|
|
541
|
+
const delta = Math.round(Number(adjusting.delta));
|
|
542
|
+
if (!delta) return;
|
|
543
|
+
const variants = CommerceModel.adjustVariantInventory(adjusting.product, adjusting.variantId, delta, adjusting.locationId || undefined);
|
|
544
|
+
/**
|
|
545
|
+
* Refuse the adjustment when the seed is unconfirmed (AGL-1358).
|
|
546
|
+
*
|
|
547
|
+
* `adjustVariantInventory` reads counts off the seeded product and
|
|
548
|
+
* returns a whole new `variants` array, which is then written over the
|
|
549
|
+
* stored one — so this is a full replace of live stock, not a delta, and
|
|
550
|
+
* `merge` could not help. The refusal also has to cover the ADJUSTMENT
|
|
551
|
+
* LOG, which is why the guard wraps both writes: a logged adjustment
|
|
552
|
+
* whose stock write never happened is worse than neither, because the
|
|
553
|
+
* history then disagrees with the count it is supposed to explain.
|
|
554
|
+
*/ const verdict = await writeGuardedBySeed({
|
|
555
|
+
subject: 'stock',
|
|
556
|
+
unreadable: productsStatus === 'error',
|
|
557
|
+
fromCache: productsFromCache
|
|
558
|
+
}, async ()=>{
|
|
559
|
+
await updateDoc(doc(firestore, 'hosts', hostId, 'products', adjusting.product.$id), {
|
|
560
|
+
variants,
|
|
561
|
+
inventory: CommerceModel.productInventory({
|
|
562
|
+
variants
|
|
563
|
+
}),
|
|
564
|
+
updatedAtMs: Date.now()
|
|
565
|
+
});
|
|
566
|
+
// Adjustment history (AGL-281): the same log the sale webhook writes.
|
|
567
|
+
await addDoc(collection(firestore, 'hosts', hostId, 'inventoryAdjustments'), _extends({
|
|
568
|
+
productId: adjusting.product.$id,
|
|
569
|
+
variantId: adjusting.variantId,
|
|
570
|
+
delta,
|
|
571
|
+
reason: adjusting.reason
|
|
572
|
+
}, adjusting.locationId ? {
|
|
573
|
+
locationId: adjusting.locationId
|
|
574
|
+
} : {}, {
|
|
575
|
+
atMs: Date.now()
|
|
576
|
+
}));
|
|
577
|
+
});
|
|
578
|
+
// Keep the dialog open with the typed delta rather than failing silently.
|
|
579
|
+
if (!verdict.ok) {
|
|
580
|
+
return void enqueueSnackbar(verdict.message, {
|
|
581
|
+
variant: 'warning',
|
|
582
|
+
persist: false
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
setAdjusting(null);
|
|
586
|
+
enqueueSnackbar('Stock adjusted', {
|
|
587
|
+
variant: 'success',
|
|
588
|
+
persist: false
|
|
589
|
+
});
|
|
590
|
+
}, [
|
|
591
|
+
adjusting,
|
|
592
|
+
firestore,
|
|
593
|
+
hostId,
|
|
594
|
+
enqueueSnackbar,
|
|
595
|
+
productsFromCache,
|
|
596
|
+
productsStatus
|
|
597
|
+
]);
|
|
598
|
+
/** The hub's allowance check for a batch of `count` new products, for its zone. */ const roomFor = useCallback((count)=>planReady ? Aglyn.checkQuota(org, 'productsPerHost', productCount + count - 1) : null, [
|
|
599
|
+
org,
|
|
600
|
+
planReady,
|
|
601
|
+
productCount
|
|
602
|
+
]);
|
|
603
|
+
const onCatalogCreated = useCallback(()=>setProductCountEpoch((epoch)=>epoch + 1), []);
|
|
604
|
+
const setImportOption = useCallback((key, on)=>setImportOptions((current)=>_extends({}, current, {
|
|
605
|
+
[key]: on
|
|
606
|
+
})), []);
|
|
607
|
+
const formatPrice = (product)=>{
|
|
608
|
+
const [min, max] = CommerceModel.productPriceRange(product);
|
|
609
|
+
return min === max ? `$${min}` : `$${min}–$${max}`;
|
|
610
|
+
};
|
|
611
|
+
const formatStock = (product)=>{
|
|
612
|
+
const total = CommerceModel.productInventory(product);
|
|
613
|
+
if (total == null) return '—';
|
|
614
|
+
return total > 0 ? String(total) : 'Sold out';
|
|
615
|
+
};
|
|
616
|
+
/**
|
|
617
|
+
* RESERVED UNITS, NAMED (AGL-2356).
|
|
618
|
+
*
|
|
619
|
+
* Checkout now takes a hold on the units a live session is about to buy, and
|
|
620
|
+
* that hold deliberately does NOT move `inventory` — the shelf count means
|
|
621
|
+
* units on the shelf, and half the product reads it that way. The
|
|
622
|
+
* consequence a merchant meets is that the storefront can refuse a sale of
|
|
623
|
+
* the third unit while this column says `3`, and without this caption the
|
|
624
|
+
* number is right and the behaviour looks broken.
|
|
625
|
+
*
|
|
626
|
+
* The house style of `stock-movements-card.component.tsx`: a trailing
|
|
627
|
+
* `caption` in `text.secondary`, rendered ONLY when there is something to
|
|
628
|
+
* say. Nothing is held on the overwhelming majority of products, so the
|
|
629
|
+
* column reads exactly as it does today.
|
|
630
|
+
*/ const formatHeld = (product)=>{
|
|
631
|
+
const held = CommerceModel.heldProductUnits(product, nowMs);
|
|
632
|
+
return held > 0 ? ` (${held} reserved)` : '';
|
|
633
|
+
};
|
|
634
|
+
return /*#__PURE__*/ _jsxs(CardDisplay, {
|
|
635
|
+
/*
|
|
636
|
+
* The header count is the SITE'S catalog, not the rows in hand.
|
|
637
|
+
*
|
|
638
|
+
* It was `products.length`, which is the filtered, ceilinged, now-paged
|
|
639
|
+
* view — so it read 500 on a 25,000-product catalog and would read 10
|
|
640
|
+
* once the table paged. `productCount` is the same server aggregate the
|
|
641
|
+
* quota gate uses. Under a filter there is no honest number to put here:
|
|
642
|
+
* the aggregate counts the whole catalog and the view counts matches, so
|
|
643
|
+
* neither describes what the reader is looking at, and the footer's own
|
|
644
|
+
* count line answers it instead.
|
|
645
|
+
*/ header: search || statusFilter !== 'all' ? 'Products' : `Products${productCount ? ` (${productCount})` : ''}`,
|
|
646
|
+
help: pluginDocsHelp('commerce', {
|
|
647
|
+
anchor: '#products-hub'
|
|
648
|
+
}),
|
|
649
|
+
contentGutterX: true,
|
|
650
|
+
contentGutterY: true,
|
|
651
|
+
children: [
|
|
652
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
653
|
+
spacing: 2,
|
|
654
|
+
children: [
|
|
655
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
656
|
+
direction: {
|
|
657
|
+
xs: 'column',
|
|
658
|
+
sm: 'row'
|
|
659
|
+
},
|
|
660
|
+
spacing: 1,
|
|
661
|
+
children: [
|
|
662
|
+
/*#__PURE__*/ _jsx(TextField, {
|
|
663
|
+
label: "Search",
|
|
664
|
+
placeholder: searchField === 'name' ? 'Product name' : searchField === 'skus' ? 'Whole SKU' : 'Whole barcode',
|
|
665
|
+
value: search,
|
|
666
|
+
onChange: (event)=>setSearch(event.target.value),
|
|
667
|
+
size: "small",
|
|
668
|
+
sx: {
|
|
669
|
+
flex: 1
|
|
670
|
+
}
|
|
671
|
+
}),
|
|
672
|
+
/*#__PURE__*/ _jsxs(TextField, {
|
|
673
|
+
label: "In",
|
|
674
|
+
value: searchField,
|
|
675
|
+
onChange: (event)=>setSearchField(event.target.value),
|
|
676
|
+
size: "small",
|
|
677
|
+
select: true,
|
|
678
|
+
sx: {
|
|
679
|
+
minWidth: 110
|
|
680
|
+
},
|
|
681
|
+
children: [
|
|
682
|
+
/*#__PURE__*/ _jsx(MenuItem, {
|
|
683
|
+
value: "name",
|
|
684
|
+
children: 'Name'
|
|
685
|
+
}),
|
|
686
|
+
/*#__PURE__*/ _jsx(MenuItem, {
|
|
687
|
+
value: "skus",
|
|
688
|
+
children: 'SKU'
|
|
689
|
+
}),
|
|
690
|
+
/*#__PURE__*/ _jsx(MenuItem, {
|
|
691
|
+
value: "barcodes",
|
|
692
|
+
children: 'Barcode'
|
|
693
|
+
})
|
|
694
|
+
]
|
|
695
|
+
}),
|
|
696
|
+
/*#__PURE__*/ _jsxs(TextField, {
|
|
697
|
+
label: "Status",
|
|
698
|
+
value: statusFilter,
|
|
699
|
+
onChange: (event)=>setStatusFilter(event.target.value),
|
|
700
|
+
size: "small",
|
|
701
|
+
select: true,
|
|
702
|
+
sx: {
|
|
703
|
+
minWidth: 130
|
|
704
|
+
},
|
|
705
|
+
children: [
|
|
706
|
+
/*#__PURE__*/ _jsx(MenuItem, {
|
|
707
|
+
value: "all",
|
|
708
|
+
children: 'All'
|
|
709
|
+
}),
|
|
710
|
+
/*#__PURE__*/ _jsx(MenuItem, {
|
|
711
|
+
value: "active",
|
|
712
|
+
children: 'Active'
|
|
713
|
+
}),
|
|
714
|
+
/*#__PURE__*/ _jsx(MenuItem, {
|
|
715
|
+
value: "draft",
|
|
716
|
+
children: 'Draft'
|
|
717
|
+
}),
|
|
718
|
+
/*#__PURE__*/ _jsx(MenuItem, {
|
|
719
|
+
value: "archived",
|
|
720
|
+
children: 'Archived'
|
|
721
|
+
})
|
|
722
|
+
]
|
|
723
|
+
}),
|
|
724
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
725
|
+
variant: "contained",
|
|
726
|
+
color: "primary",
|
|
727
|
+
size: "small",
|
|
728
|
+
disabled: !productQuota,
|
|
729
|
+
onClick: ()=>{
|
|
730
|
+
if (!productQuota) return;
|
|
731
|
+
if (!productQuota.allowed) {
|
|
732
|
+
return void enqueueSnackbar(`Your plan includes ${productQuota.limit} products — ` + 'upgrade for more', {
|
|
733
|
+
variant: 'info',
|
|
734
|
+
persist: false
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
setCreating(true);
|
|
738
|
+
},
|
|
739
|
+
children: 'Add product'
|
|
740
|
+
}),
|
|
741
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
742
|
+
size: "small",
|
|
743
|
+
disabled: !planReady,
|
|
744
|
+
onClick: ()=>{
|
|
745
|
+
setImportOptions({});
|
|
746
|
+
setImporting({
|
|
747
|
+
text: '',
|
|
748
|
+
parsed: null
|
|
749
|
+
});
|
|
750
|
+
},
|
|
751
|
+
children: 'Import'
|
|
752
|
+
}),
|
|
753
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
754
|
+
size: "small",
|
|
755
|
+
disabled: products.length === 0,
|
|
756
|
+
onClick: handleExport,
|
|
757
|
+
children: 'Export'
|
|
758
|
+
})
|
|
759
|
+
]
|
|
760
|
+
}),
|
|
761
|
+
/*#__PURE__*/ _jsx(QuotaReadoutComponent, {
|
|
762
|
+
ready: productQuota !== null,
|
|
763
|
+
used: productCount,
|
|
764
|
+
limit: (_ref2 = productQuota == null ? void 0 : productQuota.limit) != null ? _ref2 : 0,
|
|
765
|
+
noun: "product"
|
|
766
|
+
}),
|
|
767
|
+
/*#__PURE__*/ _jsx(ProductsHubZone, {
|
|
768
|
+
hostId: hostId,
|
|
769
|
+
products: products,
|
|
770
|
+
roomFor: roomFor,
|
|
771
|
+
lastImport: lastImport,
|
|
772
|
+
onCreated: onCatalogCreated
|
|
773
|
+
}),
|
|
774
|
+
products.length === 0 ? /*#__PURE__*/ _jsx(Typography, {
|
|
775
|
+
variant: "body2",
|
|
776
|
+
color: "text.secondary",
|
|
777
|
+
children: search || statusFilter !== 'all' ? 'No products match the current filters.' : 'Build your catalog: add a product, then drop commerce ' + 'blocks on any screen in the besigner.'
|
|
778
|
+
}) : /*#__PURE__*/ _jsx(Box, {
|
|
779
|
+
children: /*#__PURE__*/ _jsxs(ScrollTable, {
|
|
780
|
+
size: "small",
|
|
781
|
+
children: [
|
|
782
|
+
/*#__PURE__*/ _jsx(TableHead, {
|
|
783
|
+
children: /*#__PURE__*/ _jsxs(TableRow, {
|
|
784
|
+
children: [
|
|
785
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
786
|
+
children: 'Product'
|
|
787
|
+
}),
|
|
788
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
789
|
+
children: 'Status'
|
|
790
|
+
}),
|
|
791
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
792
|
+
children: 'Type'
|
|
793
|
+
}),
|
|
794
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
795
|
+
children: 'Price'
|
|
796
|
+
}),
|
|
797
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
798
|
+
children: 'Stock'
|
|
799
|
+
}),
|
|
800
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
801
|
+
children: 'Variants'
|
|
802
|
+
}),
|
|
803
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
804
|
+
align: "right",
|
|
805
|
+
children: 'Actions'
|
|
806
|
+
})
|
|
807
|
+
]
|
|
808
|
+
})
|
|
809
|
+
}),
|
|
810
|
+
/*#__PURE__*/ _jsx(TableBody, {
|
|
811
|
+
children: visibleProducts.map((product)=>{
|
|
812
|
+
var _STATUS_COLOR_product_status;
|
|
813
|
+
return /*#__PURE__*/ _jsxs(TableRow, {
|
|
814
|
+
hover: true,
|
|
815
|
+
children: [
|
|
816
|
+
/*#__PURE__*/ _jsxs(TableCell, {
|
|
817
|
+
sx: {
|
|
818
|
+
maxWidth: 260
|
|
819
|
+
},
|
|
820
|
+
children: [
|
|
821
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
822
|
+
variant: "body2",
|
|
823
|
+
noWrap: true,
|
|
824
|
+
children: product.name
|
|
825
|
+
}),
|
|
826
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
827
|
+
variant: "caption",
|
|
828
|
+
color: "text.secondary",
|
|
829
|
+
noWrap: true,
|
|
830
|
+
sx: {
|
|
831
|
+
display: 'block'
|
|
832
|
+
},
|
|
833
|
+
children: `/${product.slug} · id: ${product.$id}`
|
|
834
|
+
})
|
|
835
|
+
]
|
|
836
|
+
}),
|
|
837
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
838
|
+
children: /*#__PURE__*/ _jsx(Chip, {
|
|
839
|
+
label: product.status,
|
|
840
|
+
size: "small",
|
|
841
|
+
color: (_STATUS_COLOR_product_status = STATUS_COLOR[product.status]) != null ? _STATUS_COLOR_product_status : 'default',
|
|
842
|
+
variant: "outlined"
|
|
843
|
+
})
|
|
844
|
+
}),
|
|
845
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
846
|
+
children: product.type
|
|
847
|
+
}),
|
|
848
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
849
|
+
children: CommerceModel.productPriceMissing(product) ? /*#__PURE__*/ _jsx(Chip, {
|
|
850
|
+
label: "Set a price",
|
|
851
|
+
size: "small",
|
|
852
|
+
color: "warning",
|
|
853
|
+
variant: "outlined"
|
|
854
|
+
}) : formatPrice(product)
|
|
855
|
+
}),
|
|
856
|
+
/*#__PURE__*/ _jsxs(TableCell, {
|
|
857
|
+
children: [
|
|
858
|
+
formatStock(product),
|
|
859
|
+
formatHeld(product) ? /*#__PURE__*/ _jsx(Typography, {
|
|
860
|
+
variant: "caption",
|
|
861
|
+
color: "text.secondary",
|
|
862
|
+
component: "span",
|
|
863
|
+
children: formatHeld(product)
|
|
864
|
+
}) : null
|
|
865
|
+
]
|
|
866
|
+
}),
|
|
867
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
868
|
+
children: product.variants.length
|
|
869
|
+
}),
|
|
870
|
+
/*#__PURE__*/ _jsxs(TableCell, {
|
|
871
|
+
align: "right",
|
|
872
|
+
sx: {
|
|
873
|
+
whiteSpace: 'nowrap'
|
|
874
|
+
},
|
|
875
|
+
children: [
|
|
876
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
877
|
+
size: "small",
|
|
878
|
+
onClick: ()=>setEditing(product),
|
|
879
|
+
children: 'Edit'
|
|
880
|
+
}),
|
|
881
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
882
|
+
size: "small",
|
|
883
|
+
onClick: handleDuplicate(product),
|
|
884
|
+
children: 'Duplicate'
|
|
885
|
+
}),
|
|
886
|
+
product.type === 'digital' ? /*#__PURE__*/ _jsx(Button, {
|
|
887
|
+
size: "small",
|
|
888
|
+
onClick: ()=>setKeysFor(product),
|
|
889
|
+
children: 'Keys'
|
|
890
|
+
}) : null,
|
|
891
|
+
CommerceModel.productInventory(product) != null ? /*#__PURE__*/ _jsx(Button, {
|
|
892
|
+
size: "small",
|
|
893
|
+
onClick: ()=>{
|
|
894
|
+
var _ref, _ref1, _ref2;
|
|
895
|
+
var _product_variants_find, _find, _;
|
|
896
|
+
return setAdjusting({
|
|
897
|
+
product,
|
|
898
|
+
variantId: (_ref = (_product_variants_find = product.variants.find((variant)=>variant.inventory != null)) == null ? void 0 : _product_variants_find.id) != null ? _ref : product.variants[0].id,
|
|
899
|
+
delta: '',
|
|
900
|
+
reason: 'restock',
|
|
901
|
+
locationId: (_ref1 = (_ref2 = (_find = (locationDocs != null ? locationDocs : []).find((location)=>location.isDefault)) == null ? void 0 : _find.$id) != null ? _ref2 : (_ = (locationDocs != null ? locationDocs : [])[0]) == null ? void 0 : _.$id) != null ? _ref1 : ''
|
|
902
|
+
});
|
|
903
|
+
},
|
|
904
|
+
children: 'Stock'
|
|
905
|
+
}) : null,
|
|
906
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
907
|
+
size: "small",
|
|
908
|
+
onClick: handleStatus(product, product.status === 'archived' ? 'active' : 'archived'),
|
|
909
|
+
children: product.status === 'archived' ? 'Activate' : 'Archive'
|
|
910
|
+
}),
|
|
911
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
912
|
+
size: "small",
|
|
913
|
+
color: "error",
|
|
914
|
+
onClick: handleDelete(product),
|
|
915
|
+
children: 'Delete'
|
|
916
|
+
})
|
|
917
|
+
]
|
|
918
|
+
})
|
|
919
|
+
]
|
|
920
|
+
}, product.$id);
|
|
921
|
+
})
|
|
922
|
+
})
|
|
923
|
+
]
|
|
924
|
+
})
|
|
925
|
+
}),
|
|
926
|
+
products.length === 0 ? null : /*#__PURE__*/ _jsx(ListPagination, {
|
|
927
|
+
page: page,
|
|
928
|
+
pageSize: pageSize,
|
|
929
|
+
rowCount: visibleProducts.length,
|
|
930
|
+
// The rows matching the current filters, which the card holds in
|
|
931
|
+
// full below the ceiling. NOT the catalog total — that is
|
|
932
|
+
// `productCount`, and it answers a different question.
|
|
933
|
+
count: products.length,
|
|
934
|
+
onPageChange: setPage,
|
|
935
|
+
onPageSizeChange: setPageSize
|
|
936
|
+
}),
|
|
937
|
+
catalogTruncated ? /*#__PURE__*/ _jsx(Alert, {
|
|
938
|
+
severity: "info",
|
|
939
|
+
children: `This table holds ${CATALOG_CEILING} products at a time and this ` + 'catalog is larger. Search reaches every product; the CSV ' + 'export covers what the table holds.'
|
|
940
|
+
}) : null
|
|
941
|
+
]
|
|
942
|
+
}),
|
|
943
|
+
/*#__PURE__*/ _jsxs(Dialog, {
|
|
944
|
+
open: Boolean(keysFor),
|
|
945
|
+
onClose: ()=>setKeysFor(null),
|
|
946
|
+
maxWidth: "sm",
|
|
947
|
+
fullWidth: true,
|
|
948
|
+
children: [
|
|
949
|
+
/*#__PURE__*/ _jsx(DialogTitle, {
|
|
950
|
+
children: `License keys — ${(_ref3 = keysFor == null ? void 0 : keysFor.name) != null ? _ref3 : ''}`
|
|
951
|
+
}),
|
|
952
|
+
/*#__PURE__*/ _jsx(DialogContent, {
|
|
953
|
+
sx: {
|
|
954
|
+
display: 'flex',
|
|
955
|
+
flexDirection: 'column',
|
|
956
|
+
gap: 2
|
|
957
|
+
},
|
|
958
|
+
children: (()=>{
|
|
959
|
+
// The query already asked for this product's keys, so no second
|
|
960
|
+
// filter here: narrowing a window that was already narrowed is how
|
|
961
|
+
// a reader comes to believe the count covers the pool.
|
|
962
|
+
const productKeys = (keyDocs != null ? keyDocs : []).slice(0, KEY_POOL_CEILING);
|
|
963
|
+
const available = productKeys.filter((key)=>!key.assignedAtMs);
|
|
964
|
+
// RETIRED KEYS ARE COUNTED SEPARATELY (AGL-2454). A refund retires
|
|
965
|
+
// the key rather than returning it to the pool — the buyer already
|
|
966
|
+
// holds the string, so reissuing it would give two people one
|
|
967
|
+
// working key — and without this line the merchant simply watches
|
|
968
|
+
// "available" fall with nothing to explain where the key went.
|
|
969
|
+
const retired = productKeys.filter((key)=>key.revokedAtMs);
|
|
970
|
+
const assigned = productKeys.length - available.length - retired.length;
|
|
971
|
+
return /*#__PURE__*/ _jsxs(_Fragment, {
|
|
972
|
+
children: [
|
|
973
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
974
|
+
variant: "body2",
|
|
975
|
+
color: "text.secondary",
|
|
976
|
+
sx: {
|
|
977
|
+
mt: 1
|
|
978
|
+
},
|
|
979
|
+
children: `${available.length} available · ${assigned} assigned` + (retired.length ? ` · ${retired.length} retired (refunded or revoked — not reissued)` : '') + (keyPoolTruncated ? `, of the ${KEY_POOL_CEILING} keys read` : '') + '. Keys deliver automatically on purchase (receipt + account).'
|
|
980
|
+
}),
|
|
981
|
+
available.slice(0, 8).map((key)=>/*#__PURE__*/ _jsxs(Stack, {
|
|
982
|
+
direction: "row",
|
|
983
|
+
spacing: 1,
|
|
984
|
+
sx: {
|
|
985
|
+
alignItems: 'center'
|
|
986
|
+
},
|
|
987
|
+
children: [
|
|
988
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
989
|
+
variant: "caption",
|
|
990
|
+
sx: {
|
|
991
|
+
flex: 1,
|
|
992
|
+
fontFamily: 'monospace'
|
|
993
|
+
},
|
|
994
|
+
noWrap: true,
|
|
995
|
+
children: key.key
|
|
996
|
+
}),
|
|
997
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
998
|
+
size: "small",
|
|
999
|
+
color: "error",
|
|
1000
|
+
onClick: ()=>updateDoc(doc(firestore, 'hosts', hostId, 'licenseKeys', key.$id), {
|
|
1001
|
+
revokedAtMs: Date.now(),
|
|
1002
|
+
assignedAtMs: Date.now()
|
|
1003
|
+
}),
|
|
1004
|
+
children: 'Revoke'
|
|
1005
|
+
})
|
|
1006
|
+
]
|
|
1007
|
+
}, key.$id)),
|
|
1008
|
+
/*#__PURE__*/ _jsx(TextField, {
|
|
1009
|
+
label: "Add keys (one per line)",
|
|
1010
|
+
value: keysText,
|
|
1011
|
+
onChange: (event)=>setKeysText(event.target.value),
|
|
1012
|
+
size: "small",
|
|
1013
|
+
multiline: true,
|
|
1014
|
+
minRows: 3
|
|
1015
|
+
})
|
|
1016
|
+
]
|
|
1017
|
+
});
|
|
1018
|
+
})()
|
|
1019
|
+
}),
|
|
1020
|
+
/*#__PURE__*/ _jsxs(DialogActions, {
|
|
1021
|
+
children: [
|
|
1022
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
1023
|
+
onClick: ()=>setKeysFor(null),
|
|
1024
|
+
children: 'Close'
|
|
1025
|
+
}),
|
|
1026
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
1027
|
+
variant: "contained",
|
|
1028
|
+
color: "primary",
|
|
1029
|
+
disabled: !keysText.trim(),
|
|
1030
|
+
onClick: async ()=>{
|
|
1031
|
+
const keys = keysText.split('\n').map((key)=>key.trim()).filter(Boolean).slice(0, 200);
|
|
1032
|
+
for (const key of keys){
|
|
1033
|
+
await addDoc(collection(firestore, 'hosts', hostId, 'licenseKeys'), {
|
|
1034
|
+
productId: keysFor.$id,
|
|
1035
|
+
key,
|
|
1036
|
+
assignedAtMs: null,
|
|
1037
|
+
createdAtMs: Date.now()
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
1040
|
+
setKeysText('');
|
|
1041
|
+
enqueueSnackbar(`Added ${keys.length} keys`, {
|
|
1042
|
+
variant: 'success',
|
|
1043
|
+
persist: false
|
|
1044
|
+
});
|
|
1045
|
+
},
|
|
1046
|
+
children: 'Add keys'
|
|
1047
|
+
})
|
|
1048
|
+
]
|
|
1049
|
+
})
|
|
1050
|
+
]
|
|
1051
|
+
}),
|
|
1052
|
+
/*#__PURE__*/ _jsxs(Dialog, {
|
|
1053
|
+
open: Boolean(importing),
|
|
1054
|
+
onClose: ()=>setImporting(null),
|
|
1055
|
+
maxWidth: "sm",
|
|
1056
|
+
fullWidth: true,
|
|
1057
|
+
children: [
|
|
1058
|
+
/*#__PURE__*/ _jsx(DialogTitle, {
|
|
1059
|
+
children: 'Import products (CSV)'
|
|
1060
|
+
}),
|
|
1061
|
+
/*#__PURE__*/ _jsxs(DialogContent, {
|
|
1062
|
+
sx: {
|
|
1063
|
+
display: 'flex',
|
|
1064
|
+
flexDirection: 'column',
|
|
1065
|
+
gap: 2
|
|
1066
|
+
},
|
|
1067
|
+
children: [
|
|
1068
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
1069
|
+
variant: "body2",
|
|
1070
|
+
color: "text.secondary",
|
|
1071
|
+
sx: {
|
|
1072
|
+
mt: 1
|
|
1073
|
+
},
|
|
1074
|
+
children: 'Shopify-compatible columns (Handle, Title, Option/Variant ' + 'columns, Image Src). Paste the file contents or choose a file.'
|
|
1075
|
+
}),
|
|
1076
|
+
/*#__PURE__*/ _jsxs(Button, {
|
|
1077
|
+
component: "label",
|
|
1078
|
+
size: "small",
|
|
1079
|
+
sx: {
|
|
1080
|
+
alignSelf: 'flex-start'
|
|
1081
|
+
},
|
|
1082
|
+
children: [
|
|
1083
|
+
'Choose file',
|
|
1084
|
+
/*#__PURE__*/ _jsx("input", {
|
|
1085
|
+
type: "file",
|
|
1086
|
+
accept: ".csv,text/csv",
|
|
1087
|
+
hidden: true,
|
|
1088
|
+
onChange: async (event)=>{
|
|
1089
|
+
var _event_target_files;
|
|
1090
|
+
const file = (_event_target_files = event.target.files) == null ? void 0 : _event_target_files[0];
|
|
1091
|
+
if (!file) return;
|
|
1092
|
+
const text = await file.text();
|
|
1093
|
+
setImporting({
|
|
1094
|
+
text,
|
|
1095
|
+
parsed: CommerceModel.parseProductsCsv(text)
|
|
1096
|
+
});
|
|
1097
|
+
}
|
|
1098
|
+
})
|
|
1099
|
+
]
|
|
1100
|
+
}),
|
|
1101
|
+
/*#__PURE__*/ _jsx(TextField, {
|
|
1102
|
+
label: "CSV",
|
|
1103
|
+
value: (_ref4 = importing == null ? void 0 : importing.text) != null ? _ref4 : '',
|
|
1104
|
+
onChange: (event)=>setImporting({
|
|
1105
|
+
text: event.target.value,
|
|
1106
|
+
parsed: event.target.value.trim() ? CommerceModel.parseProductsCsv(event.target.value) : null
|
|
1107
|
+
}),
|
|
1108
|
+
size: "small",
|
|
1109
|
+
multiline: true,
|
|
1110
|
+
minRows: 5,
|
|
1111
|
+
maxRows: 10
|
|
1112
|
+
}),
|
|
1113
|
+
(importing == null ? void 0 : importing.parsed) ? /*#__PURE__*/ _jsxs(_Fragment, {
|
|
1114
|
+
children: [
|
|
1115
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
1116
|
+
variant: "body2",
|
|
1117
|
+
children: `Ready to import ${importing.parsed.products.length} products` + (importing.parsed.errors.length ? ` — ${importing.parsed.errors.length} rows skipped:` : '')
|
|
1118
|
+
}),
|
|
1119
|
+
importing.parsed.errors.slice(0, 5).map((error)=>/*#__PURE__*/ _jsx(Typography, {
|
|
1120
|
+
variant: "caption",
|
|
1121
|
+
color: "warning.main",
|
|
1122
|
+
children: error
|
|
1123
|
+
}, error))
|
|
1124
|
+
]
|
|
1125
|
+
}) : null,
|
|
1126
|
+
WidgetSlot && (importing == null ? void 0 : (_importing_parsed = importing.parsed) == null ? void 0 : _importing_parsed.products.length) ? /*#__PURE__*/ _jsx(WidgetSlot, {
|
|
1127
|
+
slot: "productImport",
|
|
1128
|
+
hostId: hostId,
|
|
1129
|
+
orgId: undefined,
|
|
1130
|
+
count: importing.parsed.products.length,
|
|
1131
|
+
options: importOptions,
|
|
1132
|
+
setOption: setImportOption
|
|
1133
|
+
}) : null
|
|
1134
|
+
]
|
|
1135
|
+
}),
|
|
1136
|
+
/*#__PURE__*/ _jsxs(DialogActions, {
|
|
1137
|
+
children: [
|
|
1138
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
1139
|
+
onClick: ()=>setImporting(null),
|
|
1140
|
+
children: 'Cancel'
|
|
1141
|
+
}),
|
|
1142
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
1143
|
+
variant: "contained",
|
|
1144
|
+
color: "primary",
|
|
1145
|
+
disabled: !(importing == null ? void 0 : (_importing_parsed1 = importing.parsed) == null ? void 0 : _importing_parsed1.products.length),
|
|
1146
|
+
onClick: handleImportApply,
|
|
1147
|
+
children: `Import${(importing == null ? void 0 : (_importing_parsed2 = importing.parsed) == null ? void 0 : _importing_parsed2.products.length) ? ` ${importing.parsed.products.length}` : ''}`
|
|
1148
|
+
})
|
|
1149
|
+
]
|
|
1150
|
+
})
|
|
1151
|
+
]
|
|
1152
|
+
}),
|
|
1153
|
+
/*#__PURE__*/ _jsxs(Dialog, {
|
|
1154
|
+
open: Boolean(adjusting),
|
|
1155
|
+
onClose: ()=>setAdjusting(null),
|
|
1156
|
+
maxWidth: "xs",
|
|
1157
|
+
fullWidth: true,
|
|
1158
|
+
children: [
|
|
1159
|
+
/*#__PURE__*/ _jsx(DialogTitle, {
|
|
1160
|
+
children: `Adjust stock — ${(_ref5 = adjusting == null ? void 0 : adjusting.product.name) != null ? _ref5 : ''}`
|
|
1161
|
+
}),
|
|
1162
|
+
/*#__PURE__*/ _jsxs(DialogContent, {
|
|
1163
|
+
sx: {
|
|
1164
|
+
display: 'flex',
|
|
1165
|
+
flexDirection: 'column',
|
|
1166
|
+
gap: 2
|
|
1167
|
+
},
|
|
1168
|
+
children: [
|
|
1169
|
+
/*#__PURE__*/ _jsx(TextField, {
|
|
1170
|
+
label: "Variant",
|
|
1171
|
+
value: (_ref6 = adjusting == null ? void 0 : adjusting.variantId) != null ? _ref6 : '',
|
|
1172
|
+
onChange: (event)=>setAdjusting((prev)=>prev ? _extends({}, prev, {
|
|
1173
|
+
variantId: event.target.value
|
|
1174
|
+
}) : prev),
|
|
1175
|
+
size: "small",
|
|
1176
|
+
select: true,
|
|
1177
|
+
sx: {
|
|
1178
|
+
mt: 1
|
|
1179
|
+
},
|
|
1180
|
+
children: ((_ref7 = adjusting == null ? void 0 : adjusting.product.variants) != null ? _ref7 : []).filter((variant)=>variant.inventory != null).map((variant)=>{
|
|
1181
|
+
var _variant_options;
|
|
1182
|
+
return /*#__PURE__*/ _jsx(MenuItem, {
|
|
1183
|
+
value: variant.id,
|
|
1184
|
+
children: `${Object.values((_variant_options = variant.options) != null ? _variant_options : {}).join(' / ') || 'Default'} — ${variant.inventory} in stock${CommerceModel.heldVariantUnits(adjusting == null ? void 0 : adjusting.product, variant.id, nowMs) > 0 ? `, ${CommerceModel.heldVariantUnits(adjusting == null ? void 0 : adjusting.product, variant.id, nowMs)} reserved in checkout` : ''}`
|
|
1185
|
+
}, variant.id);
|
|
1186
|
+
})
|
|
1187
|
+
}),
|
|
1188
|
+
((_ref8 = locationDocs == null ? void 0 : locationDocs.length) != null ? _ref8 : 0) > 1 ? /*#__PURE__*/ _jsx(TextField, {
|
|
1189
|
+
label: "Location",
|
|
1190
|
+
value: (_ref9 = adjusting == null ? void 0 : adjusting.locationId) != null ? _ref9 : '',
|
|
1191
|
+
onChange: (event)=>setAdjusting((prev)=>prev ? _extends({}, prev, {
|
|
1192
|
+
locationId: event.target.value
|
|
1193
|
+
}) : prev),
|
|
1194
|
+
size: "small",
|
|
1195
|
+
select: true,
|
|
1196
|
+
children: (locationDocs != null ? locationDocs : []).map((location)=>/*#__PURE__*/ _jsx(MenuItem, {
|
|
1197
|
+
value: location.$id,
|
|
1198
|
+
children: location.name
|
|
1199
|
+
}, location.$id))
|
|
1200
|
+
}) : null,
|
|
1201
|
+
/*#__PURE__*/ _jsx(TextField, {
|
|
1202
|
+
label: "Change",
|
|
1203
|
+
placeholder: "+10 or -3",
|
|
1204
|
+
value: (_ref10 = adjusting == null ? void 0 : adjusting.delta) != null ? _ref10 : '',
|
|
1205
|
+
onChange: (event)=>setAdjusting((prev)=>prev ? _extends({}, prev, {
|
|
1206
|
+
delta: event.target.value.replace(/[^0-9+-]/g, '')
|
|
1207
|
+
}) : prev),
|
|
1208
|
+
size: "small"
|
|
1209
|
+
}),
|
|
1210
|
+
/*#__PURE__*/ _jsxs(TextField, {
|
|
1211
|
+
label: "Reason",
|
|
1212
|
+
value: (_ref11 = adjusting == null ? void 0 : adjusting.reason) != null ? _ref11 : 'restock',
|
|
1213
|
+
onChange: (event)=>setAdjusting((prev)=>prev ? _extends({}, prev, {
|
|
1214
|
+
reason: event.target.value
|
|
1215
|
+
}) : prev),
|
|
1216
|
+
size: "small",
|
|
1217
|
+
select: true,
|
|
1218
|
+
children: [
|
|
1219
|
+
/*#__PURE__*/ _jsx(MenuItem, {
|
|
1220
|
+
value: "restock",
|
|
1221
|
+
children: 'Restock'
|
|
1222
|
+
}),
|
|
1223
|
+
/*#__PURE__*/ _jsx(MenuItem, {
|
|
1224
|
+
value: "correction",
|
|
1225
|
+
children: 'Correction'
|
|
1226
|
+
}),
|
|
1227
|
+
/*#__PURE__*/ _jsx(MenuItem, {
|
|
1228
|
+
value: "damage",
|
|
1229
|
+
children: 'Damaged'
|
|
1230
|
+
}),
|
|
1231
|
+
/*#__PURE__*/ _jsx(MenuItem, {
|
|
1232
|
+
value: "refund",
|
|
1233
|
+
children: 'Refund return'
|
|
1234
|
+
})
|
|
1235
|
+
]
|
|
1236
|
+
})
|
|
1237
|
+
]
|
|
1238
|
+
}),
|
|
1239
|
+
/*#__PURE__*/ _jsxs(DialogActions, {
|
|
1240
|
+
children: [
|
|
1241
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
1242
|
+
onClick: ()=>setAdjusting(null),
|
|
1243
|
+
children: 'Cancel'
|
|
1244
|
+
}),
|
|
1245
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
1246
|
+
variant: "contained",
|
|
1247
|
+
color: "primary",
|
|
1248
|
+
disabled: !Math.round(Number(adjusting == null ? void 0 : adjusting.delta)),
|
|
1249
|
+
onClick: handleAdjustSave,
|
|
1250
|
+
children: 'Apply'
|
|
1251
|
+
})
|
|
1252
|
+
]
|
|
1253
|
+
})
|
|
1254
|
+
]
|
|
1255
|
+
}),
|
|
1256
|
+
/*#__PURE__*/ _jsx(ProductEditorDialog, {
|
|
1257
|
+
hostId: hostId,
|
|
1258
|
+
product: editing,
|
|
1259
|
+
// The editor is seeded from a row of THIS listener and replaces the
|
|
1260
|
+
// whole document, so the freshness verdict belongs here (AGL-1358) —
|
|
1261
|
+
// the dialog has no listener of its own to ask.
|
|
1262
|
+
seedFromCache: productsFromCache,
|
|
1263
|
+
seedUnreadable: productsStatus === 'error',
|
|
1264
|
+
open: creating || editing !== null,
|
|
1265
|
+
onClose: ()=>{
|
|
1266
|
+
setEditing(null);
|
|
1267
|
+
setCreating(false);
|
|
1268
|
+
// The dialog reports no verdict, so a create and a cancel look
|
|
1269
|
+
// the same from here. Re-reading on both is one aggregate and
|
|
1270
|
+
// keeps the cap off the dialog's shoulders (AGL-1716).
|
|
1271
|
+
setProductCountEpoch((epoch)=>epoch + 1);
|
|
1272
|
+
}
|
|
1273
|
+
}, (_ref12 = editing == null ? void 0 : editing.$id) != null ? _ref12 : creating ? 'new' : 'closed')
|
|
1274
|
+
]
|
|
1275
|
+
});
|
|
1276
|
+
}
|
|
1277
|
+
ProductsHubCard.displayName = 'ProductsHubCard';
|
|
1278
|
+
export default ProductsHubCard;
|
|
1279
|
+
|
|
1280
|
+
//# sourceMappingURL=products-hub-card.component.js.map
|