@mohasinac/appkit 4.30.19 → 4.31.0
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/dist/_internal/client/features/layout/useSidebarSearch.d.ts +21 -5
- package/dist/_internal/client/features/layout/useSidebarSearch.js +27 -6
- package/dist/_internal/client/features/lottery/LotteryAdminEditView.d.ts +23 -3
- package/dist/_internal/client/features/lottery/LotteryAdminEditView.js +106 -87
- package/dist/_internal/client/features/lottery/LotteryPullForm.d.ts +16 -5
- package/dist/_internal/client/features/lottery/LotteryPullForm.js +65 -40
- package/dist/_internal/server/features/auth/permissions.d.ts +2 -0
- package/dist/_internal/server/features/auth/permissions.js +3 -2
- package/dist/_internal/server/features/categories/data.js +15 -4
- package/dist/_internal/server/features/classified/index.d.ts +0 -1
- package/dist/_internal/server/features/classified/index.js +0 -1
- package/dist/_internal/server/features/digital-code/index.d.ts +0 -1
- package/dist/_internal/server/features/digital-code/index.js +0 -1
- package/dist/_internal/server/features/live/index.d.ts +0 -1
- package/dist/_internal/server/features/live/index.js +0 -1
- package/dist/_internal/server/features/products/data.js +15 -5
- package/dist/_internal/server/features/products/list-public.d.ts +9 -0
- package/dist/_internal/server/features/products/list-public.js +95 -7
- package/dist/_internal/server/features/site-settings/adapters.d.ts +2 -2
- package/dist/_internal/server/features/site-settings/adapters.js +8 -5
- package/dist/_internal/server/features/tester/visibility.d.ts +2 -1
- package/dist/_internal/server/features/tester/visibility.js +2 -2
- package/dist/_internal/server/functions/scheduled.d.ts +3 -1
- package/dist/_internal/server/functions/scheduled.js +27 -1
- package/dist/_internal/server/jobs/core/hardBanCascade.js +4 -0
- package/dist/_internal/server/jobs/core/index.d.ts +2 -0
- package/dist/_internal/server/jobs/core/index.js +2 -0
- package/dist/_internal/server/jobs/core/pageViewPrune.d.ts +10 -0
- package/dist/_internal/server/jobs/core/pageViewPrune.js +44 -0
- package/dist/_internal/server/jobs/core/pageViewRollup.d.ts +2 -0
- package/dist/_internal/server/jobs/core/pageViewRollup.js +87 -0
- package/dist/_internal/server/jobs/handlers/index.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/index.js +2 -0
- package/dist/_internal/server/jobs/handlers/pageViewPrune.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/pageViewPrune.js +2 -0
- package/dist/_internal/server/jobs/handlers/pageViewRollup.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/pageViewRollup.js +2 -0
- package/dist/_internal/shared/actions/action-registry.d.ts +1 -1
- package/dist/_internal/shared/actions/action-registry.js +69 -22
- package/dist/_internal/shared/checkout/rules/classified.rule.d.ts +9 -4
- package/dist/_internal/shared/checkout/rules/classified.rule.js +7 -2
- package/dist/_internal/shared/features/layout/matchesNavQuery.d.ts +14 -0
- package/dist/_internal/shared/features/layout/matchesNavQuery.js +72 -0
- package/dist/_internal/shared/features/layout/types.d.ts +37 -1
- package/dist/_internal/shared/features/layout/types.js +19 -1
- package/dist/_internal/shared/features/notifications/action-url.d.ts +9 -1
- package/dist/_internal/shared/features/notifications/action-url.js +47 -0
- package/dist/_internal/shared/features/offers/config.d.ts +65 -0
- package/dist/_internal/shared/features/offers/config.js +73 -2
- package/dist/_internal/shared/features/orders/schema.d.ts +19 -1
- package/dist/_internal/shared/features/orders/schema.js +22 -2
- package/dist/_internal/shared/features/site-settings/types.d.ts +12 -3
- package/dist/_internal/shared/listing-types/capabilities.d.ts +8 -2
- package/dist/_internal/shared/listing-types/capabilities.js +6 -1
- package/dist/_internal/shared/listing-types/classified/ctas.js +6 -1
- package/dist/_internal/shared/listing-types/feature-flags.d.ts +10 -5
- package/dist/_internal/shared/listing-types/feature-flags.js +11 -6
- package/dist/client.d.ts +37 -20
- package/dist/client.js +36 -14
- package/dist/configs/next.js +10 -2
- package/dist/constants/api-endpoints.d.ts +18 -54
- package/dist/constants/api-endpoints.js +6 -26
- package/dist/constants/field-names.d.ts +6 -24
- package/dist/constants/field-names.js +10 -27
- package/dist/constants/geo/countries.d.ts +50 -0
- package/dist/constants/geo/countries.js +129 -0
- package/dist/constants/geo/subdivisions.d.ts +13 -0
- package/dist/constants/geo/subdivisions.js +104 -0
- package/dist/constants/index.d.ts +2 -2
- package/dist/constants/index.js +2 -2
- package/dist/constants/table-keys.d.ts +24 -0
- package/dist/constants/table-keys.js +28 -0
- package/dist/contracts/extend.d.ts +0 -9
- package/dist/contracts/index.d.ts +1 -1
- package/dist/contracts/realtime-session.js +7 -0
- package/dist/core/browser.d.ts +1 -1
- package/dist/core/browser.js +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/site-config.d.ts +0 -3
- package/dist/core/site-config.js +0 -3
- package/dist/errors/messages.d.ts +0 -8
- package/dist/errors/messages.js +0 -9
- package/dist/features/about/schemas/index.d.ts +8 -8
- package/dist/features/account/components/AddressFilters.js +32 -11
- package/dist/features/account/components/AddressForm.d.ts +26 -25
- package/dist/features/account/components/AddressForm.js +81 -103
- package/dist/features/account/components/AddressesIndexListing.js +8 -4
- package/dist/features/account/components/UserSidebar.d.ts +3 -4
- package/dist/features/account/components/UserSupportView.js +89 -58
- package/dist/features/account/components/index.d.ts +1 -7
- package/dist/features/account/components/index.js +0 -3
- package/dist/features/account/hooks/useAddresses.d.ts +4 -3
- package/dist/features/account/hooks/useAddresses.js +4 -6
- package/dist/features/account/hooks/useCollapsedSections.d.ts +51 -10
- package/dist/features/account/hooks/useCollapsedSections.js +211 -38
- package/dist/features/account/hooks/useProfile.d.ts +8 -0
- package/dist/features/account/index.d.ts +0 -3
- package/dist/features/account/index.js +0 -3
- package/dist/features/account/schemas/index.d.ts +0 -92
- package/dist/features/account/schemas/index.js +42 -20
- package/dist/features/addresses/components/AddressLocationFields.d.ts +13 -0
- package/dist/features/addresses/components/AddressLocationFields.js +41 -0
- package/dist/features/addresses/hooks/usePostalLookup.d.ts +17 -0
- package/dist/features/addresses/hooks/usePostalLookup.js +133 -0
- package/dist/features/addresses/schemas/address-form.d.ts +423 -0
- package/dist/features/addresses/schemas/address-form.js +212 -0
- package/dist/features/addresses/schemas/index.d.ts +1 -0
- package/dist/features/addresses/schemas/index.js +8 -0
- package/dist/features/admin/actions/admin-read-actions.d.ts +1 -1
- package/dist/features/admin/actions/index.d.ts +0 -1
- package/dist/features/admin/actions/index.js +0 -1
- package/dist/features/admin/actions/notification-actions.js +1 -41
- package/dist/features/admin/components/AdminActionIndexView.d.ts +32 -0
- package/dist/features/admin/components/AdminActionIndexView.js +128 -0
- package/dist/features/admin/components/AdminAdEditorView.js +159 -163
- package/dist/features/admin/components/AdminAddressEditorView.js +97 -55
- package/dist/features/admin/components/AdminBrandEditorView.d.ts +15 -0
- package/dist/features/admin/components/AdminBrandEditorView.js +113 -77
- package/dist/features/admin/components/AdminBundleEditorView.d.ts +19 -9
- package/dist/features/admin/components/AdminBundleEditorView.js +136 -168
- package/dist/features/admin/components/AdminCategoryEditorView.js +120 -43
- package/dist/features/admin/components/AdminCouponEditorView.js +145 -109
- package/dist/features/admin/components/AdminEmployeeEditorView.js +125 -56
- package/dist/features/admin/components/AdminFaqEditorView.js +112 -58
- package/dist/features/admin/components/AdminFeatureEditorView.js +108 -68
- package/dist/features/admin/components/AdminNavEditorView.js +69 -44
- package/dist/features/admin/components/AdminOrderEditorView.js +47 -10
- package/dist/features/admin/components/AdminPrizeDrawsView.js +16 -2
- package/dist/features/admin/components/AdminSidebar.d.ts +11 -7
- package/dist/features/admin/components/AdminSiteSettingsView.js +37 -37
- package/dist/features/admin/components/AdminStoreEditorView.js +75 -52
- package/dist/features/admin/components/AdminSublistingCategoryEditorView.js +71 -30
- package/dist/features/admin/components/AdminTesterChecklistItemEditorView.js +95 -65
- package/dist/features/admin/components/BrandQuickCreateForm.d.ts +10 -0
- package/dist/features/admin/components/BrandQuickCreateForm.js +49 -26
- package/dist/features/admin/components/CategoryQuickCreateForm.d.ts +11 -0
- package/dist/features/admin/components/CategoryQuickCreateForm.js +48 -26
- package/dist/features/admin/components/index.d.ts +2 -2
- package/dist/features/admin/components/index.js +1 -1
- package/dist/features/admin/constants/site-settings-tabs.d.ts +120 -0
- package/dist/features/admin/constants/site-settings-tabs.js +152 -0
- package/dist/features/admin/index.d.ts +0 -3
- package/dist/features/admin/index.js +0 -2
- package/dist/features/admin/repository/analytics-rollup.repository.d.ts +35 -0
- package/dist/features/admin/repository/analytics-rollup.repository.js +30 -0
- package/dist/features/admin/schemas/admin-editor-forms.d.ts +56 -57
- package/dist/features/admin/schemas/admin-editor-forms.js +97 -54
- package/dist/features/admin/schemas/admin-ops-forms.d.ts +57 -5
- package/dist/features/admin/schemas/admin-ops-forms.js +104 -20
- package/dist/features/admin/schemas/admin-user-form.js +7 -0
- package/dist/features/admin/schemas/firestore.d.ts +48 -95
- package/dist/features/admin/schemas/firestore.js +15 -151
- package/dist/features/admin/schemas/nav-item-form.js +9 -2
- package/dist/features/admin/server.d.ts +0 -4
- package/dist/features/admin/server.js +0 -2
- package/dist/features/admin/types/index.d.ts +0 -7
- package/dist/features/analytics/repository/page-views.repository.d.ts +14 -0
- package/dist/features/analytics/repository/page-views.repository.js +24 -0
- package/dist/features/auth/actions/index.d.ts +0 -1
- package/dist/features/auth/actions/index.js +0 -1
- package/dist/features/auth/actions/realtime-token-actions.js +1 -12
- package/dist/features/auth/auth-helpers.js +5 -7
- package/dist/features/auth/components/Guards.js +2 -1
- package/dist/features/auth/components/LoginForm.d.ts +0 -2
- package/dist/features/auth/components/LoginForm.js +1 -3
- package/dist/features/auth/components/RegisterForm.js +62 -19
- package/dist/features/auth/permissions/constants.d.ts +27 -1
- package/dist/features/auth/role-predicates.d.ts +75 -7
- package/dist/features/auth/role-predicates.js +28 -2
- package/dist/features/auth/schemas/firestore.d.ts +22 -0
- package/dist/features/auth/schemas/index.d.ts +1 -1
- package/dist/features/auth/schemas/index.js +44 -8
- package/dist/features/auth/types/index.d.ts +14 -1
- package/dist/features/blog/api/route.js +10 -2
- package/dist/features/blog/components/BlogIndexListing.js +1 -1
- package/dist/features/blog/hooks/useBlog.js +2 -2
- package/dist/features/blog/schemas/index.d.ts +4 -0
- package/dist/features/blog/schemas/index.js +2 -0
- package/dist/features/blog/types/index.d.ts +1 -1
- package/dist/features/catalogue/components/CatalogueItemEditorView.d.ts +22 -0
- package/dist/features/catalogue/components/CatalogueItemEditorView.js +112 -52
- package/dist/features/catalogue/schemas/validation.d.ts +4 -4
- package/dist/features/catalogue/schemas/validation.js +47 -10
- package/dist/features/categories/schemas/bundle-form.d.ts +142 -0
- package/dist/features/categories/schemas/bundle-form.js +149 -0
- package/dist/features/classified/components/ClassifiedDetailPageView.d.ts +5 -4
- package/dist/features/classified/components/ClassifiedDetailPageView.js +37 -16
- package/dist/features/consultation/components/ConsultationForm.js +1 -1
- package/dist/features/consultation/schemas/index.js +34 -10
- package/dist/features/corporate/schemas/index.js +36 -15
- package/dist/features/filters/FilterPanel.js +3 -3
- package/dist/features/filters/RangeFilter.d.ts +10 -0
- package/dist/features/filters/RangeFilter.js +41 -4
- package/dist/features/homepage/components/MarketplaceHomepageView.js +24 -5
- package/dist/features/homepage/hooks/useBlogArticles.js +1 -1
- package/dist/features/layout/BottomActions.js +84 -11
- package/dist/features/layout/BottomActionsContext.d.ts +1 -6
- package/dist/features/layout/BottomActionsContext.js +1 -1
- package/dist/features/layout/hooks/useBottomActions.js +2 -2
- package/dist/features/layout/hooks/useFormBottomActions.d.ts +12 -1
- package/dist/features/layout/hooks/useFormBottomActions.js +12 -6
- package/dist/features/lottery/schemas/config-write.d.ts +9 -5
- package/dist/features/lottery/schemas/config-write.js +69 -9
- package/dist/features/orders/schemas/buyer-forms.d.ts +6 -3
- package/dist/features/orders/schemas/buyer-forms.js +31 -3
- package/dist/features/orders/schemas/index.d.ts +2 -2
- package/dist/features/products/components/GroupSettingsPanel.js +3 -1
- package/dist/features/products/components/MakeOfferButton.d.ts +13 -2
- package/dist/features/products/components/MakeOfferButton.js +33 -11
- package/dist/features/products/components/ProductDetailActions.js +0 -1
- package/dist/features/products/components/ProductDetailPageView.d.ts +7 -3
- package/dist/features/products/components/ProductDetailPageView.js +8 -5
- package/dist/features/products/constants/action-defs.d.ts +1 -2
- package/dist/features/products/constants/action-defs.js +40 -43
- package/dist/features/products/repository/products.repository.d.ts +5 -1
- package/dist/features/products/repository/products.repository.js +23 -1
- package/dist/features/products/schemas/firestore.d.ts +7 -3
- package/dist/features/products/schemas/product-features.validators.js +3 -0
- package/dist/features/promotions/components/CouponCard.js +8 -7
- package/dist/features/scams/schemas/firestore.d.ts +15 -1
- package/dist/features/scams/schemas/report-form.d.ts +5 -5
- package/dist/features/scams/schemas/report-form.js +18 -3
- package/dist/features/search/action-index/derive.d.ts +49 -0
- package/dist/features/search/action-index/derive.js +121 -0
- package/dist/features/search/action-index/repository.d.ts +42 -0
- package/dist/features/search/action-index/repository.js +100 -0
- package/dist/features/search/action-index/settings-entries.d.ts +41 -0
- package/dist/features/search/action-index/settings-entries.js +268 -0
- package/dist/features/search/action-index/types.d.ts +81 -0
- package/dist/features/search/action-index/types.js +102 -0
- package/dist/features/search/action-index/validate-entry.d.ts +34 -0
- package/dist/features/search/action-index/validate-entry.js +114 -0
- package/dist/features/search/hooks/useNavSuggestions.d.ts +5 -1
- package/dist/features/search/hooks/useNavSuggestions.js +23 -3
- package/dist/features/search/repository/search.repository.js +3 -1
- package/dist/features/seller/actions/offer-actions.js +43 -24
- package/dist/features/seller/actions/seller-actions.js +29 -5
- package/dist/features/seller/components/CategoryChipPicker.d.ts +9 -0
- package/dist/features/seller/components/CategoryChipPicker.js +12 -0
- package/dist/features/seller/components/SellerAddressesView.js +5 -1
- package/dist/features/seller/components/SellerAnalyticsAlertsView.js +83 -43
- package/dist/features/seller/components/SellerArtView.d.ts +14 -7
- package/dist/features/seller/components/SellerArtView.js +4 -119
- package/dist/features/seller/components/SellerClassifiedView.d.ts +14 -7
- package/dist/features/seller/components/SellerClassifiedView.js +4 -124
- package/dist/features/seller/components/SellerCouponEditorView.d.ts +22 -1
- package/dist/features/seller/components/SellerCouponEditorView.js +91 -45
- package/dist/features/seller/components/SellerDigitalCodesView.d.ts +14 -7
- package/dist/features/seller/components/SellerDigitalCodesView.js +4 -126
- package/dist/features/seller/components/SellerListingTypeView.d.ts +66 -0
- package/dist/features/seller/components/SellerListingTypeView.js +277 -0
- package/dist/features/seller/components/SellerLiveView.d.ts +14 -7
- package/dist/features/seller/components/SellerLiveView.js +4 -126
- package/dist/features/seller/components/SellerSidebar.d.ts +3 -6
- package/dist/features/seller/components/SellerStickersView.d.ts +14 -7
- package/dist/features/seller/components/SellerStickersView.js +4 -119
- package/dist/features/seller/components/SellerSupportView.d.ts +31 -0
- package/dist/features/seller/components/SellerSupportView.js +100 -0
- package/dist/features/seller/components/index.d.ts +4 -8
- package/dist/features/seller/components/index.js +2 -4
- package/dist/features/seller/index.d.ts +0 -1
- package/dist/features/seller/index.js +0 -1
- package/dist/features/seller/schemas/coupon-form.d.ts +214 -0
- package/dist/features/seller/schemas/coupon-form.js +155 -23
- package/dist/features/seller/schemas/offer-forms.d.ts +16 -7
- package/dist/features/seller/schemas/offer-forms.js +28 -12
- package/dist/features/shell/FormShell.js +2 -2
- package/dist/features/shell/QuickFormDrawer.js +2 -2
- package/dist/features/shell/SectionForm.d.ts +19 -1
- package/dist/features/shell/SectionForm.js +104 -17
- package/dist/features/shell/build-sections.d.ts +23 -0
- package/dist/features/shell/build-sections.js +106 -8
- package/dist/features/shell/field-ui-meta.d.ts +44 -0
- package/dist/features/shell/field-ui-meta.js +14 -7
- package/dist/features/shell/form-sections.d.ts +46 -0
- package/dist/features/shell/form-sections.js +114 -0
- package/dist/features/shipments/components/AdminShipmentEditorView.js +87 -64
- package/dist/features/shipments/schemas/validation.d.ts +2 -2
- package/dist/features/shipments/schemas/validation.js +44 -13
- package/dist/features/store-extensions/schemas/custom-role-form.js +6 -2
- package/dist/features/store-extensions/schemas/listing-template-form.d.ts +7 -7
- package/dist/features/store-extensions/schemas/payout-method-form.js +14 -0
- package/dist/features/stores/actions/store-query-actions.js +3 -2
- package/dist/features/stores/api/[storeSlug]/auctions/route.js +3 -1
- package/dist/features/stores/api/[storeSlug]/products/route.js +3 -1
- package/dist/features/support/repository/support.repository.d.ts +21 -0
- package/dist/features/support/repository/support.repository.js +63 -0
- package/dist/features/support/schemas/firestore.d.ts +27 -2
- package/dist/features/support/schemas/firestore.js +2 -0
- package/dist/features/support/schemas/ticket-create-form.js +7 -0
- package/dist/features/tester/schemas/checklist-item-form.d.ts +43 -0
- package/dist/features/tester/schemas/checklist-item-form.js +109 -0
- package/dist/features/tester/seed-data/blog-tester-seed-data.js +2 -2
- package/dist/features/tester/seed-data/categories-tester-seed-data.js +2 -2
- package/dist/features/tester/seed-data/events-tester-seed-data.js +5 -5
- package/dist/features/tester/seed-data/orders-tester-seed-data.js +2 -2
- package/dist/features/tester/seed-data/products-tester-seed-data.js +48 -49
- package/dist/features/tester/seed-data/stores-tester-seed-data.js +3 -3
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +275 -29
- package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +48 -12
- package/dist/http/api-handler.js +2 -1
- package/dist/index.d.ts +24 -103
- package/dist/index.js +33 -141
- package/dist/jobs.d.ts +1 -1
- package/dist/jobs.js +1 -1
- package/dist/monitoring/browser.d.ts +0 -2
- package/dist/monitoring/browser.js +12 -1
- package/dist/monitoring/index.d.ts +0 -2
- package/dist/monitoring/index.js +0 -1
- package/dist/next/api/routeHandler.js +6 -3
- package/dist/next/index.d.ts +1 -2
- package/dist/next/index.js +0 -1
- package/dist/next/middleware/index.d.ts +0 -2
- package/dist/next/middleware/index.js +0 -1
- package/dist/next/routing/route-map.d.ts +8 -7
- package/dist/next/routing/route-map.js +10 -3
- package/dist/providers/db-firebase/admin.js +15 -1
- package/dist/providers/db-firebase/sieve.d.ts +22 -0
- package/dist/react/hooks/useListingTypeFlags.d.ts +1 -1
- package/dist/react/hooks/useListingTypeFlags.js +1 -1
- package/dist/react/hooks/usePendingFilters.js +5 -3
- package/dist/react/hooks/usePendingTable.js +15 -3
- package/dist/repositories/index.d.ts +0 -1
- package/dist/repositories/index.js +0 -1
- package/dist/schemas/registry.d.ts +197 -232
- package/dist/schemas/registry.js +5 -6
- package/dist/schemas/webhooks/razorpay.d.ts +50 -50
- package/dist/security/authorization.d.ts +9 -0
- package/dist/security/authorization.js +16 -2
- package/dist/security/browser.d.ts +1 -3
- package/dist/security/browser.js +5 -2
- package/dist/security/index.d.ts +1 -3
- package/dist/security/index.js +19 -2
- package/dist/security/pii-encrypt.js +16 -0
- package/dist/security/pii-schemas.d.ts +0 -14
- package/dist/security/pii-schemas.js +7 -15
- package/dist/seed/_helpers/category-forest.js +2 -2
- package/dist/seed/_helpers/media.d.ts +19 -0
- package/dist/seed/_helpers/media.js +75 -0
- package/dist/seed/blog-posts-seed-data.js +17 -17
- package/dist/seed/carousel-slides-seed-data.js +7 -7
- package/dist/seed/cart-seed-data.js +13 -13
- package/dist/seed/categories-seed-data.js +10 -10
- package/dist/seed/events-seed-data.js +24 -24
- package/dist/seed/factories/product.factory.js +4 -4
- package/dist/seed/factories/store.factory.js +3 -3
- package/dist/seed/factories/user.factory.js +2 -2
- package/dist/seed/grouped-listings-seed-data.js +11 -11
- package/dist/seed/homepage-sections-seed-data.js +2 -2
- package/dist/seed/index.d.ts +0 -1
- package/dist/seed/index.js +0 -1
- package/dist/seed/manifest.d.ts +1 -1
- package/dist/seed/manifest.js +0 -5
- package/dist/seed/notifications-seed-data.js +20 -1
- package/dist/seed/orders-seed-data.js +2 -2
- package/dist/seed/products-art-seed-data.js +16 -16
- package/dist/seed/products-auctions-seed-data.js +34 -34
- package/dist/seed/products-classifieds-seed-data.js +35 -37
- package/dist/seed/products-digital-codes-seed-data.js +20 -20
- package/dist/seed/products-live-items-seed-data.js +11 -11
- package/dist/seed/products-preorders-seed-data.js +13 -13
- package/dist/seed/products-prize-draws-seed-data.js +43 -43
- package/dist/seed/products-standard-seed-data.js +54 -54
- package/dist/seed/products-stickers-seed-data.js +15 -15
- package/dist/seed/reviews-seed-data.js +4 -4
- package/dist/seed/scammers-seed-data.js +3 -3
- package/dist/seed/site-settings-seed-data.js +11 -24
- package/dist/seed/store-extensions-seed-data.js +10 -10
- package/dist/seed/stores-seed-data.js +11 -11
- package/dist/seed/support-tickets-seed-data.d.ts +14 -0
- package/dist/seed/support-tickets-seed-data.js +20 -1
- package/dist/seed/users-seed-data.js +20 -20
- package/dist/server-entry.d.ts +0 -1
- package/dist/server-entry.js +0 -1
- package/dist/server.d.ts +9 -6
- package/dist/server.js +7 -13
- package/dist/styles.css +248 -68
- package/dist/tailwind-utilities.css +1 -1
- package/dist/tokens/themes/default-dark.js +2 -2
- package/dist/tokens/themes/default-light.js +2 -2
- package/dist/tokens/tokens.css +24 -4
- package/dist/ui/components/ActionRow.d.ts +50 -0
- package/dist/ui/components/ActionRow.js +53 -0
- package/dist/ui/components/ActionRow.style.css +145 -0
- package/dist/ui/components/Drawer.d.ts +10 -0
- package/dist/ui/components/Drawer.js +2 -1
- package/dist/ui/components/FilterDrawer.js +4 -1
- package/dist/ui/components/FilterDrawer.style.css +6 -12
- package/dist/ui/components/FormActionBar.js +14 -1
- package/dist/ui/components/FormActionBar.style.css +12 -11
- package/dist/ui/components/ListingLayout.js +8 -1
- package/dist/ui/components/ListingLayout.style.css +6 -13
- package/dist/ui/components/Modal.d.ts +15 -1
- package/dist/ui/components/Modal.js +17 -3
- package/dist/ui/components/QuickCreateModal.js +2 -2
- package/dist/ui/components/QuickCreateModal.style.css +78 -84
- package/dist/ui/components/RecordDetailModal.js +2 -2
- package/dist/ui/components/SideDrawer.d.ts +8 -0
- package/dist/ui/components/SideDrawer.js +2 -1
- package/dist/ui/components/SideDrawer.style.css +6 -3
- package/dist/ui/components/SideModal.d.ts +11 -1
- package/dist/ui/components/SideModal.js +3 -2
- package/dist/ui/components/SideModal.style.css +14 -0
- package/dist/ui/components/Typography.style.css +33 -24
- package/dist/ui/components/index.style.css +1 -0
- package/dist/ui/forms/FormShell.style.css +260 -311
- package/dist/ui/index.d.ts +2 -0
- package/dist/ui/index.js +1 -0
- package/dist/utils/index.d.ts +0 -1
- package/dist/utils/index.js +0 -1
- package/dist/validation/index.d.ts +1 -1
- package/dist/validation/index.js +1 -1
- package/dist/validation/schemas.d.ts +0 -19
- package/dist/validation/schemas.js +8 -19
- package/package.json +1 -1
- package/scripts/seed-cli.mjs +3 -6
|
@@ -396,6 +396,18 @@ const rawTesterChecklistItems = [
|
|
|
396
396
|
href: "/products",
|
|
397
397
|
cases: [
|
|
398
398
|
{ key: "standard-detail", label: "Standard product detail page loads correctly", href: "/products/product-tester-standard-1" },
|
|
399
|
+
{
|
|
400
|
+
key: "seed-images-are-labelled-tiles",
|
|
401
|
+
label: "Every seeded product image is a readable tile showing the item's own name — NOT a broken-image icon and NOT an empty grey box",
|
|
402
|
+
description: "Fixed 2026-08-31. BEFORE: 409 of the ~437 seeded image URLs pointed at picsum.photos, which went down (503 from its origin AND its CDN), so nearly every image on the site returned 502 and rendered as the browser's broken-image icon. AFTER: images come from `seedPhoto()`, which names its host in ONE place and renders the item's name on the tile. Open a product grid and confirm the tiles show text like \"Dranzer Phoenix\" rather than broken icons. A grid of identical blank tiles is also a fail — each should differ.",
|
|
403
|
+
href: "/products",
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
key: "unreachable-image-degrades-to-placeholder",
|
|
407
|
+
label: "An image whose upstream host is unreachable shows a neutral \"Image unavailable\" tile, not a broken-image icon",
|
|
408
|
+
description: "This is the safety net under the case above, and it is checked with a URL that CANNOT work — a real one proves nothing. Open /api/media/ext?url=https%3A%2F%2Fexample.invalid%2Fx.png directly in a browser tab. BEFORE it returned 502 with the text \"Failed to fetch external image.\", which an <img> renders as a broken icon. AFTER it must return an SVG tile reading \"Image unavailable\". In devtools the response must carry the header X-Media-Placeholder: 1 and a SHORT Cache-Control (max-age=60) — if that header is missing, the tile is being cached as though it were a real image and the outage would outlive its cause.",
|
|
409
|
+
href: "/products",
|
|
410
|
+
},
|
|
399
411
|
{
|
|
400
412
|
key: "auction-detail",
|
|
401
413
|
label: "Auction detail page shows current bid + a live, ticking countdown correctly",
|
|
@@ -461,7 +473,24 @@ const rawTesterChecklistItems = [
|
|
|
461
473
|
description: "Added 2026-08-21 — this carousel previously had no test case of its own outside the category/brand check. It is DIFFERENT from the \"Part of / Parts in this group\" set panel: this one is a horizontal themed scroller (titled by the group's theme, e.g. \"Same character\" / \"From the same set\"), it sits lower down near the related-items carousels, and it only shows for products that belong to an active, visible group. Confirm the cards show real titles and images (not placeholders) and that clicking one lands on that item's own detail page.",
|
|
462
474
|
href: "/products/product-tester-standard-1",
|
|
463
475
|
},
|
|
464
|
-
{
|
|
476
|
+
{
|
|
477
|
+
key: "classified-offer-is-the-purchase-path",
|
|
478
|
+
label: "EVERY classified listing offers a way to transact — either \"Make Offer\" or \"Request to Buy\" — and none offers Add to Cart or Buy Now",
|
|
479
|
+
description: "Changed 2026-08-31. BEFORE: the button was \"Contact Seller\" and opened a chat thread; the offer form appeared only on listings where the seller had ticked `allowOffers`, so on the other half the page showed a price and a chat box. AFTER: chat is gone and the offer IS the purchase path, so the panel is never empty. Check BOTH kinds — `classified-beyblade-stadium-set` (negotiable, allowOffers on) must read \"Make Offer\"; `classified-beyblade-burst-collection-bengaluru` (not negotiable) must read \"Request to Buy\". A classified with NO button at all is the regression this case exists to catch.",
|
|
480
|
+
href: "/classified/classified-beyblade-stadium-set",
|
|
481
|
+
},
|
|
482
|
+
{
|
|
483
|
+
key: "classified-buy-request-pinned-to-price",
|
|
484
|
+
label: "On a NON-negotiable classified, the Request-to-Buy amount is fixed at the asking price and cannot be edited down",
|
|
485
|
+
description: "The amount field is read-only and pre-filled at the listed price. Try to submit a lower number (via the browser's dev tools if the field won't take it) — the server must reject it with \"This seller is not taking lower offers\". BEFORE 2026-08-31 this listing accepted no offer at all: `allowOffers` was unset, so the form never rendered.",
|
|
486
|
+
href: "/classified/classified-beyblade-burst-collection-bengaluru",
|
|
487
|
+
},
|
|
488
|
+
{
|
|
489
|
+
key: "classified-full-price-offer-accepted",
|
|
490
|
+
label: "On a NEGOTIABLE classified, an offer at exactly the asking price is ACCEPTED, not rejected",
|
|
491
|
+
description: "BEFORE: the shared rule rejected any offer >= the listed price with \"use Add to Cart instead\" — advice that named a button a classified does not have, so a buyer willing to pay full price had no route at all. AFTER: the ceiling is inclusive on a listing with no cart. Enter exactly the listed price and submit; it must succeed. On an ORDINARY product (/products/...) the same amount must still be rejected — that is the half of the rule which did not change.",
|
|
492
|
+
href: "/classified/classified-beyblade-stadium-set",
|
|
493
|
+
},
|
|
465
494
|
{ key: "digitalcode-delivery", label: "Purchasing a digital-code listing delivers the code to the buyer post-purchase", href: "/digital-codes/digitalcode-tester-sandbox-1" },
|
|
466
495
|
{ key: "live-item-detail", label: "A live-item listing's detail page shows the livestream link correctly", href: "/live/live-tester-sandbox-1" },
|
|
467
496
|
{
|
|
@@ -1773,28 +1802,6 @@ const rawTesterChecklistItems = [
|
|
|
1773
1802
|
},
|
|
1774
1803
|
],
|
|
1775
1804
|
},
|
|
1776
|
-
{
|
|
1777
|
-
pageKey: "messaging",
|
|
1778
|
-
pageLabel: "Messaging a Seller",
|
|
1779
|
-
href: "/user/messages",
|
|
1780
|
-
cases: [
|
|
1781
|
-
{ key: "start-conversation", label: "Starting a conversation with a seller works" },
|
|
1782
|
-
{ key: "receive-reply", label: "Receiving and reading a seller's reply works" },
|
|
1783
|
-
{
|
|
1784
|
-
key: "message-realtime-update",
|
|
1785
|
-
label: "A new message appears in the RECIPIENT's thread without a manual page refresh (realtime ping-channel)",
|
|
1786
|
-
description: "Fixed 2026-08-28. BEFORE: only the SENDER saw their own message, because the send handler calls refetch() directly — the recipient saw nothing until they reloaded. Two stacked faults: the hooks subscribed without ever signing in to the realtime database, AND the `conversationIds` claim the security rule requires had no issuer anywhere (the token route only sent `chatIds`, from an unrelated collection). Both errors were swallowed, one into a literal `// ignore`. AFTER: open the same conversation as buyer in one browser and seller in another, send from one, and the other updates on its own. Test it from BOTH directions — testing only the sender's own window is what let this pass before.",
|
|
1787
|
-
href: "/user/messages",
|
|
1788
|
-
},
|
|
1789
|
-
{
|
|
1790
|
-
key: "message-live-updates-survive-bulk-job",
|
|
1791
|
-
label: "Running a bulk admin action while a message thread is open does not kill that thread's live updates",
|
|
1792
|
-
description: "Fixed 2026-08-28. BEFORE: the realtime app is a single shared connection, and finishing a bulk job signed it out globally — so an open chat or message thread silently stopped updating, and vice versa (opening a chat killed an in-flight bulk job's progress). AFTER: open a message thread, run any bulk action from an admin listing until it completes, then send a message from the other account — the thread must still update live without a reload.",
|
|
1793
|
-
href: "/user/messages",
|
|
1794
|
-
},
|
|
1795
|
-
{ key: "messages-list-thread", label: "The messages list and individual thread view both load correctly", href: "/user/messages" },
|
|
1796
|
-
],
|
|
1797
|
-
},
|
|
1798
1805
|
{
|
|
1799
1806
|
pageKey: "user-dashboard-extras",
|
|
1800
1807
|
pageLabel: "User Dashboard — Addresses, Catalogue, Settings, My Orders-by-Type",
|
|
@@ -2198,11 +2205,10 @@ const rawTesterChecklistItems = [
|
|
|
2198
2205
|
},
|
|
2199
2206
|
{
|
|
2200
2207
|
pageKey: "seller-ops-comms",
|
|
2201
|
-
pageLabel: "Seller Addresses,
|
|
2208
|
+
pageLabel: "Seller Addresses, Fulfillment & Print",
|
|
2202
2209
|
href: "/store/addresses",
|
|
2203
2210
|
cases: [
|
|
2204
2211
|
{ key: "seller-addresses-crud", label: "Seller can add and edit store addresses", href: "/store/addresses" },
|
|
2205
|
-
{ key: "seller-messages", label: "Seller messages list and thread view both work", href: "/store/messages" },
|
|
2206
2212
|
{ key: "seller-fulfillment-queue", label: "Seller fulfillment queue shows accurate pending items", href: "/store/fulfillment" },
|
|
2207
2213
|
{ key: "seller-print-center", label: "Seller print-center generates labels/invoices correctly", href: "/store/print-center" },
|
|
2208
2214
|
{ key: "seller-inventory-print", label: "Seller inventory print view works", href: "/store/print-center" },
|
|
@@ -2658,7 +2664,7 @@ const rawTesterChecklistItems = [
|
|
|
2658
2664
|
{
|
|
2659
2665
|
key: "classified-bar-no-cart",
|
|
2660
2666
|
label: "A classified listing's sticky bar offers \"Make an Offer\" and does NOT offer Add to Cart or Buy Now — classifieds are arranged directly with the seller",
|
|
2661
|
-
description: "Tapping it should jump to the
|
|
2667
|
+
description: "Tapping it should jump to the offer panel, and that panel must contain a real form (see classified-offer-is-the-purchase-path). An Add to Cart button appearing here is a real bug; so is the bar scrolling to an empty box.",
|
|
2662
2668
|
href: "/classified/classified-tester-sandbox-1",
|
|
2663
2669
|
},
|
|
2664
2670
|
{
|
|
@@ -2715,11 +2721,29 @@ const rawTesterChecklistItems = [
|
|
|
2715
2721
|
label: "IN LIGHT MODE: the small promotional badges on product cards — NEW, SALE, LIMITED, the bundle discount percentage, an auction's \"Ending soon\" / \"Reserve\" pill — are all readable against their colored background",
|
|
2716
2722
|
href: "/products",
|
|
2717
2723
|
},
|
|
2724
|
+
{
|
|
2725
|
+
key: "soft-ban-lift-notification-has-a-body",
|
|
2726
|
+
label: "Lifting a user's soft ban sends them a notification WITH a readable message, not just a title",
|
|
2727
|
+
description: "Fixed 2026-08-31. BEFORE: the route wrote `body`, `entityId` and `entityType` behind an `as any` — none of which are fields on NotificationDocument (they are `message`, `relatedId`, `relatedType`) — so the bell showed a title and an EMPTY body, and the notification never reached email or WhatsApp. AFTER: as an admin, lift any soft ban on a test user from /admin/users, then open that user's bell. The row must show both the title AND the sentence naming which restriction was lifted, and it must be clickable.",
|
|
2728
|
+
href: "/admin/users",
|
|
2729
|
+
},
|
|
2730
|
+
{
|
|
2731
|
+
key: "review-feedback-notification-delivers",
|
|
2732
|
+
label: "Sending feedback on a review notifies the buyer with the message body, and the row is clickable",
|
|
2733
|
+
description: "Fixed 2026-08-31. BEFORE: three defects on one line, all hidden by an `as any` — the body was written under the wrong field name so it rendered empty; `type: \"store_feedback\"` is not a real notification type so no channel could ever be allow-listed for it; and it skipped `actionUrl` resolution so the row went nowhere when clicked. AFTER: as a seller, send feedback on a review from /store/reviews, then check that buyer's bell — message present, row clickable.",
|
|
2734
|
+
href: "/store/reviews",
|
|
2735
|
+
},
|
|
2736
|
+
{
|
|
2737
|
+
key: "analytics-tags-absent-until-configured",
|
|
2738
|
+
label: "With no analytics IDs set, the page source contains NO googletagmanager / connect.facebook.net script tag",
|
|
2739
|
+
description: "Added 2026-08-31. siteSettings.integrations has held GA / Pixel / GTM IDs for a long time with NOTHING reading them; they now drive real tags. This case checks the OFF state, which is the one that can silently regress into tracking every visitor. With Site Settings → Integrations empty, view-source on the homepage and search for \"googletagmanager\" — a `preconnect` link is expected and fine, a `<script>` is a bug. Then set a GA ID, reload, and confirm the script now appears: a tag that never appears is the opposite failure.",
|
|
2740
|
+
href: "/admin/site",
|
|
2741
|
+
},
|
|
2718
2742
|
{
|
|
2719
2743
|
key: "notification-count-bubbles-readable",
|
|
2720
|
-
label: "The unread-count bubbles (the small number circles on the header bell icon
|
|
2721
|
-
description: "You need at least one unread notification or
|
|
2722
|
-
href: "/user/
|
|
2744
|
+
label: "The unread-count bubbles (the small number circles on the header bell icon and the cart/bottom action bar) show a readable number in BOTH light and dark mode — not a colored blob with an invisible digit",
|
|
2745
|
+
description: "You need at least one unread notification, or an item in the cart, for the bubble to appear. Check both themes. (This case used to also name the Messages nav item; buyer↔seller messaging was removed 2026-08-31.)",
|
|
2746
|
+
href: "/user/notifications",
|
|
2723
2747
|
},
|
|
2724
2748
|
{
|
|
2725
2749
|
key: "admin-status-chips-colored",
|
|
@@ -4039,6 +4063,228 @@ const rawTesterChecklistItems = [
|
|
|
4039
4063
|
],
|
|
4040
4064
|
},
|
|
4041
4065
|
]),
|
|
4066
|
+
...group("cta-layout", "CTA & Action Rows", [
|
|
4067
|
+
{
|
|
4068
|
+
pageKey: "checkout-bottom-bar",
|
|
4069
|
+
pageLabel: "Checkout — bottom action bar",
|
|
4070
|
+
href: "/checkout",
|
|
4071
|
+
cases: [
|
|
4072
|
+
{
|
|
4073
|
+
key: "back-not-squeezed",
|
|
4074
|
+
label: "Back and Continue to payment are BOTH readable at 320px width",
|
|
4075
|
+
description: "Narrow the browser to 320px and reach the Extras step. BEFORE: Back rendered as a ~44px box clipped to 'Ba' while Continue to payment took ~80% of the row. AFTER: each button is as wide as its own label needs, Continue to payment is visibly the wider of the two, and neither shows a cut-off word or an ellipsis.",
|
|
4076
|
+
},
|
|
4077
|
+
{
|
|
4078
|
+
key: "no-truncation-anywhere",
|
|
4079
|
+
label: "No CTA label is ever cut off — long ones wrap to two lines",
|
|
4080
|
+
description: "Check the payment step, whose primary reads 'Pay Online (Razorpay)'. BEFORE: a label too long for its share was clipped with an ellipsis. AFTER: the button grows taller and shows the whole label on two lines. An ellipsis anywhere in the bar is a failure.",
|
|
4081
|
+
},
|
|
4082
|
+
],
|
|
4083
|
+
},
|
|
4084
|
+
{
|
|
4085
|
+
pageKey: "product-bottom-bar",
|
|
4086
|
+
pageLabel: "Product detail — three-action bar",
|
|
4087
|
+
href: "/products/product-beyblade-original-dranzer-s",
|
|
4088
|
+
cases: [
|
|
4089
|
+
{
|
|
4090
|
+
key: "three-actions-wrap-to-second-row",
|
|
4091
|
+
label: "Wishlist / Add to Cart / Buy Now use two rows, none squeezed",
|
|
4092
|
+
description: "On a phone-width viewport. Wishlist and Add to Cart share the upper row, Buy Now gets the full-width row beneath. BEFORE: Wishlist was pinned to its content width and the row read lopsided. AFTER: the two secondaries share the upper row proportionally. Tap Add to Cart and watch the label become 'Adding…' — the number of ROWS must not change while it does.",
|
|
4093
|
+
},
|
|
4094
|
+
],
|
|
4095
|
+
},
|
|
4096
|
+
{
|
|
4097
|
+
pageKey: "editor-action-bar",
|
|
4098
|
+
pageLabel: "Editor bar — four actions",
|
|
4099
|
+
href: "/store/products/new",
|
|
4100
|
+
cases: [
|
|
4101
|
+
{
|
|
4102
|
+
key: "four-buttons-wrap-not-overflow",
|
|
4103
|
+
label: "Discard / Preview / Save draft / Publish wrap instead of overflowing",
|
|
4104
|
+
description: "Open a product editor at 375px width with unsaved changes so Discard appears. BEFORE: all four buttons were flex-shrink:0 in a non-wrapping row, so the group ran off the right edge of the bar. AFTER: they wrap onto a second line and every one stays fully on screen. Nothing should scroll sideways.",
|
|
4105
|
+
},
|
|
4106
|
+
],
|
|
4107
|
+
},
|
|
4108
|
+
{
|
|
4109
|
+
pageKey: "dialog-footers",
|
|
4110
|
+
pageLabel: "Dialog & drawer footers",
|
|
4111
|
+
href: "/admin/products",
|
|
4112
|
+
cases: [
|
|
4113
|
+
{
|
|
4114
|
+
key: "modal-footer-stays-compact-on-desktop",
|
|
4115
|
+
label: "A dialog's buttons stay compact and right-aligned at 1280px",
|
|
4116
|
+
description: "Open any admin confirm dialog on a desktop viewport. The buttons must remain content-width and packed to the right — they must NOT stretch across the whole dialog like banners. This is the deliberate difference between a dialog footer and a mobile bar; a stretched pair here is a regression.",
|
|
4117
|
+
},
|
|
4118
|
+
{
|
|
4119
|
+
key: "filter-drawer-footer-stacks-when-narrow",
|
|
4120
|
+
label: "Filter drawer's Reset all / Apply stack rather than shrink when narrow",
|
|
4121
|
+
description: "Open the mobile filter drawer on a narrow phone. Both buttons stay fully readable; if there is not room for both side by side they stack one per line. Neither may be crushed below its label.",
|
|
4122
|
+
},
|
|
4123
|
+
],
|
|
4124
|
+
},
|
|
4125
|
+
]),
|
|
4126
|
+
...group("addresses", "Addresses", [
|
|
4127
|
+
{
|
|
4128
|
+
pageKey: "postal-lookup",
|
|
4129
|
+
pageLabel: "Postal code lookup",
|
|
4130
|
+
href: "/user/addresses/new",
|
|
4131
|
+
cases: [
|
|
4132
|
+
{
|
|
4133
|
+
key: "pin-fills-city-and-state",
|
|
4134
|
+
label: "Typing a complete PIN code fills City and State",
|
|
4135
|
+
description: "BEFORE: there was no lookup anywhere in the app — two dead modules carried the shape of one that had been removed, which is why it looked built. AFTER: leave City and State empty, choose India, and type 560001 into the PIN code box. Within about half a second City and State fill themselves in. Nothing else on the form changes.",
|
|
4136
|
+
},
|
|
4137
|
+
{
|
|
4138
|
+
key: "lookup-never-overwrites",
|
|
4139
|
+
label: "The lookup never overwrites a city you typed yourself",
|
|
4140
|
+
description: "Type your own City FIRST — something deliberately unusual, e.g. 'Whitefield' — then type a PIN code that resolves to a different city. Your text must survive untouched. Autofill only ever lands in an EMPTY field; a lookup that corrects a deliberate entry is a bug the user cannot even report, because they just watch their own typing vanish.",
|
|
4141
|
+
},
|
|
4142
|
+
{
|
|
4143
|
+
key: "lookup-miss-is-a-non-event",
|
|
4144
|
+
label: "An unrecognised PIN code leaves the form usable",
|
|
4145
|
+
description: "Type a valid-format but unknown PIN code, e.g. 999999. Nothing must break: no error banner, no red field, no disabled Save. City and State simply stay empty and editable, and the address still saves. Coverage for India is thinner than India Post's, so a miss is the COMMON case and has to be a non-event.",
|
|
4146
|
+
},
|
|
4147
|
+
],
|
|
4148
|
+
},
|
|
4149
|
+
{
|
|
4150
|
+
pageKey: "postal-validation",
|
|
4151
|
+
pageLabel: "Postal code validation",
|
|
4152
|
+
href: "/user/addresses/new",
|
|
4153
|
+
cases: [
|
|
4154
|
+
{
|
|
4155
|
+
key: "letters-rejected-on-the-field",
|
|
4156
|
+
label: "A non-numeric PIN code is rejected ON THE FIELD, not by the server",
|
|
4157
|
+
description: "Enter 'abcdef' as the PIN code for an Indian address and press Save. BEFORE: the client rule was min-length-1, so this passed every check you could see and came back a server 400 with nothing marked — and one admin route checked LENGTH ONLY, so 'abcdef' was a valid PIN code as far as it was concerned. AFTER: the error appears under the PIN code field before any request is sent.",
|
|
4158
|
+
},
|
|
4159
|
+
{
|
|
4160
|
+
key: "country-decides-the-rule",
|
|
4161
|
+
label: "Switching country switches the postal rule and its label",
|
|
4162
|
+
description: "Choose Canada. BEFORE: the rule was India-only six-digits, so a real Canadian postcode like K1A 0B1 could not be saved from any surface. AFTER: the field reads 'Postal code' rather than 'PIN code', K1A 0B1 is accepted, and 560001 is now the one rejected. Switch back to India and the reverse holds.",
|
|
4163
|
+
},
|
|
4164
|
+
{
|
|
4165
|
+
key: "unknown-country-never-blocks",
|
|
4166
|
+
label: "A country with no known format accepts anything plausible",
|
|
4167
|
+
description: "Choose United Arab Emirates, which has no postal format. Any 3-12 character code must save. A postal rule exists to catch a typo, never to block a real address we have no pattern for.",
|
|
4168
|
+
},
|
|
4169
|
+
],
|
|
4170
|
+
},
|
|
4171
|
+
{
|
|
4172
|
+
pageKey: "state-picker",
|
|
4173
|
+
pageLabel: "State picker",
|
|
4174
|
+
href: "/user/addresses/new",
|
|
4175
|
+
cases: [
|
|
4176
|
+
{
|
|
4177
|
+
key: "state-is-a-picker-for-india",
|
|
4178
|
+
label: "State is a searchable picker for India, free text for countries without one",
|
|
4179
|
+
description: "BEFORE: this form and the seller drawer both wrote FREE TEXT into the same field the admin editor filled from a 36-option list, so the column held 'Karnataka', 'karnataka' and 'KA' depending on which screen created the row. AFTER: with India chosen, State is a searchable picker of the 36 states and union territories. Choose United Arab Emirates and it becomes a plain text box, because we do not enumerate its regions.",
|
|
4180
|
+
},
|
|
4181
|
+
{
|
|
4182
|
+
key: "changing-country-clears-state",
|
|
4183
|
+
label: "Changing country clears the state you had chosen",
|
|
4184
|
+
description: "Pick India, then Karnataka, then switch the country to Canada. State must be empty — 'Karnataka' is not a Canadian province, and an address whose state does not belong to its country is a row nothing can group or ship.",
|
|
4185
|
+
},
|
|
4186
|
+
],
|
|
4187
|
+
},
|
|
4188
|
+
{
|
|
4189
|
+
pageKey: "address-filters",
|
|
4190
|
+
pageLabel: "Address filters",
|
|
4191
|
+
href: "/user/addresses",
|
|
4192
|
+
cases: [
|
|
4193
|
+
{
|
|
4194
|
+
key: "filters-actually-filter",
|
|
4195
|
+
label: "Every filter in the drawer changes the list",
|
|
4196
|
+
description: "BEFORE: all three facets — Address Type, Verified, Active — read fields the address document has never had, so each compared against an empty string. They rendered, they counted toward the filter badge, and not one could ever match a row. AFTER: only Default and Standing are offered, and each visibly changes which addresses are listed.",
|
|
4197
|
+
},
|
|
4198
|
+
],
|
|
4199
|
+
},
|
|
4200
|
+
{
|
|
4201
|
+
pageKey: "unban-request",
|
|
4202
|
+
pageLabel: "Unban request",
|
|
4203
|
+
href: "/user/addresses",
|
|
4204
|
+
cases: [
|
|
4205
|
+
{
|
|
4206
|
+
key: "empty-note-says-why",
|
|
4207
|
+
label: "Submitting an empty unban request explains what is missing",
|
|
4208
|
+
description: "On a banned address, open Request Unban and press Submit with the box empty. BEFORE: the button was disabled and the handler was a bare return — nothing was said, and that note is the ENTIRE case a reviewer reads. AFTER: the button is enabled and pressing it puts an error on the field naming the 20-character minimum.",
|
|
4209
|
+
},
|
|
4210
|
+
],
|
|
4211
|
+
},
|
|
4212
|
+
]),
|
|
4213
|
+
...group("search-and-nav", "Search & navigation", [
|
|
4214
|
+
{
|
|
4215
|
+
pageKey: "header-search",
|
|
4216
|
+
pageLabel: "Header search",
|
|
4217
|
+
href: "/",
|
|
4218
|
+
cases: [
|
|
4219
|
+
{
|
|
4220
|
+
key: "search-finds-maintenance-toggle",
|
|
4221
|
+
label: "Searching 'maintenance' reaches the maintenance TOGGLE, not just Site Settings",
|
|
4222
|
+
description: "As an admin, open the header search and type 'maintenance'. BEFORE: nothing at all — the search matched nav labels only, and no nav item is called Maintenance. AFTER: a quick link appears, and clicking it opens Site Settings ON the Branding tab with the Maintenance mode toggle scrolled into view. Landing on tab 1 of nineteen and having to hunt is a FAIL.",
|
|
4223
|
+
},
|
|
4224
|
+
{
|
|
4225
|
+
key: "search-finds-by-what-it-does",
|
|
4226
|
+
label: "Words that appear nowhere in a label still find their screen",
|
|
4227
|
+
description: "Type 'refund'. BEFORE: no results — the screen is called Payouts. AFTER: Payouts appears. Repeat with 'postcode' (reaches Addresses) and 'cod' (reaches the Cash on delivery toggle). None of those words is in the label of what they find; that is the point.",
|
|
4228
|
+
},
|
|
4229
|
+
{
|
|
4230
|
+
key: "search-ranks-exact-first",
|
|
4231
|
+
label: "An exact match sorts above a mention",
|
|
4232
|
+
description: "Type 'orders'. The entry actually CALLED Orders must be first — above the guide whose description merely mentions orders. BEFORE there was no ranking at all, so alphabetical order decided and the exact match could land ninth.",
|
|
4233
|
+
},
|
|
4234
|
+
{
|
|
4235
|
+
key: "search-never-shows-what-you-cannot-reach",
|
|
4236
|
+
label: "A buyer's search surfaces nothing admin-shaped",
|
|
4237
|
+
description: "Sign in as an ordinary buyer and search 'users', 'payouts' and 'maintenance'. Not one admin entry may appear. The labels alone are a site map of the admin panel, so an entry you cannot act on is one you must not be shown.",
|
|
4238
|
+
},
|
|
4239
|
+
],
|
|
4240
|
+
},
|
|
4241
|
+
{
|
|
4242
|
+
pageKey: "sidebar-search",
|
|
4243
|
+
pageLabel: "Sidebar search",
|
|
4244
|
+
href: "/admin",
|
|
4245
|
+
cases: [
|
|
4246
|
+
{
|
|
4247
|
+
key: "sidebar-search-matches-description",
|
|
4248
|
+
label: "The sidebar filter matches what a screen is FOR",
|
|
4249
|
+
description: "In the admin sidebar's search box type 'fraud'. BEFORE: `item.label.includes(q)` — so only a screen with 'fraud' in its name matched. AFTER: Scam Registry, Address Clusters and Payment Clusters all appear, because their descriptions and keywords say so. Groups keep their normal order; only their contents narrow.",
|
|
4250
|
+
},
|
|
4251
|
+
],
|
|
4252
|
+
},
|
|
4253
|
+
{
|
|
4254
|
+
pageKey: "employee-permissions",
|
|
4255
|
+
pageLabel: "Employee sidebar",
|
|
4256
|
+
href: "/admin/team",
|
|
4257
|
+
cases: [
|
|
4258
|
+
{
|
|
4259
|
+
key: "employee-sees-only-their-permissions",
|
|
4260
|
+
label: "An employee's sidebar shows only what their permissions allow",
|
|
4261
|
+
description: "🛑 THE BIG ONE. Invite an employee with the 'Blog poster' preset and sign in as them. BEFORE: they saw all 91 admin entries — `filterNavItems` short-circuits on a missing `id`, and no nav item had one, so every requiredPermission was decorative. AFTER: about five entries, and groups with nothing left in them (Finance, Procurement, Catalog, Testing, Site, Events, Trust & Safety, System, Maintenance) disappear entirely rather than showing an empty heading. Check a second preset too — 'Trust & Safety' should see roughly 24.",
|
|
4262
|
+
},
|
|
4263
|
+
{
|
|
4264
|
+
key: "employee-sidebar-never-empty",
|
|
4265
|
+
label: "No permission preset produces a completely empty sidebar",
|
|
4266
|
+
description: "Work through the presets in the Team editor. Every one must leave at least one entry visible. An employee whose sidebar is blank cannot tell a permissions problem from a broken account, and will file it as the latter.",
|
|
4267
|
+
},
|
|
4268
|
+
],
|
|
4269
|
+
},
|
|
4270
|
+
{
|
|
4271
|
+
pageKey: "settings-deep-links",
|
|
4272
|
+
pageLabel: "Settings deep links",
|
|
4273
|
+
href: "/admin/site",
|
|
4274
|
+
cases: [
|
|
4275
|
+
{
|
|
4276
|
+
key: "tab-query-param-opens-that-tab",
|
|
4277
|
+
label: "?tab= opens the named tab",
|
|
4278
|
+
description: "Visit /admin/site?tab=fees directly. The Fees tab must be the open one. BEFORE the page ignored the URL entirely (`defaultValue=\"branding\"`), so every deep link landed on Branding.",
|
|
4279
|
+
},
|
|
4280
|
+
{
|
|
4281
|
+
key: "unknown-tab-falls-back-quietly",
|
|
4282
|
+
label: "An unknown ?tab= falls back to the default without an error",
|
|
4283
|
+
description: "Visit /admin/site?tab=nonsense. The page opens on Branding and shows no error. A rejected value must not look like a broken page — it looks like a stale bookmark, which is what it is.",
|
|
4284
|
+
},
|
|
4285
|
+
],
|
|
4286
|
+
},
|
|
4287
|
+
]),
|
|
4042
4288
|
];
|
|
4043
4289
|
const defaultPhases = assignDefaultPhases(rawTesterChecklistItems);
|
|
4044
4290
|
export const testerChecklistSeedData = rawTesterChecklistItems.map((item, index) => ({
|
|
@@ -22,9 +22,23 @@ import { FormErrorSummary } from "../../../ui/forms/FormErrorSummary";
|
|
|
22
22
|
// ---------------------------------------------------------------------------
|
|
23
23
|
// Sub-components
|
|
24
24
|
// ---------------------------------------------------------------------------
|
|
25
|
+
/**
|
|
26
|
+
* What the server sends in place of a stored access token. It is a SENTINEL,
|
|
27
|
+
* not a value — the real token never leaves the server.
|
|
28
|
+
*/
|
|
29
|
+
const MASKED_TOKEN = "••••••";
|
|
25
30
|
function MaskedTokenInput({ label, value, onChange, placeholder, helperText, }) {
|
|
26
31
|
const [revealed, setRevealed] = React.useState(false);
|
|
27
|
-
|
|
32
|
+
/*
|
|
33
|
+
* There is nothing to reveal while the field still holds the sentinel: the
|
|
34
|
+
* button used to un-mask six bullet characters, which reads as "the token is
|
|
35
|
+
* literally ••••••". Offer it only once the seller has typed something, which
|
|
36
|
+
* is the only case where the field contains a real value.
|
|
37
|
+
*/
|
|
38
|
+
const isSentinel = value === MASKED_TOKEN;
|
|
39
|
+
return (_jsxs(Div, { className: "relative", children: [_jsx(Input, { label: label, value: value, onChange: (e) => onChange(e.target.value), type: revealed && !isSentinel ? "text" : "password", placeholder: placeholder, helperText: isSentinel
|
|
40
|
+
? "A token is saved. Type a new one to replace it — the stored value is never sent back."
|
|
41
|
+
: helperText }), !isSentinel && (_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => setRevealed((r) => !r), className: "absolute right-3 top-8 text-[length:var(--appkit-text-xs)]", children: revealed ? "Hide" : "Reveal" }))] }));
|
|
28
42
|
}
|
|
29
43
|
const STEPS = [
|
|
30
44
|
{
|
|
@@ -101,12 +115,23 @@ export function SellerWhatsAppSettingsView({ hasCapability }) {
|
|
|
101
115
|
setAccessToken(cfg.accessToken ?? "");
|
|
102
116
|
setSyncEnabled(cfg.catalogSyncEnabled ?? false);
|
|
103
117
|
}, [cfg]);
|
|
104
|
-
|
|
118
|
+
/*
|
|
119
|
+
* The CONNECTION form. Credentials only.
|
|
120
|
+
*
|
|
121
|
+
* 🛑 It used to also send `catalogSyncEnabled` — the one key it sent
|
|
122
|
+
* unconditionally — while the toggle for it lives in Section 3, outside this
|
|
123
|
+
* form and below its Save button. A seller flipped the sync toggle, saw it
|
|
124
|
+
* move, pressed the Sync button beside it, and the setting was never
|
|
125
|
+
* persisted: the only control that saved it was "Save & Connect", two cards
|
|
126
|
+
* up and labelled for credentials.
|
|
127
|
+
*
|
|
128
|
+
* The toggle now owns its own write (`syncToggleMutation`), so the control
|
|
129
|
+
* and the thing that persists it are in the same place.
|
|
130
|
+
*/
|
|
105
131
|
const saveMutation = useApiMutation({
|
|
132
|
+
errorMessage: "Failed to save settings.",
|
|
106
133
|
mutationFn: async () => {
|
|
107
|
-
const payload = {
|
|
108
|
-
catalogSyncEnabled: syncEnabled,
|
|
109
|
-
};
|
|
134
|
+
const payload = {};
|
|
110
135
|
if (phoneNumber)
|
|
111
136
|
payload.phoneNumber = phoneNumber;
|
|
112
137
|
if (wabaId)
|
|
@@ -114,7 +139,7 @@ export function SellerWhatsAppSettingsView({ hasCapability }) {
|
|
|
114
139
|
if (catalogId)
|
|
115
140
|
payload.catalogId = catalogId;
|
|
116
141
|
// Only send accessToken if user has typed a new value (not the masked placeholder)
|
|
117
|
-
if (accessToken && accessToken !==
|
|
142
|
+
if (accessToken && accessToken !== MASKED_TOKEN)
|
|
118
143
|
payload.accessToken = accessToken;
|
|
119
144
|
return apiClient.put(WHATSAPP_SELLER_ENDPOINTS.SETTINGS, payload);
|
|
120
145
|
},
|
|
@@ -122,21 +147,32 @@ export function SellerWhatsAppSettingsView({ hasCapability }) {
|
|
|
122
147
|
showToast("WhatsApp settings saved", "success");
|
|
123
148
|
void queryClient.invalidateQueries({ queryKey: ["store", "whatsapp-settings"] });
|
|
124
149
|
},
|
|
125
|
-
|
|
126
|
-
|
|
150
|
+
});
|
|
151
|
+
/** Persists the catalog-sync toggle on flip — see the note above. */
|
|
152
|
+
const syncToggleMutation = useApiMutation({
|
|
153
|
+
errorMessage: "Could not change the catalog-sync setting.",
|
|
154
|
+
mutationFn: async (next) => apiClient.put(WHATSAPP_SELLER_ENDPOINTS.SETTINGS, { catalogSyncEnabled: next }),
|
|
155
|
+
onSuccess: () => {
|
|
156
|
+
void queryClient.invalidateQueries({ queryKey: ["store", "whatsapp-settings"] });
|
|
157
|
+
},
|
|
158
|
+
onError: () => {
|
|
159
|
+
// Put the switch back where the server still has it.
|
|
160
|
+
setSyncEnabled((prev) => !prev);
|
|
127
161
|
},
|
|
128
162
|
});
|
|
163
|
+
const handleSyncToggle = (next) => {
|
|
164
|
+
setSyncEnabled(next);
|
|
165
|
+
syncToggleMutation.mutate(next);
|
|
166
|
+
};
|
|
129
167
|
// Catalog sync mutation (push: site → WhatsApp)
|
|
130
168
|
const syncMutation = useApiMutation({
|
|
169
|
+
errorMessage: "Catalog sync failed.",
|
|
131
170
|
mutationFn: async () => apiClient.post(WHATSAPP_SELLER_ENDPOINTS.CATALOG_SYNC, {}),
|
|
132
171
|
onSuccess: (res) => {
|
|
133
172
|
const r = res ?? {};
|
|
134
173
|
showToast(`Synced ${r.successCount ?? 0} product${(r.successCount ?? 0) !== 1 ? "s" : ""} to WhatsApp catalog`, "success");
|
|
135
174
|
void queryClient.invalidateQueries({ queryKey: ["store", "whatsapp-settings"] });
|
|
136
175
|
},
|
|
137
|
-
onError: (err) => {
|
|
138
|
-
const msg = err instanceof Error ? err.message : "Sync failed";
|
|
139
|
-
},
|
|
140
176
|
});
|
|
141
177
|
// Catalog import (pull: WhatsApp → site). Runs as the `whatsappCatalogImport`
|
|
142
178
|
// async job — the route only enqueues, so the result arrives over the
|
|
@@ -191,7 +227,7 @@ export function SellerWhatsAppSettingsView({ hasCapability }) {
|
|
|
191
227
|
}) })] }), _jsxs(Section, { className: CLS_SECTION_CARD, children: [_jsxs(Row, { justify: "between", align: "center", className: "mb-4", children: [_jsx(Heading, { level: 2, size: "sm", weight: "semibold", color: "primary", children: "Connection" }), isLoading ? null : cfg?.connected ? (_jsx(Badge, { variant: "success", children: "Connected" })) : (_jsx(Badge, { variant: "default", children: "Not configured" }))] }), _jsxs(Form, { schema: whatsappSettingsSchema, onSubmit: (e) => {
|
|
192
228
|
e.preventDefault();
|
|
193
229
|
saveMutation.mutate();
|
|
194
|
-
}, spacing: "md", children: [_jsx(FormErrorSummary, {}), _jsx(Input, { label: "WhatsApp Business Phone Number", value: phoneNumber, onChange: (e) => setPhoneNumber(e.target.value), placeholder: "919876543210", helperText: "Digits only, include country code (e.g. 91 for India)" }), _jsx(Input, { label: "WABA ID (WhatsApp Business Account ID)", value: wabaId, onChange: (e) => setWabaId(e.target.value), placeholder: "123456789012345", helperText: "From Meta Business Manager \u2192 WhatsApp Accounts" }), _jsx(Input, { label: "Catalog ID", value: catalogId, onChange: (e) => setCatalogId(e.target.value), placeholder: "987654321098765", helperText: "From Meta Commerce Manager \u2192 Catalogs" }), _jsx(MaskedTokenInput, { label: "System User Access Token", value: accessToken, onChange: setAccessToken, placeholder: "EAAxxxxxxxx\u2026", helperText: "Long-lived token with WhatsApp Business permissions" }), _jsx(FormActions, { align: "right", children: _jsx(Button, { type: "submit", isLoading: saveMutation.isPending, disabled: saveMutation.isPending, children: saveMutation.isPending ? "Saving…" : ACTIONS.STORE["whatsapp-connect"].label }) })] })] }), _jsxs(Section, { className: CLS_SECTION_CARD, children: [_jsx(Heading, { level: 2, className: "mb-4", color: "primary", size: "sm", weight: "semibold", children: "Catalog Sync" }), _jsxs(Row, { justify: "between", align: "center", className: "mb-4", children: [_jsxs(Div, { children: [_jsx(Text, { size: "sm", color: "muted", children: "Enable catalog sync" }), _jsx(Text, { size: "xs", color: "muted", children: "When enabled, your published standard products can be synced to WhatsApp." })] }), _jsx(Toggle, { checked: syncEnabled, onChange:
|
|
230
|
+
}, spacing: "md", children: [_jsx(FormErrorSummary, {}), _jsx(Input, { label: "WhatsApp Business Phone Number", value: phoneNumber, onChange: (e) => setPhoneNumber(e.target.value), placeholder: "919876543210", helperText: "Digits only, include country code (e.g. 91 for India)" }), _jsx(Input, { label: "WABA ID (WhatsApp Business Account ID)", value: wabaId, onChange: (e) => setWabaId(e.target.value), placeholder: "123456789012345", helperText: "From Meta Business Manager \u2192 WhatsApp Accounts" }), _jsx(Input, { label: "Catalog ID", value: catalogId, onChange: (e) => setCatalogId(e.target.value), placeholder: "987654321098765", helperText: "From Meta Commerce Manager \u2192 Catalogs" }), _jsx(MaskedTokenInput, { label: "System User Access Token", value: accessToken, onChange: setAccessToken, placeholder: "EAAxxxxxxxx\u2026", helperText: "Long-lived token with WhatsApp Business permissions" }), _jsx(FormActions, { align: "right", children: _jsx(Button, { type: "submit", isLoading: saveMutation.isPending, disabled: saveMutation.isPending, children: saveMutation.isPending ? "Saving…" : ACTIONS.STORE["whatsapp-connect"].label }) })] })] }), _jsxs(Section, { className: CLS_SECTION_CARD, children: [_jsx(Heading, { level: 2, className: "mb-4", color: "primary", size: "sm", weight: "semibold", children: "Catalog Sync" }), _jsxs(Row, { justify: "between", align: "center", className: "mb-4", children: [_jsxs(Div, { children: [_jsx(Text, { size: "sm", color: "muted", children: "Enable catalog sync" }), _jsx(Text, { size: "xs", color: "muted", children: "When enabled, your published standard products can be synced to WhatsApp." })] }), _jsx(Toggle, { checked: syncEnabled, onChange: handleSyncToggle, disabled: !cfg?.connected || syncToggleMutation.isPending })] }), cfg?.lastCatalogSyncAt && (_jsxs(Row, { color: "muted", textSize: "xs", gap: "sm", align: "center", className: "mb-3", children: [_jsxs(Text, { children: ["Last sync: ", new Date(cfg.lastCatalogSyncAt).toLocaleString("en-IN")] }), cfg.lastSyncCount !== undefined && _jsxs(Text, { children: ["\u00B7 ", cfg.lastSyncCount, " products"] }), cfg.lastSyncStatus && (_jsx(Badge, { variant: STATUS_VARIANT[cfg.lastSyncStatus] ?? "default", children: cfg.lastSyncStatus }))] })), _jsx(Text, { className: "mb-3", color: "faint", size: "xs", children: "Only published standard products are synced. Auctions and pre-orders are excluded. Batches of up to 50 products per call." }), _jsxs(Row, { gap: "3", children: [_jsx(Button, { onClick: () => syncMutation.mutate(), isLoading: syncMutation.isPending, disabled: !cfg?.connected || !syncEnabled || syncMutation.isPending, variant: "secondary", children: syncMutation.isPending ? "Syncing…" : ACTIONS.STORE["whatsapp-catalog-sync"].label }), _jsx(Button, { onClick: () => void startImport(), isLoading: importing, disabled: !cfg?.connected || !syncEnabled || importing, variant: "secondary", children: importing ? "Importing…" : ACTIONS.STORE["whatsapp-catalog-import"].label })] }), _jsxs(Text, { className: "mt-3", color: "faint", size: "xs", children: [_jsx(Span, { weight: "bold", children: "Push" }), " sends your published standard products to WhatsApp.", " ", _jsx(Span, { weight: "bold", children: "Import" }), " creates draft products from your WhatsApp catalog. Products are matched by slug in the description field."] })] }), _jsxs(Section, { className: CLS_SECTION_CARD, children: [_jsx(Heading, { level: 2, className: "mb-2", color: "primary", size: "sm", weight: "semibold", children: "Purchase Announcement Preview" }), _jsx(Text, { className: "mb-3", color: "muted", size: "xs", children: "This message is sent automatically to your phone and the platform admin when a new order is placed." }), _jsx(Div, { textSize: "sm", className: "font-mono", color: "primary", surface: "muted", padding: "inline", rounded: "lg", children: buildPurchaseAnnouncementMessage({
|
|
195
231
|
buyerName: "Ravi K.",
|
|
196
232
|
firstItemName: "Charizard PSA 9",
|
|
197
233
|
additionalItemCount: 2,
|
package/dist/http/api-handler.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { normalizeError } from "../errors/normalize";
|
|
2
|
+
import { isAccountDisabled } from "../features/auth/role-predicates";
|
|
2
3
|
import { createApiHandlerFactory } from "../next";
|
|
3
4
|
import { applyRateLimit } from "../security";
|
|
4
5
|
import { AuthenticationError, AuthorizationError, ERROR_MESSAGES, handleApiError, } from "../errors";
|
|
@@ -63,7 +64,7 @@ async function requireAuthFromRequest(request) {
|
|
|
63
64
|
if (!user) {
|
|
64
65
|
throw new AuthenticationError(ERROR_MESSAGES.USER.NOT_AUTHENTICATED);
|
|
65
66
|
}
|
|
66
|
-
if (user
|
|
67
|
+
if (isAccountDisabled(user)) {
|
|
67
68
|
throw new AuthenticationError(ERROR_MESSAGES.AUTH.ACCOUNT_DISABLED);
|
|
68
69
|
}
|
|
69
70
|
return user;
|