@open-mercato/core 0.9.1-develop.7473.1.de5b22888f → 0.9.1-develop.7475.1.28ca93ed97
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/.turbo/turbo-build.log +2 -2
- package/dist/generated/entities/catalog_price_history_entry/index.js +53 -0
- package/dist/generated/entities/catalog_price_history_entry/index.js.map +7 -0
- package/dist/generated/entities/customer_group_terms/index.js +2 -0
- package/dist/generated/entities/customer_group_terms/index.js.map +2 -2
- package/dist/generated/entities/ecommerce_store/index.js +33 -0
- package/dist/generated/entities/ecommerce_store/index.js.map +7 -0
- package/dist/generated/entities/ecommerce_store_channel_binding/index.js +29 -0
- package/dist/generated/entities/ecommerce_store_channel_binding/index.js.map +7 -0
- package/dist/generated/entities/ecommerce_store_domain_binding/index.js +23 -0
- package/dist/generated/entities/ecommerce_store_domain_binding/index.js.map +7 -0
- package/dist/generated/entities.ids.generated.js +8 -1
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +72 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/helpers/integration/customerGroupsFixtures.js.map +2 -2
- package/dist/modules/catalog/acl.js +8 -1
- package/dist/modules/catalog/acl.js.map +2 -2
- package/dist/modules/catalog/api/config/omnibus/route.js +184 -0
- package/dist/modules/catalog/api/config/omnibus/route.js.map +7 -0
- package/dist/modules/catalog/api/prices/history/route.js +131 -0
- package/dist/modules/catalog/api/prices/history/route.js.map +7 -0
- package/dist/modules/catalog/api/prices/omnibus-preview/route.js +184 -0
- package/dist/modules/catalog/api/prices/omnibus-preview/route.js.map +7 -0
- package/dist/modules/catalog/api/products/route.js +54 -245
- package/dist/modules/catalog/api/products/route.js.map +3 -3
- package/dist/modules/catalog/api/tags/route.js +6 -0
- package/dist/modules/catalog/api/tags/route.js.map +2 -2
- package/dist/modules/catalog/backend/config/catalog/page.js +3 -1
- package/dist/modules/catalog/backend/config/catalog/page.js.map +2 -2
- package/dist/modules/catalog/cli.js +80 -2
- package/dist/modules/catalog/cli.js.map +2 -2
- package/dist/modules/catalog/commands/categories.js +51 -2
- package/dist/modules/catalog/commands/categories.js.map +2 -2
- package/dist/modules/catalog/commands/prices.js +43 -1
- package/dist/modules/catalog/commands/prices.js.map +2 -2
- package/dist/modules/catalog/commands/products.js +71 -0
- package/dist/modules/catalog/commands/products.js.map +2 -2
- package/dist/modules/catalog/commands/variants.js +7 -0
- package/dist/modules/catalog/commands/variants.js.map +2 -2
- package/dist/modules/catalog/components/OmnibusSettings.js +703 -0
- package/dist/modules/catalog/components/OmnibusSettings.js.map +7 -0
- package/dist/modules/catalog/components/PriceEditorOmnibusRow.js +152 -0
- package/dist/modules/catalog/components/PriceEditorOmnibusRow.js.map +7 -0
- package/dist/modules/catalog/components/lookupSources.js +31 -0
- package/dist/modules/catalog/components/lookupSources.js.map +7 -0
- package/dist/modules/catalog/components/prices/PriceScopeSelectors.js +8 -2
- package/dist/modules/catalog/components/prices/PriceScopeSelectors.js.map +2 -2
- package/dist/modules/catalog/components/products/VariantBuilder.js +11 -1
- package/dist/modules/catalog/components/products/VariantBuilder.js.map +2 -2
- package/dist/modules/catalog/data/entities.js +122 -1
- package/dist/modules/catalog/data/entities.js.map +2 -2
- package/dist/modules/catalog/data/validators.js +78 -0
- package/dist/modules/catalog/data/validators.js.map +2 -2
- package/dist/modules/catalog/di.js +18 -0
- package/dist/modules/catalog/di.js.map +2 -2
- package/dist/modules/catalog/lib/omnibus.js +285 -0
- package/dist/modules/catalog/lib/omnibus.js.map +7 -0
- package/dist/modules/catalog/lib/omnibusBackfill.js +146 -0
- package/dist/modules/catalog/lib/omnibusBackfill.js.map +7 -0
- package/dist/modules/catalog/lib/omnibusCache.js +89 -0
- package/dist/modules/catalog/lib/omnibusCache.js.map +7 -0
- package/dist/modules/catalog/lib/omnibusConfig.js +64 -0
- package/dist/modules/catalog/lib/omnibusConfig.js.map +7 -0
- package/dist/modules/catalog/lib/omnibusHistoryQueries.js +191 -0
- package/dist/modules/catalog/lib/omnibusHistoryQueries.js.map +7 -0
- package/dist/modules/catalog/lib/omnibusPresentedEntry.js +73 -0
- package/dist/modules/catalog/lib/omnibusPresentedEntry.js.map +7 -0
- package/dist/modules/catalog/lib/omnibusProductListEnrichment.js +94 -0
- package/dist/modules/catalog/lib/omnibusProductListEnrichment.js.map +7 -0
- package/dist/modules/catalog/lib/omnibusTypes.js +115 -0
- package/dist/modules/catalog/lib/omnibusTypes.js.map +7 -0
- package/dist/modules/catalog/lib/optionSchemaTranslations.js +55 -0
- package/dist/modules/catalog/lib/optionSchemaTranslations.js.map +7 -0
- package/dist/modules/catalog/lib/priceHistoryQuery.js +103 -0
- package/dist/modules/catalog/lib/priceHistoryQuery.js.map +7 -0
- package/dist/modules/catalog/lib/pricing.js +26 -2
- package/dist/modules/catalog/lib/pricing.js.map +2 -2
- package/dist/modules/catalog/lib/productFilters.js +273 -0
- package/dist/modules/catalog/lib/productFilters.js.map +7 -0
- package/dist/modules/catalog/lib/productScopeKeys.js +105 -0
- package/dist/modules/catalog/lib/productScopeKeys.js.map +7 -0
- package/dist/modules/catalog/lib/productScopeReindex.js +73 -0
- package/dist/modules/catalog/lib/productScopeReindex.js.map +7 -0
- package/dist/modules/catalog/migrations/Migration20261005105352_catalog.js +39 -0
- package/dist/modules/catalog/migrations/Migration20261005105352_catalog.js.map +7 -0
- package/dist/modules/catalog/migrations/Migration20261005150824_catalog.js +39 -0
- package/dist/modules/catalog/migrations/Migration20261005150824_catalog.js.map +7 -0
- package/dist/modules/catalog/services/catalogOmnibusService.js +480 -0
- package/dist/modules/catalog/services/catalogOmnibusService.js.map +7 -0
- package/dist/modules/catalog/setup.js +3 -1
- package/dist/modules/catalog/setup.js.map +2 -2
- package/dist/modules/catalog/subscribers/product-scope-keys-category-deleted.js +16 -0
- package/dist/modules/catalog/subscribers/product-scope-keys-category-deleted.js.map +7 -0
- package/dist/modules/catalog/subscribers/product-scope-keys-category-updated.js +17 -0
- package/dist/modules/catalog/subscribers/product-scope-keys-category-updated.js.map +7 -0
- package/dist/modules/catalog/translations.js +10 -0
- package/dist/modules/catalog/translations.js.map +2 -2
- package/dist/modules/configs/lib/upgrade-actions.js +28 -0
- package/dist/modules/configs/lib/upgrade-actions.js.map +2 -2
- package/dist/modules/customer_groups/api/customer-groups/[id]/terms/route.js +5 -0
- package/dist/modules/customer_groups/api/customer-groups/[id]/terms/route.js.map +2 -2
- package/dist/modules/customer_groups/components/CustomerGroupTermsSection.js +111 -3
- package/dist/modules/customer_groups/components/CustomerGroupTermsSection.js.map +2 -2
- package/dist/modules/customer_groups/components/customerGroupAssortmentScope.js +43 -0
- package/dist/modules/customer_groups/components/customerGroupAssortmentScope.js.map +7 -0
- package/dist/modules/customer_groups/data/entities.js +3 -0
- package/dist/modules/customer_groups/data/entities.js.map +2 -2
- package/dist/modules/customer_groups/data/validators.js +9 -0
- package/dist/modules/customer_groups/data/validators.js.map +2 -2
- package/dist/modules/customer_groups/migrations/Migration20261005110250_customer_groups.js +17 -0
- package/dist/modules/customer_groups/migrations/Migration20261005110250_customer_groups.js.map +7 -0
- package/dist/modules/customer_groups/services/customerGroupsService.js +62 -15
- package/dist/modules/customer_groups/services/customerGroupsService.js.map +2 -2
- package/dist/modules/ecommerce/acl.js +13 -0
- package/dist/modules/ecommerce/acl.js.map +7 -0
- package/dist/modules/ecommerce/api/domain-mappings/route.js +72 -0
- package/dist/modules/ecommerce/api/domain-mappings/route.js.map +7 -0
- package/dist/modules/ecommerce/api/openapi.js +27 -0
- package/dist/modules/ecommerce/api/openapi.js.map +7 -0
- package/dist/modules/ecommerce/api/store-channel-bindings/[id]/assortment-count/route.js +109 -0
- package/dist/modules/ecommerce/api/store-channel-bindings/[id]/assortment-count/route.js.map +7 -0
- package/dist/modules/ecommerce/api/store-channel-bindings/crud.js +265 -0
- package/dist/modules/ecommerce/api/store-channel-bindings/crud.js.map +7 -0
- package/dist/modules/ecommerce/api/store-channel-bindings/route.js +67 -0
- package/dist/modules/ecommerce/api/store-channel-bindings/route.js.map +7 -0
- package/dist/modules/ecommerce/api/store-domain-bindings/crud.js +296 -0
- package/dist/modules/ecommerce/api/store-domain-bindings/crud.js.map +7 -0
- package/dist/modules/ecommerce/api/store-domain-bindings/route.js +73 -0
- package/dist/modules/ecommerce/api/store-domain-bindings/route.js.map +7 -0
- package/dist/modules/ecommerce/api/storefront/categories/[slug]/route.js +87 -0
- package/dist/modules/ecommerce/api/storefront/categories/[slug]/route.js.map +7 -0
- package/dist/modules/ecommerce/api/storefront/categories/openapiSchemas.js +50 -0
- package/dist/modules/ecommerce/api/storefront/categories/openapiSchemas.js.map +7 -0
- package/dist/modules/ecommerce/api/storefront/categories/route.js +75 -0
- package/dist/modules/ecommerce/api/storefront/categories/route.js.map +7 -0
- package/dist/modules/ecommerce/api/storefront/context/route.js +163 -0
- package/dist/modules/ecommerce/api/storefront/context/route.js.map +7 -0
- package/dist/modules/ecommerce/api/storefront/products/[idOrHandle]/route.js +87 -0
- package/dist/modules/ecommerce/api/storefront/products/[idOrHandle]/route.js.map +7 -0
- package/dist/modules/ecommerce/api/storefront/products/openapiSchemas.js +177 -0
- package/dist/modules/ecommerce/api/storefront/products/openapiSchemas.js.map +7 -0
- package/dist/modules/ecommerce/api/storefront/products/route.js +78 -0
- package/dist/modules/ecommerce/api/storefront/products/route.js.map +7 -0
- package/dist/modules/ecommerce/api/storefront/search/suggest/openapiSchemas.js +25 -0
- package/dist/modules/ecommerce/api/storefront/search/suggest/openapiSchemas.js.map +7 -0
- package/dist/modules/ecommerce/api/storefront/search/suggest/route.js +75 -0
- package/dist/modules/ecommerce/api/storefront/search/suggest/route.js.map +7 -0
- package/dist/modules/ecommerce/api/storefront/storefrontRouteSupport.js +58 -0
- package/dist/modules/ecommerce/api/storefront/storefrontRouteSupport.js.map +7 -0
- package/dist/modules/ecommerce/api/stores/[id]/branding/route.js +183 -0
- package/dist/modules/ecommerce/api/stores/[id]/branding/route.js.map +7 -0
- package/dist/modules/ecommerce/api/stores/[id]/preview-branding/route.js +92 -0
- package/dist/modules/ecommerce/api/stores/[id]/preview-branding/route.js.map +7 -0
- package/dist/modules/ecommerce/api/stores/crud.js +401 -0
- package/dist/modules/ecommerce/api/stores/crud.js.map +7 -0
- package/dist/modules/ecommerce/api/stores/route.js +64 -0
- package/dist/modules/ecommerce/api/stores/route.js.map +7 -0
- package/dist/modules/ecommerce/backend/config/ecommerce/[id]/page.js +9 -0
- package/dist/modules/ecommerce/backend/config/ecommerce/[id]/page.js.map +7 -0
- package/dist/modules/ecommerce/backend/config/ecommerce/[id]/page.meta.js +16 -0
- package/dist/modules/ecommerce/backend/config/ecommerce/[id]/page.meta.js.map +7 -0
- package/dist/modules/ecommerce/backend/config/ecommerce/page.js +9 -0
- package/dist/modules/ecommerce/backend/config/ecommerce/page.js.map +7 -0
- package/dist/modules/ecommerce/backend/config/ecommerce/page.meta.js +16 -0
- package/dist/modules/ecommerce/backend/config/ecommerce/page.meta.js.map +7 -0
- package/dist/modules/ecommerce/commands/stores.js +110 -0
- package/dist/modules/ecommerce/commands/stores.js.map +7 -0
- package/dist/modules/ecommerce/components/ChannelAssortmentCount.js +90 -0
- package/dist/modules/ecommerce/components/ChannelAssortmentCount.js.map +7 -0
- package/dist/modules/ecommerce/components/ChannelBindingFormSections.js +33 -0
- package/dist/modules/ecommerce/components/ChannelBindingFormSections.js.map +7 -0
- package/dist/modules/ecommerce/components/ChannelLookupPicker.js +11 -0
- package/dist/modules/ecommerce/components/ChannelLookupPicker.js.map +7 -0
- package/dist/modules/ecommerce/components/DomainStatusWarning.js +24 -0
- package/dist/modules/ecommerce/components/DomainStatusWarning.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreAvailabilityDefaultsSection.js +190 -0
- package/dist/modules/ecommerce/components/StoreAvailabilityDefaultsSection.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreBrandingControls.js +67 -0
- package/dist/modules/ecommerce/components/StoreBrandingControls.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreBrandingPreview.js +69 -0
- package/dist/modules/ecommerce/components/StoreBrandingPreview.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreBrandingTab.js +218 -0
- package/dist/modules/ecommerce/components/StoreBrandingTab.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreChannelBindingDialog.js +270 -0
- package/dist/modules/ecommerce/components/StoreChannelBindingDialog.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreChannelsTab.js +313 -0
- package/dist/modules/ecommerce/components/StoreChannelsTab.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreCreateDialog.js +164 -0
- package/dist/modules/ecommerce/components/StoreCreateDialog.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreDomainBindingDialog.js +162 -0
- package/dist/modules/ecommerce/components/StoreDomainBindingDialog.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreDomainsTab.js +357 -0
- package/dist/modules/ecommerce/components/StoreDomainsTab.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreEditPage.js +155 -0
- package/dist/modules/ecommerce/components/StoreEditPage.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreGeneralTab.js +167 -0
- package/dist/modules/ecommerce/components/StoreGeneralTab.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreListPage.js +290 -0
- package/dist/modules/ecommerce/components/StoreListPage.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreSeoTab.js +132 -0
- package/dist/modules/ecommerce/components/StoreSeoTab.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreStatusBadge.js +20 -0
- package/dist/modules/ecommerce/components/StoreStatusBadge.js.map +7 -0
- package/dist/modules/ecommerce/components/channelLookups.js +28 -0
- package/dist/modules/ecommerce/components/channelLookups.js.map +7 -0
- package/dist/modules/ecommerce/components/storeAdmin.js +41 -0
- package/dist/modules/ecommerce/components/storeAdmin.js.map +7 -0
- package/dist/modules/ecommerce/components/storeBrandingForm.js +197 -0
- package/dist/modules/ecommerce/components/storeBrandingForm.js.map +7 -0
- package/dist/modules/ecommerce/components/storeChannels.js +140 -0
- package/dist/modules/ecommerce/components/storeChannels.js.map +7 -0
- package/dist/modules/ecommerce/components/storeDomains.js +167 -0
- package/dist/modules/ecommerce/components/storeDomains.js.map +7 -0
- package/dist/modules/ecommerce/components/storeEditTabs.js +62 -0
- package/dist/modules/ecommerce/components/storeEditTabs.js.map +7 -0
- package/dist/modules/ecommerce/components/storeGeneral.js +95 -0
- package/dist/modules/ecommerce/components/storeGeneral.js.map +7 -0
- package/dist/modules/ecommerce/components/storeSeo.js +69 -0
- package/dist/modules/ecommerce/components/storeSeo.js.map +7 -0
- package/dist/modules/ecommerce/components/useChannelAssortmentCount.js +45 -0
- package/dist/modules/ecommerce/components/useChannelAssortmentCount.js.map +7 -0
- package/dist/modules/ecommerce/components/useStoreAccess.js +28 -0
- package/dist/modules/ecommerce/components/useStoreAccess.js.map +7 -0
- package/dist/modules/ecommerce/data/enrichers.js +157 -0
- package/dist/modules/ecommerce/data/enrichers.js.map +7 -0
- package/dist/modules/ecommerce/data/entities.js +197 -0
- package/dist/modules/ecommerce/data/entities.js.map +7 -0
- package/dist/modules/ecommerce/data/validators.js +406 -0
- package/dist/modules/ecommerce/data/validators.js.map +7 -0
- package/dist/modules/ecommerce/di.js +11 -0
- package/dist/modules/ecommerce/di.js.map +7 -0
- package/dist/modules/ecommerce/events.js +27 -0
- package/dist/modules/ecommerce/events.js.map +7 -0
- package/dist/modules/ecommerce/index.js +16 -0
- package/dist/modules/ecommerce/index.js.map +7 -0
- package/dist/modules/ecommerce/lib/assortmentCount.js +112 -0
- package/dist/modules/ecommerce/lib/assortmentCount.js.map +7 -0
- package/dist/modules/ecommerce/lib/brandingStyles.js +184 -0
- package/dist/modules/ecommerce/lib/brandingStyles.js.map +7 -0
- package/dist/modules/ecommerce/lib/buyerContext.js +284 -0
- package/dist/modules/ecommerce/lib/buyerContext.js.map +7 -0
- package/dist/modules/ecommerce/lib/cacheInvalidation.js +186 -0
- package/dist/modules/ecommerce/lib/cacheInvalidation.js.map +7 -0
- package/dist/modules/ecommerce/lib/cacheKeys.js +236 -0
- package/dist/modules/ecommerce/lib/cacheKeys.js.map +7 -0
- package/dist/modules/ecommerce/lib/createConflictRecheck.js +29 -0
- package/dist/modules/ecommerce/lib/createConflictRecheck.js.map +7 -0
- package/dist/modules/ecommerce/lib/crudEvents.js +94 -0
- package/dist/modules/ecommerce/lib/crudEvents.js.map +7 -0
- package/dist/modules/ecommerce/lib/crudSupport.js +64 -0
- package/dist/modules/ecommerce/lib/crudSupport.js.map +7 -0
- package/dist/modules/ecommerce/lib/domainBindingRebind.js +99 -0
- package/dist/modules/ecommerce/lib/domainBindingRebind.js.map +7 -0
- package/dist/modules/ecommerce/lib/domainMappingSummaries.js +39 -0
- package/dist/modules/ecommerce/lib/domainMappingSummaries.js.map +7 -0
- package/dist/modules/ecommerce/lib/exclusiveFlag.js +57 -0
- package/dist/modules/ecommerce/lib/exclusiveFlag.js.map +7 -0
- package/dist/modules/ecommerce/lib/references.js +70 -0
- package/dist/modules/ecommerce/lib/references.js.map +7 -0
- package/dist/modules/ecommerce/lib/seedDraftStore.js +99 -0
- package/dist/modules/ecommerce/lib/seedDraftStore.js.map +7 -0
- package/dist/modules/ecommerce/lib/storeBindingCascade.js +109 -0
- package/dist/modules/ecommerce/lib/storeBindingCascade.js.map +7 -0
- package/dist/modules/ecommerce/lib/storeBranding.js +40 -0
- package/dist/modules/ecommerce/lib/storeBranding.js.map +7 -0
- package/dist/modules/ecommerce/lib/storeBrandingRoute.js +76 -0
- package/dist/modules/ecommerce/lib/storeBrandingRoute.js.map +7 -0
- package/dist/modules/ecommerce/lib/storeContext.js +393 -0
- package/dist/modules/ecommerce/lib/storeContext.js.map +7 -0
- package/dist/modules/ecommerce/lib/storeContextService.js +43 -0
- package/dist/modules/ecommerce/lib/storeContextService.js.map +7 -0
- package/dist/modules/ecommerce/lib/storeLocale.js +60 -0
- package/dist/modules/ecommerce/lib/storeLocale.js.map +7 -0
- package/dist/modules/ecommerce/lib/storeNotifications.js +41 -0
- package/dist/modules/ecommerce/lib/storeNotifications.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontCatalogSupport.js +248 -0
- package/dist/modules/ecommerce/lib/storefrontCatalogSupport.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontCategories.js +241 -0
- package/dist/modules/ecommerce/lib/storefrontCategories.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontCategoryCache.js +71 -0
- package/dist/modules/ecommerce/lib/storefrontCategoryCache.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontDetail.js +447 -0
- package/dist/modules/ecommerce/lib/storefrontDetail.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontFacets.js +441 -0
- package/dist/modules/ecommerce/lib/storefrontFacets.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontOptionFilter.js +15 -0
- package/dist/modules/ecommerce/lib/storefrontOptionFilter.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontPricing.js +393 -0
- package/dist/modules/ecommerce/lib/storefrontPricing.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontProductCache.js +90 -0
- package/dist/modules/ecommerce/lib/storefrontProductCache.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontProductScope.js +107 -0
- package/dist/modules/ecommerce/lib/storefrontProductScope.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontProducts.js +670 -0
- package/dist/modules/ecommerce/lib/storefrontProducts.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontQuery.js +156 -0
- package/dist/modules/ecommerce/lib/storefrontQuery.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontRateLimit.js +99 -0
- package/dist/modules/ecommerce/lib/storefrontRateLimit.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontSearch.js +303 -0
- package/dist/modules/ecommerce/lib/storefrontSearch.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontSearchCache.js +35 -0
- package/dist/modules/ecommerce/lib/storefrontSearchCache.js.map +7 -0
- package/dist/modules/ecommerce/lib/subscriberSupport.js +33 -0
- package/dist/modules/ecommerce/lib/subscriberSupport.js.map +7 -0
- package/dist/modules/ecommerce/lib/types.js +1 -0
- package/dist/modules/ecommerce/lib/types.js.map +7 -0
- package/dist/modules/ecommerce/migrations/Migration20261005113233_ecommerce.js +32 -0
- package/dist/modules/ecommerce/migrations/Migration20261005113233_ecommerce.js.map +7 -0
- package/dist/modules/ecommerce/migrations/Migration20261006112733_ecommerce.js +17 -0
- package/dist/modules/ecommerce/migrations/Migration20261006112733_ecommerce.js.map +7 -0
- package/dist/modules/ecommerce/notifications.client.js +47 -0
- package/dist/modules/ecommerce/notifications.client.js.map +7 -0
- package/dist/modules/ecommerce/notifications.js +48 -0
- package/dist/modules/ecommerce/notifications.js.map +7 -0
- package/dist/modules/ecommerce/search.js +83 -0
- package/dist/modules/ecommerce/search.js.map +7 -0
- package/dist/modules/ecommerce/setup.js +17 -0
- package/dist/modules/ecommerce/setup.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/assortment-empty-notifications.js +14 -0
- package/dist/modules/ecommerce/subscribers/assortment-empty-notifications.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/availability-policy-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/availability-policy-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/catalog-category-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/catalog-category-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/catalog-price-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/catalog-price-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/catalog-product-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/catalog-product-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/catalog-variant-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/catalog-variant-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/customer-group-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/customer-group-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/customer-group-membership-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/customer-group-membership-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/customer-group-terms-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/customer-group-terms-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/customer-user-deleted-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/customer-user-deleted-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/customer-user-updated-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/customer-user-updated-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/domain-binding-rebind.js +16 -0
- package/dist/modules/ecommerce/subscribers/domain-binding-rebind.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/domain-mapping-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/domain-mapping-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/store-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/store-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/store-channel-binding-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/store-channel-binding-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/store-domain-binding-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/store-domain-binding-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/store-notifications.js +14 -0
- package/dist/modules/ecommerce/subscribers/store-notifications.js.map +7 -0
- package/dist/modules/query_index/lib/batch.js +23 -9
- package/dist/modules/query_index/lib/batch.js.map +2 -2
- package/dist/modules/query_index/lib/doc-enrichers.js +212 -0
- package/dist/modules/query_index/lib/doc-enrichers.js.map +7 -0
- package/dist/modules/query_index/lib/document.js +2 -0
- package/dist/modules/query_index/lib/document.js.map +2 -2
- package/dist/modules/query_index/lib/engine.js +68 -1
- package/dist/modules/query_index/lib/engine.js.map +2 -2
- package/dist/modules/query_index/lib/indexer.js +7 -0
- package/dist/modules/query_index/lib/indexer.js.map +2 -2
- package/dist/modules/query_index/lib/search-tokens.js +2 -0
- package/dist/modules/query_index/lib/search-tokens.js.map +2 -2
- package/dist/modules/query_index/migrations/Migration20261005201500_query_index.js +17 -0
- package/dist/modules/query_index/migrations/Migration20261005201500_query_index.js.map +7 -0
- package/dist/modules/translations/components/TranslationManager.js +3 -3
- package/dist/modules/translations/components/TranslationManager.js.map +2 -2
- package/dist/modules/translations/data/validators.js +1 -1
- package/dist/modules/translations/data/validators.js.map +1 -1
- package/dist/modules/translations/lib/batch.js +21 -1
- package/dist/modules/translations/lib/batch.js.map +2 -2
- package/dist/modules/translations/lib/helpers.js +3 -0
- package/dist/modules/translations/lib/helpers.js.map +2 -2
- package/dist/modules/translations/lib/resolve-field-list.js +14 -2
- package/dist/modules/translations/lib/resolve-field-list.js.map +2 -2
- package/generated/entities/catalog_price_history_entry/index.ts +25 -0
- package/generated/entities/customer_group_terms/index.ts +1 -0
- package/generated/entities/ecommerce_store/index.ts +15 -0
- package/generated/entities/ecommerce_store_channel_binding/index.ts +13 -0
- package/generated/entities/ecommerce_store_domain_binding/index.ts +10 -0
- package/generated/entities.ids.generated.ts +8 -1
- package/generated/entity-fields-registry.ts +72 -0
- package/package.json +7 -7
- package/src/helpers/integration/customerGroupsFixtures.ts +2 -0
- package/src/modules/auth/i18n/de.json +8 -0
- package/src/modules/auth/i18n/en.json +8 -0
- package/src/modules/auth/i18n/es.json +8 -0
- package/src/modules/auth/i18n/ko.json +8 -0
- package/src/modules/auth/i18n/pl.json +8 -0
- package/src/modules/catalog/AGENTS.md +5 -2
- package/src/modules/catalog/acl.ts +7 -0
- package/src/modules/catalog/api/config/omnibus/route.ts +204 -0
- package/src/modules/catalog/api/prices/history/route.ts +139 -0
- package/src/modules/catalog/api/prices/omnibus-preview/route.ts +202 -0
- package/src/modules/catalog/api/products/route.ts +53 -308
- package/src/modules/catalog/api/tags/route.ts +6 -0
- package/src/modules/catalog/backend/config/catalog/page.tsx +2 -0
- package/src/modules/catalog/cli.ts +84 -1
- package/src/modules/catalog/commands/categories.ts +54 -2
- package/src/modules/catalog/commands/prices.ts +43 -1
- package/src/modules/catalog/commands/products.ts +84 -1
- package/src/modules/catalog/commands/variants.ts +7 -0
- package/src/modules/catalog/components/OmnibusSettings.tsx +832 -0
- package/src/modules/catalog/components/PriceEditorOmnibusRow.tsx +219 -0
- package/src/modules/catalog/components/lookupSources.ts +29 -0
- package/src/modules/catalog/components/prices/PriceScopeSelectors.tsx +9 -0
- package/src/modules/catalog/components/products/VariantBuilder.tsx +9 -0
- package/src/modules/catalog/data/entities.ts +123 -1
- package/src/modules/catalog/data/validators.ts +118 -0
- package/src/modules/catalog/di.ts +23 -0
- package/src/modules/catalog/i18n/de.json +77 -0
- package/src/modules/catalog/i18n/en.json +77 -0
- package/src/modules/catalog/i18n/es.json +77 -0
- package/src/modules/catalog/i18n/ko.json +77 -0
- package/src/modules/catalog/i18n/pl.json +77 -0
- package/src/modules/catalog/lib/omnibus.ts +406 -0
- package/src/modules/catalog/lib/omnibusBackfill.ts +198 -0
- package/src/modules/catalog/lib/omnibusCache.ts +111 -0
- package/src/modules/catalog/lib/omnibusConfig.ts +73 -0
- package/src/modules/catalog/lib/omnibusHistoryQueries.ts +248 -0
- package/src/modules/catalog/lib/omnibusPresentedEntry.ts +76 -0
- package/src/modules/catalog/lib/omnibusProductListEnrichment.ts +125 -0
- package/src/modules/catalog/lib/omnibusTypes.ts +202 -0
- package/src/modules/catalog/lib/optionSchemaTranslations.ts +55 -0
- package/src/modules/catalog/lib/priceHistoryQuery.ts +155 -0
- package/src/modules/catalog/lib/pricing.ts +42 -4
- package/src/modules/catalog/lib/productFilters.ts +349 -0
- package/src/modules/catalog/lib/productScopeKeys.ts +162 -0
- package/src/modules/catalog/lib/productScopeReindex.ts +139 -0
- package/src/modules/catalog/migrations/.snapshot-open-mercato.json +572 -0
- package/src/modules/catalog/migrations/Migration20261005105352_catalog.ts +39 -0
- package/src/modules/catalog/migrations/Migration20261005150824_catalog.ts +39 -0
- package/src/modules/catalog/services/catalogOmnibusService.ts +642 -0
- package/src/modules/catalog/setup.ts +2 -0
- package/src/modules/catalog/subscribers/product-scope-keys-category-deleted.ts +18 -0
- package/src/modules/catalog/subscribers/product-scope-keys-category-updated.ts +19 -0
- package/src/modules/catalog/translations.ts +12 -0
- package/src/modules/configs/i18n/de.json +5 -1
- package/src/modules/configs/i18n/en.json +5 -1
- package/src/modules/configs/i18n/es.json +5 -1
- package/src/modules/configs/i18n/ko.json +5 -1
- package/src/modules/configs/i18n/pl.json +5 -1
- package/src/modules/configs/lib/upgrade-actions.ts +35 -0
- package/src/modules/customer_groups/api/customer-groups/[id]/terms/route.ts +7 -0
- package/src/modules/customer_groups/components/CustomerGroupTermsSection.tsx +124 -2
- package/src/modules/customer_groups/components/customerGroupAssortmentScope.ts +46 -0
- package/src/modules/customer_groups/data/entities.ts +4 -0
- package/src/modules/customer_groups/data/validators.ts +14 -0
- package/src/modules/customer_groups/i18n/de.json +12 -0
- package/src/modules/customer_groups/i18n/en.json +12 -0
- package/src/modules/customer_groups/i18n/es.json +12 -0
- package/src/modules/customer_groups/i18n/ko.json +12 -0
- package/src/modules/customer_groups/i18n/pl.json +12 -0
- package/src/modules/customer_groups/migrations/.snapshot-open-mercato.json +17 -0
- package/src/modules/customer_groups/migrations/Migration20261005110250_customer_groups.ts +15 -0
- package/src/modules/customer_groups/services/customerGroupsService.ts +74 -23
- package/src/modules/ecommerce/acl.ts +9 -0
- package/src/modules/ecommerce/api/domain-mappings/route.ts +76 -0
- package/src/modules/ecommerce/api/openapi.ts +28 -0
- package/src/modules/ecommerce/api/store-channel-bindings/[id]/assortment-count/route.ts +141 -0
- package/src/modules/ecommerce/api/store-channel-bindings/crud.ts +283 -0
- package/src/modules/ecommerce/api/store-channel-bindings/route.ts +67 -0
- package/src/modules/ecommerce/api/store-domain-bindings/crud.ts +324 -0
- package/src/modules/ecommerce/api/store-domain-bindings/route.ts +73 -0
- package/src/modules/ecommerce/api/storefront/categories/[slug]/route.ts +94 -0
- package/src/modules/ecommerce/api/storefront/categories/openapiSchemas.ts +63 -0
- package/src/modules/ecommerce/api/storefront/categories/route.ts +78 -0
- package/src/modules/ecommerce/api/storefront/context/route.ts +203 -0
- package/src/modules/ecommerce/api/storefront/products/[idOrHandle]/route.ts +94 -0
- package/src/modules/ecommerce/api/storefront/products/openapiSchemas.ts +193 -0
- package/src/modules/ecommerce/api/storefront/products/route.ts +81 -0
- package/src/modules/ecommerce/api/storefront/search/suggest/openapiSchemas.ts +25 -0
- package/src/modules/ecommerce/api/storefront/search/suggest/route.ts +78 -0
- package/src/modules/ecommerce/api/storefront/storefrontRouteSupport.ts +68 -0
- package/src/modules/ecommerce/api/stores/[id]/branding/route.ts +205 -0
- package/src/modules/ecommerce/api/stores/[id]/preview-branding/route.ts +99 -0
- package/src/modules/ecommerce/api/stores/crud.ts +457 -0
- package/src/modules/ecommerce/api/stores/route.ts +66 -0
- package/src/modules/ecommerce/backend/config/ecommerce/[id]/page.meta.ts +12 -0
- package/src/modules/ecommerce/backend/config/ecommerce/[id]/page.tsx +5 -0
- package/src/modules/ecommerce/backend/config/ecommerce/page.meta.ts +12 -0
- package/src/modules/ecommerce/backend/config/ecommerce/page.tsx +5 -0
- package/src/modules/ecommerce/commands/stores.ts +154 -0
- package/src/modules/ecommerce/components/ChannelAssortmentCount.tsx +138 -0
- package/src/modules/ecommerce/components/ChannelBindingFormSections.tsx +40 -0
- package/src/modules/ecommerce/components/ChannelLookupPicker.tsx +5 -0
- package/src/modules/ecommerce/components/DomainStatusWarning.tsx +32 -0
- package/src/modules/ecommerce/components/StoreAvailabilityDefaultsSection.tsx +219 -0
- package/src/modules/ecommerce/components/StoreBrandingControls.tsx +73 -0
- package/src/modules/ecommerce/components/StoreBrandingPreview.tsx +77 -0
- package/src/modules/ecommerce/components/StoreBrandingTab.tsx +242 -0
- package/src/modules/ecommerce/components/StoreChannelBindingDialog.tsx +306 -0
- package/src/modules/ecommerce/components/StoreChannelsTab.tsx +375 -0
- package/src/modules/ecommerce/components/StoreCreateDialog.tsx +204 -0
- package/src/modules/ecommerce/components/StoreDomainBindingDialog.tsx +198 -0
- package/src/modules/ecommerce/components/StoreDomainsTab.tsx +426 -0
- package/src/modules/ecommerce/components/StoreEditPage.tsx +193 -0
- package/src/modules/ecommerce/components/StoreGeneralTab.tsx +189 -0
- package/src/modules/ecommerce/components/StoreListPage.tsx +326 -0
- package/src/modules/ecommerce/components/StoreSeoTab.tsx +152 -0
- package/src/modules/ecommerce/components/StoreStatusBadge.tsx +22 -0
- package/src/modules/ecommerce/components/channelLookups.ts +24 -0
- package/src/modules/ecommerce/components/storeAdmin.ts +56 -0
- package/src/modules/ecommerce/components/storeBrandingForm.ts +230 -0
- package/src/modules/ecommerce/components/storeChannels.ts +210 -0
- package/src/modules/ecommerce/components/storeDomains.ts +221 -0
- package/src/modules/ecommerce/components/storeEditTabs.tsx +76 -0
- package/src/modules/ecommerce/components/storeGeneral.ts +155 -0
- package/src/modules/ecommerce/components/storeSeo.ts +78 -0
- package/src/modules/ecommerce/components/useChannelAssortmentCount.ts +67 -0
- package/src/modules/ecommerce/components/useStoreAccess.ts +36 -0
- package/src/modules/ecommerce/data/enrichers.ts +213 -0
- package/src/modules/ecommerce/data/entities.ts +168 -0
- package/src/modules/ecommerce/data/validators.ts +587 -0
- package/src/modules/ecommerce/di.ts +9 -0
- package/src/modules/ecommerce/events.ts +27 -0
- package/src/modules/ecommerce/i18n/de.json +341 -0
- package/src/modules/ecommerce/i18n/en.json +341 -0
- package/src/modules/ecommerce/i18n/es.json +341 -0
- package/src/modules/ecommerce/i18n/ko.json +341 -0
- package/src/modules/ecommerce/i18n/pl.json +341 -0
- package/src/modules/ecommerce/index.ts +14 -0
- package/src/modules/ecommerce/lib/assortmentCount.ts +175 -0
- package/src/modules/ecommerce/lib/brandingStyles.ts +215 -0
- package/src/modules/ecommerce/lib/buyerContext.ts +401 -0
- package/src/modules/ecommerce/lib/cacheInvalidation.ts +302 -0
- package/src/modules/ecommerce/lib/cacheKeys.ts +340 -0
- package/src/modules/ecommerce/lib/createConflictRecheck.ts +31 -0
- package/src/modules/ecommerce/lib/crudEvents.ts +128 -0
- package/src/modules/ecommerce/lib/crudSupport.ts +85 -0
- package/src/modules/ecommerce/lib/domainBindingRebind.ts +138 -0
- package/src/modules/ecommerce/lib/domainMappingSummaries.ts +71 -0
- package/src/modules/ecommerce/lib/exclusiveFlag.ts +90 -0
- package/src/modules/ecommerce/lib/references.ts +93 -0
- package/src/modules/ecommerce/lib/seedDraftStore.ts +125 -0
- package/src/modules/ecommerce/lib/storeBindingCascade.ts +138 -0
- package/src/modules/ecommerce/lib/storeBranding.ts +48 -0
- package/src/modules/ecommerce/lib/storeBrandingRoute.ts +91 -0
- package/src/modules/ecommerce/lib/storeContext.ts +669 -0
- package/src/modules/ecommerce/lib/storeContextService.ts +71 -0
- package/src/modules/ecommerce/lib/storeLocale.ts +88 -0
- package/src/modules/ecommerce/lib/storeNotifications.ts +55 -0
- package/src/modules/ecommerce/lib/storefrontCatalogSupport.ts +427 -0
- package/src/modules/ecommerce/lib/storefrontCategories.ts +419 -0
- package/src/modules/ecommerce/lib/storefrontCategoryCache.ts +113 -0
- package/src/modules/ecommerce/lib/storefrontDetail.ts +654 -0
- package/src/modules/ecommerce/lib/storefrontFacets.ts +697 -0
- package/src/modules/ecommerce/lib/storefrontOptionFilter.ts +44 -0
- package/src/modules/ecommerce/lib/storefrontPricing.ts +583 -0
- package/src/modules/ecommerce/lib/storefrontProductCache.ts +126 -0
- package/src/modules/ecommerce/lib/storefrontProductScope.ts +181 -0
- package/src/modules/ecommerce/lib/storefrontProducts.ts +963 -0
- package/src/modules/ecommerce/lib/storefrontQuery.ts +215 -0
- package/src/modules/ecommerce/lib/storefrontRateLimit.ts +126 -0
- package/src/modules/ecommerce/lib/storefrontSearch.ts +454 -0
- package/src/modules/ecommerce/lib/storefrontSearchCache.ts +49 -0
- package/src/modules/ecommerce/lib/subscriberSupport.ts +41 -0
- package/src/modules/ecommerce/lib/types.ts +69 -0
- package/src/modules/ecommerce/migrations/.snapshot-open-mercato.json +829 -0
- package/src/modules/ecommerce/migrations/Migration20261005113233_ecommerce.ts +32 -0
- package/src/modules/ecommerce/migrations/Migration20261006112733_ecommerce.ts +15 -0
- package/src/modules/ecommerce/notifications.client.ts +46 -0
- package/src/modules/ecommerce/notifications.ts +46 -0
- package/src/modules/ecommerce/search.ts +93 -0
- package/src/modules/ecommerce/setup.ts +15 -0
- package/src/modules/ecommerce/subscribers/assortment-empty-notifications.ts +12 -0
- package/src/modules/ecommerce/subscribers/availability-policy-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/catalog-category-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/catalog-price-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/catalog-product-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/catalog-variant-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/customer-group-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/customer-group-membership-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/customer-group-terms-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/customer-user-deleted-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/customer-user-updated-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/domain-binding-rebind.ts +14 -0
- package/src/modules/ecommerce/subscribers/domain-mapping-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/store-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/store-channel-binding-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/store-domain-binding-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/store-notifications.ts +12 -0
- package/src/modules/query_index/lib/batch.ts +25 -9
- package/src/modules/query_index/lib/doc-enrichers.ts +306 -0
- package/src/modules/query_index/lib/document.ts +2 -0
- package/src/modules/query_index/lib/engine.ts +58 -1
- package/src/modules/query_index/lib/indexer.ts +8 -0
- package/src/modules/query_index/lib/search-tokens.ts +2 -0
- package/src/modules/query_index/migrations/Migration20261005201500_query_index.ts +42 -0
- package/src/modules/translations/components/TranslationManager.tsx +3 -3
- package/src/modules/translations/data/validators.ts +1 -1
- package/src/modules/translations/lib/batch.ts +47 -0
- package/src/modules/translations/lib/helpers.ts +4 -0
- package/src/modules/translations/lib/resolve-field-list.ts +16 -2
|
@@ -0,0 +1,832 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import { Plus, Trash2 } from 'lucide-react'
|
|
5
|
+
import { Alert, AlertDescription, AlertTitle } from '@open-mercato/ui/primitives/alert'
|
|
6
|
+
import { Button } from '@open-mercato/ui/primitives/button'
|
|
7
|
+
import { IconButton } from '@open-mercato/ui/primitives/icon-button'
|
|
8
|
+
import { Input } from '@open-mercato/ui/primitives/input'
|
|
9
|
+
import { FormField } from '@open-mercato/ui/primitives/form-field'
|
|
10
|
+
import { SwitchField } from '@open-mercato/ui/primitives/switch-field'
|
|
11
|
+
import { TagInput } from '@open-mercato/ui/primitives/tag-input'
|
|
12
|
+
import {
|
|
13
|
+
Select,
|
|
14
|
+
SelectContent,
|
|
15
|
+
SelectItem,
|
|
16
|
+
SelectTrigger,
|
|
17
|
+
SelectValue,
|
|
18
|
+
} from '@open-mercato/ui/primitives/select'
|
|
19
|
+
import { LoadingMessage, ErrorMessage } from '@open-mercato/ui/backend/detail'
|
|
20
|
+
import { flash } from '@open-mercato/ui/backend/FlashMessages'
|
|
21
|
+
import { useBackendChrome } from '@open-mercato/ui/backend/BackendChromeProvider'
|
|
22
|
+
import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'
|
|
23
|
+
import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
|
|
24
|
+
import { hasFeature } from '@open-mercato/shared/security/features'
|
|
25
|
+
import { useOrganizationScopeDetail, useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
|
|
26
|
+
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
27
|
+
import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
28
|
+
import { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'
|
|
29
|
+
import type {
|
|
30
|
+
OmnibusConfig,
|
|
31
|
+
OmnibusMinimizationAxis,
|
|
32
|
+
OmnibusNoChannelMode,
|
|
33
|
+
} from '../lib/omnibusTypes'
|
|
34
|
+
import { PriceChannelSelect, PricePriceKindSelect } from './prices/PriceScopeSelectors'
|
|
35
|
+
|
|
36
|
+
const logger = createLogger('catalog').child({ component: 'OmnibusSettings' })
|
|
37
|
+
|
|
38
|
+
const OMNIBUS_CONFIG_PATH = '/api/catalog/config/omnibus'
|
|
39
|
+
const MUTATION_CONTEXT_ID = 'catalog-omnibus-settings'
|
|
40
|
+
const DEFAULT_LOOKBACK_DAYS = 30
|
|
41
|
+
const MIN_LOOKBACK_DAYS = 1
|
|
42
|
+
const MAX_LOOKBACK_DAYS = 365
|
|
43
|
+
const COUNTRY_CODE_PATTERN = /^[A-Z]{2}$/
|
|
44
|
+
|
|
45
|
+
type StoredOmnibusConfig = Partial<OmnibusConfig>
|
|
46
|
+
|
|
47
|
+
type ChannelAxisValue = OmnibusMinimizationAxis | 'inherit'
|
|
48
|
+
|
|
49
|
+
type OmnibusChannelRow = {
|
|
50
|
+
key: string
|
|
51
|
+
channelId: string
|
|
52
|
+
presentedPriceKindId: string
|
|
53
|
+
countryCode: string
|
|
54
|
+
lookbackDays: string
|
|
55
|
+
minimizationAxis: ChannelAxisValue
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
type OmnibusFormState = {
|
|
59
|
+
enabled: boolean
|
|
60
|
+
lookbackDays: string
|
|
61
|
+
enabledCountryCodes: string[]
|
|
62
|
+
noChannelMode: OmnibusNoChannelMode
|
|
63
|
+
minimizationAxis: OmnibusMinimizationAxis
|
|
64
|
+
defaultPresentedPriceKindId: string
|
|
65
|
+
channels: OmnibusChannelRow[]
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
type OmnibusChannelPatch = {
|
|
69
|
+
presentedPriceKindId: string
|
|
70
|
+
countryCode?: string
|
|
71
|
+
lookbackDays?: number
|
|
72
|
+
minimizationAxis?: OmnibusMinimizationAxis
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
type OmnibusConfigPatchBody = {
|
|
76
|
+
enabled: boolean
|
|
77
|
+
lookbackDays: number
|
|
78
|
+
enabledCountryCodes: string[]
|
|
79
|
+
noChannelMode: OmnibusNoChannelMode
|
|
80
|
+
minimizationAxis: OmnibusMinimizationAxis
|
|
81
|
+
defaultPresentedPriceKindId: string | null
|
|
82
|
+
channels: Record<string, OmnibusChannelPatch>
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
type FieldErrors = Record<string, string>
|
|
86
|
+
|
|
87
|
+
type OmnibusMutationContext = {
|
|
88
|
+
formId: string
|
|
89
|
+
resourceKind: string
|
|
90
|
+
resourceId: string
|
|
91
|
+
retryLastMutation: () => Promise<boolean>
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
type OmnibusErrorBody = {
|
|
95
|
+
error?: unknown
|
|
96
|
+
field?: unknown
|
|
97
|
+
channels?: unknown
|
|
98
|
+
details?: { fieldErrors?: Record<string, unknown> } | null
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
class OmnibusSaveError extends Error {
|
|
102
|
+
readonly status: number
|
|
103
|
+
readonly body: OmnibusErrorBody | null
|
|
104
|
+
|
|
105
|
+
constructor(status: number, body: OmnibusErrorBody | null) {
|
|
106
|
+
super(`[internal] catalog omnibus config save failed with status ${status}`)
|
|
107
|
+
this.status = status
|
|
108
|
+
this.body = body
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
let rowSequence = 0
|
|
113
|
+
|
|
114
|
+
function nextRowKey(): string {
|
|
115
|
+
rowSequence += 1
|
|
116
|
+
return `omnibus-channel-${rowSequence}`
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function toFormState(config: StoredOmnibusConfig | null): OmnibusFormState {
|
|
120
|
+
const channels = Object.entries(config?.channels ?? {}).map(([channelId, channel]): OmnibusChannelRow => ({
|
|
121
|
+
key: nextRowKey(),
|
|
122
|
+
channelId,
|
|
123
|
+
presentedPriceKindId: channel.presentedPriceKindId ?? '',
|
|
124
|
+
countryCode: channel.countryCode ?? '',
|
|
125
|
+
lookbackDays: typeof channel.lookbackDays === 'number' ? String(channel.lookbackDays) : '',
|
|
126
|
+
minimizationAxis: channel.minimizationAxis ?? 'inherit',
|
|
127
|
+
}))
|
|
128
|
+
return {
|
|
129
|
+
enabled: config?.enabled === true,
|
|
130
|
+
lookbackDays: String(config?.lookbackDays ?? DEFAULT_LOOKBACK_DAYS),
|
|
131
|
+
enabledCountryCodes: Array.isArray(config?.enabledCountryCodes) ? [...config.enabledCountryCodes] : [],
|
|
132
|
+
noChannelMode: config?.noChannelMode ?? 'best_effort',
|
|
133
|
+
minimizationAxis: config?.minimizationAxis ?? 'gross',
|
|
134
|
+
defaultPresentedPriceKindId: config?.defaultPresentedPriceKindId ?? '',
|
|
135
|
+
channels,
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function parseLookbackDays(raw: string): number | null {
|
|
140
|
+
const trimmed = raw.trim()
|
|
141
|
+
if (!/^\d+$/.test(trimmed)) return null
|
|
142
|
+
const value = Number(trimmed)
|
|
143
|
+
if (!Number.isInteger(value) || value < MIN_LOOKBACK_DAYS || value > MAX_LOOKBACK_DAYS) return null
|
|
144
|
+
return value
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function channelErrorKey(rowKey: string, field: string): string {
|
|
148
|
+
return `channels.${rowKey}.${field}`
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
152
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function asStoredConfig(value: unknown): StoredOmnibusConfig | null {
|
|
156
|
+
return isRecord(value) ? (value as StoredOmnibusConfig) : null
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function listStaleBackfillScopes(stored: StoredOmnibusConfig | null, form: OmnibusFormState): string[] {
|
|
160
|
+
const coverage = stored?.backfillCoverage ?? {}
|
|
161
|
+
const globalLookback = parseLookbackDays(form.lookbackDays)
|
|
162
|
+
if (globalLookback === null) return []
|
|
163
|
+
const channelLookbacks = new Map(
|
|
164
|
+
form.channels
|
|
165
|
+
.filter((row) => row.channelId)
|
|
166
|
+
.map((row) => [row.channelId, parseLookbackDays(row.lookbackDays) ?? globalLookback]),
|
|
167
|
+
)
|
|
168
|
+
return Object.entries(coverage)
|
|
169
|
+
.filter(([scopeId, entry]) => {
|
|
170
|
+
const effective = scopeId ? channelLookbacks.get(scopeId) ?? globalLookback : globalLookback
|
|
171
|
+
return entry.lookbackDays < effective
|
|
172
|
+
})
|
|
173
|
+
.map(([scopeId]) => scopeId)
|
|
174
|
+
.sort((left, right) => (left < right ? -1 : left > right ? 1 : 0))
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function OmnibusSettings() {
|
|
178
|
+
const t = useT()
|
|
179
|
+
const scopeVersion = useOrganizationScopeVersion()
|
|
180
|
+
const scopeTenantId = useOrganizationScopeDetail().tenantId
|
|
181
|
+
const { payload: chromePayload, isReady: chromeReady } = useBackendChrome()
|
|
182
|
+
const grantedFeatures = chromePayload?.grantedFeatures
|
|
183
|
+
const canManage = hasFeature(grantedFeatures, 'catalog.settings.manage')
|
|
184
|
+
const canView = canManage || hasFeature(grantedFeatures, 'catalog.settings.view')
|
|
185
|
+
|
|
186
|
+
const [stored, setStored] = React.useState<StoredOmnibusConfig | null>(null)
|
|
187
|
+
const [form, setForm] = React.useState<OmnibusFormState>(() => toFormState(null))
|
|
188
|
+
const [loading, setLoading] = React.useState(true)
|
|
189
|
+
const [loadError, setLoadError] = React.useState<string | null>(null)
|
|
190
|
+
const [saving, setSaving] = React.useState(false)
|
|
191
|
+
const [fieldErrors, setFieldErrors] = React.useState<FieldErrors>({})
|
|
192
|
+
const [formError, setFormError] = React.useState<string | null>(null)
|
|
193
|
+
const [backfillChannels, setBackfillChannels] = React.useState<string[] | null>(null)
|
|
194
|
+
const [backfillChannelNames, setBackfillChannelNames] = React.useState<Record<string, string>>({})
|
|
195
|
+
const [reloadToken, setReloadToken] = React.useState(0)
|
|
196
|
+
const loadedSourceRef = React.useRef<{ tenantId: string | null; configKey: string } | null>(null)
|
|
197
|
+
|
|
198
|
+
const { runMutation, retryLastMutation } = useGuardedMutation<OmnibusMutationContext>({
|
|
199
|
+
contextId: MUTATION_CONTEXT_ID,
|
|
200
|
+
blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
const readOnly = !canManage || saving
|
|
204
|
+
|
|
205
|
+
const errorMessages = React.useMemo<Record<string, string>>(() => ({
|
|
206
|
+
omnibus_country_code_alpha2_required: t('catalog.omnibus.errors.countryCodeFormat', 'Use a two-letter ISO country code.'),
|
|
207
|
+
omnibus_country_code_eu_not_allowed: t('catalog.omnibus.errors.countryCodeEu', 'List individual member states instead of "EU".'),
|
|
208
|
+
omnibus_country_code_unknown: t('catalog.omnibus.errors.countryCodeUnknown', 'Unknown country code.'),
|
|
209
|
+
omnibus_presented_price_kind_required: t('catalog.omnibus.errors.presentedPriceKindRequired', 'Select a default presented price kind, or configure a channel with its own price kind.'),
|
|
210
|
+
omnibus_field_not_supported: t('catalog.omnibus.errors.fieldNotSupported', 'This setting is not supported.'),
|
|
211
|
+
omnibus_derogation_not_supported: t('catalog.omnibus.errors.fieldNotSupported', 'This setting is not supported.'),
|
|
212
|
+
omnibus_backfill_coverage_read_only: t('catalog.omnibus.errors.fieldNotSupported', 'This setting is not supported.'),
|
|
213
|
+
}), [t])
|
|
214
|
+
const invalidValueMessage = t('catalog.omnibus.errors.invalidValue', 'Invalid value.')
|
|
215
|
+
const lookbackRangeMessage = t('catalog.omnibus.errors.lookbackRange', 'Enter a whole number of days between 1 and 365.')
|
|
216
|
+
const requiredMessage = t('catalog.omnibus.errors.required', 'This field is required.')
|
|
217
|
+
const duplicateChannelMessage = t('catalog.omnibus.errors.duplicateChannel', 'This channel is already configured.')
|
|
218
|
+
const saveErrorMessage = t('catalog.omnibus.errors.save', 'Failed to save Omnibus settings.')
|
|
219
|
+
const staleBackfillScopes = React.useMemo(() => listStaleBackfillScopes(stored, form), [form, stored])
|
|
220
|
+
const channelNameLookupKey = React.useMemo(
|
|
221
|
+
() => Array.from(new Set([...(backfillChannels ?? []), ...staleBackfillScopes].filter((channelId) => channelId.length > 0))).sort((left, right) => (left < right ? -1 : left > right ? 1 : 0)).join(','),
|
|
222
|
+
[backfillChannels, staleBackfillScopes],
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
React.useEffect(() => {
|
|
226
|
+
const channelIds = channelNameLookupKey ? channelNameLookupKey.split(',') : []
|
|
227
|
+
if (!channelIds.length) return
|
|
228
|
+
let cancelled = false
|
|
229
|
+
const query = new URLSearchParams({ ids: channelIds.join(','), pageSize: String(Math.min(channelIds.length, 100)) })
|
|
230
|
+
apiCall<{ items?: Array<Record<string, unknown>> }>(`/api/sales/channels?${query.toString()}`)
|
|
231
|
+
.then((call) => {
|
|
232
|
+
if (cancelled || !call?.ok) return
|
|
233
|
+
const items = Array.isArray(call.result?.items) ? call.result.items : []
|
|
234
|
+
const names: Record<string, string> = {}
|
|
235
|
+
for (const item of items) {
|
|
236
|
+
if (typeof item.id === 'string' && typeof item.name === 'string' && item.name.length) names[item.id] = item.name
|
|
237
|
+
}
|
|
238
|
+
setBackfillChannelNames((previous) => ({ ...previous, ...names }))
|
|
239
|
+
})
|
|
240
|
+
.catch((err) => {
|
|
241
|
+
logger.warn('catalog.omnibus.settings.backfillChannelNames failed', { err })
|
|
242
|
+
getTelemetryRuntime()?.reportError(err, { module: 'catalog', code: 'catalog.omnibus_channel_names_load_failed' })
|
|
243
|
+
})
|
|
244
|
+
return () => {
|
|
245
|
+
cancelled = true
|
|
246
|
+
}
|
|
247
|
+
}, [channelNameLookupKey])
|
|
248
|
+
|
|
249
|
+
const loadErrorMessage = t('catalog.omnibus.errors.load', 'Failed to load Omnibus settings.')
|
|
250
|
+
|
|
251
|
+
React.useEffect(() => {
|
|
252
|
+
if (!chromeReady || !canView) return
|
|
253
|
+
let cancelled = false
|
|
254
|
+
const previous = loadedSourceRef.current
|
|
255
|
+
if (!previous) setLoading(true)
|
|
256
|
+
setLoadError(null)
|
|
257
|
+
apiCall<unknown>(OMNIBUS_CONFIG_PATH)
|
|
258
|
+
.then((call) => {
|
|
259
|
+
if (cancelled) return
|
|
260
|
+
if (!call.ok) {
|
|
261
|
+
loadedSourceRef.current = null
|
|
262
|
+
setLoadError(loadErrorMessage)
|
|
263
|
+
return
|
|
264
|
+
}
|
|
265
|
+
const config = asStoredConfig(call.result)
|
|
266
|
+
const configKey = JSON.stringify(config)
|
|
267
|
+
const keepsEdits =
|
|
268
|
+
previous !== null &&
|
|
269
|
+
previous.configKey === configKey &&
|
|
270
|
+
(previous.tenantId === null || previous.tenantId === scopeTenantId)
|
|
271
|
+
loadedSourceRef.current = { tenantId: scopeTenantId, configKey }
|
|
272
|
+
setStored(config)
|
|
273
|
+
if (keepsEdits) return
|
|
274
|
+
setForm(toFormState(config))
|
|
275
|
+
setFieldErrors({})
|
|
276
|
+
setFormError(null)
|
|
277
|
+
setBackfillChannels(null)
|
|
278
|
+
})
|
|
279
|
+
.catch((err) => {
|
|
280
|
+
logger.error('catalog.omnibus.settings.load failed', { err })
|
|
281
|
+
getTelemetryRuntime()?.reportError(err, { module: 'catalog', code: 'catalog.omnibus_settings_load_failed' })
|
|
282
|
+
if (!cancelled) setLoadError(loadErrorMessage)
|
|
283
|
+
})
|
|
284
|
+
.finally(() => {
|
|
285
|
+
if (!cancelled) setLoading(false)
|
|
286
|
+
})
|
|
287
|
+
return () => {
|
|
288
|
+
cancelled = true
|
|
289
|
+
}
|
|
290
|
+
}, [canView, chromeReady, loadErrorMessage, reloadToken, scopeTenantId, scopeVersion])
|
|
291
|
+
|
|
292
|
+
const translateServerError = React.useCallback(
|
|
293
|
+
(messages: unknown): string => {
|
|
294
|
+
const first = Array.isArray(messages) ? messages.find((entry) => typeof entry === 'string') : null
|
|
295
|
+
if (typeof first !== 'string') return invalidValueMessage
|
|
296
|
+
return errorMessages[first] ?? invalidValueMessage
|
|
297
|
+
},
|
|
298
|
+
[errorMessages, invalidValueMessage],
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
const mapServerFieldErrors = React.useCallback(
|
|
302
|
+
(raw: Record<string, unknown>): { fields: FieldErrors; unmatched: string | null } => {
|
|
303
|
+
const fields: FieldErrors = {}
|
|
304
|
+
let unmatched: string | null = null
|
|
305
|
+
for (const [path, messages] of Object.entries(raw)) {
|
|
306
|
+
const message = translateServerError(messages)
|
|
307
|
+
const segments = path.split('.')
|
|
308
|
+
const head = segments[0]
|
|
309
|
+
if (head === 'channels' && segments.length >= 2) {
|
|
310
|
+
const row = form.channels.find((entry) => entry.channelId === segments[1])
|
|
311
|
+
const field = segments[2] ?? 'channelId'
|
|
312
|
+
if (row) {
|
|
313
|
+
fields[channelErrorKey(row.key, field)] = message
|
|
314
|
+
continue
|
|
315
|
+
}
|
|
316
|
+
} else if (head && head !== '_root' && head in form) {
|
|
317
|
+
fields[head] = fields[head] ?? message
|
|
318
|
+
continue
|
|
319
|
+
}
|
|
320
|
+
unmatched = unmatched ?? message
|
|
321
|
+
}
|
|
322
|
+
return { fields, unmatched }
|
|
323
|
+
},
|
|
324
|
+
[form, translateServerError],
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
const buildPatch = React.useCallback((): { patch: OmnibusConfigPatchBody | null; errors: FieldErrors } => {
|
|
328
|
+
const errors: FieldErrors = {}
|
|
329
|
+
const lookbackDays = parseLookbackDays(form.lookbackDays)
|
|
330
|
+
if (lookbackDays === null) errors.lookbackDays = lookbackRangeMessage
|
|
331
|
+
const channels: Record<string, OmnibusChannelPatch> = {}
|
|
332
|
+
for (const row of form.channels) {
|
|
333
|
+
const channelId = row.channelId.trim()
|
|
334
|
+
if (!channelId) {
|
|
335
|
+
errors[channelErrorKey(row.key, 'channelId')] = requiredMessage
|
|
336
|
+
} else if (channels[channelId]) {
|
|
337
|
+
errors[channelErrorKey(row.key, 'channelId')] = duplicateChannelMessage
|
|
338
|
+
}
|
|
339
|
+
if (!row.presentedPriceKindId) errors[channelErrorKey(row.key, 'presentedPriceKindId')] = requiredMessage
|
|
340
|
+
const countryCode = row.countryCode.trim().toUpperCase()
|
|
341
|
+
if (countryCode && !COUNTRY_CODE_PATTERN.test(countryCode)) {
|
|
342
|
+
errors[channelErrorKey(row.key, 'countryCode')] = errorMessages.omnibus_country_code_alpha2_required
|
|
343
|
+
}
|
|
344
|
+
const channelLookback = row.lookbackDays.trim() ? parseLookbackDays(row.lookbackDays) : undefined
|
|
345
|
+
if (channelLookback === null) errors[channelErrorKey(row.key, 'lookbackDays')] = lookbackRangeMessage
|
|
346
|
+
if (!channelId || channels[channelId]) continue
|
|
347
|
+
channels[channelId] = {
|
|
348
|
+
presentedPriceKindId: row.presentedPriceKindId,
|
|
349
|
+
...(countryCode ? { countryCode } : {}),
|
|
350
|
+
...(typeof channelLookback === 'number' ? { lookbackDays: channelLookback } : {}),
|
|
351
|
+
...(row.minimizationAxis !== 'inherit' ? { minimizationAxis: row.minimizationAxis } : {}),
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
if (Object.keys(errors).length > 0 || lookbackDays === null) return { patch: null, errors }
|
|
355
|
+
return {
|
|
356
|
+
patch: {
|
|
357
|
+
enabled: form.enabled,
|
|
358
|
+
lookbackDays,
|
|
359
|
+
enabledCountryCodes: form.enabledCountryCodes,
|
|
360
|
+
noChannelMode: form.noChannelMode,
|
|
361
|
+
minimizationAxis: form.minimizationAxis,
|
|
362
|
+
defaultPresentedPriceKindId: form.defaultPresentedPriceKindId || null,
|
|
363
|
+
channels,
|
|
364
|
+
},
|
|
365
|
+
errors,
|
|
366
|
+
}
|
|
367
|
+
}, [duplicateChannelMessage, errorMessages, form, lookbackRangeMessage, requiredMessage])
|
|
368
|
+
|
|
369
|
+
const handleSubmit = React.useCallback(async () => {
|
|
370
|
+
if (!canManage || saving) return
|
|
371
|
+
const { patch, errors } = buildPatch()
|
|
372
|
+
setFormError(null)
|
|
373
|
+
setBackfillChannels(null)
|
|
374
|
+
setFieldErrors(errors)
|
|
375
|
+
if (!patch) return
|
|
376
|
+
setSaving(true)
|
|
377
|
+
try {
|
|
378
|
+
const saved = await runMutation({
|
|
379
|
+
operation: async () => {
|
|
380
|
+
// optimistic-lock-exempt: single tenant-scoped Omnibus config blob merged server-side; no per-record version to compare
|
|
381
|
+
const call = await apiCall<unknown>(OMNIBUS_CONFIG_PATH, {
|
|
382
|
+
method: 'PATCH',
|
|
383
|
+
headers: { 'content-type': 'application/json' },
|
|
384
|
+
body: JSON.stringify(patch),
|
|
385
|
+
})
|
|
386
|
+
if (!call.ok) {
|
|
387
|
+
throw new OmnibusSaveError(call.status, isRecord(call.result) ? (call.result as OmnibusErrorBody) : null)
|
|
388
|
+
}
|
|
389
|
+
return asStoredConfig(call.result)
|
|
390
|
+
},
|
|
391
|
+
context: {
|
|
392
|
+
formId: MUTATION_CONTEXT_ID,
|
|
393
|
+
resourceKind: 'catalog.settings',
|
|
394
|
+
resourceId: 'omnibus',
|
|
395
|
+
retryLastMutation,
|
|
396
|
+
},
|
|
397
|
+
mutationPayload: patch,
|
|
398
|
+
})
|
|
399
|
+
loadedSourceRef.current = { tenantId: scopeTenantId, configKey: JSON.stringify(saved) }
|
|
400
|
+
setStored(saved)
|
|
401
|
+
setForm(toFormState(saved))
|
|
402
|
+
setFieldErrors({})
|
|
403
|
+
flash(t('catalog.omnibus.messages.saved', 'Omnibus settings saved.'), 'success')
|
|
404
|
+
} catch (err) {
|
|
405
|
+
if (err instanceof OmnibusSaveError) {
|
|
406
|
+
const body = err.body
|
|
407
|
+
if (err.status === 422 && body?.error === 'backfill_required_before_enable') {
|
|
408
|
+
const channels = Array.isArray(body.channels)
|
|
409
|
+
? body.channels.filter((entry): entry is string => typeof entry === 'string')
|
|
410
|
+
: []
|
|
411
|
+
setBackfillChannels(channels)
|
|
412
|
+
return
|
|
413
|
+
}
|
|
414
|
+
const detailFieldErrors = body?.details?.fieldErrors
|
|
415
|
+
const rawFieldErrors = isRecord(detailFieldErrors) ? detailFieldErrors : null
|
|
416
|
+
if (err.status === 400 && rawFieldErrors) {
|
|
417
|
+
const mapped = mapServerFieldErrors(rawFieldErrors)
|
|
418
|
+
setFieldErrors(mapped.fields)
|
|
419
|
+
setFormError(mapped.unmatched ?? t('catalog.omnibus.errors.invalid', 'Fix the highlighted fields and try again.'))
|
|
420
|
+
return
|
|
421
|
+
}
|
|
422
|
+
setFormError(saveErrorMessage)
|
|
423
|
+
return
|
|
424
|
+
}
|
|
425
|
+
logger.error('catalog.omnibus.settings.save failed', { err })
|
|
426
|
+
getTelemetryRuntime()?.reportError(err, { module: 'catalog', code: 'catalog.omnibus_settings_save_failed' })
|
|
427
|
+
setFormError(err instanceof Error && !err.message.startsWith('[internal]') ? err.message : saveErrorMessage)
|
|
428
|
+
} finally {
|
|
429
|
+
setSaving(false)
|
|
430
|
+
}
|
|
431
|
+
}, [buildPatch, canManage, mapServerFieldErrors, retryLastMutation, runMutation, saveErrorMessage, saving, scopeTenantId, t])
|
|
432
|
+
|
|
433
|
+
const handleCancel = React.useCallback(() => {
|
|
434
|
+
setForm(toFormState(stored))
|
|
435
|
+
setFieldErrors({})
|
|
436
|
+
setFormError(null)
|
|
437
|
+
setBackfillChannels(null)
|
|
438
|
+
}, [stored])
|
|
439
|
+
|
|
440
|
+
const handleKeyDown = React.useCallback(
|
|
441
|
+
(event: React.KeyboardEvent<HTMLFormElement>) => {
|
|
442
|
+
if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {
|
|
443
|
+
event.preventDefault()
|
|
444
|
+
void handleSubmit()
|
|
445
|
+
return
|
|
446
|
+
}
|
|
447
|
+
if (event.key === 'Escape' && !event.defaultPrevented && canManage) {
|
|
448
|
+
event.preventDefault()
|
|
449
|
+
handleCancel()
|
|
450
|
+
}
|
|
451
|
+
},
|
|
452
|
+
[canManage, handleCancel, handleSubmit],
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
const updateForm = React.useCallback((patch: Partial<OmnibusFormState>) => {
|
|
456
|
+
setForm((prev) => ({ ...prev, ...patch }))
|
|
457
|
+
}, [])
|
|
458
|
+
|
|
459
|
+
const updateChannel = React.useCallback((rowKey: string, patch: Partial<OmnibusChannelRow>) => {
|
|
460
|
+
setForm((prev) => ({
|
|
461
|
+
...prev,
|
|
462
|
+
channels: prev.channels.map((row) => (row.key === rowKey ? { ...row, ...patch } : row)),
|
|
463
|
+
}))
|
|
464
|
+
}, [])
|
|
465
|
+
|
|
466
|
+
const addChannel = React.useCallback(() => {
|
|
467
|
+
setForm((prev) => ({
|
|
468
|
+
...prev,
|
|
469
|
+
channels: [
|
|
470
|
+
...prev.channels,
|
|
471
|
+
{ key: nextRowKey(), channelId: '', presentedPriceKindId: '', countryCode: '', lookbackDays: '', minimizationAxis: 'inherit' },
|
|
472
|
+
],
|
|
473
|
+
}))
|
|
474
|
+
}, [])
|
|
475
|
+
|
|
476
|
+
const removeChannel = React.useCallback((rowKey: string) => {
|
|
477
|
+
setForm((prev) => ({ ...prev, channels: prev.channels.filter((row) => row.key !== rowKey) }))
|
|
478
|
+
}, [])
|
|
479
|
+
|
|
480
|
+
const formatChannel = React.useCallback(
|
|
481
|
+
(channelId: string) => (backfillChannelNames[channelId] ? `${backfillChannelNames[channelId]} (${channelId})` : channelId),
|
|
482
|
+
[backfillChannelNames],
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
const formatScope = React.useCallback(
|
|
486
|
+
(scopeId: string) => (scopeId ? formatChannel(scopeId) : t('catalog.omnibus.settings.backfill.globalScope', 'All channels (unscoped)')),
|
|
487
|
+
[formatChannel, t],
|
|
488
|
+
)
|
|
489
|
+
|
|
490
|
+
if (!chromeReady) {
|
|
491
|
+
return <LoadingMessage label={t('catalog.omnibus.settings.loading', 'Loading Omnibus settings…')} />
|
|
492
|
+
}
|
|
493
|
+
if (!canView) return null
|
|
494
|
+
|
|
495
|
+
const noChannelModeLabels: Record<OmnibusNoChannelMode, string> = {
|
|
496
|
+
best_effort: t('catalog.omnibus.settings.noChannelMode.bestEffort', 'Best effort (use global settings)'),
|
|
497
|
+
require_channel: t('catalog.omnibus.settings.noChannelMode.requireChannel', 'Require a channel'),
|
|
498
|
+
}
|
|
499
|
+
const axisLabels: Record<OmnibusMinimizationAxis, string> = {
|
|
500
|
+
gross: t('catalog.omnibus.settings.axis.gross', 'Gross (B2C)'),
|
|
501
|
+
net: t('catalog.omnibus.settings.axis.net', 'Net (B2B)'),
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
return (
|
|
505
|
+
<section className="border bg-card text-card-foreground shadow-sm" data-testid="catalog-omnibus-settings">
|
|
506
|
+
<div className="border-b px-6 py-4 space-y-1">
|
|
507
|
+
<h2 className="text-lg font-semibold">{t('catalog.omnibus.settings.title', 'Omnibus price tracking')}</h2>
|
|
508
|
+
<p className="text-sm text-muted-foreground">
|
|
509
|
+
{t(
|
|
510
|
+
'catalog.omnibus.settings.description',
|
|
511
|
+
'Show the lowest price from the lookback period next to announced price reductions (EU Directive 2019/2161).',
|
|
512
|
+
)}
|
|
513
|
+
</p>
|
|
514
|
+
</div>
|
|
515
|
+
<div className="px-6 py-4">
|
|
516
|
+
{loading ? (
|
|
517
|
+
<LoadingMessage label={t('catalog.omnibus.settings.loading', 'Loading Omnibus settings…')} />
|
|
518
|
+
) : loadError ? (
|
|
519
|
+
<ErrorMessage
|
|
520
|
+
label={loadError}
|
|
521
|
+
action={(
|
|
522
|
+
<Button type="button" variant="outline" size="sm" onClick={() => setReloadToken((token) => token + 1)}>
|
|
523
|
+
{t('catalog.omnibus.settings.actions.retry', 'Retry')}
|
|
524
|
+
</Button>
|
|
525
|
+
)}
|
|
526
|
+
/>
|
|
527
|
+
) : (
|
|
528
|
+
<form
|
|
529
|
+
className="space-y-6"
|
|
530
|
+
noValidate
|
|
531
|
+
onKeyDown={handleKeyDown}
|
|
532
|
+
onSubmit={(event) => {
|
|
533
|
+
event.preventDefault()
|
|
534
|
+
void handleSubmit()
|
|
535
|
+
}}
|
|
536
|
+
>
|
|
537
|
+
{!canManage ? (
|
|
538
|
+
<Alert status="information">
|
|
539
|
+
<AlertDescription>
|
|
540
|
+
{t('catalog.omnibus.settings.readOnly', 'You can view these settings but not change them.')}
|
|
541
|
+
</AlertDescription>
|
|
542
|
+
</Alert>
|
|
543
|
+
) : null}
|
|
544
|
+
{backfillChannels ? (
|
|
545
|
+
<Alert status="error" data-testid="catalog-omnibus-backfill-required">
|
|
546
|
+
<AlertTitle>{t('catalog.omnibus.settings.backfill.requiredTitle', 'Backfill required before enabling')}</AlertTitle>
|
|
547
|
+
<AlertDescription>
|
|
548
|
+
<p>
|
|
549
|
+
{t(
|
|
550
|
+
'catalog.omnibus.settings.backfill.requiredDescription',
|
|
551
|
+
'Price history must be backfilled for every in-scope EU channel before Omnibus can be enabled. Run "mercato catalog omnibus:backfill --tenant <tenantId>" (add "--channel-id <channelId>" per channel), then save again.',
|
|
552
|
+
)}
|
|
553
|
+
</p>
|
|
554
|
+
{backfillChannels.length > 0 ? (
|
|
555
|
+
<p className="mt-1">
|
|
556
|
+
{t('catalog.omnibus.settings.backfill.channels', 'Channels without backfill: {{channels}}', {
|
|
557
|
+
channels: backfillChannels.map(formatChannel).join(', '),
|
|
558
|
+
})}
|
|
559
|
+
</p>
|
|
560
|
+
) : null}
|
|
561
|
+
</AlertDescription>
|
|
562
|
+
</Alert>
|
|
563
|
+
) : null}
|
|
564
|
+
{staleBackfillScopes.length > 0 ? (
|
|
565
|
+
<Alert status="warning" data-testid="catalog-omnibus-backfill-stale">
|
|
566
|
+
<AlertTitle>{t('catalog.omnibus.settings.backfill.staleTitle', 'Lookback increased since the last backfill')}</AlertTitle>
|
|
567
|
+
<AlertDescription>
|
|
568
|
+
{t(
|
|
569
|
+
'catalog.omnibus.settings.backfill.staleDescription',
|
|
570
|
+
'Re-run the backfill so the longer window has baseline history: {{scopes}}',
|
|
571
|
+
{ scopes: staleBackfillScopes.map(formatScope).join(', ') },
|
|
572
|
+
)}
|
|
573
|
+
</AlertDescription>
|
|
574
|
+
</Alert>
|
|
575
|
+
) : null}
|
|
576
|
+
{formError ? <ErrorMessage label={formError} /> : null}
|
|
577
|
+
|
|
578
|
+
<SwitchField
|
|
579
|
+
label={t('catalog.omnibus.settings.enabled', 'Enable Omnibus reference prices')}
|
|
580
|
+
description={t(
|
|
581
|
+
'catalog.omnibus.settings.enabledDescription',
|
|
582
|
+
'When enabled, products in EU channels expose the lowest price from the lookback window.',
|
|
583
|
+
)}
|
|
584
|
+
checked={form.enabled}
|
|
585
|
+
disabled={readOnly}
|
|
586
|
+
onCheckedChange={(next) => updateForm({ enabled: next })}
|
|
587
|
+
/>
|
|
588
|
+
|
|
589
|
+
<div className="grid gap-4 md:grid-cols-2">
|
|
590
|
+
<FormField
|
|
591
|
+
label={t('catalog.omnibus.settings.lookbackDays', 'Lookback window (days)')}
|
|
592
|
+
description={t('catalog.omnibus.settings.lookbackDaysDescription', 'Between 1 and 365 days. The directive requires at least 30.')}
|
|
593
|
+
error={fieldErrors.lookbackDays}
|
|
594
|
+
required
|
|
595
|
+
disabled={readOnly}
|
|
596
|
+
>
|
|
597
|
+
<Input
|
|
598
|
+
type="number"
|
|
599
|
+
inputMode="numeric"
|
|
600
|
+
min={MIN_LOOKBACK_DAYS}
|
|
601
|
+
max={MAX_LOOKBACK_DAYS}
|
|
602
|
+
value={form.lookbackDays}
|
|
603
|
+
onChange={(event) => updateForm({ lookbackDays: event.target.value })}
|
|
604
|
+
data-testid="catalog-omnibus-lookback"
|
|
605
|
+
/>
|
|
606
|
+
</FormField>
|
|
607
|
+
<FormField
|
|
608
|
+
label={t('catalog.omnibus.settings.enabledCountryCodes', 'EU member states')}
|
|
609
|
+
description={t(
|
|
610
|
+
'catalog.omnibus.settings.enabledCountryCodesDescription',
|
|
611
|
+
'Two-letter ISO codes of the member states where Omnibus applies. Leave empty to disable it for every channel.',
|
|
612
|
+
)}
|
|
613
|
+
error={fieldErrors.enabledCountryCodes}
|
|
614
|
+
disabled={readOnly}
|
|
615
|
+
>
|
|
616
|
+
<TagInput
|
|
617
|
+
value={form.enabledCountryCodes}
|
|
618
|
+
onChange={(next) => updateForm({ enabledCountryCodes: Array.from(new Set(next.map((code) => code.trim().toUpperCase()))) })}
|
|
619
|
+
validate={(tag) => {
|
|
620
|
+
const code = tag.trim().toUpperCase()
|
|
621
|
+
if (code === 'EU') return errorMessages.omnibus_country_code_eu_not_allowed
|
|
622
|
+
return COUNTRY_CODE_PATTERN.test(code) || errorMessages.omnibus_country_code_alpha2_required
|
|
623
|
+
}}
|
|
624
|
+
placeholder={t('catalog.omnibus.settings.enabledCountryCodesPlaceholder', 'e.g. PL, DE')}
|
|
625
|
+
aria-label={t('catalog.omnibus.settings.enabledCountryCodes', 'EU member states')}
|
|
626
|
+
removeTagLabel={(tag) => t('catalog.omnibus.settings.removeCountry', 'Remove {{code}}', { code: tag })}
|
|
627
|
+
disabled={readOnly}
|
|
628
|
+
/>
|
|
629
|
+
</FormField>
|
|
630
|
+
<FormField
|
|
631
|
+
label={t('catalog.omnibus.settings.noChannelModeLabel', 'Requests without a channel')}
|
|
632
|
+
error={fieldErrors.noChannelMode}
|
|
633
|
+
disabled={readOnly}
|
|
634
|
+
>
|
|
635
|
+
<Select
|
|
636
|
+
value={form.noChannelMode}
|
|
637
|
+
onValueChange={(next) => updateForm({ noChannelMode: next as OmnibusNoChannelMode })}
|
|
638
|
+
disabled={readOnly}
|
|
639
|
+
>
|
|
640
|
+
<SelectTrigger>
|
|
641
|
+
<SelectValue>{noChannelModeLabels[form.noChannelMode]}</SelectValue>
|
|
642
|
+
</SelectTrigger>
|
|
643
|
+
<SelectContent>
|
|
644
|
+
{(Object.keys(noChannelModeLabels) as OmnibusNoChannelMode[]).map((mode) => (
|
|
645
|
+
<SelectItem key={mode} value={mode}>{noChannelModeLabels[mode]}</SelectItem>
|
|
646
|
+
))}
|
|
647
|
+
</SelectContent>
|
|
648
|
+
</Select>
|
|
649
|
+
</FormField>
|
|
650
|
+
<FormField
|
|
651
|
+
label={t('catalog.omnibus.settings.minimizationAxis', 'Compare prices by')}
|
|
652
|
+
error={fieldErrors.minimizationAxis}
|
|
653
|
+
disabled={readOnly}
|
|
654
|
+
>
|
|
655
|
+
<Select
|
|
656
|
+
value={form.minimizationAxis}
|
|
657
|
+
onValueChange={(next) => updateForm({ minimizationAxis: next as OmnibusMinimizationAxis })}
|
|
658
|
+
disabled={readOnly}
|
|
659
|
+
>
|
|
660
|
+
<SelectTrigger>
|
|
661
|
+
<SelectValue>{axisLabels[form.minimizationAxis]}</SelectValue>
|
|
662
|
+
</SelectTrigger>
|
|
663
|
+
<SelectContent>
|
|
664
|
+
{(Object.keys(axisLabels) as OmnibusMinimizationAxis[]).map((axis) => (
|
|
665
|
+
<SelectItem key={axis} value={axis}>{axisLabels[axis]}</SelectItem>
|
|
666
|
+
))}
|
|
667
|
+
</SelectContent>
|
|
668
|
+
</Select>
|
|
669
|
+
</FormField>
|
|
670
|
+
<FormField
|
|
671
|
+
label={t('catalog.omnibus.settings.defaultPresentedPriceKind', 'Default presented price kind')}
|
|
672
|
+
description={t(
|
|
673
|
+
'catalog.omnibus.settings.defaultPresentedPriceKindDescription',
|
|
674
|
+
'The price kind shown to shoppers, used when a channel has no override. Required when Omnibus is enabled without channel overrides.',
|
|
675
|
+
)}
|
|
676
|
+
error={fieldErrors.defaultPresentedPriceKindId}
|
|
677
|
+
disabled={readOnly}
|
|
678
|
+
className="md:col-span-2"
|
|
679
|
+
>
|
|
680
|
+
<PricePriceKindSelect
|
|
681
|
+
value={form.defaultPresentedPriceKindId}
|
|
682
|
+
onChange={(next) => updateForm({ defaultPresentedPriceKindId: next })}
|
|
683
|
+
disabled={readOnly}
|
|
684
|
+
clearable
|
|
685
|
+
/>
|
|
686
|
+
</FormField>
|
|
687
|
+
</div>
|
|
688
|
+
|
|
689
|
+
<div className="space-y-3">
|
|
690
|
+
<div className="flex items-start justify-between gap-4">
|
|
691
|
+
<div className="space-y-1">
|
|
692
|
+
<h3 className="text-sm font-semibold">{t('catalog.omnibus.settings.channels.title', 'Channel overrides')}</h3>
|
|
693
|
+
<p className="text-xs text-muted-foreground">
|
|
694
|
+
{t(
|
|
695
|
+
'catalog.omnibus.settings.channels.description',
|
|
696
|
+
'Map sales channels to a member state and override the presented price kind, lookback or comparison axis per channel.',
|
|
697
|
+
)}
|
|
698
|
+
</p>
|
|
699
|
+
</div>
|
|
700
|
+
{canManage ? (
|
|
701
|
+
<Button type="button" variant="outline" size="sm" onClick={addChannel} disabled={saving}>
|
|
702
|
+
<Plus className="size-4" aria-hidden />
|
|
703
|
+
{t('catalog.omnibus.settings.channels.add', 'Add channel')}
|
|
704
|
+
</Button>
|
|
705
|
+
) : null}
|
|
706
|
+
</div>
|
|
707
|
+
{form.channels.length === 0 ? (
|
|
708
|
+
<p className="text-sm text-muted-foreground">
|
|
709
|
+
{t('catalog.omnibus.settings.channels.empty', 'No channel overrides. Global settings apply to every channel.')}
|
|
710
|
+
</p>
|
|
711
|
+
) : (
|
|
712
|
+
form.channels.map((row) => {
|
|
713
|
+
const rowError = (field: string) => fieldErrors[channelErrorKey(row.key, field)]
|
|
714
|
+
return (
|
|
715
|
+
<div key={row.key} className="space-y-3 rounded-md border p-4" data-testid="catalog-omnibus-channel-row">
|
|
716
|
+
<div className="flex items-start justify-between gap-2">
|
|
717
|
+
<div className="grid flex-1 gap-4 md:grid-cols-2">
|
|
718
|
+
<FormField
|
|
719
|
+
label={t('catalog.omnibus.settings.channels.channel', 'Channel')}
|
|
720
|
+
error={rowError('channelId')}
|
|
721
|
+
disabled={readOnly}
|
|
722
|
+
>
|
|
723
|
+
<PriceChannelSelect
|
|
724
|
+
value={row.channelId}
|
|
725
|
+
onChange={(next) => updateChannel(row.key, { channelId: next })}
|
|
726
|
+
disabled={readOnly}
|
|
727
|
+
/>
|
|
728
|
+
</FormField>
|
|
729
|
+
<FormField
|
|
730
|
+
label={t('catalog.omnibus.settings.channels.presentedPriceKind', 'Presented price kind')}
|
|
731
|
+
error={rowError('presentedPriceKindId')}
|
|
732
|
+
disabled={readOnly}
|
|
733
|
+
>
|
|
734
|
+
<PricePriceKindSelect
|
|
735
|
+
value={row.presentedPriceKindId}
|
|
736
|
+
onChange={(next) => updateChannel(row.key, { presentedPriceKindId: next })}
|
|
737
|
+
disabled={readOnly}
|
|
738
|
+
/>
|
|
739
|
+
</FormField>
|
|
740
|
+
<FormField
|
|
741
|
+
label={t('catalog.omnibus.settings.channels.countryCode', 'Member state')}
|
|
742
|
+
description={t('catalog.omnibus.settings.channels.countryCodeDescription', 'Leave empty for non-EU channels.')}
|
|
743
|
+
error={rowError('countryCode')}
|
|
744
|
+
disabled={readOnly}
|
|
745
|
+
>
|
|
746
|
+
<Input
|
|
747
|
+
value={row.countryCode}
|
|
748
|
+
maxLength={2}
|
|
749
|
+
onChange={(event) => updateChannel(row.key, { countryCode: event.target.value.toUpperCase() })}
|
|
750
|
+
placeholder={t('catalog.omnibus.settings.channels.countryCodePlaceholder', 'e.g. PL')}
|
|
751
|
+
/>
|
|
752
|
+
</FormField>
|
|
753
|
+
<FormField
|
|
754
|
+
label={t('catalog.omnibus.settings.channels.lookbackDays', 'Lookback override (days)')}
|
|
755
|
+
description={t('catalog.omnibus.settings.channels.lookbackDaysDescription', 'Leave empty to use the global window.')}
|
|
756
|
+
error={rowError('lookbackDays')}
|
|
757
|
+
disabled={readOnly}
|
|
758
|
+
>
|
|
759
|
+
<Input
|
|
760
|
+
type="number"
|
|
761
|
+
inputMode="numeric"
|
|
762
|
+
min={MIN_LOOKBACK_DAYS}
|
|
763
|
+
max={MAX_LOOKBACK_DAYS}
|
|
764
|
+
value={row.lookbackDays}
|
|
765
|
+
onChange={(event) => updateChannel(row.key, { lookbackDays: event.target.value })}
|
|
766
|
+
/>
|
|
767
|
+
</FormField>
|
|
768
|
+
<FormField
|
|
769
|
+
label={t('catalog.omnibus.settings.channels.minimizationAxis', 'Compare prices by')}
|
|
770
|
+
error={rowError('minimizationAxis')}
|
|
771
|
+
disabled={readOnly}
|
|
772
|
+
>
|
|
773
|
+
<Select
|
|
774
|
+
value={row.minimizationAxis}
|
|
775
|
+
onValueChange={(next) => updateChannel(row.key, { minimizationAxis: next as ChannelAxisValue })}
|
|
776
|
+
disabled={readOnly}
|
|
777
|
+
>
|
|
778
|
+
<SelectTrigger>
|
|
779
|
+
<SelectValue>
|
|
780
|
+
{row.minimizationAxis === 'inherit'
|
|
781
|
+
? t('catalog.omnibus.settings.channels.axisInherit', 'Use global setting')
|
|
782
|
+
: axisLabels[row.minimizationAxis]}
|
|
783
|
+
</SelectValue>
|
|
784
|
+
</SelectTrigger>
|
|
785
|
+
<SelectContent>
|
|
786
|
+
<SelectItem value="inherit">
|
|
787
|
+
{t('catalog.omnibus.settings.channels.axisInherit', 'Use global setting')}
|
|
788
|
+
</SelectItem>
|
|
789
|
+
{(Object.keys(axisLabels) as OmnibusMinimizationAxis[]).map((axis) => (
|
|
790
|
+
<SelectItem key={axis} value={axis}>{axisLabels[axis]}</SelectItem>
|
|
791
|
+
))}
|
|
792
|
+
</SelectContent>
|
|
793
|
+
</Select>
|
|
794
|
+
</FormField>
|
|
795
|
+
</div>
|
|
796
|
+
{canManage ? (
|
|
797
|
+
<IconButton
|
|
798
|
+
type="button"
|
|
799
|
+
variant="ghost"
|
|
800
|
+
size="sm"
|
|
801
|
+
aria-label={t('catalog.omnibus.settings.channels.remove', 'Remove channel override')}
|
|
802
|
+
onClick={() => removeChannel(row.key)}
|
|
803
|
+
disabled={saving}
|
|
804
|
+
>
|
|
805
|
+
<Trash2 className="size-4" aria-hidden />
|
|
806
|
+
</IconButton>
|
|
807
|
+
) : null}
|
|
808
|
+
</div>
|
|
809
|
+
</div>
|
|
810
|
+
)
|
|
811
|
+
})
|
|
812
|
+
)}
|
|
813
|
+
</div>
|
|
814
|
+
|
|
815
|
+
{canManage ? (
|
|
816
|
+
<div className="flex items-center justify-end gap-2">
|
|
817
|
+
<Button type="button" variant="outline" onClick={handleCancel} disabled={saving}>
|
|
818
|
+
{t('catalog.omnibus.settings.actions.cancel', 'Cancel')}
|
|
819
|
+
</Button>
|
|
820
|
+
<Button type="submit" disabled={saving} data-testid="catalog-omnibus-save">
|
|
821
|
+
{saving
|
|
822
|
+
? t('catalog.omnibus.settings.actions.saving', 'Saving…')
|
|
823
|
+
: t('catalog.omnibus.settings.actions.save', 'Save')}
|
|
824
|
+
</Button>
|
|
825
|
+
</div>
|
|
826
|
+
) : null}
|
|
827
|
+
</form>
|
|
828
|
+
)}
|
|
829
|
+
</div>
|
|
830
|
+
</section>
|
|
831
|
+
)
|
|
832
|
+
}
|