@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
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { Li, useApiMutation } from "@mohasinac/appkit/client";
|
|
4
|
-
import { useState } from "react";
|
|
4
|
+
import { useMemo, useState } from "react";
|
|
5
5
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|
6
|
-
import { Button, Div,
|
|
7
|
-
import { FieldInput } from "../../../ui/forms/FieldInput";
|
|
6
|
+
import { Button, Div, Row, SideDrawer, Span, Stack, Text, Ul, useToast } from "../../../ui";
|
|
8
7
|
import { FieldTextarea } from "../../../ui/forms/FieldTextarea";
|
|
8
|
+
import { FormErrorSummary } from "../../../ui/forms/FormErrorSummary";
|
|
9
|
+
import { FormShellContext, useFormShellState } from "../../../ui/forms/FormShell";
|
|
10
|
+
import { applyZodIssues } from "../../../ui/forms/apply-zod-issues";
|
|
11
|
+
import { buildSectionsFromSchema, visibleValues } from "../../shell/build-sections";
|
|
12
|
+
import { SectionForm, useSectionFormNav } from "../../shell/SectionForm";
|
|
9
13
|
import { apiClient } from "../../../http";
|
|
10
14
|
import { supportTicketCreateSchema } from "../../support/schemas/ticket-create-form";
|
|
11
|
-
import {
|
|
15
|
+
import { TicketCategoryValues } from "../../support/schemas/firestore";
|
|
12
16
|
import { SUPPORT_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
13
17
|
const __P = {
|
|
14
18
|
p3: "p-[var(--appkit-space-3)]",
|
|
@@ -18,23 +22,44 @@ const __O = {
|
|
|
18
22
|
yAuto: "overflow-y-auto",
|
|
19
23
|
};
|
|
20
24
|
// --- Constants ---------------------------------------------------------------
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Category labels.
|
|
27
|
+
*
|
|
28
|
+
* Keyed off `TicketCategoryValues` rather than written as a parallel array, so
|
|
29
|
+
* a twelfth category cannot be added to the schema and silently omitted here —
|
|
30
|
+
* Root Cause #61, which the schema itself already guards on its own side.
|
|
31
|
+
*
|
|
32
|
+
* There are ten, so the generator renders this as a `PaginatedSelect`: it was
|
|
33
|
+
* a native `<Select>` with no `name`, which is over the five-option threshold
|
|
34
|
+
* AND unreachable by `applyZodIssues`.
|
|
35
|
+
*/
|
|
36
|
+
const CATEGORY_LABELS = {
|
|
37
|
+
[TicketCategoryValues.ORDER_ISSUE]: "Order issue",
|
|
38
|
+
[TicketCategoryValues.BILLING_PAYMENT]: "Billing / payment",
|
|
39
|
+
[TicketCategoryValues.ACCOUNT]: "Account",
|
|
40
|
+
[TicketCategoryValues.LISTING_DISPUTE]: "Listing dispute",
|
|
41
|
+
/*
|
|
42
|
+
* The hand-written array this replaced had TEN entries and the schema has
|
|
43
|
+
* ELEVEN — `scam_report` was simply missing, so a user could never file one
|
|
44
|
+
* from this drawer even though the route accepts it.
|
|
45
|
+
*/
|
|
46
|
+
[TicketCategoryValues.SCAM_REPORT]: "Report a scam",
|
|
47
|
+
[TicketCategoryValues.REFUND_REQUEST]: "Refund request",
|
|
48
|
+
[TicketCategoryValues.AUCTION_DISPUTE]: "Auction dispute",
|
|
28
49
|
// ST-4 — sellers use this to request changes to admin-controlled
|
|
29
50
|
// store fields (status, capabilities, verification badge).
|
|
30
|
-
|
|
51
|
+
[TicketCategoryValues.STORE_CHANGE_REQUEST]: "Store change request (sellers)",
|
|
31
52
|
// ST-3 — buyers/sellers request mutation of order line items
|
|
32
|
-
|
|
53
|
+
[TicketCategoryValues.ORDER_MODIFICATION_REQUEST]: "Order modification request",
|
|
33
54
|
// ST-5 — appeal a soft or hard ban; bypasses the create_support_tickets
|
|
34
55
|
// soft-ban guard server-side.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
56
|
+
[TicketCategoryValues.UNBAN_REQUEST]: "Appeal a ban (unban request)",
|
|
57
|
+
[TicketCategoryValues.GENERAL]: "General",
|
|
58
|
+
};
|
|
59
|
+
const CATEGORY_OPTIONS = Object.values(TicketCategoryValues).map((value) => ({
|
|
60
|
+
value,
|
|
61
|
+
label: CATEGORY_LABELS[value] ?? value.replace(/_/g, " "),
|
|
62
|
+
}));
|
|
38
63
|
const CLS_MSG_USER = "border bg-[var(--appkit-color-surface)]/40 border-[var(--appkit-color-border)]";
|
|
39
64
|
const CLS_MSG_STAFF = "bg-info-surface border border-info dark:bg-info-surface dark:border-info";
|
|
40
65
|
const STATUS_BADGE = {
|
|
@@ -49,17 +74,20 @@ const ROLE_LABEL = {
|
|
|
49
74
|
support: "Support",
|
|
50
75
|
admin: "Admin",
|
|
51
76
|
};
|
|
77
|
+
const EMPTY_TICKET = {
|
|
78
|
+
category: TicketCategoryValues.GENERAL,
|
|
79
|
+
subject: "",
|
|
80
|
+
description: "",
|
|
81
|
+
orderId: "",
|
|
82
|
+
};
|
|
52
83
|
export function UserSupportView(_props) {
|
|
53
84
|
const queryClient = useQueryClient();
|
|
54
85
|
const { showToast } = useToast();
|
|
55
86
|
const [detailOpen, setDetailOpen] = useState(false);
|
|
56
87
|
const [selectedTicket, setSelectedTicket] = useState(null);
|
|
57
88
|
const [newTicketOpen, setNewTicketOpen] = useState(false);
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
const [newCategory, setNewCategory] = useState("general");
|
|
61
|
-
const [newDescription, setNewDescription] = useState("");
|
|
62
|
-
const [newOrderId, setNewOrderId] = useState("");
|
|
89
|
+
const [newTicket, setNewTicket] = useState(EMPTY_TICKET);
|
|
90
|
+
const patchTicket = (partial) => setNewTicket((prev) => Object.assign({}, prev, partial));
|
|
63
91
|
// Reply state
|
|
64
92
|
const [replyBody, setReplyBody] = useState("");
|
|
65
93
|
const { data, isLoading, error } = useQuery({
|
|
@@ -70,44 +98,45 @@ export function UserSupportView(_props) {
|
|
|
70
98
|
const invalidate = () => queryClient.invalidateQueries({ queryKey: ["user", "support-tickets"] });
|
|
71
99
|
const createMutation = useApiMutation({
|
|
72
100
|
errorMessage: "Failed to create ticket.",
|
|
73
|
-
mutationFn: async () =>
|
|
74
|
-
/*
|
|
75
|
-
* This drawer validated NOTHING. Its sibling page
|
|
76
|
-
* (`/user/support/new`) hand-rolled a `canSubmit` boolean that required
|
|
77
|
-
* an order id for an `order_issue` ticket — a real rule, since an order
|
|
78
|
-
* complaint with no order is unactionable — and the route did not
|
|
79
|
-
* enforce it either. So the same ticket was acceptable or not purely
|
|
80
|
-
* depending on which surface the user happened to open.
|
|
81
|
-
*
|
|
82
|
-
* One schema now, across both surfaces and the route.
|
|
83
|
-
*/
|
|
84
|
-
const parsed = supportTicketCreateSchema.safeParse({
|
|
85
|
-
subject: newSubject.trim(),
|
|
86
|
-
category: newCategory,
|
|
87
|
-
description: newDescription.trim(),
|
|
88
|
-
orderId: newOrderId.trim() || undefined,
|
|
89
|
-
});
|
|
90
|
-
if (!parsed.success) {
|
|
91
|
-
/*
|
|
92
|
-
* ValidationError, not a bare Error — it carries the issue LIST, so
|
|
93
|
-
* the failure keeps which field caused it. A plain Error flattens the
|
|
94
|
-
* whole thing to one string, which is what `audit-unvalidated-safeparse`
|
|
95
|
-
* exists to stop. It caught this in my own code.
|
|
96
|
-
*/
|
|
97
|
-
throw new ValidationError(parsed.error.issues[0]?.message ?? "Check the ticket details.", parsed.error.issues);
|
|
98
|
-
}
|
|
99
|
-
await apiClient.post(SUPPORT_ENDPOINTS.TICKETS, parsed.data);
|
|
100
|
-
},
|
|
101
|
+
mutationFn: async (values) => apiClient.post(SUPPORT_ENDPOINTS.TICKETS, values),
|
|
101
102
|
onSuccess: () => {
|
|
102
103
|
showToast("Support ticket created.", "success");
|
|
103
104
|
setNewTicketOpen(false);
|
|
104
|
-
|
|
105
|
-
setNewCategory("general");
|
|
106
|
-
setNewDescription("");
|
|
107
|
-
setNewOrderId("");
|
|
105
|
+
setNewTicket(EMPTY_TICKET);
|
|
108
106
|
invalidate();
|
|
109
107
|
},
|
|
110
108
|
});
|
|
109
|
+
/*
|
|
110
|
+
* This drawer validated NOTHING before the schema landed, and its sibling
|
|
111
|
+
* page (`/user/support/new`) hand-rolled a `canSubmit` that required an
|
|
112
|
+
* order id for an `order_issue` ticket — a real rule the route did not
|
|
113
|
+
* enforce either, so the same ticket was acceptable or not depending purely
|
|
114
|
+
* on which surface the user opened. One schema now covers both and the
|
|
115
|
+
* route; the parse below is what reports it, per field.
|
|
116
|
+
*/
|
|
117
|
+
const sections = useMemo(() => buildSectionsFromSchema(supportTicketCreateSchema, {
|
|
118
|
+
options: { category: CATEGORY_OPTIONS },
|
|
119
|
+
}), []);
|
|
120
|
+
const nav = useSectionFormNav(sections, newTicket, { scope: "user:support-ticket" });
|
|
121
|
+
const { shellCtx, setFieldError, clearErrors } = useFormShellState(supportTicketCreateSchema, {
|
|
122
|
+
sections: nav.sectionMeta,
|
|
123
|
+
onGoToSection: nav.goToSection,
|
|
124
|
+
fieldToSectionIndex: nav.fieldToSectionIndex,
|
|
125
|
+
});
|
|
126
|
+
const submitTicket = () => {
|
|
127
|
+
clearErrors();
|
|
128
|
+
/*
|
|
129
|
+
* `visibleValues` is what keeps an order id typed before switching
|
|
130
|
+
* category out of a ticket that has nothing to do with an order — the
|
|
131
|
+
* `when` on that field hides the control and never clears it.
|
|
132
|
+
*/
|
|
133
|
+
const parsed = supportTicketCreateSchema.safeParse(visibleValues(supportTicketCreateSchema, newTicket));
|
|
134
|
+
if (!parsed.success) {
|
|
135
|
+
applyZodIssues(parsed.error.issues, setFieldError);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
createMutation.mutate(parsed.data);
|
|
139
|
+
};
|
|
111
140
|
const replyMutation = useApiMutation({
|
|
112
141
|
errorMessage: "Failed to send reply.",
|
|
113
142
|
mutationFn: async () => {
|
|
@@ -119,16 +148,18 @@ export function UserSupportView(_props) {
|
|
|
119
148
|
invalidate();
|
|
120
149
|
},
|
|
121
150
|
});
|
|
122
|
-
return (_jsxs(_Fragment, { children: [_jsxs(Div, { className: "mx-auto max-w-2xl", paddingY: "y-lg", paddingX: "x-md", children: [_jsxs(Row, { className: "mb-4", align: "center", justify: "between", children: [_jsx(Text, { size: "xl", weight: "semibold", color: "primary", children: "Support Tickets" }), _jsx(Button, { type: "button", variant: "primary", size: "sm", onClick: () => setNewTicketOpen(true), children: "New ticket" })] }), renderTicketListArea({ isLoading, error, tickets, setSelectedTicket, setDetailOpen })] }),
|
|
151
|
+
return (_jsxs(_Fragment, { children: [_jsxs(Div, { className: "mx-auto max-w-2xl", paddingY: "y-lg", paddingX: "x-md", children: [_jsxs(Row, { className: "mb-4", align: "center", justify: "between", children: [_jsx(Text, { size: "xl", weight: "semibold", color: "primary", children: "Support Tickets" }), _jsx(Button, { type: "button", variant: "primary", size: "sm", onClick: () => setNewTicketOpen(true), children: "New ticket" })] }), renderTicketListArea({ isLoading, error, tickets, setSelectedTicket, setDetailOpen })] }), _jsx(SideDrawer, { isOpen: newTicketOpen, onClose: () => setNewTicketOpen(false), title: "Open a support ticket", children: _jsx(Stack, { className: `${__P.p4}`, gap: "md", children: _jsxs(FormShellContext.Provider, { value: shellCtx, children: [_jsx(FormErrorSummary, {}), _jsx(SectionForm, { sections: sections, values: newTicket, onChange: patchTicket, onSubmit: submitTicket, schema: supportTicketCreateSchema, openIds: nav.openIds, onOpenChange: nav.setOpenIds, isLoading: createMutation.isPending, submitLabel: "Submit ticket", onCancel: () => setNewTicketOpen(false),
|
|
152
|
+
/*
|
|
153
|
+
* A drawer owns its own footer — `useIsInsideOverlay` already
|
|
154
|
+
* suppresses the viewport-fixed bar, which would otherwise
|
|
155
|
+
* render BEHIND the backdrop. Stated rather than relied on.
|
|
156
|
+
*/
|
|
157
|
+
bottomBar: false })] }) }) }), renderTicketDetailDrawer({ detailOpen, setDetailOpen, selectedTicket, replyBody, setReplyBody, replyMutation })] }));
|
|
123
158
|
}
|
|
124
159
|
function renderTicketListArea(props) {
|
|
125
160
|
const { isLoading, error, tickets, setSelectedTicket, setDetailOpen } = props;
|
|
126
161
|
return (_jsxs(_Fragment, { children: [isLoading && (_jsx(Stack, { gap: "3", children: [1, 2, 3].map((i) => _jsx(Div, { className: "h-16 animate-pulse", surface: "subtle", rounded: "lg" }, i)) })), error && (_jsx(Div, { textSize: "sm", className: "border border-error/20", color: "error", surface: "danger-surface", padding: "inline", rounded: "xl", children: "Failed to load support tickets." })), !isLoading && tickets.length === 0 && (_jsx(Div, { className: "text-left", surface: "muted", paddingY: "y-2xl", paddingX: "x-lg", rounded: "xl", border: "default", children: _jsx(Text, { size: "sm", color: "muted", children: "You haven't opened any support tickets yet." }) })), _jsx(Ul, { spacing: "loose", children: tickets.map((ticket) => (_jsx(Li, { children: _jsx(Button, { rounded: "xl", variant: "ghost", type: "button", shadow: "sm", border: "default", paddingX: "md", paddingY: "md", className: "w-full bg-[var(--appkit-color-surface)] text-left hover:border-[var(--appkit-color-primary-300)] transition-colors", onClick: () => { setSelectedTicket(ticket); setDetailOpen(true); }, children: _jsxs(Row, { align: "start", justify: "between", gap: "sm", children: [_jsxs(Div, { className: "min-w-0 flex-1", children: [_jsx(Text, { className: "truncate", color: "primary", weight: "medium", children: ticket.subject }), _jsxs(Text, { size: "xs", color: "muted", children: [ticket.category.replace(/_/g, " "), ticket.orderId ? ` · Order: ${ticket.orderId}` : ""] })] }), _jsx(Span, { size: "xs", weight: "medium", className: `shrink-0 inline-flex ${STATUS_BADGE[ticket.status] ?? STATUS_BADGE.open}`, padding: "pill-sm", rounded: "full", children: ticket.status.replace(/_/g, " ") })] }) }) }, ticket.id))) })] }));
|
|
127
162
|
}
|
|
128
|
-
function renderNewTicketDrawer(props) {
|
|
129
|
-
const { newTicketOpen, setNewTicketOpen, newCategory, setNewCategory, newSubject, setNewSubject, newOrderId, setNewOrderId, newDescription, setNewDescription, createMutation } = props;
|
|
130
|
-
return (_jsx(SideDrawer, { isOpen: newTicketOpen, onClose: () => setNewTicketOpen(false), title: "Open a support ticket", children: _jsxs(Stack, { className: `${__P.p4}`, gap: "md", children: [_jsx(Select, { label: "Category", options: CATEGORY_OPTIONS, value: newCategory, onValueChange: setNewCategory }), _jsx(FieldInput, { name: "subject", label: "Subject", type: "text", value: newSubject, onChange: setNewSubject, placeholder: "Brief description of the issue" }), newCategory === "order_issue" && (_jsx(FieldInput, { name: "orderId", label: "Order ID", type: "text", value: newOrderId, onChange: setNewOrderId, placeholder: "e.g. order-3-20260508-a1b2c3" })), _jsx(FieldTextarea, { name: "description", label: "Description", value: newDescription, onChange: setNewDescription, rows: 4, placeholder: "Describe the issue in detail\u2026" }), _jsxs(FormActions, { align: "right", children: [_jsx(Button, { type: "button", variant: "secondary", onClick: () => setNewTicketOpen(false), children: "Cancel" }), _jsx(Button, { type: "button", isLoading: createMutation.isPending, disabled: !newSubject.trim() || !newDescription.trim() || createMutation.isPending, onClick: () => createMutation.mutate(), children: "Submit ticket" })] })] }) }));
|
|
131
|
-
}
|
|
132
163
|
function renderTicketDetailDrawer(props) {
|
|
133
164
|
const { detailOpen, setDetailOpen, selectedTicket, replyBody, setReplyBody, replyMutation } = props;
|
|
134
165
|
return (_jsx(SideDrawer, { isOpen: detailOpen, onClose: () => setDetailOpen(false), title: selectedTicket?.subject ?? "Ticket", children: selectedTicket && (_jsxs(Stack, { className: `${__P.p4}`, gap: "md", children: [_jsxs(Row, { wrap: true, gap: "sm", children: [_jsx(Span, { size: "xs", weight: "medium", className: `inline-flex ${STATUS_BADGE[selectedTicket.status] ?? STATUS_BADGE.open}`, rounded: "full", padding: "pill-sm-tall", children: selectedTicket.status.replace(/_/g, " ") }), _jsx(Span, { size: "xs", rounded: "full", padding: "pill-sm-tall", surface: "subtle", color: "muted", children: selectedTicket.category.replace(/_/g, " ") }), selectedTicket.orderId && _jsxs(Span, { size: "xs", rounded: "full", padding: "pill-sm-tall", surface: "subtle", color: "muted", children: ["Order: ", selectedTicket.orderId] })] }), selectedTicket.description && (_jsxs(Div, { className: `${__P.p3}`, rounded: "lg", surface: "muted", border: "default", children: [_jsx(Text, { className: "mb-1 tracking-wide", size: "xs", weight: "semibold", color: "muted", transform: "uppercase", children: "Your description" }), _jsx(Text, { className: "whitespace-pre-wrap", color: "primary", size: "sm", children: selectedTicket.description })] })), (selectedTicket.messages ?? []).length > 0 && (_jsxs(Stack, { gap: "sm", children: [_jsx(Text, { className: "tracking-wide", color: "muted", size: "xs", weight: "semibold", transform: "uppercase", children: "Messages" }), _jsx(Stack, { className: `max-h-72 ${__O.yAuto}`, gap: "sm", children: (selectedTicket.messages ?? []).map((msg, i) => (_jsxs(Div, { textSize: "sm", className: `${__P.p3} ${msg.authorRole === "user" ? CLS_MSG_USER : CLS_MSG_STAFF}`, rounded: "lg", children: [_jsxs(Row, { color: "muted", textSize: "xs", className: "mb-1", align: "center", gap: "sm", children: [_jsx(Span, { weight: "medium", color: "muted", children: ROLE_LABEL[msg.authorRole ?? "user"] ?? msg.authorRole }), msg.createdAt && _jsx(Span, { children: new Date(msg.createdAt).toLocaleString() })] }), _jsx(Text, { className: "whitespace-pre-wrap", color: "primary", children: msg.body })] }, msg.id ?? i))) })] })), selectedTicket.status !== "closed" && selectedTicket.status !== "resolved" && (_jsxs(Stack, { gap: "sm", children: [_jsx(FieldTextarea, { name: "reply", label: "Reply", value: replyBody, onChange: setReplyBody, rows: 3, placeholder: "Add a message to your ticket\u2026" }), _jsx(Button, { type: "button", variant: "primary", size: "sm", isLoading: replyMutation.isPending, disabled: !replyBody.trim() || replyMutation.isPending, onClick: () => replyMutation.mutate(), children: "Send reply" })] }))] })) }));
|
|
@@ -3,7 +3,7 @@ export type { UserSidebarProps, UserNavItem } from "./UserSidebar";
|
|
|
3
3
|
export { AddressCard, AddressBook } from "./AddressBook";
|
|
4
4
|
export type { AddressCardAddress, AddressCardProps } from "./AddressBook";
|
|
5
5
|
export { AddressForm } from "./AddressForm";
|
|
6
|
-
export type { AddressFormLabels
|
|
6
|
+
export type { AddressFormLabels } from "./AddressForm";
|
|
7
7
|
export { AddressSelectorCreate } from "./AddressSelectorCreate";
|
|
8
8
|
export { NotificationBell } from "./NotificationBell";
|
|
9
9
|
export type { NotificationBellLabels, NotificationBellProps, NotificationBellRenderLinkProps, } from "./NotificationBell";
|
|
@@ -27,12 +27,6 @@ export { UserNotificationsView } from "./UserNotificationsView";
|
|
|
27
27
|
export type { UserNotificationsViewProps, UserNotificationsViewLabels, } from "./UserNotificationsView";
|
|
28
28
|
export { UserOffersPanel } from "./UserOffersPanel";
|
|
29
29
|
export type { UserOffersPanelProps } from "./UserOffersPanel";
|
|
30
|
-
export { MessagesView } from "./MessagesView";
|
|
31
|
-
export type { MessagesViewProps, MessagesViewLabels } from "./MessagesView";
|
|
32
|
-
export { ChatList } from "./ChatList";
|
|
33
|
-
export type { ChatListProps, ChatListLabels } from "./ChatList";
|
|
34
|
-
export { ChatWindow } from "./ChatWindow";
|
|
35
|
-
export type { ChatWindowProps, ChatWindowLabels } from "./ChatWindow";
|
|
36
30
|
export { BecomeSellerView } from "./BecomeSellerView";
|
|
37
31
|
export type { BecomeSellerViewProps, BecomeSellerViewLabels, } from "./BecomeSellerView";
|
|
38
32
|
export { UserOrderTrackView } from "./UserOrderTrackView";
|
|
@@ -13,9 +13,6 @@ export { OrderDetailView } from "./OrderDetailView";
|
|
|
13
13
|
export { UserAddressesView } from "./UserAddressesView";
|
|
14
14
|
export { UserNotificationsView } from "./UserNotificationsView";
|
|
15
15
|
export { UserOffersPanel } from "./UserOffersPanel";
|
|
16
|
-
export { MessagesView } from "./MessagesView";
|
|
17
|
-
export { ChatList } from "./ChatList";
|
|
18
|
-
export { ChatWindow } from "./ChatWindow";
|
|
19
16
|
export { BecomeSellerView } from "./BecomeSellerView";
|
|
20
17
|
export { UserOrderTrackView } from "./UserOrderTrackView";
|
|
21
18
|
export { UserReturnsView } from "./UserReturnsView";
|
|
@@ -42,9 +42,10 @@ interface SetDefaultAddressData {
|
|
|
42
42
|
}
|
|
43
43
|
export interface AddressFilterParams {
|
|
44
44
|
q?: string;
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
/** Only the owner's default address. */
|
|
46
|
+
defaultOnly?: boolean;
|
|
47
|
+
/** Pipe-joined `AddressBanStatus` values. */
|
|
48
|
+
banStatus?: string;
|
|
48
49
|
}
|
|
49
50
|
export declare function useAddresses(options?: {
|
|
50
51
|
enabled?: boolean;
|
|
@@ -10,12 +10,10 @@ export function useAddresses(options) {
|
|
|
10
10
|
const sp = new URLSearchParams();
|
|
11
11
|
if (options?.filters?.q)
|
|
12
12
|
sp.set("q", options.filters.q);
|
|
13
|
-
if (options?.filters?.
|
|
14
|
-
sp.set("
|
|
15
|
-
if (options?.filters?.
|
|
16
|
-
sp.set("
|
|
17
|
-
if (options?.filters?.activeOnly)
|
|
18
|
-
sp.set("activeOnly", "true");
|
|
13
|
+
if (options?.filters?.defaultOnly)
|
|
14
|
+
sp.set("defaultOnly", "true");
|
|
15
|
+
if (options?.filters?.banStatus)
|
|
16
|
+
sp.set("banStatus", options.filters.banStatus);
|
|
19
17
|
const qs = sp.toString();
|
|
20
18
|
const base = options?.listEndpoint ?? DEFAULT_ENDPOINTS.list;
|
|
21
19
|
const endpoint = qs ? `${base}?${qs}` : base;
|
|
@@ -1,11 +1,47 @@
|
|
|
1
|
+
export type SectionExpandMode = "multi" | "single";
|
|
2
|
+
export interface UseSectionStateOptions {
|
|
3
|
+
/**
|
|
4
|
+
* `"{portal}:{surface}"`, e.g. `"admin:dashboard"` or `"store:product-form"`.
|
|
5
|
+
*
|
|
6
|
+
* **The empty string means EPHEMERAL** — in-memory only, no profile read, no
|
|
7
|
+
* profile write, no localStorage. It exists so a surface can adopt this hook
|
|
8
|
+
* before it has chosen a durable scope name, without every such surface
|
|
9
|
+
* writing junk under `sectionState[""]` and without the legacy-derivation
|
|
10
|
+
* branch below reading another page's `collapsedSections` into it.
|
|
11
|
+
*/
|
|
12
|
+
scope: string;
|
|
13
|
+
/** Every section id this surface manages, in display order. */
|
|
14
|
+
sectionIds: string[];
|
|
15
|
+
/**
|
|
16
|
+
* Which sections are open for a user who has never touched this surface.
|
|
17
|
+
* Defaults to the FIRST section — "required first, everything else
|
|
18
|
+
* collapsed". Pass `[]` for all-collapsed.
|
|
19
|
+
*/
|
|
20
|
+
defaultOpen?: string[];
|
|
21
|
+
/** `"multi"` (default) lets several sections be open at once. `"single"` is
|
|
22
|
+
* accordion: opening one closes its siblings. */
|
|
23
|
+
mode?: SectionExpandMode;
|
|
24
|
+
}
|
|
25
|
+
export interface UseSectionStateResult {
|
|
26
|
+
isOpen: (id: string) => boolean;
|
|
27
|
+
toggle: (id: string) => void;
|
|
28
|
+
/** Force a section open without closing anything — what a validation error
|
|
29
|
+
* needs. Never collapses, even under `mode: "single"`. */
|
|
30
|
+
open: (id: string) => void;
|
|
31
|
+
/** Replace the whole open set. For a caller that already computes the next
|
|
32
|
+
* array itself (`SectionForm`'s controlled/uncontrolled `setOpenIds`
|
|
33
|
+
* contract) rather than expressing the change as a per-id toggle. */
|
|
34
|
+
setOpenIds: (ids: string[]) => void;
|
|
35
|
+
openIds: string[];
|
|
36
|
+
}
|
|
37
|
+
export declare function useSectionState({ scope, sectionIds, defaultOpen, mode, }: UseSectionStateOptions): UseSectionStateResult;
|
|
1
38
|
export interface UseCollapsedSectionsOptions {
|
|
2
|
-
/** Every section ID this page manages, in display order.
|
|
3
|
-
* globally unique across pages (prefix with a page key, e.g.
|
|
4
|
-
* "admin-dashboard:alerts") — uiPreferences.collapsedSections is a single
|
|
5
|
-
* flat list shared by the whole user document, not namespaced server-side.
|
|
6
|
-
* A section ID absent from the saved list defaults to collapsed, which is
|
|
7
|
-
* also the state for a user who has never touched this page. */
|
|
39
|
+
/** Every section ID this page manages, in display order. */
|
|
8
40
|
sectionIds: string[];
|
|
41
|
+
/** Scope key. Optional only so the pre-existing call sites keep compiling;
|
|
42
|
+
* they share `"legacy:collapsed-sections"`, which reproduces the old global
|
|
43
|
+
* behaviour exactly. New code should call `useSectionState` directly. */
|
|
44
|
+
scope?: string;
|
|
9
45
|
}
|
|
10
46
|
export interface UseCollapsedSectionsResult {
|
|
11
47
|
isCollapsed: (id: string) => boolean;
|
|
@@ -14,7 +50,12 @@ export interface UseCollapsedSectionsResult {
|
|
|
14
50
|
* everything collapsed. */
|
|
15
51
|
toggle: (id: string) => void;
|
|
16
52
|
}
|
|
17
|
-
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
|
|
53
|
+
/**
|
|
54
|
+
* @deprecated Prefer `useSectionState`, which is namespaced per surface,
|
|
55
|
+
* supports several sections open at once, and defaults to section 1 open.
|
|
56
|
+
*
|
|
57
|
+
* Kept so the existing dashboard call sites need no edit. Semantics are
|
|
58
|
+
* byte-identical to the original: default all-collapsed (`defaultOpen: []`)
|
|
59
|
+
* and accordion (`mode: "single"`).
|
|
60
|
+
*/
|
|
61
|
+
export declare function useCollapsedSections({ sectionIds, scope, }: UseCollapsedSectionsOptions): UseCollapsedSectionsResult;
|
|
@@ -1,63 +1,236 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
3
|
+
import { normalizeError } from "../../../errors/normalize";
|
|
3
4
|
import { useCurrentProfile, useUpdateCurrentProfile } from "./useProfile";
|
|
4
5
|
const PERSIST_DEBOUNCE_MS = 600;
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
*
|
|
8
|
-
|
|
6
|
+
const LOCAL_KEY = "letitrip:section-state";
|
|
7
|
+
/**
|
|
8
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
9
|
+
* Persisted, per-user, per-surface open/closed state for collapsible sections.
|
|
10
|
+
*
|
|
11
|
+
* `useSectionState` is the implementation; `useCollapsedSections` below is a
|
|
12
|
+
* thin adapter preserving the older hook's exact semantics for its existing
|
|
13
|
+
* call sites.
|
|
14
|
+
*
|
|
15
|
+
* 🛑 THE STORED VALUE IS WHAT IS **OPEN**, NOT WHAT IS COLLAPSED.
|
|
16
|
+
*
|
|
17
|
+
* The predecessor stored `uiPreferences.collapsedSections`: one flat, global,
|
|
18
|
+
* un-namespaced array of collapsed ids, shared by every page and kept apart
|
|
19
|
+
* only by a naming convention. Two consequences, both of which the polarity
|
|
20
|
+
* flip removes rather than patches:
|
|
21
|
+
*
|
|
22
|
+
* - a never-seen surface had to DEFAULT to collapsed, because "absent" and
|
|
23
|
+
* "deliberately collapsed" were the same value. Storing what is open makes
|
|
24
|
+
* a never-seen scope resolve to `defaultOpen` by absence, so "section 1
|
|
25
|
+
* open, the rest collapsed" needs no sentinel;
|
|
26
|
+
* - one page's ids could collide with another's.
|
|
27
|
+
*
|
|
28
|
+
* The scope key is `"{portal}:{surface}"` and is supplied by the caller. It is
|
|
29
|
+
* deliberately NOT derived from the section ids — a hash would change the
|
|
30
|
+
* moment a section is added, silently resetting every user's layout.
|
|
31
|
+
*
|
|
32
|
+
* ── localStorage mirror ─────────────────────────────────────────────────────
|
|
33
|
+
* The profile arrives over the network, so a server-only read paints every
|
|
34
|
+
* section in its default state and then snaps to the user's real layout a
|
|
35
|
+
* moment later. Writes go to localStorage synchronously (instant, and it
|
|
36
|
+
* survives for signed-out users) and to the user document debounced (durable,
|
|
37
|
+
* and it follows the user to another device). localStorage seeds first paint;
|
|
38
|
+
* the profile is authoritative once it lands.
|
|
39
|
+
*
|
|
40
|
+
* ── Migration ───────────────────────────────────────────────────────────────
|
|
41
|
+
* One-way and lossless: the first time a scope is read with no `sectionState`
|
|
42
|
+
* entry, its open set is derived from the legacy `collapsedSections`
|
|
43
|
+
* (`sectionIds − collapsed`) and persisted under the new key. `collapsedSections`
|
|
44
|
+
* keeps being written for one release so a rollback loses nothing.
|
|
45
|
+
*/
|
|
46
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
47
|
+
// localStorage mirror
|
|
48
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
49
|
+
function readLocalScopes() {
|
|
50
|
+
if (typeof window === "undefined")
|
|
51
|
+
return {};
|
|
52
|
+
try {
|
|
53
|
+
const raw = window.localStorage.getItem(LOCAL_KEY);
|
|
54
|
+
if (!raw)
|
|
55
|
+
return {};
|
|
56
|
+
const parsed = JSON.parse(raw);
|
|
57
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
58
|
+
return {};
|
|
59
|
+
// Validate rather than trust: this value survives across releases and a
|
|
60
|
+
// shape written by an older version must degrade to "no preference", not
|
|
61
|
+
// to a render crash. Every entry is checked, so the cast above is a claim
|
|
62
|
+
// this loop then makes true.
|
|
63
|
+
const out = {};
|
|
64
|
+
for (const [scope, ids] of Object.entries(parsed)) {
|
|
65
|
+
if (Array.isArray(ids))
|
|
66
|
+
out[scope] = ids.filter((id) => typeof id === "string");
|
|
67
|
+
}
|
|
68
|
+
return out;
|
|
69
|
+
// localStorage throws on access in private mode, and JSON.parse throws on a
|
|
70
|
+
// value some other version wrote. Neither is reportable — the user document
|
|
71
|
+
// is the durable copy and this is only a first-paint cache — but the throw
|
|
72
|
+
// is still normalized rather than swallowed raw.
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
void normalizeError(err);
|
|
76
|
+
return {};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function writeLocalScope(scope, openIds) {
|
|
80
|
+
if (typeof window === "undefined")
|
|
81
|
+
return;
|
|
82
|
+
try {
|
|
83
|
+
const all = readLocalScopes();
|
|
84
|
+
all[scope] = openIds;
|
|
85
|
+
window.localStorage.setItem(LOCAL_KEY, JSON.stringify(all));
|
|
86
|
+
// setItem throws on quota exhaustion and in private mode. Same reasoning as
|
|
87
|
+
// readLocalScopes — the debounced profile PATCH is the durable write, so
|
|
88
|
+
// losing the mirror costs one frame of default layout and nothing else.
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
void normalizeError(err);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
export function useSectionState({ scope, sectionIds, defaultOpen, mode = "multi", }) {
|
|
9
95
|
const { data: profile } = useCurrentProfile();
|
|
10
96
|
const updateProfile = useUpdateCurrentProfile();
|
|
11
|
-
// Full server-side list, including other pages' entries this hook must
|
|
12
|
-
// never clobber when it persists its own page's next state.
|
|
13
|
-
const savedFullListRef = useRef([]);
|
|
14
|
-
const [localCollapsed, setLocalCollapsed] = useState(null);
|
|
15
|
-
const debounceRef = useRef(null);
|
|
16
97
|
const sectionIdsRef = useRef(sectionIds);
|
|
17
98
|
sectionIdsRef.current = sectionIds;
|
|
99
|
+
const fallbackOpen = useMemo(() => defaultOpen ?? (sectionIds.length > 0 ? [sectionIds[0]] : []),
|
|
100
|
+
// Only the first id matters for the default, so this does not need to
|
|
101
|
+
// re-run every time the array identity changes.
|
|
102
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
103
|
+
[defaultOpen, sectionIds[0], sectionIds.length]);
|
|
104
|
+
const fallbackOpenRef = useRef(fallbackOpen);
|
|
105
|
+
fallbackOpenRef.current = fallbackOpen;
|
|
106
|
+
// Server copy of EVERY scope, so persisting this surface never clobbers
|
|
107
|
+
// another's entry.
|
|
108
|
+
const savedScopesRef = useRef({});
|
|
109
|
+
const legacyCollapsedRef = useRef(undefined);
|
|
110
|
+
const debounceRef = useRef(null);
|
|
111
|
+
/** `scope: ""` opts out of every durable store — see the option's docstring. */
|
|
112
|
+
const persistent = scope !== "";
|
|
113
|
+
// Seeded synchronously from localStorage so first paint is already correct.
|
|
114
|
+
const [localOpen, setLocalOpen] = useState(() => {
|
|
115
|
+
if (!persistent)
|
|
116
|
+
return null;
|
|
117
|
+
const cached = readLocalScopes()[scope];
|
|
118
|
+
return cached ? new Set(cached) : null;
|
|
119
|
+
});
|
|
18
120
|
useEffect(() => {
|
|
19
|
-
if (!profile)
|
|
121
|
+
if (!persistent || !profile)
|
|
20
122
|
return;
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
123
|
+
const prefs = profile.uiPreferences;
|
|
124
|
+
savedScopesRef.current = prefs?.sectionState ?? {};
|
|
125
|
+
legacyCollapsedRef.current = prefs?.collapsedSections;
|
|
126
|
+
const stored = savedScopesRef.current[scope];
|
|
127
|
+
if (stored) {
|
|
128
|
+
setLocalOpen(new Set(stored));
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
// No entry for this scope yet — derive one from the legacy flat list if the
|
|
132
|
+
// user has one, otherwise fall back to the default. Persisting is left to
|
|
133
|
+
// the first real toggle: writing on mere page view would fire a profile
|
|
134
|
+
// PATCH for every visitor on every dashboard.
|
|
135
|
+
const legacy = legacyCollapsedRef.current;
|
|
136
|
+
if (legacy !== undefined) {
|
|
137
|
+
const collapsed = new Set(legacy);
|
|
138
|
+
setLocalOpen(new Set(sectionIdsRef.current.filter((id) => !collapsed.has(id))));
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
setLocalOpen(new Set(fallbackOpenRef.current));
|
|
142
|
+
}
|
|
143
|
+
}, [profile, scope, persistent]);
|
|
36
144
|
useEffect(() => () => {
|
|
37
145
|
if (debounceRef.current)
|
|
38
146
|
clearTimeout(debounceRef.current);
|
|
39
147
|
}, []);
|
|
40
|
-
const persist = useCallback((
|
|
148
|
+
const persist = useCallback((next) => {
|
|
149
|
+
if (!persistent)
|
|
150
|
+
return;
|
|
151
|
+
const openIds = sectionIdsRef.current.filter((id) => next.has(id));
|
|
152
|
+
writeLocalScope(scope, openIds);
|
|
153
|
+
savedScopesRef.current = { ...savedScopesRef.current, [scope]: openIds };
|
|
41
154
|
if (debounceRef.current)
|
|
42
155
|
clearTimeout(debounceRef.current);
|
|
43
156
|
debounceRef.current = setTimeout(() => {
|
|
157
|
+
// Keep writing the legacy list for one release: derived from this
|
|
158
|
+
// surface's ids only, merged over whatever other pages recorded, so a
|
|
159
|
+
// rollback finds the same state it would have written itself.
|
|
44
160
|
const ids = sectionIdsRef.current;
|
|
45
|
-
const others =
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
161
|
+
const others = (legacyCollapsedRef.current ?? []).filter((id) => !ids.includes(id));
|
|
162
|
+
const collapsedHere = ids.filter((id) => !next.has(id));
|
|
163
|
+
const mergedLegacy = [...others, ...collapsedHere];
|
|
164
|
+
legacyCollapsedRef.current = mergedLegacy;
|
|
165
|
+
updateProfile.mutate({
|
|
166
|
+
uiPreferences: {
|
|
167
|
+
sectionState: { [scope]: openIds },
|
|
168
|
+
collapsedSections: mergedLegacy,
|
|
169
|
+
},
|
|
170
|
+
});
|
|
49
171
|
}, PERSIST_DEBOUNCE_MS);
|
|
50
|
-
}, [updateProfile]);
|
|
51
|
-
const
|
|
172
|
+
}, [scope, persistent, updateProfile]);
|
|
173
|
+
const effectiveOpen = localOpen ?? new Set(fallbackOpen);
|
|
174
|
+
const isOpen = useCallback((id) => (localOpen ?? new Set(fallbackOpenRef.current)).has(id), [localOpen]);
|
|
52
175
|
const toggle = useCallback((id) => {
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
const next =
|
|
56
|
-
|
|
57
|
-
|
|
176
|
+
setLocalOpen((prev) => {
|
|
177
|
+
const current = prev ?? new Set(fallbackOpenRef.current);
|
|
178
|
+
const next = new Set(current);
|
|
179
|
+
if (next.has(id))
|
|
180
|
+
next.delete(id);
|
|
181
|
+
else if (mode === "single") {
|
|
182
|
+
next.clear();
|
|
183
|
+
next.add(id);
|
|
184
|
+
}
|
|
185
|
+
else
|
|
186
|
+
next.add(id);
|
|
187
|
+
persist(next);
|
|
188
|
+
return next;
|
|
189
|
+
});
|
|
190
|
+
}, [mode, persist]);
|
|
191
|
+
const open = useCallback((id) => {
|
|
192
|
+
setLocalOpen((prev) => {
|
|
193
|
+
const current = prev ?? new Set(fallbackOpenRef.current);
|
|
194
|
+
if (current.has(id))
|
|
195
|
+
return current;
|
|
196
|
+
// Deliberately additive even under `mode: "single"`: this exists for
|
|
197
|
+
// error recovery, and collapsing the section the user was just editing
|
|
198
|
+
// in order to reveal the one that failed loses their place.
|
|
199
|
+
const next = new Set(current);
|
|
200
|
+
next.add(id);
|
|
201
|
+
persist(next);
|
|
202
|
+
return next;
|
|
203
|
+
});
|
|
204
|
+
}, [persist]);
|
|
205
|
+
const setOpenIds = useCallback((ids) => {
|
|
206
|
+
setLocalOpen(() => {
|
|
207
|
+
const next = new Set(ids);
|
|
58
208
|
persist(next);
|
|
59
209
|
return next;
|
|
60
210
|
});
|
|
61
211
|
}, [persist]);
|
|
212
|
+
const openIds = useMemo(() => sectionIds.filter((id) => effectiveOpen.has(id)),
|
|
213
|
+
// effectiveOpen is derived from localOpen; depending on the Set identity
|
|
214
|
+
// directly would recompute on every render.
|
|
215
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
216
|
+
[sectionIds, localOpen, fallbackOpen]);
|
|
217
|
+
return { isOpen, toggle, open, setOpenIds, openIds };
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* @deprecated Prefer `useSectionState`, which is namespaced per surface,
|
|
221
|
+
* supports several sections open at once, and defaults to section 1 open.
|
|
222
|
+
*
|
|
223
|
+
* Kept so the existing dashboard call sites need no edit. Semantics are
|
|
224
|
+
* byte-identical to the original: default all-collapsed (`defaultOpen: []`)
|
|
225
|
+
* and accordion (`mode: "single"`).
|
|
226
|
+
*/
|
|
227
|
+
export function useCollapsedSections({ sectionIds, scope = "legacy:collapsed-sections", }) {
|
|
228
|
+
const { isOpen, toggle } = useSectionState({
|
|
229
|
+
scope,
|
|
230
|
+
sectionIds,
|
|
231
|
+
defaultOpen: [],
|
|
232
|
+
mode: "single",
|
|
233
|
+
});
|
|
234
|
+
const isCollapsed = useCallback((id) => !isOpen(id), [isOpen]);
|
|
62
235
|
return { isCollapsed, toggle };
|
|
63
236
|
}
|