@mohasinac/appkit 4.26.0 → 4.28.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/lottery/LotteryAdminEditView.d.ts +16 -16
- package/dist/_internal/client/features/lottery/LotteryAdminEditView.js +29 -6
- package/dist/_internal/client/features/lottery/LotteryListView.js +13 -1
- package/dist/_internal/server/features/bundles/data.d.ts +19 -0
- package/dist/_internal/server/features/bundles/data.js +32 -0
- package/dist/_internal/server/features/bundles/index.d.ts +1 -1
- package/dist/_internal/server/features/bundles/index.js +1 -1
- package/dist/_internal/server/features/checkout/actions.js +6 -4
- package/dist/_internal/server/features/classified/metadata.js +3 -1
- package/dist/_internal/server/features/digital-code/metadata.js +3 -1
- package/dist/_internal/server/features/live/metadata.js +3 -1
- package/dist/_internal/server/features/refunds/actions.js +3 -2
- package/dist/_internal/server/features/reviews/actions.js +3 -2
- package/dist/_internal/server/features/seo/sitemap.js +189 -40
- package/dist/_internal/server/jobs/core/listingProcessor.d.ts +0 -1
- package/dist/_internal/server/jobs/core/listingProcessor.js +19 -1
- package/dist/_internal/shared/actions/action-registry.js +14 -0
- package/dist/_internal/shared/checkout/order-math.d.ts +25 -3
- package/dist/_internal/shared/features/checkout/config.d.ts +3 -3
- package/dist/_internal/shared/features/checkout/config.js +6 -3
- package/dist/client.d.ts +32 -2
- package/dist/client.js +37 -2
- package/dist/constants/api-endpoints.d.ts +21 -3
- package/dist/constants/api-endpoints.js +14 -1
- package/dist/constants/field-names.d.ts +2 -2
- package/dist/constants/field-names.js +2 -2
- package/dist/core/newsletter.repository.js +14 -1
- package/dist/features/about/components/FAQPageView.js +1 -1
- package/dist/features/about/components/FeesView.js +1 -1
- package/dist/features/account/components/AddressForm.d.ts +2 -0
- package/dist/features/account/components/AddressForm.js +5 -1
- package/dist/features/account/components/UserBidsView.js +1 -1
- package/dist/features/account/components/UserSidebar.js +1 -1
- package/dist/features/account/components/UserSupportView.js +24 -2
- package/dist/features/account/hooks/useAddresses.d.ts +29 -1
- package/dist/features/account/hooks/useAddresses.js +4 -1
- package/dist/features/account/hooks/useProfile.d.ts +1 -1
- package/dist/features/account/hooks/useProfile.js +1 -1
- package/dist/features/addresses/repository/addresses.repository.d.ts +13 -0
- package/dist/features/addresses/repository/addresses.repository.js +19 -0
- package/dist/features/admin/actions/chat-actions.js +5 -1
- package/dist/features/admin/components/AdminAdsView.js +1 -1
- package/dist/features/admin/components/AdminBidsView.js +6 -1
- package/dist/features/admin/components/AdminCarouselGroupEditorView.d.ts +9 -1
- package/dist/features/admin/components/AdminCarouselGroupEditorView.js +42 -10
- package/dist/features/admin/components/AdminCarouselView.js +1 -1
- package/dist/features/admin/components/AdminFeatureEditorView.js +1 -1
- package/dist/features/admin/components/AdminGroupedListingsView.js +14 -2
- package/dist/features/admin/components/AdminSidebar.js +1 -1
- package/dist/features/admin/components/AdminViewCards.js +2 -2
- package/dist/features/admin/components/DataTable.js +2 -2
- package/dist/features/admin/schemas/firestore.js +9 -3
- package/dist/features/auctions/actions/bid-actions.js +45 -4
- package/dist/features/auctions/components/AuctionBottomActions.d.ts +21 -6
- package/dist/features/auctions/components/AuctionBottomActions.js +42 -11
- package/dist/features/auctions/components/AuctionDetailPageView.d.ts +8 -3
- package/dist/features/auctions/components/AuctionDetailPageView.js +2 -2
- package/dist/features/auctions/components/MarketplaceAuctionCard.js +4 -5
- package/dist/features/auctions/components/PlaceBidFormClient.d.ts +27 -2
- package/dist/features/auctions/components/PlaceBidFormClient.js +42 -20
- package/dist/features/auth/components/SocialAuthButtons.js +1 -1
- package/dist/features/auth/hooks/useAuth.d.ts +23 -1
- package/dist/features/auth/hooks/useAuth.js +27 -1
- package/dist/features/auth/permissions/constants.d.ts +1 -1
- package/dist/features/auth/permissions/constants.js +3 -0
- package/dist/features/auth/repository/session.repository.d.ts +16 -1
- package/dist/features/auth/repository/session.repository.js +23 -0
- package/dist/features/auth/repository/user.repository.js +13 -2
- package/dist/features/blog/components/BlogListView.js +2 -2
- package/dist/features/cart/components/CartPriceBreakdown.d.ts +11 -1
- package/dist/features/cart/components/CartPriceBreakdown.js +3 -3
- package/dist/features/cart/components/StoreAddonsPicker.d.ts +9 -0
- package/dist/features/cart/components/StoreAddonsPicker.js +15 -5
- package/dist/features/cart/components/index.d.ts +1 -1
- package/dist/features/cart/components/index.js +1 -1
- package/dist/features/cart/hooks/useGuestCartMerge.d.ts +8 -1
- package/dist/features/cart/hooks/useGuestCartMerge.js +2 -1
- package/dist/features/cart/utils/line-total.d.ts +25 -0
- package/dist/features/cart/utils/line-total.js +26 -0
- package/dist/features/catalogue/repository/catalogue.repository.d.ts +12 -0
- package/dist/features/catalogue/repository/catalogue.repository.js +15 -0
- package/dist/features/categories/components/CategoryTree.js +2 -2
- package/dist/features/contact/components/ContactForm.js +1 -1
- package/dist/features/copilot/hooks/useCopilotFeedback.d.ts +1 -1
- package/dist/features/copilot/hooks/useCopilotFeedback.js +1 -1
- package/dist/features/events/components/AdminEventEditorView.js +16 -9
- package/dist/features/events/components/AdminEventEntriesView.js +7 -13
- package/dist/features/events/components/EventRafflesSection.js +14 -2
- package/dist/features/events/components/EventStatusBadge.js +1 -1
- package/dist/features/events/components/event-entry-detail-fields.d.ts +24 -0
- package/dist/features/events/components/event-entry-detail-fields.js +13 -0
- package/dist/features/faq/components/ContactCTA.js +1 -1
- package/dist/features/faq/components/FAQHelpfulButtons.js +2 -2
- package/dist/features/faq/repository/faqs.repository.d.ts +16 -1
- package/dist/features/faq/repository/faqs.repository.js +64 -54
- package/dist/features/faq/schemas/firestore.d.ts +3 -2
- package/dist/features/faq/schemas/firestore.js +2 -2
- package/dist/features/faq/schemas/index.d.ts +3 -3
- package/dist/features/faq/schemas/index.js +1 -1
- package/dist/features/filters/FilterFacetSection.js +2 -2
- package/dist/features/forms/Toggle.js +1 -1
- package/dist/features/grouped/components/GroupedListingEditorView.d.ts +18 -0
- package/dist/features/grouped/components/GroupedListingEditorView.js +192 -0
- package/dist/features/homepage/components/AdvertisementBanner.js +2 -2
- package/dist/features/homepage/components/BrandsSection.js +1 -1
- package/dist/features/homepage/components/CTABannerSection.js +1 -1
- package/dist/features/homepage/components/HomepageCustomerReviewsSection.js +1 -1
- package/dist/features/homepage/components/HomepageSectionBoundary.d.ts +39 -0
- package/dist/features/homepage/components/HomepageSectionBoundary.js +49 -0
- package/dist/features/homepage/components/ShopByCategorySection.js +1 -1
- package/dist/features/homepage/components/WelcomeSection.js +1 -1
- package/dist/features/homepage/lib/section-renderer.js +2 -1
- package/dist/features/layout/AppLayoutShell.js +5 -5
- package/dist/features/layout/BottomActions.js +5 -5
- package/dist/features/layout/BottomSheet.js +1 -1
- package/dist/features/layout/MainNavbar.js +1 -1
- package/dist/features/layout/NavbarLayout.js +1 -1
- package/dist/features/layout/TitleBarLayout.js +3 -3
- package/dist/features/layout/hooks/useFormBottomActions.d.ts +11 -1
- package/dist/features/layout/hooks/useFormBottomActions.js +16 -2
- package/dist/features/lottery/schemas/config-write.d.ts +141 -0
- package/dist/features/lottery/schemas/config-write.js +139 -0
- package/dist/features/media/MediaPickerModal.js +3 -1
- package/dist/features/media/MediaVideo.js +4 -2
- package/dist/features/media/components/MediaSlider.js +1 -1
- package/dist/features/orders/components/OrdersList.js +2 -2
- package/dist/features/payments/repository/payout.repository.d.ts +11 -1
- package/dist/features/payments/repository/payout.repository.js +14 -6
- package/dist/features/pre-orders/components/MarketplacePreorderCard.js +3 -2
- package/dist/features/pre-orders/components/PreOrderBadge.js +1 -1
- package/dist/features/pre-orders/components/PreOrderDetailPageView.js +1 -1
- package/dist/features/products/columns/productTableColumns.js +3 -3
- package/dist/features/products/components/CompareOverlay.js +1 -1
- package/dist/features/products/components/PrizeDrawDetailPageView.js +1 -1
- package/dist/features/products/components/PrizeDrawEntryActions.js +2 -2
- package/dist/features/products/components/PrizeDrawsSection.js +5 -1
- package/dist/features/products/components/ProductDetailActions.js +11 -7
- package/dist/features/products/components/ProductDetailPageView.js +3 -3
- package/dist/features/products/components/ProductFeatureBadges.js +2 -2
- package/dist/features/products/components/ProductFeaturesSelector.js +1 -1
- package/dist/features/products/components/ProductFilters.js +15 -8
- package/dist/features/products/components/ProductGrid.js +8 -17
- package/dist/features/products/components/ProductTabs.js +7 -4
- package/dist/features/products/components/ProductTabsShell.js +2 -7
- package/dist/features/products/constants/action-defs.d.ts +21 -4
- package/dist/features/products/constants/action-defs.js +7 -3
- package/dist/features/products/hooks/useRelatedProducts.js +2 -1
- package/dist/features/products/repository/products.repository.d.ts +1 -1
- package/dist/features/products/repository/products.repository.js +27 -13
- package/dist/features/products/schemas/firestore.d.ts +3 -2
- package/dist/features/products/schemas/firestore.js +1 -1
- package/dist/features/reviews/actions/review-actions.d.ts +8 -1
- package/dist/features/reviews/actions/review-actions.js +14 -3
- package/dist/features/reviews/components/ReviewDetailShell.js +10 -4
- package/dist/features/reviews/components/ReviewModal.js +1 -1
- package/dist/features/reviews/repository/reviews.repository.d.ts +25 -1
- package/dist/features/reviews/repository/reviews.repository.js +53 -13
- package/dist/features/reviews/schemas/firestore.d.ts +6 -0
- package/dist/features/scams/components/ScamRegistryView.js +2 -2
- package/dist/features/search/components/Search.js +3 -3
- package/dist/features/search/components/SearchResultsSection.js +1 -1
- package/dist/features/seller/components/SellerAddressesView.js +3 -1
- package/dist/features/seller/components/SellerBidsView.js +6 -1
- package/dist/features/seller/components/SellerSidebar.js +1 -1
- package/dist/features/shell/SectionForm.d.ts +21 -1
- package/dist/features/shell/SectionForm.js +15 -3
- package/dist/features/shell/build-sections.js +10 -1
- package/dist/features/shell/field-ui-meta.d.ts +21 -0
- package/dist/features/shipments/components/AdminShipmentLotItemsView.js +23 -6
- package/dist/features/site-settings/components/ActionPermissionsManager.js +1 -1
- package/dist/features/store-extensions/repository/store-extensions.repositories.d.ts +21 -4
- package/dist/features/store-extensions/repository/store-extensions.repositories.js +24 -4
- package/dist/features/store-extensions/schemas/shipping-config-form.js +13 -0
- package/dist/features/store-extensions/schemas/store-category-form.js +1 -1
- package/dist/features/stores/actions/store-query-actions.js +25 -1
- package/dist/features/stores/components/InteractiveStoreCard.js +1 -1
- package/dist/features/stores/components/StoreHeader.js +1 -1
- package/dist/features/stores/components/StoreNavTabs.js +20 -8
- package/dist/features/support/repository/support.repository.d.ts +12 -0
- package/dist/features/support/repository/support.repository.js +15 -0
- package/dist/features/support/schemas/ticket-create-form.d.ts +33 -0
- package/dist/features/support/schemas/ticket-create-form.js +82 -0
- package/dist/features/tester/seed-data/products-tester-seed-data.js +108 -2
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +235 -13
- package/dist/features/wishlist/components/WishlistHeartButton.d.ts +55 -0
- package/dist/features/wishlist/components/WishlistHeartButton.js +52 -0
- package/dist/features/wishlist/components/index.d.ts +2 -1
- package/dist/features/wishlist/components/index.js +1 -1
- package/dist/index.d.ts +10 -3
- package/dist/index.js +19 -4
- package/dist/monitoring/error-tracking.js +39 -5
- package/dist/monitoring/server-logger.js +70 -32
- package/dist/next/components/ErrorView.js +1 -1
- package/dist/next/routing/route-map.d.ts +39 -7
- package/dist/next/routing/route-map.js +34 -4
- package/dist/providers/db-firebase/base.repository.d.ts +30 -0
- package/dist/providers/db-firebase/base.repository.js +56 -9
- package/dist/react/ErrorBoundary.js +9 -1
- package/dist/react/contexts/SessionContext.js +6 -2
- package/dist/react/hooks/useAuthGate.d.ts +22 -0
- package/dist/react/hooks/useAuthGate.js +86 -7
- package/dist/schemas/registry.d.ts +3 -3
- package/dist/security/index.d.ts +1 -1
- package/dist/security/index.js +1 -1
- package/dist/security/pii-encrypt.d.ts +13 -5
- package/dist/security/pii-encrypt.js +132 -6
- package/dist/security/pii-schemas.d.ts +20 -4
- package/dist/security/pii-schemas.js +23 -5
- package/dist/seed/_helpers/product-search-txt.d.ts +18 -0
- package/dist/seed/_helpers/product-search-txt.js +33 -0
- package/dist/seed/faq-seed-data.d.ts +16 -0
- package/dist/seed/faq-seed-data.js +22 -1
- package/dist/seed/products-art-seed-data.js +49 -7
- package/dist/seed/products-auctions-seed-data.js +12 -4
- package/dist/seed/products-classifieds-seed-data.d.ts +5 -0
- package/dist/seed/products-classifieds-seed-data.js +65 -9
- package/dist/seed/products-digital-codes-seed-data.d.ts +5 -0
- package/dist/seed/products-digital-codes-seed-data.js +65 -9
- package/dist/seed/products-live-items-seed-data.d.ts +5 -0
- package/dist/seed/products-live-items-seed-data.js +33 -5
- package/dist/seed/products-preorders-seed-data.d.ts +1 -1
- package/dist/seed/products-preorders-seed-data.js +6 -2
- package/dist/seed/products-prize-draws-seed-data.d.ts +5 -0
- package/dist/seed/products-prize-draws-seed-data.js +57 -8
- package/dist/seed/products-standard-seed-data.js +6 -2
- package/dist/seed/products-stickers-seed-data.js +49 -7
- package/dist/seed/site-settings-seed-data.js +5 -3
- package/dist/seo/json-ld.d.ts +44 -2
- package/dist/seo/json-ld.js +57 -11
- package/dist/server.d.ts +1 -1
- package/dist/server.js +1 -1
- package/dist/styles.css +368 -90
- package/dist/tailwind-utilities.css +1 -1
- package/dist/tokens/index.d.ts +1 -1
- package/dist/tokens/index.js +1 -1
- package/dist/tokens/themes/default-dark.js +6 -0
- package/dist/tokens/themes/default-light.js +5 -0
- package/dist/tokens/tokens.css +84 -0
- package/dist/ui/DataTable.js +1 -1
- package/dist/ui/DataTable.style.css +3 -3
- package/dist/ui/components/BulkActionBar.style.css +1 -3
- package/dist/ui/components/Button.js +15 -6
- package/dist/ui/components/Dropdown.style.css +1 -4
- package/dist/ui/components/FilterChipGroup.js +1 -1
- package/dist/ui/components/FilterDrawer.style.css +1 -1
- package/dist/ui/components/Icon.d.ts +53 -0
- package/dist/ui/components/Icon.js +41 -0
- package/dist/ui/components/IconButton.d.ts +13 -0
- package/dist/ui/components/IconButton.js +13 -0
- package/dist/ui/components/IconButton.style.css +18 -10
- package/dist/ui/components/ListingLayout.style.css +2 -10
- package/dist/ui/components/ListingToolbar.js +6 -6
- package/dist/ui/components/Modal.js +1 -1
- package/dist/ui/components/PaginatedSelect.style.css +3 -3
- package/dist/ui/components/Pagination.style.css +1 -6
- package/dist/ui/components/RichTextEditor.js +1 -1
- package/dist/ui/components/RowActionMenu.style.css +1 -1
- package/dist/ui/components/SideDrawer.style.css +1 -1
- package/dist/ui/components/Slider.js +1 -1
- package/dist/ui/components/TabBarShell.d.ts +78 -0
- package/dist/ui/components/TabBarShell.js +95 -0
- package/dist/ui/components/Tabs.js +12 -8
- package/dist/ui/components/Tabs.style.css +249 -41
- package/dist/ui/components/ViewToggle.style.css +1 -5
- package/dist/ui/components/tabs-overflow.d.ts +17 -0
- package/dist/ui/components/tabs-overflow.js +142 -0
- package/dist/ui/icons/icon-registry.d.ts +38 -0
- package/dist/ui/icons/icon-registry.js +37 -0
- package/dist/ui/index.d.ts +6 -0
- package/dist/ui/index.js +3 -0
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/media-url.d.ts +27 -4
- package/dist/utils/media-url.js +37 -6
- package/dist/utils/search-txt.d.ts +71 -0
- package/dist/utils/search-txt.js +118 -0
- package/package.json +1 -1
package/dist/client.js
CHANGED
|
@@ -12,6 +12,12 @@ export { ApiError, isApiError } from "./client/api/ApiError";
|
|
|
12
12
|
export { surfaceError } from "./client/api/surface-error";
|
|
13
13
|
export { reportClientError, } from "./client/observability/reportClientError";
|
|
14
14
|
export { installClientErrorReporter, useClientErrorReporter, } from "./client/observability/installClientErrorReporter";
|
|
15
|
+
// [CLIENT-SAFE] Error-tracker registration. The consumer calls setErrorTracker
|
|
16
|
+
// once at bootstrap to route every trackError() — notably the `error.digest`
|
|
17
|
+
// captured by ErrorView / GlobalError / ErrorBoundary — into reportClientError.
|
|
18
|
+
// Without a registration, trackError falls back to console.error and the digest
|
|
19
|
+
// (the only link to the server-side error) is lost.
|
|
20
|
+
export { setErrorTracker, trackError, ErrorCategory, ErrorSeverity, } from "./monitoring/error-tracking";
|
|
15
21
|
// [CLIENT-SAFE] React Query wrappers — auto-toast / auto-inline-field-error
|
|
16
22
|
// on failure, optional `loadingMessage` for long ops.
|
|
17
23
|
export { useApiMutation } from "./client/api/useApiMutation";
|
|
@@ -51,6 +57,7 @@ export { IconBox } from "./ui/components/IconBox";
|
|
|
51
57
|
export { Alert } from "./ui/components/Alert";
|
|
52
58
|
export { Divider } from "./ui/components/Divider";
|
|
53
59
|
export { Tabs, TabsList, TabsTrigger, TabsContent } from "./ui/components/Tabs";
|
|
60
|
+
export { TabBarShell, TabBarButton, TabsNavSelect } from "./ui/components/TabBarShell";
|
|
54
61
|
export { SellerProductsView } from "./features/seller/components/SellerProductsView";
|
|
55
62
|
export { SellerOrderDetailPanel } from "./features/seller/components/SellerOrdersView";
|
|
56
63
|
export { SellerPayoutDetailContent } from "./features/seller/components/SellerPayoutsView";
|
|
@@ -144,6 +151,16 @@ export { LotteryDetailView } from "./_internal/client/features/lottery/LotteryDe
|
|
|
144
151
|
export { LotteryEntriesView } from "./_internal/client/features/lottery/LotteryEntriesView";
|
|
145
152
|
export { LotteryAdminSlotView } from "./_internal/client/features/lottery/LotteryAdminSlotView";
|
|
146
153
|
export { LotteryAdminEditView } from "./_internal/client/features/lottery/LotteryAdminEditView";
|
|
154
|
+
export { lotteryConfigWriteSchema, lotterySlotWriteSchema } from "./features/lottery/schemas/config-write";
|
|
155
|
+
// The SectionForm engine + its schema-derived section builder. Client-only:
|
|
156
|
+
// consumer forms need these and the bare package resolves to the SERVER entry.
|
|
157
|
+
export { SectionForm, useSectionFormNav } from "./features/shell/SectionForm";
|
|
158
|
+
export { buildSectionsFromSchema } from "./features/shell/build-sections";
|
|
159
|
+
export { reportCreateSchema } from "./features/store-extensions/schemas/report-create-form";
|
|
160
|
+
export { itemRequestCreateSchema } from "./features/store-extensions/schemas/item-request-create-form";
|
|
161
|
+
export { supportTicketCreateSchema } from "./features/support/schemas/ticket-create-form";
|
|
162
|
+
export { analyticsCardCreateSchema, ANALYTICS_CARD_TYPES } from "./features/store-extensions/schemas/analytics-forms";
|
|
163
|
+
export { QuickFormDrawer } from "./features/shell/QuickFormDrawer";
|
|
147
164
|
export { PrizeDrawLotteryDetailView } from "./_internal/client/features/lottery/PrizeDrawLotteryDetailView";
|
|
148
165
|
// [CLIENT-ONLY]-Cannot run in SSR mode â€" uses browser-only APIs (window, navigator, localStorage, matchMedia, DOM events) that do not exist in Node.js.
|
|
149
166
|
// BottomSheet - Shared export for bottom sheet.
|
|
@@ -258,10 +275,19 @@ export { useCollapsedSections } from "./features/account/hooks/useCollapsedSecti
|
|
|
258
275
|
export { PageViewTracker } from "./features/analytics/components/PageViewTracker";
|
|
259
276
|
export { PAGE_VIEW_ENTITY_TYPES } from "./features/analytics/types";
|
|
260
277
|
export { AuctionBidsTable } from "./features/auctions/components/AuctionBidsTable";
|
|
278
|
+
// The ONE bid-detail field builder, shared by all three portals' list modals
|
|
279
|
+
// and — since W22 — their detail pages. `viewer` is what decides whether the
|
|
280
|
+
// bidder's identity is shown, so keeping it in one place is what stops a
|
|
281
|
+
// buyer-facing surface ever rendering another bidder's name.
|
|
282
|
+
export { buildBidDetailFields, bidStatusBadge } from "./features/auctions/components/bid-detail-fields";
|
|
283
|
+
// Same reason, one feature over: the entry detail modal and its full page
|
|
284
|
+
// render from one builder rather than two lists that can drift.
|
|
285
|
+
export { buildEventEntryDetailFields } from "./features/events/components/event-entry-detail-fields";
|
|
261
286
|
export { ProtectedRoute, AuthStatusPanel, ForgotPasswordView, LoginForm, RegisterForm, ResetPasswordView, VerifyEmailView } from "./features/auth/index";
|
|
262
287
|
export { useLogout, useLogin, useGoogleLogin, useLinkGoogleAccount, useRegister, useForgotPassword, useResetPassword, useVerifyEmail, useChangeEmail } from "./features/auth/index";
|
|
263
288
|
export { CartView, CartItemRow, CartGroupLineRow, CartSummary, CartPriceBreakdown, CartDrawer, CheckoutView, CheckoutSuccessView, CheckoutAddressStep, useGuestCart, useCartCount, useAddToCart, useCart, useGuestCartMerge, useCartQuery } from "./features/cart/index";
|
|
264
|
-
export { StoreAddonsPicker } from "./features/cart/index";
|
|
289
|
+
export { StoreAddonsPicker, hasAnyStoreAddon } from "./features/cart/index";
|
|
290
|
+
export { clientLineTotal } from "./features/cart/utils/line-total";
|
|
265
291
|
export { getCartOps, CART_OPS_CHANGE_EVENT } from "./features/cart/utils/pending-ops";
|
|
266
292
|
export { useAddresses, useCreateAddress, useUpdateAddress, useDeleteAddress, useSetDefaultAddress, useAddress } from "./features/account/index";
|
|
267
293
|
export { AddressBook, AddressCard, AddressForm } from "./features/account/index";
|
|
@@ -278,10 +304,14 @@ export { useActiveAd } from "./features/homepage/hooks/useActiveAd";
|
|
|
278
304
|
export { registerAdSlot, registerAdSlots, unregisterAdSlot, clearAdRegistry, setAdConsentGranted, isAdConsentGranted, isAdSlotRenderable, } from "./features/homepage/ad-registry";
|
|
279
305
|
export { WishlistView, useGuestWishlist, useWishlistWithGuest } from "./features/wishlist/index";
|
|
280
306
|
export { WishlistCapWatcher } from "./features/wishlist/components/WishlistCapWatcher";
|
|
307
|
+
// The single wishlist heart. Exported here too because it is a "use client"
|
|
308
|
+
// UI component and consumer client code imports from this entry.
|
|
309
|
+
export { WishlistHeartButton } from "./features/wishlist/components/WishlistHeartButton";
|
|
281
310
|
export { SyncManagerMount } from "./core/components/SyncManagerMount";
|
|
282
311
|
export { useWishlistCount, useWishlistCountWithLimit, WISHLIST_CAP_EVENT } from "./features/wishlist/hooks/useWishlistCount";
|
|
283
312
|
export { getGuestWishlistItems, addToGuestWishlist, removeFromGuestWishlist, isInGuestWishlist, clearGuestWishlist, getGuestWishlistCount, getGuestWishlistByType, } from "./features/wishlist/utils/guest-wishlist";
|
|
284
313
|
export { InteractiveProductCard } from "./features/products/index";
|
|
314
|
+
export { GroupedListingEditorView } from "./features/grouped/components/GroupedListingEditorView";
|
|
285
315
|
export { GroupedListingDetailView } from "./features/grouped/components/GroupedListingDetailView";
|
|
286
316
|
export { GroupMemberPicker } from "./features/products/components/GroupMemberPicker";
|
|
287
317
|
export { MarketplaceBundleCard } from "./features/products/components/MarketplaceBundleCard";
|
|
@@ -400,7 +430,12 @@ export { normalizeError, getErrorMessage, isApiNormalized, isAppNormalized, isFi
|
|
|
400
430
|
// never touches the sibling crypto-importing helpers.
|
|
401
431
|
export { buildPaymentProofReviewMessage } from "./features/whatsapp-bot/helpers/payment-proof-message";
|
|
402
432
|
export { CommandPalette, useCommandPaletteHotkey } from "./features/shell/index";
|
|
403
|
-
export { AdminBlogEditorView, AdminBundleEditorView, AdminCarouselEditorView, AdminCategoryEditorView, AdminCouponEditorView, AdminFaqEditorView, AdminFeatureEditorView, AdminOrderEditorView, AdminPayoutMarkPaidModal, AdminAuditLogView, ViewAuditLogEntryModal, AdminProductEditorView, AdminScammerEditorView,
|
|
433
|
+
export { AdminBlogEditorView, AdminBundleEditorView, AdminCarouselEditorView, AdminCategoryEditorView, AdminCouponEditorView, AdminFaqEditorView, AdminFeatureEditorView, AdminOrderEditorView, AdminPayoutMarkPaidModal, AdminAuditLogView, ViewAuditLogEntryModal, AdminProductEditorView, AdminScammerEditorView,
|
|
434
|
+
// Client-exported for the W22 /admin/team/{new,[id]/edit} pages. It was
|
|
435
|
+
// reachable only from `index.ts` (the SERVER entry), which a "use client"
|
|
436
|
+
// page must never import — that is the Turbopack client-bundle trap.
|
|
437
|
+
AdminEmployeeEditorView, AdminNavEditorView, AdminUserEditorView, AdminStoreEditorView, AdminSublistingCategoryEditorView, AdminSupportTicketDetailView, } from "./features/admin/index";
|
|
438
|
+
export { PageLoader } from "./ui/index";
|
|
404
439
|
export { ADMIN_CHECKOUT_BYPASS_FLAG_KEY } from "./features/admin/schemas/firestore";
|
|
405
440
|
export { SpinWheelView, EventRaffleEntryForm } from "./features/events/index";
|
|
406
441
|
export { Card, CardBody } from "./ui/index";
|
|
@@ -25,7 +25,6 @@ export declare const AUTH_ENDPOINTS: {
|
|
|
25
25
|
readonly SESSION_ACTIVITY: "/api/auth/session/activity";
|
|
26
26
|
readonly SESSION_VALIDATE: "/api/auth/session/validate";
|
|
27
27
|
readonly EVENT_INIT: "/api/auth/event/init";
|
|
28
|
-
readonly RESEND_VERIFICATION: "/api/auth/resend-verification";
|
|
29
28
|
};
|
|
30
29
|
export declare const ACCOUNT_ENDPOINTS: {
|
|
31
30
|
/** @param userId — Firestore user document id */
|
|
@@ -51,6 +50,7 @@ export declare const ACCOUNT_ENDPOINTS: {
|
|
|
51
50
|
readonly HISTORY_MERGE: "/api/user/history/merge";
|
|
52
51
|
readonly REVIEWS: "/api/user/reviews";
|
|
53
52
|
readonly BIDS: "/api/user/bids";
|
|
53
|
+
readonly BID_BY_ID: (id: string) => string;
|
|
54
54
|
readonly EXPORT: "/api/user/export";
|
|
55
55
|
/** Claimed-coupons wallet — GET list, POST claim, DELETE by id. */
|
|
56
56
|
readonly COUPONS: "/api/user/coupons";
|
|
@@ -150,6 +150,12 @@ export declare const ADMIN_ENDPOINTS: {
|
|
|
150
150
|
readonly EVENTS: "/api/admin/events";
|
|
151
151
|
readonly EVENT_BY_ID: (id: string) => string;
|
|
152
152
|
readonly EVENT_STATUS: (id: string) => string;
|
|
153
|
+
/**
|
|
154
|
+
* The ONLY sanctioned writer of `lotteryConfig`. The generic EVENT_BY_ID
|
|
155
|
+
* PATCH rejects that field — it is `.passthrough()`, and letting a slot
|
|
156
|
+
* array through it erased every already-pulled slot.
|
|
157
|
+
*/
|
|
158
|
+
readonly EVENT_LOTTERY_CONFIG: (id: string) => string;
|
|
153
159
|
readonly EVENT_STATS: (id: string) => string;
|
|
154
160
|
readonly EVENT_ENTRIES: (id: string) => string;
|
|
155
161
|
readonly EVENT_ENTRIES_EXPORT: (id: string) => string;
|
|
@@ -488,7 +494,6 @@ export declare const API_ENDPOINTS: {
|
|
|
488
494
|
readonly SESSION_ACTIVITY: "/api/auth/session/activity";
|
|
489
495
|
readonly SESSION_VALIDATE: "/api/auth/session/validate";
|
|
490
496
|
readonly EVENT_INIT: "/api/auth/event/init";
|
|
491
|
-
readonly RESEND_VERIFICATION: "/api/auth/resend-verification";
|
|
492
497
|
};
|
|
493
498
|
readonly ACCOUNT: {
|
|
494
499
|
/** @param userId — Firestore user document id */
|
|
@@ -514,6 +519,7 @@ export declare const API_ENDPOINTS: {
|
|
|
514
519
|
readonly HISTORY_MERGE: "/api/user/history/merge";
|
|
515
520
|
readonly REVIEWS: "/api/user/reviews";
|
|
516
521
|
readonly BIDS: "/api/user/bids";
|
|
522
|
+
readonly BID_BY_ID: (id: string) => string;
|
|
517
523
|
readonly EXPORT: "/api/user/export";
|
|
518
524
|
/** Claimed-coupons wallet — GET list, POST claim, DELETE by id. */
|
|
519
525
|
readonly COUPONS: "/api/user/coupons";
|
|
@@ -613,6 +619,12 @@ export declare const API_ENDPOINTS: {
|
|
|
613
619
|
readonly EVENTS: "/api/admin/events";
|
|
614
620
|
readonly EVENT_BY_ID: (id: string) => string;
|
|
615
621
|
readonly EVENT_STATUS: (id: string) => string;
|
|
622
|
+
/**
|
|
623
|
+
* The ONLY sanctioned writer of `lotteryConfig`. The generic EVENT_BY_ID
|
|
624
|
+
* PATCH rejects that field — it is `.passthrough()`, and letting a slot
|
|
625
|
+
* array through it erased every already-pulled slot.
|
|
626
|
+
*/
|
|
627
|
+
readonly EVENT_LOTTERY_CONFIG: (id: string) => string;
|
|
616
628
|
readonly EVENT_STATS: (id: string) => string;
|
|
617
629
|
readonly EVENT_ENTRIES: (id: string) => string;
|
|
618
630
|
readonly EVENT_ENTRIES_EXPORT: (id: string) => string;
|
|
@@ -953,7 +965,6 @@ export declare const API_ROUTES: {
|
|
|
953
965
|
readonly SESSION_ACTIVITY: "/api/auth/session/activity";
|
|
954
966
|
readonly SESSION_VALIDATE: "/api/auth/session/validate";
|
|
955
967
|
readonly EVENT_INIT: "/api/auth/event/init";
|
|
956
|
-
readonly RESEND_VERIFICATION: "/api/auth/resend-verification";
|
|
957
968
|
};
|
|
958
969
|
readonly ACCOUNT: {
|
|
959
970
|
/** @param userId — Firestore user document id */
|
|
@@ -979,6 +990,7 @@ export declare const API_ROUTES: {
|
|
|
979
990
|
readonly HISTORY_MERGE: "/api/user/history/merge";
|
|
980
991
|
readonly REVIEWS: "/api/user/reviews";
|
|
981
992
|
readonly BIDS: "/api/user/bids";
|
|
993
|
+
readonly BID_BY_ID: (id: string) => string;
|
|
982
994
|
readonly EXPORT: "/api/user/export";
|
|
983
995
|
/** Claimed-coupons wallet — GET list, POST claim, DELETE by id. */
|
|
984
996
|
readonly COUPONS: "/api/user/coupons";
|
|
@@ -1078,6 +1090,12 @@ export declare const API_ROUTES: {
|
|
|
1078
1090
|
readonly EVENTS: "/api/admin/events";
|
|
1079
1091
|
readonly EVENT_BY_ID: (id: string) => string;
|
|
1080
1092
|
readonly EVENT_STATUS: (id: string) => string;
|
|
1093
|
+
/**
|
|
1094
|
+
* The ONLY sanctioned writer of `lotteryConfig`. The generic EVENT_BY_ID
|
|
1095
|
+
* PATCH rejects that field — it is `.passthrough()`, and letting a slot
|
|
1096
|
+
* array through it erased every already-pulled slot.
|
|
1097
|
+
*/
|
|
1098
|
+
readonly EVENT_LOTTERY_CONFIG: (id: string) => string;
|
|
1081
1099
|
readonly EVENT_STATS: (id: string) => string;
|
|
1082
1100
|
readonly EVENT_ENTRIES: (id: string) => string;
|
|
1083
1101
|
readonly EVENT_ENTRIES_EXPORT: (id: string) => string;
|
|
@@ -37,7 +37,13 @@ export const AUTH_ENDPOINTS = {
|
|
|
37
37
|
SESSION_ACTIVITY: "/api/auth/session/activity",
|
|
38
38
|
SESSION_VALIDATE: "/api/auth/session/validate",
|
|
39
39
|
EVENT_INIT: "/api/auth/event/init",
|
|
40
|
-
|
|
40
|
+
/*
|
|
41
|
+
* RESEND_VERIFICATION was here and is gone. The route was deleted in the
|
|
42
|
+
* Firebase-native auth migration (Root Cause #54: the Admin SDK cannot send
|
|
43
|
+
* email, only generate action links), and the constant outlived it — so the
|
|
44
|
+
* one hook that used it 404'd on every call. `useResendVerification` now
|
|
45
|
+
* calls the client SDK directly and needs no endpoint. Do not re-add this.
|
|
46
|
+
*/
|
|
41
47
|
};
|
|
42
48
|
// ---------------------------------------------------------------------------
|
|
43
49
|
// User / Account
|
|
@@ -66,6 +72,7 @@ export const ACCOUNT_ENDPOINTS = {
|
|
|
66
72
|
HISTORY_MERGE: "/api/user/history/merge",
|
|
67
73
|
REVIEWS: "/api/user/reviews",
|
|
68
74
|
BIDS: "/api/user/bids",
|
|
75
|
+
BID_BY_ID: (id) => `/api/user/bids/${id}`,
|
|
69
76
|
EXPORT: "/api/user/export",
|
|
70
77
|
/** Claimed-coupons wallet — GET list, POST claim, DELETE by id. */
|
|
71
78
|
COUPONS: "/api/user/coupons",
|
|
@@ -177,6 +184,12 @@ export const ADMIN_ENDPOINTS = {
|
|
|
177
184
|
EVENTS: "/api/admin/events",
|
|
178
185
|
EVENT_BY_ID: (id) => `/api/admin/events/${id}`,
|
|
179
186
|
EVENT_STATUS: (id) => `/api/admin/events/${id}/status`,
|
|
187
|
+
/**
|
|
188
|
+
* The ONLY sanctioned writer of `lotteryConfig`. The generic EVENT_BY_ID
|
|
189
|
+
* PATCH rejects that field — it is `.passthrough()`, and letting a slot
|
|
190
|
+
* array through it erased every already-pulled slot.
|
|
191
|
+
*/
|
|
192
|
+
EVENT_LOTTERY_CONFIG: (id) => `/api/admin/events/${id}/lottery-config`,
|
|
180
193
|
EVENT_STATS: (id) => `/api/admin/events/${id}/stats`,
|
|
181
194
|
EVENT_ENTRIES: (id) => `/api/admin/events/${id}/entries`,
|
|
182
195
|
EVENT_ENTRIES_EXPORT: (id) => `/api/admin/events/${id}/entries/export`,
|
|
@@ -122,7 +122,7 @@ export declare const PRODUCT_FIELDS: {
|
|
|
122
122
|
* matches zero documents — always map across via this constant.
|
|
123
123
|
*/
|
|
124
124
|
readonly SUBLISTING_CATEGORY_ID: "sublistingCategoryId";
|
|
125
|
-
readonly
|
|
125
|
+
readonly SEARCH_TXT: "searchTxt";
|
|
126
126
|
readonly CREATED_AT: "createdAt";
|
|
127
127
|
readonly UPDATED_AT: "updatedAt";
|
|
128
128
|
readonly STATUS_VALUES: {
|
|
@@ -812,7 +812,7 @@ export declare const FAQ_FIELDS: {
|
|
|
812
812
|
readonly ORDER: "order";
|
|
813
813
|
readonly PRIORITY: "priority";
|
|
814
814
|
readonly TAGS: "tags";
|
|
815
|
-
readonly
|
|
815
|
+
readonly SEARCH_TXT: "searchTxt";
|
|
816
816
|
readonly RELATED_FAQS: "relatedFAQs";
|
|
817
817
|
readonly USE_SITE_SETTINGS: "useSiteSettings";
|
|
818
818
|
readonly VARIABLES: "variables";
|
|
@@ -131,7 +131,7 @@ export const PRODUCT_FIELDS = {
|
|
|
131
131
|
* matches zero documents — always map across via this constant.
|
|
132
132
|
*/
|
|
133
133
|
SUBLISTING_CATEGORY_ID: "sublistingCategoryId",
|
|
134
|
-
|
|
134
|
+
SEARCH_TXT: "searchTxt",
|
|
135
135
|
CREATED_AT: "createdAt",
|
|
136
136
|
UPDATED_AT: "updatedAt",
|
|
137
137
|
STATUS_VALUES: {
|
|
@@ -949,7 +949,7 @@ export const FAQ_FIELDS = {
|
|
|
949
949
|
ORDER: "order",
|
|
950
950
|
PRIORITY: "priority",
|
|
951
951
|
TAGS: "tags",
|
|
952
|
-
|
|
952
|
+
SEARCH_TXT: "searchTxt",
|
|
953
953
|
RELATED_FAQS: "relatedFAQs",
|
|
954
954
|
USE_SITE_SETTINGS: "useSiteSettings",
|
|
955
955
|
VARIABLES: "variables",
|
|
@@ -16,7 +16,20 @@ export const NEWSLETTER_SUBSCRIBER_FIELDS = {
|
|
|
16
16
|
POPUP: "popup",
|
|
17
17
|
},
|
|
18
18
|
};
|
|
19
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Subscriber email, encrypted at rest.
|
|
21
|
+
*
|
|
22
|
+
* Was an empty array, so the encrypt/decrypt calls wrapped around it were both
|
|
23
|
+
* no-ops and every subscriber address sat in cleartext — with a perfectly good
|
|
24
|
+
* `emailIndex` beside it.
|
|
25
|
+
*
|
|
26
|
+
* Safe to enable because the lookup path never depended on the plaintext:
|
|
27
|
+
* `emailIndex` is written explicitly on create and queried first on read, with
|
|
28
|
+
* a plaintext-equality fallback for rows written before this, and
|
|
29
|
+
* `decryptNewsletterDoc` already covers all eight read sites. `encryptPiiFields`
|
|
30
|
+
* derives the same `emailIndex` name, so the two agree rather than conflict.
|
|
31
|
+
*/
|
|
32
|
+
const NEWSLETTER_PII_FIELDS = ["email"];
|
|
20
33
|
function resolveRepository() {
|
|
21
34
|
const provider = getProviders().db;
|
|
22
35
|
if (!provider) {
|
|
@@ -25,7 +25,7 @@ export async function FAQPageView({ category, faqs = [], } = {}) {
|
|
|
25
25
|
: [];
|
|
26
26
|
const visibleFaqs = (category ? faqs.filter((f) => f.category === category) : faqs).map(toDisplayFaq);
|
|
27
27
|
const activeCategory = categories.find((c) => c.slug === category);
|
|
28
|
-
return (_jsxs(Div, { className: "-mx-4 md:-mx-6 lg:-mx-8 -mt-6 sm:-mt-8 lg:-mt-10", children: [_jsx(Section, { color: "inverse", tone: "accent-banner", padding: "banner", children: _jsxs(Container, { size: "md", className: "text-center", children: [_jsx(Heading, { color: "inverse", level: 1, variant: "none", className: "mb-3", children: activeCategory ? activeCategory.label : t("title") }), _jsx(Text, { color: "inverse", variant: "none", className: "/80 max-w-2xl mx-auto", children: t("subtitle") })] }) }), _jsx(Container, { size: "md", padding: "content-banner", children: _jsxs(Stack, { direction: "md-row", gap: "xl", children: [categories.length > 0 && (_jsxs(Aside, { className: "md:w-56 flex-shrink-0", children: [_jsx(Heading, { level: 3, className: "uppercase tracking-wide mb-3", color: "muted", size: "sm", weight: "semibold", children: t("categoriesLabel") }), _jsxs(Nav, { "aria-label": "FAQ categories", spacing: "xs", children: [_jsxs(TextLink, { rounded: "lg", href: String(ROUTES.PUBLIC.FAQS), paddingX: "sm", paddingY: "xs", layout: "flex", align: "center", justify: "between", weight: !category ? "semibold" : undefined, className: `transition-colors ${!category ? "bg-primary/10 text-primary" : "hover:bg-
|
|
28
|
+
return (_jsxs(Div, { className: "-mx-4 md:-mx-6 lg:-mx-8 -mt-6 sm:-mt-8 lg:-mt-10", children: [_jsx(Section, { color: "inverse", tone: "accent-banner", padding: "banner", children: _jsxs(Container, { size: "md", className: "text-center", children: [_jsx(Heading, { color: "inverse", level: 1, variant: "none", className: "mb-3", children: activeCategory ? activeCategory.label : t("title") }), _jsx(Text, { color: "inverse", variant: "none", className: "/80 max-w-2xl mx-auto", children: t("subtitle") })] }) }), _jsx(Container, { size: "md", padding: "content-banner", children: _jsxs(Stack, { direction: "md-row", gap: "xl", children: [categories.length > 0 && (_jsxs(Aside, { className: "md:w-56 flex-shrink-0", children: [_jsx(Heading, { level: 3, className: "uppercase tracking-wide mb-3", color: "muted", size: "sm", weight: "semibold", children: t("categoriesLabel") }), _jsxs(Nav, { "aria-label": "FAQ categories", spacing: "xs", children: [_jsxs(TextLink, { rounded: "lg", href: String(ROUTES.PUBLIC.FAQS), paddingX: "sm", paddingY: "xs", layout: "flex", align: "center", justify: "between", weight: !category ? "semibold" : undefined, className: `transition-colors ${!category ? "bg-primary/10 text-primary" : "hover:bg-surface-hover"}`, size: "sm", children: [_jsxs(Span, { className: `${flex.row}`, gap: "md", children: [_jsx(HelpCircle, { className: "w-4 h-4" }), t("allCategories")] }), _jsx(ChevronRight, { className: "w-3.5 h-3.5 opacity-50" })] }), categories.map((cat) => (_jsxs(TextLink, { rounded: "lg", href: String(ROUTES.PUBLIC.FAQ_CATEGORY(cat.slug)), paddingX: "sm", paddingY: "xs", layout: "flex", align: "center", justify: "between", weight: category === cat.slug ? "semibold" : undefined, className: `transition-colors ${category === cat.slug ? "bg-primary/10 text-primary" : "hover:bg-surface-hover"}`, size: "sm", children: [_jsxs(Span, { className: `${flex.row}`, gap: "md", children: [_jsx(Span, { children: cat.icon }), cat.label] }), _jsx(ChevronRight, { className: "w-3.5 h-3.5 opacity-50" })] }, cat.slug)))] })] })), _jsxs(Div, { className: "flex-1 min-w-0", children: [visibleFaqs.length === 0 ? (_jsxs(Section, { className: `text-center`, border: "default", surface: "subtle", rounded: "2xl", padding: "y-4xl", children: [_jsx(HelpCircle, { className: "w-10 h-10 mx-auto mb-3 text-zinc-300 dark:text-[var(--appkit-color-text-muted)]" }), _jsx(Heading, { level: 3, className: "mb-2", size: "base", children: t("emptyTitle") }), _jsx(Text, { variant: "secondary", size: "sm", children: t("emptyText") })] })) : (_jsx(FAQSearchableList, { faqs: visibleFaqs, labels: {
|
|
29
29
|
searchPlaceholder: t("searchPlaceholder"),
|
|
30
30
|
noResults: t("searchNoResults"),
|
|
31
31
|
} })), _jsxs(Section, { className: `mt-10 text-center`, border: "default", surface: "subtle", rounded: "2xl", padding: "lg", children: [_jsx(Heading, { level: 3, className: "mb-2", size: "base", children: t("stillNeedHelpTitle") }), _jsx(Text, { variant: "secondary", className: "mb-4", size: "sm", children: t("stillNeedHelpText") }), _jsxs(Row, { align: "center", justify: "center", gap: "md", wrap: true, children: [_jsx(TextLink, { href: String(ROUTES.PUBLIC.HELP), children: t("helpCenter") }), _jsx(TextLink, { href: String(ROUTES.PUBLIC.CONTACT), variant: "muted", children: t("contactUs") })] })] })] })] }) })] }));
|
|
@@ -95,7 +95,7 @@ export async function FeesView({} = {}) {
|
|
|
95
95
|
return (_jsxs(Div, { className: "-mx-4 md:-mx-6 lg:-mx-8 -mt-6 sm:-mt-8 lg:-mt-10", children: [_jsx(Section, { color: "inverse", tone: "accent-banner", padding: "banner", children: _jsxs(Div, { className: `${page.container.sm} text-center`, children: [_jsx(Heading, { color: "inverse", level: 1, variant: "none", className: "mb-4", children: t("title") }), _jsx(Text, { color: "inverse", variant: "none", className: "/80 max-w-2xl mx-auto", children: t("subtitle") })] }) }), _jsxs(Stack, { gap: "section", className: `${page.container.sm}`, padding: "content-banner", children: [renderFeeTableSection(t, themed, FEE_ROWS), renderPayoutExampleSection(t, themed, OFFER_PAYOUT_ROWS), renderDisclaimerSection(t, themed)] })] }));
|
|
96
96
|
}
|
|
97
97
|
function renderFeeTableSection(t, themed, rows) {
|
|
98
|
-
return (_jsxs(Section, { children: [_jsx(Heading, { level: 2, className: "mb-6", children: t("tableTitle") }), _jsx(Div, { className: `${__O.xAuto}`, border: "default", rounded: "xl", children: _jsxs(Table, { size: "sm", children: [_jsx(Thead, { className: themed.bgSecondary, children: _jsxs(Tr, { children: [_jsx(Th, { className: "text-left", padding: "md", weight: "semibold", children: t("colFeeType") }), _jsx(Th, { className: "text-left", padding: "md", weight: "semibold", children: t("colRate") }), _jsx(Th, { className: "text-left", padding: "md", weight: "semibold", children: t("colPaidBy") }), _jsx(Th, { className: "text-left hidden md:table-cell", padding: "md", weight: "semibold", children: t("colNote") })] }) }), _jsx(Tbody, { className: "divide-y divide-neutral-100 divide-[var(--appkit-color-border)]", children: rows.map((row) => (_jsxs(Tr, { className: `${themed.bgPrimary} hover:bg-
|
|
98
|
+
return (_jsxs(Section, { children: [_jsx(Heading, { level: 2, className: "mb-6", children: t("tableTitle") }), _jsx(Div, { className: `${__O.xAuto}`, border: "default", rounded: "xl", children: _jsxs(Table, { size: "sm", children: [_jsx(Thead, { className: themed.bgSecondary, children: _jsxs(Tr, { children: [_jsx(Th, { className: "text-left", padding: "md", weight: "semibold", children: t("colFeeType") }), _jsx(Th, { className: "text-left", padding: "md", weight: "semibold", children: t("colRate") }), _jsx(Th, { className: "text-left", padding: "md", weight: "semibold", children: t("colPaidBy") }), _jsx(Th, { className: "text-left hidden md:table-cell", padding: "md", weight: "semibold", children: t("colNote") })] }) }), _jsx(Tbody, { className: "divide-y divide-neutral-100 divide-[var(--appkit-color-border)]", children: rows.map((row) => (_jsxs(Tr, { className: `${themed.bgPrimary} hover:bg-surface-hover`, children: [_jsx(Td, { weight: "medium", padding: "md", children: row.category }), _jsx(Td, { className: CLS_RATE_CELL, children: row.rate }), _jsx(Td, { padding: "md", children: _jsx(Caption, { children: row.who }) }), _jsx(Td, { className: "text-[var(--appkit-color-text-muted)] hidden md:table-cell", padding: "md", size: "xs", children: row.note })] }, row.category))) })] }) }), _jsx(Caption, { className: "mt-3 block", color: "muted", children: t("tableNote") })] }));
|
|
99
99
|
}
|
|
100
100
|
function renderPayoutExampleSection(t, themed, rows) {
|
|
101
101
|
return (_jsxs(Section, { children: [_jsx(Heading, { level: 2, className: "mb-3", children: t("payoutExampleTitle") }), _jsx(Text, { variant: "secondary", className: "mb-6", children: t("payoutExampleSubtitle") }), _jsxs(Div, { className: `${__P.p5} max-w-sm`, border: "default", surface: "muted", rounded: "xl", children: [_jsx(Heading, { level: 3, className: "mb-4", size: "base", children: t("payoutExampleProduct") }), _jsx(Stack, { gap: "sm", children: rows.map((row, i) => (_jsxs(Row, { textWeight: "bold", textSize: "sm", className: `${row.highlight ? "border-t border-[var(--appkit-color-border)] mt-2" : ""}`, paddingY: row.highlight ? "t-xs" : undefined, justify: "between", children: [_jsx(Text, { className: row.highlight ? "font-bold" : "text-[var(--appkit-color-text-muted)]", children: row.label }), _jsx(Text, { className: row.highlight ? CLS_HIGHLIGHT : "", children: row.example })] }, i))) })] })] }));
|
|
@@ -5,6 +5,7 @@ export interface AddressFormLabels {
|
|
|
5
5
|
phone: string;
|
|
6
6
|
addressLine1: string;
|
|
7
7
|
addressLine2: string;
|
|
8
|
+
landmark: string;
|
|
8
9
|
city: string;
|
|
9
10
|
state: string;
|
|
10
11
|
postalCode: string;
|
|
@@ -20,6 +21,7 @@ export interface AddressFormPlaceholders {
|
|
|
20
21
|
phone: string;
|
|
21
22
|
addressLine1: string;
|
|
22
23
|
addressLine2: string;
|
|
24
|
+
landmark: string;
|
|
23
25
|
city: string;
|
|
24
26
|
state: string;
|
|
25
27
|
postalCode: string;
|
|
@@ -15,6 +15,7 @@ const addressFormSchema = z.object({
|
|
|
15
15
|
phone: z.string().min(1, "Phone number is required"),
|
|
16
16
|
addressLine1: z.string().min(1, "Address is required"),
|
|
17
17
|
addressLine2: z.string().optional(),
|
|
18
|
+
landmark: z.string().max(100, "Keep the landmark under 100 characters.").optional(),
|
|
18
19
|
city: z.string().min(1, "City is required"),
|
|
19
20
|
state: z.string().min(1, "State is required"),
|
|
20
21
|
postalCode: z.string().min(1, "Postal code is required"),
|
|
@@ -27,6 +28,7 @@ const DEFAULT_LABELS = {
|
|
|
27
28
|
phone: "Phone Number",
|
|
28
29
|
addressLine1: "Address Line 1",
|
|
29
30
|
addressLine2: "Address Line 2",
|
|
31
|
+
landmark: "Landmark",
|
|
30
32
|
city: "City",
|
|
31
33
|
state: "State",
|
|
32
34
|
postalCode: "Postal Code",
|
|
@@ -42,6 +44,7 @@ const DEFAULT_PLACEHOLDERS = {
|
|
|
42
44
|
phone: "Enter phone number",
|
|
43
45
|
addressLine1: "Street address, P.O. box",
|
|
44
46
|
addressLine2: "Apartment, suite, unit, building, floor, etc.",
|
|
47
|
+
landmark: "Opposite the Metro station, next to the temple, etc.",
|
|
45
48
|
city: "City",
|
|
46
49
|
state: "State/Province",
|
|
47
50
|
postalCode: "Postal/ZIP code",
|
|
@@ -58,6 +61,7 @@ export function AddressForm({ initialData, onSubmit, onCancel, isLoading = false
|
|
|
58
61
|
phone: initialData?.phone || "",
|
|
59
62
|
addressLine1: initialData?.addressLine1 || "",
|
|
60
63
|
addressLine2: initialData?.addressLine2 || "",
|
|
64
|
+
landmark: initialData?.landmark || "",
|
|
61
65
|
city: initialData?.city || "",
|
|
62
66
|
state: initialData?.state || "",
|
|
63
67
|
postalCode: initialData?.postalCode || "",
|
|
@@ -114,5 +118,5 @@ export function AddressForm({ initialData, onSubmit, onCancel, isLoading = false
|
|
|
114
118
|
submitLabel: effectiveSubmitLabel,
|
|
115
119
|
cancelLabel: mergedLabels.cancel,
|
|
116
120
|
isLoading,
|
|
117
|
-
}, children: ({ setFieldError, clearErrors }) => (_jsxs(_Fragment, { children: [_jsx(FormField, { label: mergedLabels.label, name: "label", type: "text", value: formData.label, onChange: (value) => handleChange("label", value), placeholder: mergedPlaceholders.label, required: true }), _jsx(FormField, { label: mergedLabels.fullName, name: "fullName", type: "text", value: formData.fullName, onChange: (value) => handleChange("fullName", value), placeholder: mergedPlaceholders.fullName, required: true }), _jsx(FormField, { label: mergedLabels.phone, name: "phone", type: "tel", value: formData.phone, onChange: (value) => handleChange("phone", value), placeholder: mergedPlaceholders.phone, required: true }), _jsx(FormField, { label: mergedLabels.addressLine1, name: "addressLine1", type: "text", value: formData.addressLine1, onChange: (value) => handleChange("addressLine1", value), placeholder: mergedPlaceholders.addressLine1, required: true }), _jsx(FormField, { label: mergedLabels.addressLine2, name: "addressLine2", type: "text", value: formData.addressLine2, onChange: (value) => handleChange("addressLine2", value), placeholder: mergedPlaceholders.addressLine2 }), _jsxs(FormGroup, { columns: 3, children: [_jsx(FormField, { label: mergedLabels.city, name: "city", type: "text", value: formData.city, onChange: (value) => handleChange("city", value), placeholder: mergedPlaceholders.city, required: true }), _jsx(FormField, { label: mergedLabels.state, name: "state", type: "text", value: formData.state, onChange: (value) => handleChange("state", value), placeholder: mergedPlaceholders.state, required: true }), _jsx(FormField, { label: mergedLabels.postalCode, name: "postalCode", type: "text", value: formData.postalCode, onChange: (value) => handleChange("postalCode", value), placeholder: mergedPlaceholders.postalCode, required: true })] }), _jsx(FormField, { label: mergedLabels.country, name: "country", type: "text", value: formData.country, onChange: (value) => handleChange("country", value), placeholder: mergedPlaceholders.country, required: true }), _jsx(Checkbox, { checked: formData.isDefault, onChange: (e) => handleChange("isDefault", e.target.checked), label: mergedLabels.setDefault }), _jsx(FormErrorSummary, {}), _jsxs(Row, { padding: "t-xs", align: "center", justify: "start", gap: "xs", children: [_jsx(Button, { type: "button", variant: "outline", onClick: onCancel, disabled: isLoading, children: mergedLabels.cancel }), _jsx(Button, { type: "submit", variant: "primary", disabled: isLoading, isLoading: isLoading, onClick: () => void submitAddress({ setFieldError, clearErrors }), children: isLoading ? mergedLabels.loading : effectiveSubmitLabel })] })] })) }));
|
|
121
|
+
}, children: ({ setFieldError, clearErrors }) => (_jsxs(_Fragment, { children: [_jsx(FormField, { label: mergedLabels.label, name: "label", type: "text", value: formData.label, onChange: (value) => handleChange("label", value), placeholder: mergedPlaceholders.label, required: true }), _jsx(FormField, { label: mergedLabels.fullName, name: "fullName", type: "text", value: formData.fullName, onChange: (value) => handleChange("fullName", value), placeholder: mergedPlaceholders.fullName, required: true }), _jsx(FormField, { label: mergedLabels.phone, name: "phone", type: "tel", value: formData.phone, onChange: (value) => handleChange("phone", value), placeholder: mergedPlaceholders.phone, required: true }), _jsx(FormField, { label: mergedLabels.addressLine1, name: "addressLine1", type: "text", value: formData.addressLine1, onChange: (value) => handleChange("addressLine1", value), placeholder: mergedPlaceholders.addressLine1, required: true }), _jsx(FormField, { label: mergedLabels.addressLine2, name: "addressLine2", type: "text", value: formData.addressLine2, onChange: (value) => handleChange("addressLine2", value), placeholder: mergedPlaceholders.addressLine2 }), _jsx(FormField, { label: mergedLabels.landmark, name: "landmark", type: "text", value: formData.landmark ?? "", onChange: (value) => handleChange("landmark", value), placeholder: mergedPlaceholders.landmark }), _jsxs(FormGroup, { columns: 3, children: [_jsx(FormField, { label: mergedLabels.city, name: "city", type: "text", value: formData.city, onChange: (value) => handleChange("city", value), placeholder: mergedPlaceholders.city, required: true }), _jsx(FormField, { label: mergedLabels.state, name: "state", type: "text", value: formData.state, onChange: (value) => handleChange("state", value), placeholder: mergedPlaceholders.state, required: true }), _jsx(FormField, { label: mergedLabels.postalCode, name: "postalCode", type: "text", value: formData.postalCode, onChange: (value) => handleChange("postalCode", value), placeholder: mergedPlaceholders.postalCode, required: true })] }), _jsx(FormField, { label: mergedLabels.country, name: "country", type: "text", value: formData.country, onChange: (value) => handleChange("country", value), placeholder: mergedPlaceholders.country, required: true }), _jsx(Checkbox, { checked: formData.isDefault, onChange: (e) => handleChange("isDefault", e.target.checked), label: mergedLabels.setDefault }), _jsx(FormErrorSummary, {}), _jsxs(Row, { padding: "t-xs", align: "center", justify: "start", gap: "xs", children: [_jsx(Button, { type: "button", variant: "outline", onClick: onCancel, disabled: isLoading, children: mergedLabels.cancel }), _jsx(Button, { type: "submit", variant: "primary", disabled: isLoading, isLoading: isLoading, onClick: () => void submitAddress({ setFieldError, clearErrors }), children: isLoading ? mergedLabels.loading : effectiveSubmitLabel })] })] })) }));
|
|
118
122
|
}
|
|
@@ -51,5 +51,5 @@ export function UserBidsView() {
|
|
|
51
51
|
},
|
|
52
52
|
};
|
|
53
53
|
const badge = detail ? bidStatusBadge(detail.status) : null;
|
|
54
|
-
return (_jsxs(_Fragment, { children: [_jsx(DataListingView, { config: config }), _jsx(RecordDetailModal, { isOpen: detail !== null, onClose: () => setDetail(null), title: detail?.productTitle || "Bid", badges: badge ? [{ label: badge.label }] : undefined, fields: detail ? buildBidDetailFields(detail, "buyer") : undefined, footer: detail ? (_jsx(TextLink, { href: String(ROUTES.PUBLIC.AUCTION_DETAIL(detail.productId)), children: "View auction \u2192" })) : undefined })] }));
|
|
54
|
+
return (_jsxs(_Fragment, { children: [_jsx(DataListingView, { config: config }), _jsx(RecordDetailModal, { isOpen: detail !== null, onClose: () => setDetail(null), title: detail?.productTitle || "Bid", badges: badge ? [{ label: badge.label }] : undefined, fields: detail ? buildBidDetailFields(detail, "buyer") : undefined, footer: detail ? (_jsxs(_Fragment, { children: [_jsx(TextLink, { href: String(ROUTES.USER.BID_DETAIL(detail.id)), children: "Open full page \u2192" }), _jsx(TextLink, { href: String(ROUTES.PUBLIC.AUCTION_DETAIL(detail.productId)), children: "View auction \u2192" })] })) : undefined })] }));
|
|
55
55
|
}
|
|
@@ -27,7 +27,7 @@ function NavLink({ item, isActive, onClick }) {
|
|
|
27
27
|
};
|
|
28
28
|
const linkClass = `flex items-center justify-end gap-[var(--appkit-space-2-5)] rounded-lg px-[var(--appkit-space-3)] py-[var(--appkit-space-2)] text-[0.8125rem] font-medium leading-tight transition-colors ${isActive
|
|
29
29
|
? "bg-primary-50 dark:bg-primary-900/25 text-primary-700 dark:text-primary-300"
|
|
30
|
-
: "text-[var(--appkit-color-text-muted)] hover:bg-
|
|
30
|
+
: "text-[var(--appkit-color-text-muted)] hover:bg-surface-hover hover:text-zinc-900 dark:hover:text-zinc-100"}`;
|
|
31
31
|
return (_jsxs(_Fragment, { children: [_jsxs(Link, { href: item.href, onClick: handleClick, className: linkClass, children: [item.icon && _jsx(Span, { size: "base", className: "shrink-0 opacity-70", children: item.icon }), _jsx(Span, { className: "truncate", children: item.label })] }), showConfirm && item.confirm && (_jsx(ConfirmDeleteModal, { isOpen: true, title: item.confirm.title ?? "Confirm", message: item.confirm.message, confirmText: "Continue", variant: "warning", onConfirm: () => {
|
|
32
32
|
setShowConfirm(false);
|
|
33
33
|
onClick?.();
|
|
@@ -7,6 +7,8 @@ import { Button, Div, FormActions, Row, Select, SideDrawer, Span, Stack, Text, U
|
|
|
7
7
|
import { FieldInput } from "../../../ui/forms/FieldInput";
|
|
8
8
|
import { FieldTextarea } from "../../../ui/forms/FieldTextarea";
|
|
9
9
|
import { apiClient } from "../../../http";
|
|
10
|
+
import { supportTicketCreateSchema } from "../../support/schemas/ticket-create-form";
|
|
11
|
+
import { ValidationError } from "../../../errors/validation-error";
|
|
10
12
|
import { SUPPORT_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
11
13
|
const __P = {
|
|
12
14
|
p3: "p-[var(--appkit-space-3)]",
|
|
@@ -33,7 +35,7 @@ const CATEGORY_OPTIONS = [
|
|
|
33
35
|
{ label: "Appeal a ban (unban request)", value: "unban_request" },
|
|
34
36
|
{ label: "General", value: "general" },
|
|
35
37
|
];
|
|
36
|
-
const CLS_MSG_USER = "
|
|
38
|
+
const CLS_MSG_USER = "border bg-[var(--appkit-color-surface)]/40 border-[var(--appkit-color-border)]";
|
|
37
39
|
const CLS_MSG_STAFF = "bg-info-surface border border-info dark:bg-info-surface dark:border-info";
|
|
38
40
|
const STATUS_BADGE = {
|
|
39
41
|
open: "bg-info-surface text-info",
|
|
@@ -68,12 +70,32 @@ export function UserSupportView(_props) {
|
|
|
68
70
|
const invalidate = () => queryClient.invalidateQueries({ queryKey: ["user", "support-tickets"] });
|
|
69
71
|
const createMutation = useApiMutation({
|
|
70
72
|
mutationFn: async () => {
|
|
71
|
-
|
|
73
|
+
/*
|
|
74
|
+
* This drawer validated NOTHING. Its sibling page
|
|
75
|
+
* (`/user/support/new`) hand-rolled a `canSubmit` boolean that required
|
|
76
|
+
* an order id for an `order_issue` ticket — a real rule, since an order
|
|
77
|
+
* complaint with no order is unactionable — and the route did not
|
|
78
|
+
* enforce it either. So the same ticket was acceptable or not purely
|
|
79
|
+
* depending on which surface the user happened to open.
|
|
80
|
+
*
|
|
81
|
+
* One schema now, across both surfaces and the route.
|
|
82
|
+
*/
|
|
83
|
+
const parsed = supportTicketCreateSchema.safeParse({
|
|
72
84
|
subject: newSubject.trim(),
|
|
73
85
|
category: newCategory,
|
|
74
86
|
description: newDescription.trim(),
|
|
75
87
|
orderId: newOrderId.trim() || undefined,
|
|
76
88
|
});
|
|
89
|
+
if (!parsed.success) {
|
|
90
|
+
/*
|
|
91
|
+
* ValidationError, not a bare Error — it carries the issue LIST, so
|
|
92
|
+
* the failure keeps which field caused it. A plain Error flattens the
|
|
93
|
+
* whole thing to one string, which is what `audit-unvalidated-safeparse`
|
|
94
|
+
* exists to stop. It caught this in my own code.
|
|
95
|
+
*/
|
|
96
|
+
throw new ValidationError(parsed.error.issues[0]?.message ?? "Check the ticket details.", parsed.error.issues);
|
|
97
|
+
}
|
|
98
|
+
await apiClient.post(SUPPORT_ENDPOINTS.TICKETS, parsed.data);
|
|
77
99
|
},
|
|
78
100
|
onSuccess: () => {
|
|
79
101
|
showToast("Support ticket created.", "success");
|
|
@@ -9,6 +9,18 @@ export interface Address {
|
|
|
9
9
|
state: string;
|
|
10
10
|
postalCode: string;
|
|
11
11
|
country: string;
|
|
12
|
+
/**
|
|
13
|
+
* A nearby reference point ("opposite the Metro station").
|
|
14
|
+
*
|
|
15
|
+
* Store addresses have always accepted it — `POST /api/store/addresses`
|
|
16
|
+
* declares `landmark: z.string().max(100).optional()` and
|
|
17
|
+
* `SellerAddressesView`'s drawer collects it — while this shape did not.
|
|
18
|
+
* So an address created in that drawer WITH a landmark, then edited through
|
|
19
|
+
* any surface built on `AddressForm`, was saved back with `landmark`
|
|
20
|
+
* absent and lost it. Added here, on the form, and on the `Address` type so
|
|
21
|
+
* the round trip is lossless.
|
|
22
|
+
*/
|
|
23
|
+
landmark?: string;
|
|
12
24
|
isDefault?: boolean;
|
|
13
25
|
}
|
|
14
26
|
export interface AddressFormData {
|
|
@@ -21,6 +33,8 @@ export interface AddressFormData {
|
|
|
21
33
|
state: string;
|
|
22
34
|
postalCode: string;
|
|
23
35
|
country: string;
|
|
36
|
+
/** See Address.landmark — the form must carry it or an edit drops it. */
|
|
37
|
+
landmark?: string;
|
|
24
38
|
isDefault?: boolean;
|
|
25
39
|
}
|
|
26
40
|
interface SetDefaultAddressData {
|
|
@@ -46,8 +60,22 @@ export declare function useCreateAddress(options?: {
|
|
|
46
60
|
onSuccess?: (data: Address) => void;
|
|
47
61
|
onError?: (error: Error) => void;
|
|
48
62
|
}): import("@tanstack/react-query").UseMutationResult<Address, Error, AddressFormData, unknown>;
|
|
49
|
-
|
|
63
|
+
/**
|
|
64
|
+
* Update options.
|
|
65
|
+
*
|
|
66
|
+
* `method` exists because the two portals disagree: `/api/user/addresses/[id]`
|
|
67
|
+
* exports PATCH and `/api/store/addresses/[id]` exports PUT, and
|
|
68
|
+
* `SellerAddressesView` hardcodes PUT against the latter. Changing the route's
|
|
69
|
+
* verb would break that working call site, so the hook bends instead.
|
|
70
|
+
*
|
|
71
|
+
* Caught by `audit-client-verb-match` before the store pages were written —
|
|
72
|
+
* this hook would have 405'd the moment they reused it.
|
|
73
|
+
*/
|
|
74
|
+
export interface UpdateAddressOptions {
|
|
50
75
|
byIdEndpoint?: (id: string) => string;
|
|
76
|
+
method?: "PUT" | "PATCH";
|
|
77
|
+
}
|
|
78
|
+
export declare function useUpdateAddress(id: string, options?: UpdateAddressOptions & {
|
|
51
79
|
onSuccess?: (data: Address) => void;
|
|
52
80
|
onError?: (error: Error) => void;
|
|
53
81
|
}): import("@tanstack/react-query").UseMutationResult<Address, Error, AddressFormData, unknown>;
|
|
@@ -48,8 +48,11 @@ export function useCreateAddress(options) {
|
|
|
48
48
|
export function useUpdateAddress(id, options) {
|
|
49
49
|
const queryClient = useQueryClient();
|
|
50
50
|
const byIdEndpoint = options?.byIdEndpoint ?? DEFAULT_ENDPOINTS.byId;
|
|
51
|
+
const method = options?.method ?? "PATCH";
|
|
51
52
|
return useMutation({
|
|
52
|
-
mutationFn: (data) =>
|
|
53
|
+
mutationFn: (data) => method === "PUT"
|
|
54
|
+
? apiClient.put(byIdEndpoint(id), data)
|
|
55
|
+
: apiClient.patch(byIdEndpoint(id), data),
|
|
53
56
|
onSuccess: (data) => {
|
|
54
57
|
queryClient.invalidateQueries({ queryKey: ["addresses"] });
|
|
55
58
|
options?.onSuccess?.(data);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useApiMutation } from "
|
|
1
|
+
import { useApiMutation } from "../../../client/api/useApiMutation";
|
|
2
2
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|
3
3
|
import { apiClient } from "../../../http";
|
|
4
4
|
import { ACCOUNT_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
@@ -11,6 +11,19 @@ export declare class AddressesRepository extends BaseRepository<AddressDocument>
|
|
|
11
11
|
listByOwnerType(ownerType: AddressOwnerType, limit?: number): Promise<AddressDocument[]>;
|
|
12
12
|
countByOwner(ownerType: AddressOwnerType, ownerId: string): Promise<number>;
|
|
13
13
|
createForOwner(ownerType: AddressOwnerType, ownerId: string, input: AddressCreateInput): Promise<AddressDocument>;
|
|
14
|
+
/**
|
|
15
|
+
* Read one address, scoped to its owner.
|
|
16
|
+
*
|
|
17
|
+
* The owner check is `ownerType` AND `ownerId` — the same pair
|
|
18
|
+
* `updateForOwner` and `deleteForOwner` use, because `addresses` is one
|
|
19
|
+
* top-level collection holding both buyer and store addresses discriminated
|
|
20
|
+
* by `ownerType` (SB-UNI-A). Checking only `ownerId` would let a seller read
|
|
21
|
+
* their own personal address through the store endpoint and vice versa.
|
|
22
|
+
*
|
|
23
|
+
* Returns null rather than throwing: the caller turns it into a 404, and a
|
|
24
|
+
* missing address and a foreign one must be indistinguishable to the client.
|
|
25
|
+
*/
|
|
26
|
+
getForOwner(ownerType: AddressOwnerType, ownerId: string, addressId: string): Promise<AddressDocument | null>;
|
|
14
27
|
updateForOwner(ownerType: AddressOwnerType, ownerId: string, addressId: string, input: AddressUpdateInput): Promise<AddressDocument>;
|
|
15
28
|
deleteForOwner(ownerType: AddressOwnerType, ownerId: string, addressId: string): Promise<void>;
|
|
16
29
|
setDefault(ownerType: AddressOwnerType, ownerId: string, addressId: string): Promise<AddressDocument>;
|
|
@@ -115,6 +115,25 @@ export class AddressesRepository extends BaseRepository {
|
|
|
115
115
|
throw new DatabaseError(`Failed to create address for ${ownerType}:${ownerId}`, error);
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* Read one address, scoped to its owner.
|
|
120
|
+
*
|
|
121
|
+
* The owner check is `ownerType` AND `ownerId` — the same pair
|
|
122
|
+
* `updateForOwner` and `deleteForOwner` use, because `addresses` is one
|
|
123
|
+
* top-level collection holding both buyer and store addresses discriminated
|
|
124
|
+
* by `ownerType` (SB-UNI-A). Checking only `ownerId` would let a seller read
|
|
125
|
+
* their own personal address through the store endpoint and vice versa.
|
|
126
|
+
*
|
|
127
|
+
* Returns null rather than throwing: the caller turns it into a 404, and a
|
|
128
|
+
* missing address and a foreign one must be indistinguishable to the client.
|
|
129
|
+
*/
|
|
130
|
+
async getForOwner(ownerType, ownerId, addressId) {
|
|
131
|
+
const existing = await this.findById(addressId);
|
|
132
|
+
if (!existing || existing.ownerType !== ownerType || existing.ownerId !== ownerId) {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
return existing;
|
|
136
|
+
}
|
|
118
137
|
async updateForOwner(ownerType, ownerId, addressId, input) {
|
|
119
138
|
const existing = await this.findById(addressId);
|
|
120
139
|
if (!existing || existing.ownerType !== ownerType || existing.ownerId !== ownerId) {
|
|
@@ -14,6 +14,7 @@ import { getAdminRealtimeDb } from "../../../providers/db-firebase/index";
|
|
|
14
14
|
import { AuthorizationError, NotFoundError, ValidationError, } from "../../../errors/index";
|
|
15
15
|
import { ERROR_MESSAGES } from "../../../errors/messages";
|
|
16
16
|
import { serverLogger } from "../../../monitoring/index";
|
|
17
|
+
import { normalizeError } from "../../../errors/normalize";
|
|
17
18
|
export async function getChatRooms(userId, chatEnabled) {
|
|
18
19
|
if (!chatEnabled)
|
|
19
20
|
throw new AuthorizationError("Chat is temporarily unavailable.");
|
|
@@ -77,7 +78,10 @@ export async function sendChatMessage(userId, chatId, message) {
|
|
|
77
78
|
: (room.buyerName ?? "Member");
|
|
78
79
|
await msgRef.set({ userId, userName, message, timestamp: Date.now() });
|
|
79
80
|
chatRepository.updateLastMessage(chatId, message).catch((err) => {
|
|
80
|
-
serverLogger.warn("Failed to update chat lastMessage", {
|
|
81
|
+
serverLogger.warn("Failed to update chat lastMessage", {
|
|
82
|
+
chatId,
|
|
83
|
+
error: normalizeError(err).message,
|
|
84
|
+
});
|
|
81
85
|
});
|
|
82
86
|
serverLogger.debug("sendChatMessage", { userId, chatId });
|
|
83
87
|
return { messageId: id, timestamp: Date.now() };
|
|
@@ -116,7 +116,7 @@ export function AdminAdsView({ endpoint = ADMIN_ENDPOINTS.ADS, labels = {}, crea
|
|
|
116
116
|
render: (row) => {
|
|
117
117
|
const nextStatus = row.status === "active" ? "paused" : "active";
|
|
118
118
|
const cannotPublish = nextStatus === "active" && row.publishReady === false;
|
|
119
|
-
return (_jsxs(Row, { align: "center", gap: "sm", children: [renderEditLink ? renderEditLink(row) : (_jsx(TextLink, { variant: "bare", href: `/admin/ads/${row.id}/edit`, rounded: "md", paddingX: "sm", size: "xs", layout: "inline-flex", align: "center", className: "h-8 border border-
|
|
119
|
+
return (_jsxs(Row, { align: "center", gap: "sm", children: [renderEditLink ? renderEditLink(row) : (_jsx(TextLink, { variant: "bare", href: `/admin/ads/${row.id}/edit`, rounded: "md", paddingX: "sm", size: "xs", layout: "inline-flex", align: "center", className: "h-8 border border-[var(--appkit-color-border)]", children: "Edit" })), _jsx(Button, { size: "sm", variant: "outline", disabled: statusMutation.isPending || cannotPublish, onClick: () => statusMutation.mutate({ id: row.id, nextStatus }), children: row.status === "active" ? "Pause" : "Publish" })] }));
|
|
120
120
|
},
|
|
121
121
|
},
|
|
122
122
|
];
|