@mohasinac/appkit 4.64.1 → 4.65.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/.build-stamp.json +3 -3
- package/dist/_internal/server/features/checkout/actions.js +18 -1
- package/dist/_internal/server/features/search/index-build/sources.js +27 -12
- package/dist/_internal/server/features/seo/sitemap.js +78 -12
- package/dist/_internal/server/features/site-settings/adapters.d.ts +1 -1
- package/dist/_internal/server/features/site-settings/adapters.js +16 -1
- package/dist/_internal/server/features/stores/adapters.d.ts +1 -1
- package/dist/_internal/server/features/stores/adapters.js +14 -0
- package/dist/_internal/server/features/stores/tabs.d.ts +19 -15
- package/dist/_internal/server/features/stores/tabs.js +20 -21
- package/dist/_internal/server/jobs/core/analyticsAlertEvaluate.js +9 -4
- package/dist/_internal/server/jobs/core/categoryTaxonomyBackfill.js +22 -0
- package/dist/_internal/server/jobs/core/countersReconcile.js +0 -1
- package/dist/_internal/server/jobs/handlers/onCatalogueSubmittedForApproval.js +1 -1
- package/dist/_internal/shared/features/site-settings/types.d.ts +10 -2
- package/dist/_internal/shared/features/sticker-trace/trace/segment.js +13 -2
- package/dist/_internal/shared/listing-types/feature-flags.d.ts +54 -0
- package/dist/_internal/shared/listing-types/feature-flags.js +72 -0
- package/dist/client.d.ts +5 -2
- package/dist/client.js +8 -1
- package/dist/constants/field-names.d.ts +1 -0
- package/dist/constants/field-names.js +14 -0
- package/dist/features/account/api/[userId]/addresses/route.js +1 -5
- package/dist/features/addresses/repository/addresses.repository.js +1 -1
- package/dist/features/admin/api/bids/route.js +1 -5
- package/dist/features/admin/api/coupons/route.js +1 -5
- package/dist/features/admin/api/products/route.js +1 -5
- package/dist/features/admin/api/reviews/route.js +1 -5
- package/dist/features/admin/components/AdminCarouselEditorView.js +0 -1
- package/dist/features/admin/components/AdminFeatureEditorView.js +1 -2
- package/dist/features/admin/components/AdminGuideHubView.js +1 -1
- package/dist/features/admin/components/AdminProductEditorView.js +4 -1
- package/dist/features/admin/components/AdminSiteSettingsView.js +19 -3
- package/dist/features/admin/components/OwnerQuickActions.d.ts +7 -1
- package/dist/features/admin/components/OwnerQuickActions.js +1 -1
- package/dist/features/admin/schemas/admin-editor-forms.js +0 -1
- package/dist/features/admin/schemas/firestore.d.ts +52 -0
- package/dist/features/admin/schemas/firestore.js +20 -0
- package/dist/features/auctions/api/route.js +1 -8
- package/dist/features/auctions/components/AuctionDetailPageView.js +1 -7
- package/dist/features/auth/hooks/useAuth.js +1 -1
- package/dist/features/before-after/api/route.js +1 -5
- package/dist/features/blog/api/route.js +1 -8
- package/dist/features/blog/components/BlogIndexPageView.d.ts +2 -3
- package/dist/features/blog/components/BlogIndexPageView.js +6 -9
- package/dist/features/cart/actions/cart-actions.js +1 -1
- package/dist/features/cart/api/route.js +1 -5
- package/dist/features/cart/utils/guest-cart.js +1 -5
- package/dist/features/catalogue/repository/catalogue.repository.js +1 -1
- package/dist/features/categories/api/route.js +5 -23
- package/dist/features/categories/components/BundlesListView.d.ts +2 -3
- package/dist/features/categories/components/BundlesListView.js +2 -5
- package/dist/features/categories/components/CategoriesIndexListing.js +47 -5
- package/dist/features/categories/components/CategoriesIndexPageView.d.ts +2 -3
- package/dist/features/categories/components/CategoriesIndexPageView.js +0 -32
- package/dist/features/categories/constants/category-tabs.d.ts +1 -1
- package/dist/features/categories/constants/category-tabs.js +7 -0
- package/dist/features/categories/hooks/useCategories.d.ts +6 -0
- package/dist/features/categories/hooks/useCategories.js +73 -2
- package/dist/features/categories/types/index.d.ts +1 -1
- package/dist/features/classified/components/ClassifiedDetailPageView.js +2 -8
- package/dist/features/collections/api/route.js +1 -5
- package/dist/features/consultation/api/route.js +1 -5
- package/dist/features/contact/email.js +2 -5
- package/dist/features/corporate/api/route.js +1 -5
- package/dist/features/digital-codes/components/DigitalCodeDetailPageView.js +1 -7
- package/dist/features/events/components/AdminEventEditorView.js +1 -1
- package/dist/features/events/components/EventsListPageView.d.ts +2 -3
- package/dist/features/events/components/EventsListPageView.js +8 -11
- package/dist/features/faq/constants/help-guides.d.ts +8 -5
- package/dist/features/faq/constants/help-guides.js +9 -5
- package/dist/features/filters/AsyncFacetSection.js +2 -2
- package/dist/features/filters/FilterFacetSection.js +5 -5
- package/dist/features/filters/RangeFilter.js +2 -2
- package/dist/features/filters/SwitchFilter.js +2 -2
- package/dist/features/filters/filterUtils.d.ts +0 -2
- package/dist/features/filters/filterUtils.js +0 -4
- package/dist/features/filters/index.d.ts +1 -1
- package/dist/features/filters/index.js +1 -1
- package/dist/features/forms/Checkbox.js +4 -3
- package/dist/features/forms/Input.js +5 -4
- package/dist/features/forms/Radio.js +6 -5
- package/dist/features/forms/Select.js +3 -2
- package/dist/features/forms/Slider.js +3 -2
- package/dist/features/forms/Textarea.js +3 -2
- package/dist/features/forms/Toggle.js +3 -3
- package/dist/features/forms/index.d.ts +1 -1
- package/dist/features/forms/index.js +1 -1
- package/dist/features/forms/utils.d.ts +0 -2
- package/dist/features/forms/utils.js +0 -4
- package/dist/features/grouped/schemas/grouped-listing-form.d.ts +6 -6
- package/dist/features/grouped/schemas/index.d.ts +2 -2
- package/dist/features/history/utils/guest-history.js +1 -5
- package/dist/features/homepage/components/CustomCardsSection.js +8 -2
- package/dist/features/homepage/components/FeaturedBundlesSection.d.ts +6 -1
- package/dist/features/homepage/components/FeaturedBundlesSection.js +1 -1
- package/dist/features/layout/AppLayoutShell.d.ts +23 -1
- package/dist/features/layout/AppLayoutShell.js +13 -12
- package/dist/features/layout/BottomNavItem.d.ts +9 -1
- package/dist/features/layout/BottomNavItem.js +18 -4
- package/dist/features/layout/index.d.ts +1 -1
- package/dist/features/live/components/LiveItemDetailPageView.js +1 -7
- package/dist/features/media/MediaLightbox.js +3 -5
- package/dist/features/orders/components/OrderCompletionPanel.d.ts +1 -1
- package/dist/features/orders/components/OrderCompletionPanel.js +1 -1
- package/dist/features/orders/components/RaiseDisputeForm.js +1 -1
- package/dist/features/orders/components/RefundRequestView.js +1 -1
- package/dist/features/parts/schemas/firestore.d.ts +274 -0
- package/dist/features/parts/schemas/firestore.js +19 -0
- package/dist/features/payments/repository/saved-payment-methods.repository.js +1 -1
- package/dist/features/pre-orders/api/admin/route.js +1 -5
- package/dist/features/pre-orders/components/PreOrderDetailPageView.js +1 -7
- package/dist/features/products/api/route.js +1 -3
- package/dist/features/products/components/CustomSectionTabContent.js +0 -5
- package/dist/features/products/components/GroupMemberPicker.js +2 -2
- package/dist/features/products/components/GroupSettingsPanel.js +1 -1
- package/dist/features/products/components/PrizeDrawDetailPageView.js +1 -7
- package/dist/features/products/components/PrizeDrawEntryActions.d.ts +1 -1
- package/dist/features/products/components/PrizeDrawEntryActions.js +1 -1
- package/dist/features/products/components/ProductDetailPageView.js +1 -7
- package/dist/features/products/repository/products.repository.js +0 -1
- package/dist/features/promotions/api/route.js +1 -8
- package/dist/features/reviews/components/ReviewsIndexPageView.d.ts +2 -3
- package/dist/features/reviews/components/ReviewsIndexPageView.js +9 -12
- package/dist/features/seller/api/coupons/route.js +1 -5
- package/dist/features/seller/api/products/route.js +1 -5
- package/dist/features/seller/components/SellerProductShell.js +1 -2
- package/dist/features/seller/hooks/useSellerListingData.js +0 -30
- package/dist/features/stores/api/[storeSlug]/auctions/route.js +1 -10
- package/dist/features/stores/api/[storeSlug]/products/route.js +1 -10
- package/dist/features/stores/components/InteractiveStoreCard.js +9 -1
- package/dist/features/stores/components/StoreGuideHubView.js +1 -1
- package/dist/features/stores/components/StoresIndexPageView.d.ts +2 -3
- package/dist/features/stores/components/StoresIndexPageView.js +6 -9
- package/dist/features/stores/schemas/firestore.d.ts +27 -0
- package/dist/features/stores/schemas/firestore.js +2 -0
- package/dist/features/wishlist/utils/guest-wishlist.js +1 -5
- package/dist/index.d.ts +2 -1
- package/dist/index.js +7 -1
- package/dist/providers/db-firebase/admin-app-lite.js +14 -2
- package/dist/providers/db-firebase/admin.js +8 -2
- package/dist/react/hooks/useBrowsePageFlags.d.ts +26 -0
- package/dist/react/hooks/useBrowsePageFlags.js +18 -0
- package/dist/schemas/registry.d.ts +4 -4
- package/dist/security/csp.d.ts +32 -2
- package/dist/security/csp.js +32 -2
- package/dist/styles.css +1 -1
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/Button.js +1 -1
- package/dist/ui/components/Chart.js +0 -1
- package/dist/ui/rich-text/RichText.js +1 -3
- package/dist/ui/rich-text/RichTextRenderer.js +1 -3
- package/dist/utils/browser-storage.d.ts +14 -0
- package/dist/utils/browser-storage.js +18 -0
- package/dist/utils/classnames.d.ts +19 -0
- package/dist/utils/classnames.js +21 -0
- package/dist/utils/search-params.d.ts +44 -0
- package/dist/utils/search-params.js +46 -0
- package/dist/utils/sieve-builder.d.ts +13 -0
- package/dist/utils/sieve-builder.js +22 -0
- package/dist/utils/string.formatter.d.ts +12 -0
- package/dist/utils/string.formatter.js +17 -0
- package/package.json +2 -2
|
@@ -18,6 +18,78 @@
|
|
|
18
18
|
* Future-expansion Pattern 5. Slots with Phase 7 (CTA registry consults the
|
|
19
19
|
* same flag to hide actions for disabled types).
|
|
20
20
|
*/
|
|
21
|
+
// Route-map only — a pure constants module with no firebase-admin in its
|
|
22
|
+
// import chain, so this stays safe in the client bundle (appkit Export Rules).
|
|
23
|
+
import { ROUTES } from "../../../next/routing/route-map.js";
|
|
24
|
+
/**
|
|
25
|
+
* Declared as `Record<BrowsePageKey, true>` for the same reason
|
|
26
|
+
* `ALL_LISTING_TYPES_MAP` is: adding a key to the union without adding it here
|
|
27
|
+
* is a COMPILE error rather than a section that silently can never be hidden.
|
|
28
|
+
* Root Cause #61 — every hand-written enumeration of a union in this codebase
|
|
29
|
+
* has drifted at least once.
|
|
30
|
+
*/
|
|
31
|
+
const ALL_BROWSE_PAGES_MAP = {
|
|
32
|
+
categories: true,
|
|
33
|
+
brands: true,
|
|
34
|
+
franchise: true,
|
|
35
|
+
stores: true,
|
|
36
|
+
};
|
|
37
|
+
export const ALL_BROWSE_PAGES = Object.keys(ALL_BROWSE_PAGES_MAP);
|
|
38
|
+
/**
|
|
39
|
+
* `true` when the section is missing from the flag map OR explicitly enabled.
|
|
40
|
+
*
|
|
41
|
+
* Absent-means-enabled, matching its two siblings: a section has to be
|
|
42
|
+
* EXPLICITLY `false` to close. An install that predates this setting keeps
|
|
43
|
+
* every section open, so adding the field cannot take a site's navigation away
|
|
44
|
+
* from it.
|
|
45
|
+
*/
|
|
46
|
+
export function isBrowsePageEnabled(page, settings) {
|
|
47
|
+
return settings?.listings?.browsePages?.[page] !== false;
|
|
48
|
+
}
|
|
49
|
+
export function enabledBrowsePages(settings) {
|
|
50
|
+
return ALL_BROWSE_PAGES.filter((p) => isBrowsePageEnabled(p, settings));
|
|
51
|
+
}
|
|
52
|
+
/** Narrow an untrusted string — a route param, a form value — to the union. */
|
|
53
|
+
export function isBrowsePageKey(value) {
|
|
54
|
+
return typeof value === "string" && value in ALL_BROWSE_PAGES_MAP;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The public path prefixes each section owns.
|
|
58
|
+
*
|
|
59
|
+
* 🛑 `/sellers` maps to `stores` — see the `BrowsePageKey` note. Two doors,
|
|
60
|
+
* one directory, one flag.
|
|
61
|
+
*/
|
|
62
|
+
const BROWSE_PAGE_PREFIXES = [
|
|
63
|
+
[String(ROUTES.PUBLIC.CATEGORIES), "categories"],
|
|
64
|
+
[String(ROUTES.PUBLIC.BRANDS), "brands"],
|
|
65
|
+
[String(ROUTES.PUBLIC.FRANCHISE), "franchise"],
|
|
66
|
+
[String(ROUTES.PUBLIC.STORES), "stores"],
|
|
67
|
+
[String(ROUTES.PUBLIC.SELLERS), "stores"],
|
|
68
|
+
];
|
|
69
|
+
/**
|
|
70
|
+
* Which browse section a link points into, or `null` for a link that is not in
|
|
71
|
+
* one. Used to drop nav, sidebar, mobile-sheet and footer entries for a closed
|
|
72
|
+
* section.
|
|
73
|
+
*
|
|
74
|
+
* 🛑 Matches on an EXACT path or a `/`-delimited descendant, never a bare
|
|
75
|
+
* `startsWith`. `/store` is the SELLER DASHBOARD and `/stores` is the public
|
|
76
|
+
* directory, so a prefix test would hide a signed-in seller's own dashboard
|
|
77
|
+
* link the moment an admin closed the public storefronts — a different
|
|
78
|
+
* surface, a different audience, one character apart.
|
|
79
|
+
*
|
|
80
|
+
* Query strings and hashes are tolerated because footer and nav hrefs are
|
|
81
|
+
* hand-authored and some carry them.
|
|
82
|
+
*/
|
|
83
|
+
export function browsePageForPath(href) {
|
|
84
|
+
if (typeof href !== "string" || href.length === 0)
|
|
85
|
+
return null;
|
|
86
|
+
const path = href.split(/[?#]/)[0].replace(/\/+$/, "") || "/";
|
|
87
|
+
for (const [prefix, key] of BROWSE_PAGE_PREFIXES) {
|
|
88
|
+
if (path === prefix || path.startsWith(`${prefix}/`))
|
|
89
|
+
return key;
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
21
93
|
/**
|
|
22
94
|
* `true` when the type is missing from the flag map OR explicitly enabled.
|
|
23
95
|
* Defaulting to enabled keeps legacy data working — a flag has to be
|
package/dist/client.d.ts
CHANGED
|
@@ -223,7 +223,7 @@ export { AppLayoutShell, LocaleSwitcher, useDashboardNav, BottomActionsProvider,
|
|
|
223
223
|
* visible Buy/Checkout button. See `useCtaReveal`.
|
|
224
224
|
*/
|
|
225
225
|
export { ctaAnchorProps, CTA_ANCHOR_ATTR } from "./features/layout/index";
|
|
226
|
-
export type { AppLayoutShellProps, AppLayoutShellSidebarLink, AppLayoutShellSidebarSection, DashboardNavState, MainNavbarItem, LayoutClientProps } from "./features/layout/index";
|
|
226
|
+
export type { AppLayoutShellProps, AppLayoutShellSidebarLink, AppLayoutShellSidebarSection, SidebarItemActionRenderer, DashboardNavState, MainNavbarItem, LayoutClientProps } from "./features/layout/index";
|
|
227
227
|
export { ListingLayout } from "./ui/components/ListingLayout";
|
|
228
228
|
export type { ListingLayoutProps, ListingLayoutLabels } from "./ui/components/ListingLayout";
|
|
229
229
|
export { Search } from "./features/search/components";
|
|
@@ -364,6 +364,8 @@ export { useAuthGate } from "./react/hooks/useAuthGate";
|
|
|
364
364
|
export type { UseAuthGateReturn } from "./react/hooks/useAuthGate";
|
|
365
365
|
export { useListingTypeFlags } from "./react/hooks/useListingTypeFlags";
|
|
366
366
|
export type { ListingTypeFlags, ListingTypeFlagsShape, } from "./react/hooks/useListingTypeFlags";
|
|
367
|
+
export { useBrowsePageFlags } from "./react/hooks/useBrowsePageFlags";
|
|
368
|
+
export type { BrowsePageFlags, BrowsePageFlagsShape, } from "./react/hooks/useBrowsePageFlags";
|
|
367
369
|
export { useActionDispatch } from "./react/hooks/use-action-dispatch";
|
|
368
370
|
export type { DispatchAction, UseActionDispatchOptions } from "./react/hooks/use-action-dispatch";
|
|
369
371
|
export { usePanelStore } from "./stores/panel-store";
|
|
@@ -437,7 +439,8 @@ export type { TrackArgs as TrackHistoryArgs } from "./features/history/hooks/use
|
|
|
437
439
|
export { WISHLIST_MAX, HISTORY_MAX, CART_MAX_ITEMS, } from "./constants/limits";
|
|
438
440
|
export { normalizeListingType, isAuctionListing, isPreOrderListing, isStandardListing, isPrizeDrawListing, isClassifiedListing, isDigitalCodeListing, isLiveListing, isArtListing, isStickersListing, } from "./features/products/utils/listing-type";
|
|
439
441
|
export { isFinalSale, acceptsChangeOfMindReturns, } from "./features/products/constants/final-sale";
|
|
440
|
-
export { isListingTypeEnabled, isCategoryTypeEnabled, enabledListingTypes, enabledCategoryTypes, ALL_LISTING_TYPES, isListingType, } from "./_internal/shared/listing-types/feature-flags";
|
|
442
|
+
export { isListingTypeEnabled, isCategoryTypeEnabled, enabledListingTypes, enabledCategoryTypes, ALL_LISTING_TYPES, isListingType, isBrowsePageEnabled, enabledBrowsePages, ALL_BROWSE_PAGES, isBrowsePageKey, } from "./_internal/shared/listing-types/feature-flags";
|
|
443
|
+
export type { BrowsePageKey } from "./_internal/shared/listing-types/feature-flags";
|
|
441
444
|
export { ADVANCED_LISTING_TYPE_CAPABILITY, ADVANCED_LISTING_TYPES, BASIC_LISTING_TYPES, LISTING_TYPE_TIER, hasAdvancedListingTypes, isAdvancedListingType, isListingTypeAllowedFor, listingTypesAllowedFor, } from "./_internal/shared/listing-types/tiers";
|
|
442
445
|
export type { ListingTypeAccessContext, ListingTypeTier, } from "./_internal/shared/listing-types/tiers";
|
|
443
446
|
export { actionTracker, setActionTrackerSink, resetActionTrackerSink, type ActionEvent, type ActionTrackerSink, } from "./_internal/shared/listing-types/action-tracker";
|
package/dist/client.js
CHANGED
|
@@ -403,6 +403,9 @@ export { useAuthGate } from "./react/hooks/useAuthGate.js";
|
|
|
403
403
|
// [CLIENT-ONLY] W1-43 — useListingTypeFlags: read featureFlags.listingTypes
|
|
404
404
|
// from siteSettings; hide nav/search/picker entries for disabled types.
|
|
405
405
|
export { useListingTypeFlags } from "./react/hooks/useListingTypeFlags.js";
|
|
406
|
+
// [CLIENT-ONLY] useBrowsePageFlags: read listings.browsePages from
|
|
407
|
+
// siteSettings; drop nav/sidebar/footer links into a closed browse section.
|
|
408
|
+
export { useBrowsePageFlags } from "./react/hooks/useBrowsePageFlags.js";
|
|
406
409
|
// Action dispatch hook + panel store
|
|
407
410
|
export { useActionDispatch } from "./react/hooks/use-action-dispatch.js";
|
|
408
411
|
export { usePanelStore } from "./stores/panel-store.js";
|
|
@@ -470,7 +473,11 @@ export { normalizeListingType, isAuctionListing, isPreOrderListing, isStandardLi
|
|
|
470
473
|
// `finalSale` field. See the module docstring for why `!p.finalSale` is wrong.
|
|
471
474
|
export { isFinalSale, acceptsChangeOfMindReturns, } from "./features/products/constants/final-sale.js";
|
|
472
475
|
// SB-UNI-X4 2026-05-13 — per-type feature-flag helpers (client-safe).
|
|
473
|
-
export { isListingTypeEnabled, isCategoryTypeEnabled, enabledListingTypes, enabledCategoryTypes, ALL_LISTING_TYPES, isListingType,
|
|
476
|
+
export { isListingTypeEnabled, isCategoryTypeEnabled, enabledListingTypes, enabledCategoryTypes, ALL_LISTING_TYPES, isListingType,
|
|
477
|
+
// Browse-section gates. Client-safe and genuinely needed here: the nav,
|
|
478
|
+
// sidebar, mobile sheet and footer all filter on them, and a link into a
|
|
479
|
+
// closed section is a 404 on every page of the site.
|
|
480
|
+
isBrowsePageEnabled, enabledBrowsePages, ALL_BROWSE_PAGES, isBrowsePageKey, } from "./_internal/shared/listing-types/feature-flags.js";
|
|
474
481
|
// Basic/advanced listing-type split. Client-safe: the nav filter and any
|
|
475
482
|
// seller-facing picker need the same rule the server enforces, and deriving it
|
|
476
483
|
// twice is how the ten hand-written copies of the union happened.
|
|
@@ -603,6 +603,7 @@ export declare const CATEGORY_FIELDS: {
|
|
|
603
603
|
readonly SUBLISTING: "sublisting";
|
|
604
604
|
readonly BUNDLE: "bundle";
|
|
605
605
|
readonly FRANCHISE: "franchise";
|
|
606
|
+
readonly CHARACTER: "character";
|
|
606
607
|
};
|
|
607
608
|
readonly BUNDLE_STOCK_STATUS_VALUES: {
|
|
608
609
|
readonly AVAILABLE: "available";
|
|
@@ -715,6 +715,20 @@ export const CATEGORY_FIELDS = {
|
|
|
715
715
|
* A brand is who MADE it (Takara-Tomy); a franchise is what it IS FROM.
|
|
716
716
|
*/
|
|
717
717
|
FRANCHISE: "franchise",
|
|
718
|
+
/*
|
|
719
|
+
* WHO USED the bey in the story — Tyson Granger, Kai Hiwatari. The third
|
|
720
|
+
* sibling of BRAND and FRANCHISE and tier-0 for the same reason: a row the
|
|
721
|
+
* product points AT, never one it lives under. Who made it / what it is
|
|
722
|
+
* from / who used it.
|
|
723
|
+
*
|
|
724
|
+
* 🛑 Match on the SLUG, never the display name. `brand-rows.ts` trap 1
|
|
725
|
+
* records that brands match on display NAME, so renaming a brand silently
|
|
726
|
+
* orphans its whole catalogue; franchise was added later and deliberately
|
|
727
|
+
* took the stable key instead. Characters follow franchise — and it matters
|
|
728
|
+
* more here, because character names are routinely re-localised between
|
|
729
|
+
* the Japanese and English releases.
|
|
730
|
+
*/
|
|
731
|
+
CHARACTER: "character",
|
|
718
732
|
},
|
|
719
733
|
BUNDLE_STOCK_STATUS_VALUES: {
|
|
720
734
|
AVAILABLE: "available",
|
|
@@ -11,11 +11,7 @@
|
|
|
11
11
|
import { NextResponse } from "next/server.js";
|
|
12
12
|
import { getProviders } from "../../../../../contracts/index.js";
|
|
13
13
|
import { normalizeError } from "../../../../../errors/normalize.js";
|
|
14
|
-
|
|
15
|
-
const v = url.searchParams.get(key);
|
|
16
|
-
const n = v !== null ? Number(v) : NaN;
|
|
17
|
-
return Number.isFinite(n) ? n : fallback;
|
|
18
|
-
}
|
|
14
|
+
import { numParam } from "../../../../../utils/search-params.js";
|
|
19
15
|
export async function GET(request, { params }) {
|
|
20
16
|
try {
|
|
21
17
|
const { userId } = await params;
|
|
@@ -12,7 +12,7 @@ import { normalizeError } from "../../../errors/normalize.js";
|
|
|
12
12
|
*/
|
|
13
13
|
import { DatabaseError } from "../../../errors/index.js";
|
|
14
14
|
import { serverLogger } from "../../../monitoring/index.js";
|
|
15
|
-
import { BaseRepository, getFirestoreCount, prepareForFirestore
|
|
15
|
+
import { BaseRepository, getFirestoreCount, prepareForFirestore } from "../../../providers/db-firebase/index.js";
|
|
16
16
|
import { maskPii, revealPii, ADDRESS_PII_FIELDS, } from "../../../security/index.js";
|
|
17
17
|
import { buildTokensIndex } from "../../../security/blind-index-tokens.js";
|
|
18
18
|
import { ADDRESS_FIELDS, ADDRESSES_COLLECTION, } from "../schemas/index.js";
|
|
@@ -12,11 +12,7 @@
|
|
|
12
12
|
import { NextResponse } from "next/server.js";
|
|
13
13
|
import { getProviders } from "../../../../contracts/index.js";
|
|
14
14
|
import { createRouteHandler } from "../../../../next/api/routeHandler.js";
|
|
15
|
-
|
|
16
|
-
const v = url.searchParams.get(key);
|
|
17
|
-
const n = v !== null ? Number(v) : NaN;
|
|
18
|
-
return Number.isFinite(n) ? n : fallback;
|
|
19
|
-
}
|
|
15
|
+
import { numParam } from "../../../../utils/search-params.js";
|
|
20
16
|
export const GET = createRouteHandler({
|
|
21
17
|
roles: ["admin", "moderator"],
|
|
22
18
|
handler: async ({ request }) => {
|
|
@@ -12,11 +12,7 @@
|
|
|
12
12
|
import { NextResponse } from "next/server.js";
|
|
13
13
|
import { getProviders } from "../../../../contracts/index.js";
|
|
14
14
|
import { createRouteHandler } from "../../../../next/api/routeHandler.js";
|
|
15
|
-
|
|
16
|
-
const v = url.searchParams.get(key);
|
|
17
|
-
const n = v !== null ? Number(v) : NaN;
|
|
18
|
-
return Number.isFinite(n) ? n : fallback;
|
|
19
|
-
}
|
|
15
|
+
import { numParam } from "../../../../utils/search-params.js";
|
|
20
16
|
export const GET = createRouteHandler({
|
|
21
17
|
roles: ["admin", "moderator"],
|
|
22
18
|
handler: async ({ request }) => {
|
|
@@ -12,11 +12,7 @@
|
|
|
12
12
|
import { NextResponse } from "next/server.js";
|
|
13
13
|
import { getProviders } from "../../../../contracts/index.js";
|
|
14
14
|
import { createRouteHandler } from "../../../../next/api/routeHandler.js";
|
|
15
|
-
|
|
16
|
-
const v = url.searchParams.get(key);
|
|
17
|
-
const n = v !== null ? Number(v) : NaN;
|
|
18
|
-
return Number.isFinite(n) ? n : fallback;
|
|
19
|
-
}
|
|
15
|
+
import { numParam } from "../../../../utils/search-params.js";
|
|
20
16
|
export const GET = createRouteHandler({
|
|
21
17
|
roles: ["admin", "moderator"],
|
|
22
18
|
handler: async ({ request }) => {
|
|
@@ -12,11 +12,7 @@
|
|
|
12
12
|
import { NextResponse } from "next/server.js";
|
|
13
13
|
import { getProviders } from "../../../../contracts/index.js";
|
|
14
14
|
import { createRouteHandler } from "../../../../next/api/routeHandler.js";
|
|
15
|
-
|
|
16
|
-
const v = url.searchParams.get(key);
|
|
17
|
-
const n = v !== null ? Number(v) : NaN;
|
|
18
|
-
return Number.isFinite(n) ? n : fallback;
|
|
19
|
-
}
|
|
15
|
+
import { numParam } from "../../../../utils/search-params.js";
|
|
20
16
|
export const GET = createRouteHandler({
|
|
21
17
|
roles: ["admin", "moderator"],
|
|
22
18
|
handler: async ({ request }) => {
|
|
@@ -17,7 +17,6 @@ const __P = {
|
|
|
17
17
|
};
|
|
18
18
|
const CLS_PANEL = "rounded-xl border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] p-[var(--appkit-space-4)] space-y-3";
|
|
19
19
|
const CLS_ROW_BETWEEN = "flex items-center justify-between";
|
|
20
|
-
const CLS_SECTION_HEADING = "text-[length:var(--appkit-text-sm)] font-semibold text-[var(--appkit-color-text)]";
|
|
21
20
|
const carouselSlideSchema = z.object({
|
|
22
21
|
title: z.string().min(1, "Slide title is required").max(150),
|
|
23
22
|
order: z.number().int().min(0),
|
|
@@ -4,7 +4,7 @@ import { Row } from "@mohasinac/appkit/ui";
|
|
|
4
4
|
import { useApiMutation } from "@mohasinac/appkit/client";
|
|
5
5
|
import React from "react";
|
|
6
6
|
import { useQuery } from "@tanstack/react-query";
|
|
7
|
-
import { Checkbox, ConfirmDeleteModal, Div, Label, StackedViewShell, Text, useToast
|
|
7
|
+
import { Checkbox, ConfirmDeleteModal, Div, Label, StackedViewShell, Text, useToast } from "../../../ui/index.js";
|
|
8
8
|
import { apiClient } from "../../../http/index.js";
|
|
9
9
|
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints.js";
|
|
10
10
|
import { FormErrorSummary, applyZodIssues } from "../../../ui/forms/index.js";
|
|
@@ -156,7 +156,6 @@ export function AdminFeatureEditorView({ featureId, fixedScope, fixedStoreId, em
|
|
|
156
156
|
* the productTypes minimum). The button now stays enabled and the schema
|
|
157
157
|
* reports what is actually wrong, on the field itself.
|
|
158
158
|
*/
|
|
159
|
-
const isDisabled = isSubmitting;
|
|
160
159
|
const sections = React.useMemo(() => buildSectionsFromSchema(productFeatureFormSchema, {
|
|
161
160
|
options: {
|
|
162
161
|
iconColor: [...PRODUCT_FEATURE_ICON_COLOR_OPTIONS],
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Users, Package, Store, ShoppingBag, FileText, Settings, Shield, BarChart2, AlertTriangle, BookOpen, CreditCard
|
|
2
|
+
import { Users, Package, Store, ShoppingBag, FileText, Settings, Shield, BarChart2, AlertTriangle, BookOpen, CreditCard } from "lucide-react";
|
|
3
3
|
import { Alert, Div, Grid, Heading, Row, Section, Stack, Text, TextLink } from "../../../ui/index.js";
|
|
4
4
|
import { ROUTES } from "../../../next/routing/route-map.js";
|
|
5
5
|
const CLS_WARN_PANEL = "rounded-2xl border border-amber-200 bg-warning-surface dark:border-amber-800 p-[var(--appkit-space-6)]";
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
// audit-form-mutation-hook-multi-ok: saveMutation is the single combined save
|
|
4
|
+
// across all tabs; deleteMutation is a separate destructive action, not a
|
|
5
|
+
// second per-tab save.
|
|
3
6
|
import { useApiMutation } from "@mohasinac/appkit/client";
|
|
4
7
|
import React from "react";
|
|
5
8
|
import { z } from "zod";
|
|
6
9
|
import { useQuery } from "@tanstack/react-query";
|
|
7
|
-
import { Button, Card, CardBody, ConfirmDeleteModal, Div, PaginatedSelect, Stack, StackedViewShell, Tabs, TabsContent, TabsList, TabsTrigger, Text, useToast, Show, StickyToolbar
|
|
10
|
+
import { Button, Card, CardBody, ConfirmDeleteModal, Div, PaginatedSelect, Stack, StackedViewShell, Tabs, TabsContent, TabsList, TabsTrigger, Text, useToast, Show, StickyToolbar } from "../../../ui/index.js";
|
|
8
11
|
import { useFormShellState, applyZodIssues, FormErrorSummary, FormShellContext } from "../../../ui/forms/index.js";
|
|
9
12
|
import { SectionForm, useSectionFormNav } from "../../shell/index.js";
|
|
10
13
|
import { PRODUCT_MAX_IMAGES } from "../../../_internal/shared/media/limits.js";
|
|
@@ -18,7 +18,7 @@ import { apiClient } from "../../../http/index.js";
|
|
|
18
18
|
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints.js";
|
|
19
19
|
import { ROUTES } from "../../../next/routing/route-map.js";
|
|
20
20
|
import { NOTIFICATION_TYPE_TABS } from "../../../constants/notification-types.js";
|
|
21
|
-
import { ALL_LISTING_TYPES } from "../../../_internal/shared/listing-types/feature-flags.js";
|
|
21
|
+
import { ALL_LISTING_TYPES, ALL_BROWSE_PAGES, } from "../../../_internal/shared/listing-types/feature-flags.js";
|
|
22
22
|
import { pluginFor } from "../../../_internal/shared/listing-types/_registry.js";
|
|
23
23
|
import { SITE_SETTINGS_TABS, isSiteSettingsTabId, } from "../constants/site-settings-tabs.js";
|
|
24
24
|
import { ThemeManagerView, } from "../../site-settings/components/ThemeManagerView.js";
|
|
@@ -316,6 +316,7 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
|
|
|
316
316
|
// Per-listing-type / per-category-type visibility flags.
|
|
317
317
|
const [listingTypeFlags, setListingTypeFlags] = React.useState({});
|
|
318
318
|
const [categoryTypeFlags, setCategoryTypeFlags] = React.useState({});
|
|
319
|
+
const [browsePageFlags, setBrowsePageFlags] = React.useState({});
|
|
319
320
|
// Per-channel notification-type allowlists — empty array means "all types".
|
|
320
321
|
const [notifEmailTypes, setNotifEmailTypes] = React.useState([]);
|
|
321
322
|
const [notifWhatsappTypes, setNotifWhatsappTypes] = React.useState([]);
|
|
@@ -557,6 +558,7 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
|
|
|
557
558
|
setCodMaxOrderValue(s.payment?.codMaxOrderValue ?? COD_MAX_ORDER_VALUE_FALLBACK);
|
|
558
559
|
setListingTypeFlags(s.listings?.listingTypes ?? {});
|
|
559
560
|
setCategoryTypeFlags(s.listings?.categoryTypes ?? {});
|
|
561
|
+
setBrowsePageFlags(s.listings?.browsePages ?? {});
|
|
560
562
|
setNotifEmailTypes(s.notificationChannels?.email?.types ?? []);
|
|
561
563
|
setNotifWhatsappTypes(s.notificationChannels?.whatsapp?.types ?? []);
|
|
562
564
|
setBidIncrementTiers(s.auctionConfig?.bidIncrementTiers ?? DEFAULT_AUCTION_BID_INCREMENT_TIERS);
|
|
@@ -798,7 +800,11 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
|
|
|
798
800
|
// wholesale, so omitting one here would wipe it. The load-time spread this
|
|
799
801
|
// used to carry existed to preserve `adminCheckoutBypass`, which is now a
|
|
800
802
|
// permission rather than a stored flag.
|
|
801
|
-
listings: {
|
|
803
|
+
listings: {
|
|
804
|
+
listingTypes: listingTypeFlags,
|
|
805
|
+
categoryTypes: categoryTypeFlags,
|
|
806
|
+
browsePages: browsePageFlags,
|
|
807
|
+
},
|
|
802
808
|
notificationChannels: {
|
|
803
809
|
inApp: { enabled: true, readOnly: true },
|
|
804
810
|
email: { enabled: notifEmailEnabled, minPriority: notifEmailMinPriority, types: notifEmailTypes },
|
|
@@ -866,6 +872,16 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
|
|
|
866
872
|
*/
|
|
867
873
|
const LISTING_TYPE_KEYS = ALL_LISTING_TYPES;
|
|
868
874
|
const CATEGORY_TYPE_KEYS = ["category", "sublisting", "brand", "bundle"];
|
|
875
|
+
/* Derived for the same reason LISTING_TYPE_KEYS is — a hand-written copy of
|
|
876
|
+
* a union in this file has already drifted once, in both directions. */
|
|
877
|
+
const BROWSE_PAGE_KEYS = ALL_BROWSE_PAGES;
|
|
878
|
+
/** What each section actually withdraws, in the admin's own vocabulary. */
|
|
879
|
+
const BROWSE_PAGE_META = {
|
|
880
|
+
categories: { label: "Categories", hint: "/categories and every category page" },
|
|
881
|
+
brands: { label: "Brands", hint: "/brands and every brand page" },
|
|
882
|
+
franchise: { label: "Franchises", hint: "/franchise and every franchise page" },
|
|
883
|
+
stores: { label: "Stores & Sellers", hint: "/stores, /sellers and every storefront" },
|
|
884
|
+
};
|
|
869
885
|
const NOTIF_TYPE_OPTIONS = NOTIFICATION_TYPE_TABS.filter((t) => t.id !== "All").map((t) => ({ value: t.id, label: t.label }));
|
|
870
886
|
/*
|
|
871
887
|
* Validate the payload that is actually sent, then save.
|
|
@@ -915,7 +931,7 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
|
|
|
915
931
|
// The plugin's own display label, not the raw key — the
|
|
916
932
|
// literal version rendered "pre-order" and
|
|
917
933
|
// "digital-code" at an admin.
|
|
918
|
-
label: pluginFor(key).typeLabel, checked: listingTypeFlags[key] ?? true, onChange: (v) => setListingTypeFlags((prev) => ({ ...prev, [key]: v })) }, key))) })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "semibold", children: "Category types" }), _jsx(Grid, { cols: 2, gap: "sm", children: CATEGORY_TYPE_KEYS.map((key) => (_jsx(Toggle, { label: key, checked: categoryTypeFlags[key] ?? true, onChange: (v) => setCategoryTypeFlags((prev) => ({ ...prev, [key]: v })) }, key))) })] })] }) }), _jsx(TabsContent, { value: "integrations", children: _jsxs(SettingsTabForm, { id: "integrations", label: "Integrations", children: [_jsx(Text, { size: "xs", color: "muted", children: "Keys are masked in transit and stored encrypted. Click Reveal to view." }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Razorpay" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(MaskedInput, { label: "Razorpay Key ID", value: razorpayKeyId, onChange: setRazorpayKeyId, placeholder: "rzp_test_\u2026 / rzp_live_\u2026" }), _jsx(MaskedInput, { label: "Razorpay Key Secret", value: razorpayKeySecret, onChange: setRazorpayKeySecret, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" }), _jsx(MaskedInput, { label: "Razorpay Webhook Secret", value: razorpayWebhookSecret, onChange: setRazorpayWebhookSecret, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "PhonePe" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(MaskedInput, { label: "PhonePe Client ID", value: phonepeClientId, onChange: setPhonepeClientId, placeholder: "MERCHANTID_\u2026" }), _jsx(MaskedInput, { label: "PhonePe Client Secret", value: phonepeClientSecret, onChange: setPhonepeClientSecret, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" }), _jsx(Input, { label: "PhonePe Client Version", value: phonepeClientVersion, onChange: (e) => setPhonepeClientVersion(e.target.value), placeholder: "1" }), _jsx(Select, { label: "PhonePe Environment", options: PHONEPE_ENVIRONMENT_OPTIONS, value: phonepeEnvironment, onValueChange: setPhonepeEnvironment }), _jsx(MaskedInput, { label: "PhonePe Webhook Username", value: phonepeWebhookUsername, onChange: setPhonepeWebhookUsername, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" }), _jsx(MaskedInput, { label: "PhonePe Webhook Password", value: phonepeWebhookPassword, onChange: setPhonepeWebhookPassword, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "SMTP / Email" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "SMTP host", value: smtpHost, onChange: (e) => setSmtpHost(e.target.value), placeholder: "smtp.sendgrid.net" }), _jsx(Input, { label: "SMTP port", value: smtpPort, onChange: (e) => setSmtpPort(e.target.value), type: "number", placeholder: "587" }), _jsx(Input, { label: "SMTP user", value: smtpUser, onChange: (e) => setSmtpUser(e.target.value), placeholder: "apikey" })] }), _jsx(Input, { label: "From address", value: smtpFrom, onChange: (e) => setSmtpFrom(e.target.value), placeholder: "noreply@letitrip.in", type: "email" }), _jsx(Text, { size: "xs", color: "muted", children: "Transactional email is delivered through Resend \u2014 set the Resend API key below. These SMTP fields are stored but not currently used for delivery." })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Google Places (store reviews)" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(MaskedInput, { label: "Google Maps API key", value: googleMapsApiKey, onChange: setGoogleMapsApiKey, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" }), _jsx(Input, { label: "Default Google Place ID", value: googlePlaceId, onChange: (e) => setGooglePlaceId(e.target.value), placeholder: "ChIJ\u2026" })] })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Analytics & Tracking" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Google Analytics ID", value: gaMeasurementId, onChange: (e) => setGaMeasurementId(e.target.value), placeholder: "G-XXXXXXXXXX" }), _jsx(Input, { label: "Facebook Pixel ID", value: fbPixelId, onChange: (e) => setFbPixelId(e.target.value), placeholder: "XXXXXXXXXXXXXXXX" }), _jsx(Input, { label: "GTM Container ID", value: gtmContainerId, onChange: (e) => setGtmContainerId(e.target.value), placeholder: "GTM-XXXXXXX" }), _jsx(Input, { label: "Microsoft Clarity Project ID", value: clarityProjectId, onChange: (e) => setClarityProjectId(e.target.value), placeholder: "abcdefghij", helperText: "Heatmaps and session replay. Recording is strictly masked \u2014 no text or input values are captured. Leave blank to keep Clarity off." })] })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Meta (Instagram & Facebook Social Feed)" }), _jsx(Text, { size: "xs", color: "muted", children: "Used by the Social Feed section to fetch Instagram and Facebook posts via Meta Graph API v19." }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(MaskedInput, { label: "Page Access Token", value: metaPageAccessToken, onChange: setMetaPageAccessToken, placeholder: "EAAxxxxxxx\u2026" }), _jsx(Input, { label: "Page ID (or handle)", value: metaPageId, onChange: (e) => setMetaPageId(e.target.value), placeholder: "letitrip" })] })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "TikTok for Developers (Social Feed)" }), _jsx(Text, { size: "xs", color: "muted", children: "Client credentials + long-lived access token from TikTok for Developers. Used to list your account's public videos." }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(MaskedInput, { label: "Client Key", value: tiktokClientKey, onChange: setTiktokClientKey, placeholder: "aw\u2026" }), _jsx(MaskedInput, { label: "Client Secret", value: tiktokClientSecret, onChange: setTiktokClientSecret, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" }), _jsx(MaskedInput, { label: "Access Token (long-lived)", value: tiktokAccessToken, onChange: setTiktokAccessToken, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "DeviantArt OAuth2 (Social Feed)" }), _jsx(Text, { size: "xs", color: "muted", children: "Client credentials for DeviantArt gallery fetching (client-credentials OAuth2 flow \u2014 no user login required)." }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(MaskedInput, { label: "Client ID", value: deviantartClientId, onChange: setDeviantartClientId, placeholder: "1234" }), _jsx(MaskedInput, { label: "Client Secret", value: deviantartClientSecret, onChange: setDeviantartClientSecret, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] })] })] }) }), _jsx(TabsContent, { value: "shipping", children: _jsxs(SettingsTabForm, { id: "shipping", label: "Shipping", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Payment methods" }), _jsx(Div, { id: "setting-razorpay-enabled", children: _jsx(Toggle, { label: "Razorpay (online card/UPI/netbanking) enabled \u2014 needs Key ID + Key Secret under Credentials", checked: razorpayEnabled, onChange: setRazorpayEnabled }) }), _jsx(Div, { id: "setting-phonepe-enabled", children: _jsx(Toggle, { label: "PhonePe (online card/UPI) enabled \u2014 ignored while Razorpay is on; only one online gateway runs at a time", checked: phonepeEnabled, onChange: setPhonepeEnabled }) }), _jsx(Div, { id: "setting-manual-payment-enabled", children: _jsx(Toggle, { label: "Manual UPI/bank transfer enabled", checked: upiManualEnabled, onChange: setUpiManualEnabled }) }), _jsx(Div, { id: "setting-cod-enabled", children: _jsx(Toggle, { label: "Cash on delivery (COD) enabled", checked: codEnabled, onChange: setCodEnabled }) }), _jsx(Input, { label: "Free shipping threshold (\u20B9)", value: String(freeShippingThreshold), onChange: (e) => setFreeShippingThreshold(parseInt(e.target.value) || 0), type: "number", min: 0, helperText: "Orders above this amount get free shipping." }), _jsx(Select, { label: "Default carrier", options: CARRIER_OPTIONS, value: defaultCarrier, onValueChange: setDefaultCarrier }), _jsx(Input, { label: "Max delivery radius (km, 0 = no limit)", value: String(maxDeliveryRadius), onChange: (e) => setMaxDeliveryRadius(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "COD maximum order value (\u20B9)",
|
|
934
|
+
label: pluginFor(key).typeLabel, checked: listingTypeFlags[key] ?? true, onChange: (v) => setListingTypeFlags((prev) => ({ ...prev, [key]: v })) }, key))) })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "semibold", children: "Category types" }), _jsx(Grid, { cols: 2, gap: "sm", children: CATEGORY_TYPE_KEYS.map((key) => (_jsx(Toggle, { label: key, checked: categoryTypeFlags[key] ?? true, onChange: (v) => setCategoryTypeFlags((prev) => ({ ...prev, [key]: v })) }, key))) })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "semibold", children: "Browse sections" }), _jsx(Text, { size: "xs", color: "muted", children: "A closed section returns Not Found and disappears from the navbar, the sidebar, the mobile menu, the footer and the sitemap. Products, categories and stores are untouched \u2014 only the public way in is withdrawn, and re-opening restores it." }), _jsx(Grid, { cols: 2, gap: "sm", children: BROWSE_PAGE_KEYS.map((key) => (_jsxs(Stack, { gap: "xs", children: [_jsx(Toggle, { label: BROWSE_PAGE_META[key]?.label ?? key, checked: browsePageFlags[key] ?? true, onChange: (v) => setBrowsePageFlags((prev) => ({ ...prev, [key]: v })) }), _jsx(Text, { size: "xs", color: "muted", children: BROWSE_PAGE_META[key]?.hint })] }, key))) })] })] }) }), _jsx(TabsContent, { value: "integrations", children: _jsxs(SettingsTabForm, { id: "integrations", label: "Integrations", children: [_jsx(Text, { size: "xs", color: "muted", children: "Keys are masked in transit and stored encrypted. Click Reveal to view." }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Razorpay" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(MaskedInput, { label: "Razorpay Key ID", value: razorpayKeyId, onChange: setRazorpayKeyId, placeholder: "rzp_test_\u2026 / rzp_live_\u2026" }), _jsx(MaskedInput, { label: "Razorpay Key Secret", value: razorpayKeySecret, onChange: setRazorpayKeySecret, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" }), _jsx(MaskedInput, { label: "Razorpay Webhook Secret", value: razorpayWebhookSecret, onChange: setRazorpayWebhookSecret, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "PhonePe" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(MaskedInput, { label: "PhonePe Client ID", value: phonepeClientId, onChange: setPhonepeClientId, placeholder: "MERCHANTID_\u2026" }), _jsx(MaskedInput, { label: "PhonePe Client Secret", value: phonepeClientSecret, onChange: setPhonepeClientSecret, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" }), _jsx(Input, { label: "PhonePe Client Version", value: phonepeClientVersion, onChange: (e) => setPhonepeClientVersion(e.target.value), placeholder: "1" }), _jsx(Select, { label: "PhonePe Environment", options: PHONEPE_ENVIRONMENT_OPTIONS, value: phonepeEnvironment, onValueChange: setPhonepeEnvironment }), _jsx(MaskedInput, { label: "PhonePe Webhook Username", value: phonepeWebhookUsername, onChange: setPhonepeWebhookUsername, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" }), _jsx(MaskedInput, { label: "PhonePe Webhook Password", value: phonepeWebhookPassword, onChange: setPhonepeWebhookPassword, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "SMTP / Email" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "SMTP host", value: smtpHost, onChange: (e) => setSmtpHost(e.target.value), placeholder: "smtp.sendgrid.net" }), _jsx(Input, { label: "SMTP port", value: smtpPort, onChange: (e) => setSmtpPort(e.target.value), type: "number", placeholder: "587" }), _jsx(Input, { label: "SMTP user", value: smtpUser, onChange: (e) => setSmtpUser(e.target.value), placeholder: "apikey" })] }), _jsx(Input, { label: "From address", value: smtpFrom, onChange: (e) => setSmtpFrom(e.target.value), placeholder: "noreply@letitrip.in", type: "email" }), _jsx(Text, { size: "xs", color: "muted", children: "Transactional email is delivered through Resend \u2014 set the Resend API key below. These SMTP fields are stored but not currently used for delivery." })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Google Places (store reviews)" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(MaskedInput, { label: "Google Maps API key", value: googleMapsApiKey, onChange: setGoogleMapsApiKey, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" }), _jsx(Input, { label: "Default Google Place ID", value: googlePlaceId, onChange: (e) => setGooglePlaceId(e.target.value), placeholder: "ChIJ\u2026" })] })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Analytics & Tracking" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Google Analytics ID", value: gaMeasurementId, onChange: (e) => setGaMeasurementId(e.target.value), placeholder: "G-XXXXXXXXXX" }), _jsx(Input, { label: "Facebook Pixel ID", value: fbPixelId, onChange: (e) => setFbPixelId(e.target.value), placeholder: "XXXXXXXXXXXXXXXX" }), _jsx(Input, { label: "GTM Container ID", value: gtmContainerId, onChange: (e) => setGtmContainerId(e.target.value), placeholder: "GTM-XXXXXXX" }), _jsx(Input, { label: "Microsoft Clarity Project ID", value: clarityProjectId, onChange: (e) => setClarityProjectId(e.target.value), placeholder: "abcdefghij", helperText: "Heatmaps and session replay. Recording is strictly masked \u2014 no text or input values are captured. Leave blank to keep Clarity off." })] })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Meta (Instagram & Facebook Social Feed)" }), _jsx(Text, { size: "xs", color: "muted", children: "Used by the Social Feed section to fetch Instagram and Facebook posts via Meta Graph API v19." }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(MaskedInput, { label: "Page Access Token", value: metaPageAccessToken, onChange: setMetaPageAccessToken, placeholder: "EAAxxxxxxx\u2026" }), _jsx(Input, { label: "Page ID (or handle)", value: metaPageId, onChange: (e) => setMetaPageId(e.target.value), placeholder: "letitrip" })] })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "TikTok for Developers (Social Feed)" }), _jsx(Text, { size: "xs", color: "muted", children: "Client credentials + long-lived access token from TikTok for Developers. Used to list your account's public videos." }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(MaskedInput, { label: "Client Key", value: tiktokClientKey, onChange: setTiktokClientKey, placeholder: "aw\u2026" }), _jsx(MaskedInput, { label: "Client Secret", value: tiktokClientSecret, onChange: setTiktokClientSecret, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" }), _jsx(MaskedInput, { label: "Access Token (long-lived)", value: tiktokAccessToken, onChange: setTiktokAccessToken, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "DeviantArt OAuth2 (Social Feed)" }), _jsx(Text, { size: "xs", color: "muted", children: "Client credentials for DeviantArt gallery fetching (client-credentials OAuth2 flow \u2014 no user login required)." }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(MaskedInput, { label: "Client ID", value: deviantartClientId, onChange: setDeviantartClientId, placeholder: "1234" }), _jsx(MaskedInput, { label: "Client Secret", value: deviantartClientSecret, onChange: setDeviantartClientSecret, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] })] })] }) }), _jsx(TabsContent, { value: "shipping", children: _jsxs(SettingsTabForm, { id: "shipping", label: "Shipping", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Payment methods" }), _jsx(Div, { id: "setting-razorpay-enabled", children: _jsx(Toggle, { label: "Razorpay (online card/UPI/netbanking) enabled \u2014 needs Key ID + Key Secret under Credentials", checked: razorpayEnabled, onChange: setRazorpayEnabled }) }), _jsx(Div, { id: "setting-phonepe-enabled", children: _jsx(Toggle, { label: "PhonePe (online card/UPI) enabled \u2014 ignored while Razorpay is on; only one online gateway runs at a time", checked: phonepeEnabled, onChange: setPhonepeEnabled }) }), _jsx(Div, { id: "setting-manual-payment-enabled", children: _jsx(Toggle, { label: "Manual UPI/bank transfer enabled", checked: upiManualEnabled, onChange: setUpiManualEnabled }) }), _jsx(Div, { id: "setting-cod-enabled", children: _jsx(Toggle, { label: "Cash on delivery (COD) enabled", checked: codEnabled, onChange: setCodEnabled }) }), _jsx(Input, { label: "Free shipping threshold (\u20B9)", value: String(freeShippingThreshold), onChange: (e) => setFreeShippingThreshold(parseInt(e.target.value) || 0), type: "number", min: 0, helperText: "Orders above this amount get free shipping." }), _jsx(Select, { label: "Default carrier", options: CARRIER_OPTIONS, value: defaultCarrier, onValueChange: setDefaultCarrier }), _jsx(Input, { label: "Max delivery radius (km, 0 = no limit)", value: String(maxDeliveryRadius), onChange: (e) => setMaxDeliveryRadius(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "COD maximum order value (\u20B9)",
|
|
919
935
|
/*
|
|
920
936
|
🛑 A MAXIMUM, sitting directly above a THRESHOLD that is a
|
|
921
937
|
minimum. The helper text says which way each one runs,
|
|
@@ -23,6 +23,12 @@ export interface OwnerQuickActionsProps {
|
|
|
23
23
|
* `card` sits in a hero corner and uses a scrim so it reads on photography;
|
|
24
24
|
* `detail` sits in a header row beside the title.
|
|
25
25
|
*/
|
|
26
|
+
/**
|
|
27
|
+
* 🛑 ACCEPTED AND NOT READ since `onMedia` took over the dark-surface
|
|
28
|
+
* decision — see the note at the `onMedia` branch below. 36 call sites still
|
|
29
|
+
* pass it, so it is declared rather than deleted; delete it WITH those call
|
|
30
|
+
* sites, not ahead of them.
|
|
31
|
+
*/
|
|
26
32
|
placement?: "card" | "detail";
|
|
27
33
|
/**
|
|
28
34
|
* 🛑 Set this whenever `placement="detail"` sits on a DARK cover hero.
|
|
@@ -43,4 +49,4 @@ export interface OwnerQuickActionsProps {
|
|
|
43
49
|
onMedia?: boolean;
|
|
44
50
|
className?: string;
|
|
45
51
|
}
|
|
46
|
-
export declare function OwnerQuickActions({ resource, editHref, onDelete, onClone, ownerId, storeId, recordLabel,
|
|
52
|
+
export declare function OwnerQuickActions({ resource, editHref, onDelete, onClone, ownerId, storeId, recordLabel, onMedia, className, }: OwnerQuickActionsProps): import("react").JSX.Element | null;
|
|
@@ -108,7 +108,7 @@ const RESOURCE_LEAVES = {
|
|
|
108
108
|
USER: { bucket: "ADMIN", edit: "manage-user" },
|
|
109
109
|
SECTION: { bucket: "ADMIN", edit: "edit-section" },
|
|
110
110
|
};
|
|
111
|
-
export function OwnerQuickActions({ resource, editHref, onDelete, onClone, ownerId, storeId, recordLabel,
|
|
111
|
+
export function OwnerQuickActions({ resource, editHref, onDelete, onClone, ownerId, storeId, recordLabel, onMedia = false, className = "", }) {
|
|
112
112
|
const session = useOptionalSession();
|
|
113
113
|
const isOwnerOrAdmin = useIsOwner(ownerId);
|
|
114
114
|
const isAdmin = useIsAdmin();
|
|
@@ -30,7 +30,6 @@ import { annotate } from "../../shell/field-ui-meta.js";
|
|
|
30
30
|
import { mediaUrlSchema } from "../../../validation/schemas.js";
|
|
31
31
|
import { StoreStatusValues } from "../../stores/schemas/firestore.js";
|
|
32
32
|
import { ALL_STORE_CAPABILITIES } from "../../auth/permissions/constants.js";
|
|
33
|
-
const PHONE_RE = /^(\+?91[-\s]?)?[6-9]\d{9}$/;
|
|
34
33
|
/*
|
|
35
34
|
* `adminAddressFormSchema` is DELETED (W5 / D19).
|
|
36
35
|
*
|
|
@@ -670,6 +670,39 @@ export interface SiteSettingsDocument extends BaseDocument {
|
|
|
670
670
|
search?: {
|
|
671
671
|
indexEnabled?: boolean;
|
|
672
672
|
};
|
|
673
|
+
/**
|
|
674
|
+
* The WhatsApp catalogue feed — `/whatsapp/{storeSlug}/feed.csv`, read on a
|
|
675
|
+
* schedule by each seller's own Meta Commerce Manager.
|
|
676
|
+
*
|
|
677
|
+
* ## 🛑 A group of its own, for the same reason `search` is
|
|
678
|
+
*
|
|
679
|
+
* It is NOT in `notificationChannels.whatsapp`, which governs the platform's
|
|
680
|
+
* outbound order-update templates — a different channel with a different cost
|
|
681
|
+
* (per-template spend vs. a public CSV), owned by the platform rather than
|
|
682
|
+
* pointed at by each seller. Nor is it in `payment` or any other group that
|
|
683
|
+
* `AdminSiteSettingsView.buildFullPayload` rewrites WHOLE from form state: a
|
|
684
|
+
* fifth key in such a group is silently erased the next time an admin saves
|
|
685
|
+
* anything on that page (Root Cause #76), and a kill switch that a routine
|
|
686
|
+
* save turns back on fails in the direction of the thing it exists to stop.
|
|
687
|
+
*
|
|
688
|
+
* ## 🛑 Default ON — reader tests `!== false`
|
|
689
|
+
*
|
|
690
|
+
* An absent field, an unseeded document and a failed settings read must all
|
|
691
|
+
* mean "feeds work". Meta EMPTIES a connected catalogue the first time a
|
|
692
|
+
* scheduled fetch returns a file with no rows, so a `=== true` reader would
|
|
693
|
+
* wipe every seller's WhatsApp catalogue on the first Firestore blip — the
|
|
694
|
+
* same fail-open argument `allPurchasableTypes()` in the feed route is
|
|
695
|
+
* written around. The only state that disables it is somebody deliberately
|
|
696
|
+
* writing `false`.
|
|
697
|
+
*
|
|
698
|
+
* Switching it off empties EVERY store's feed on its next fetch, which is a
|
|
699
|
+
* platform-wide retraction and the reason it has no admin-UI toggle today —
|
|
700
|
+
* like `search.indexEnabled`, it is a deliberate Firestore-level switch. The
|
|
701
|
+
* per-store equivalent a seller owns is `StoreDocument.whatsappFeedEnabled`.
|
|
702
|
+
*/
|
|
703
|
+
whatsappFeed?: {
|
|
704
|
+
enabled?: boolean;
|
|
705
|
+
};
|
|
673
706
|
emailSettings: {
|
|
674
707
|
fromName: string;
|
|
675
708
|
fromEmail: string;
|
|
@@ -728,6 +761,25 @@ export interface SiteSettingsDocument extends BaseDocument {
|
|
|
728
761
|
brand?: boolean;
|
|
729
762
|
bundle?: boolean;
|
|
730
763
|
};
|
|
764
|
+
/**
|
|
765
|
+
* Which public BROWSE SECTIONS are open — `/categories`, `/brands`,
|
|
766
|
+
* `/franchise`, `/stores` (+ `/sellers`).
|
|
767
|
+
*
|
|
768
|
+
* A third axis, not a rename of the two above: `listingTypes` says what the
|
|
769
|
+
* marketplace sells, `categoryTypes` says what a taxonomy row may be, and
|
|
770
|
+
* this says whether a public entry point is open. Closing `/brands` does
|
|
771
|
+
* not untag a single product — `BrandDetailPageView`'s query is unchanged
|
|
772
|
+
* and brand rows keep driving it. It withdraws the front door.
|
|
773
|
+
*
|
|
774
|
+
* ⚠️ Same absent-means-enabled contract as its siblings, and the same
|
|
775
|
+
* warning applies: `BrowsePageKey` and `ALL_BROWSE_PAGES_MAP`
|
|
776
|
+
* (`_internal/shared/listing-types/feature-flags.ts`) are the authority.
|
|
777
|
+
* That Record makes a missing member a compile error; this interface does
|
|
778
|
+
* not, and the `listingTypes` field above is the record of what happens
|
|
779
|
+
* when the two drift (it listed 8 types after the union reached 10).
|
|
780
|
+
* Add a new section THERE, not here.
|
|
781
|
+
*/
|
|
782
|
+
browsePages?: Partial<Record<string, boolean>>;
|
|
731
783
|
};
|
|
732
784
|
/**
|
|
733
785
|
* Admin-authored HTML overrides for the public policy pages. When a key holds
|
|
@@ -371,6 +371,26 @@ export const DEFAULT_SITE_SETTINGS_DATA = {
|
|
|
371
371
|
brand: true,
|
|
372
372
|
bundle: false,
|
|
373
373
|
},
|
|
374
|
+
/*
|
|
375
|
+
* Every browse section CLOSED, as of 2026-10-24.
|
|
376
|
+
*
|
|
377
|
+
* Spelled out rather than left absent even though absent means open: this
|
|
378
|
+
* block is the "never configured" baseline, and the whole point of the
|
|
379
|
+
* setting is that these four are not ready to show. Relying on the
|
|
380
|
+
* permissive default would open all four on a fresh install, which is the
|
|
381
|
+
* opposite of the decision.
|
|
382
|
+
*
|
|
383
|
+
* Re-opening one is a single toggle in Site Settings -> Listings -> Browse
|
|
384
|
+
* sections. Nothing else has to change: the routes, the nav entries, the
|
|
385
|
+
* footer links and the sitemap rows all come back on their own, because
|
|
386
|
+
* all four read this same flag rather than having been deleted.
|
|
387
|
+
*/
|
|
388
|
+
browsePages: {
|
|
389
|
+
categories: false,
|
|
390
|
+
brands: false,
|
|
391
|
+
franchise: false,
|
|
392
|
+
stores: false,
|
|
393
|
+
},
|
|
374
394
|
},
|
|
375
395
|
shipping: {
|
|
376
396
|
estimatedDays: 5,
|
|
@@ -13,14 +13,7 @@
|
|
|
13
13
|
import { NextResponse } from "next/server.js";
|
|
14
14
|
import { getProviders } from "../../../contracts/index.js";
|
|
15
15
|
import { normalizeError } from "../../../errors/normalize.js";
|
|
16
|
-
|
|
17
|
-
return url.searchParams.get(key);
|
|
18
|
-
}
|
|
19
|
-
function numParam(url, key, fallback) {
|
|
20
|
-
const v = url.searchParams.get(key);
|
|
21
|
-
const n = v !== null ? Number(v) : NaN;
|
|
22
|
-
return Number.isFinite(n) ? n : fallback;
|
|
23
|
-
}
|
|
16
|
+
import { numParam, param } from "../../../utils/search-params.js";
|
|
24
17
|
// --- GET /api/bids ------------------------------------------------------------
|
|
25
18
|
// Query params:
|
|
26
19
|
// auctionId — filter bids for a specific auction product (recommended)
|
|
@@ -13,7 +13,7 @@ const CLS_LIVE_BADGE = "inline-block rounded-full bg-warning-surface dark:bg-war
|
|
|
13
13
|
import { ROUTES } from "../../../next/routing/route-map.js";
|
|
14
14
|
import { getDefaultCurrency } from "../../../core/baseline-resolver.js";
|
|
15
15
|
import { formatCurrency } from "../../../utils/number.formatter.js";
|
|
16
|
-
import {
|
|
16
|
+
import { toDescriptionHtml } from "../../../utils/string.formatter.js";
|
|
17
17
|
import { safeDisplayName } from "../../../security/index.js";
|
|
18
18
|
import { getSiteSettingsGlobal } from "../../admin/utils/getSiteSettingsGlobal.js";
|
|
19
19
|
import { isBuyNowAvailable, resolveMinBid, resolveMinBidIncrement, } from "../../../_internal/shared/features/auctions/config.js";
|
|
@@ -44,12 +44,6 @@ import { SublistingCarouselSection } from "../../products/components/SublistingC
|
|
|
44
44
|
import { isFinalSale } from "../../products/constants/final-sale.js";
|
|
45
45
|
import { OwnerQuickActions } from "../../admin/components/OwnerQuickActions.js";
|
|
46
46
|
import { PurchaseReviewAffordance } from "../../reviews/components/PurchaseReviewAffordance.js";
|
|
47
|
-
function toDescriptionHtml(raw) {
|
|
48
|
-
if (!raw)
|
|
49
|
-
return "";
|
|
50
|
-
const s = typeof raw === "string" ? raw : JSON.stringify(raw);
|
|
51
|
-
return normalizeRichTextHtml(s);
|
|
52
|
-
}
|
|
53
47
|
function renderAuctionInfoPanel(props) {
|
|
54
48
|
const { productId, storeId, title, currentBid, currency, bidCount, isEnded, endDate, buyNowPrice, buyNowAvailable, featured, freeShipping, condition, finalSale, category, categoryName, brand, brandSlug, productFeatures, features, descriptionHtml, safeSeller, storeHref } = props;
|
|
55
49
|
return (_jsxs(Stack, { gap: "md", children: [_jsxs(Div, { children: [_jsxs(Row, { gap: "xs", wrap: true, className: "mb-2", children: [_jsx(Span, { size: "xs", weight: "semibold", className: CLS_LIVE_BADGE, children: "\uD83C\uDFF7\uFE0F Live Auction" }), _jsx(AuctionLiveStatusBadge, { auctionEndDate: endDate, initialIsEnded: isEnded })] }), _jsx(Heading, { level: 1, className: "leading-snug", smSize: "2xl", color: "primary", size: "xl", weight: "bold", children: title }), _jsx(OwnerQuickActions, { resource: "PRODUCT", placement: "detail", recordLabel: title, storeId: storeId, editHref: String(ROUTES.STORE.PRODUCTS_EDIT(productId)) })] }), _jsxs(Div, { children: [_jsx(Text, { className: "mb-0.5", color: "muted", size: "xs", children: "Current bid" }), _jsx(LiveBidPrice, { productId: productId, currentBid: currentBid, bidCount: bidCount, currency: currency, isEnded: isEnded, priceSize: "2xl", badgeSize: "sm" }), endDate && !isEnded && (_jsxs(Text, { className: "mt-1.5", color: "muted", size: "sm", children: ["Ends in ", _jsx(Span, { weight: "medium", color: "muted", children: _jsx(CountdownDisplay, { targetDate: endDate, format: "auto", expiredLabel: "Ended" }) })] })), endDate && _jsxs(Text, { className: "mt-0.5", color: "faint", size: "xs", children: [isEnded ? "Ended" : "Ends", " ", endDate.toLocaleString()] })] }), buyNowAvailable && buyNowPrice !== null && (_jsxs(Row, { align: "center", gap: "sm", className: "border border-[var(--appkit-color-primary-200)] dark:border-[var(--appkit-color-primary-800)] bg-primary-50 dark:bg-primary-900/20", padding: "inlineSm", rounded: "lg", children: [_jsx(Span, { size: "xs", color: "muted", children: "Buy Now:" }), _jsx(Span, { size: "base", weight: "bold", className: "text-primary-700 dark:text-primary-300", children: _jsx(GatedPrice, { label: "Sign in to see the Buy Now price", children: formatCurrency(buyNowPrice, currency) }) }), _jsx(Span, { size: "xs", color: "faint", children: "\u2014 in \u201CPlace a bid\u201D" })] })), _jsx(ProductFeatureBadges, { featured: featured, freeShipping: freeShipping, condition: condition ?? undefined, finalSale: finalSale, labels: { featured: "Featured", fasterDelivery: "Faster Delivery", ratedSeller: "Rated Seller", condition: "Condition", conditionNew: "New", conditionUsed: "Used", conditionBroken: "For Parts", conditionRefurbished: "Refurbished", returnable: "Returnable", finalSale: "Final Sale", freeShipping: "Free Shipping", codAvailable: "Cash on Delivery", emiAvailable: "EMI Available", wishlistCount: (n) => `${n} wishlisted`, categoryProductCount: (n, cat) => `${n} in ${cat}` } }), (categoryName || category || brand) && (_jsxs(Row, { gap: "sm", wrap: true, children: [category && _jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category)), className: "inline-flex items-center rounded-full border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] px-[var(--appkit-space-2-5)] py-[var(--appkit-space-1)] text-[length:var(--appkit-text-xs)] font-medium text-[var(--appkit-color-text-muted)] transition-colors hover:border-primary-300 hover:bg-primary-surface hover:text-primary-700 dark:hover:border-primary-700/60 dark:hover:text-primary-400", children: categoryName || category }), !category && categoryName && _jsx(Span, { layout: "inline-flex", size: "xs", weight: "medium", border: "subtle", rounded: "full", padding: "pill-sm-tall", surface: "muted", color: "muted", children: categoryName }), brand && brandSlug && _jsx(Link, { href: String(ROUTES.PUBLIC.BRAND_DETAIL(brandSlug)), className: "inline-flex items-center rounded-full border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] px-[var(--appkit-space-2-5)] py-[var(--appkit-space-1)] text-[length:var(--appkit-text-xs)] font-medium text-[var(--appkit-color-text-muted)] transition-colors hover:border-primary-300 hover:bg-primary-surface hover:text-primary-700 dark:hover:border-primary-700/60 dark:hover:text-primary-400", children: brand }), brand && !brandSlug && _jsx(Span, { layout: "inline-flex", size: "xs", weight: "medium", border: "subtle", rounded: "full", padding: "pill-sm-tall", surface: "muted", color: "muted", children: brand })] })), productFeatures && features.length > 0 && _jsx(FeatureBadgeList, { productFeatureIds: features, features: productFeatures }), !productFeatures && features.length > 0 && (_jsxs(Div, { border: "subtle", surface: "muted", padding: "inline", rounded: "xl", children: [_jsx(Text, { className: "mb-2 tracking-wide", color: "muted", size: "xs", weight: "semibold", transform: "uppercase", children: "About this item" }), _jsx(Ul, { spacing: "comfortable", size: "sm", color: "primary", children: features.map((f, i) => _jsxs(Li, { layout: "flex-start", gap: "2", children: [_jsx(Span, { className: "mt-0.5 flex-shrink-0 text-primary-500", children: "\u2022" }), f] }, i)) })] })), descriptionHtml && _jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "text-[length:var(--appkit-text-sm)] leading-relaxed text-[var(--appkit-color-text-muted)] line-clamp-4" }), safeSeller && (_jsx(Div, { className: `${__P.p3}`, border: "subtle", rounded: "xl", surface: "muted", children: _jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Div, { children: [_jsx(Text, { className: "text-[10px] tracking-wide mb-0.5", color: "faint", transform: "uppercase", children: "Listed by" }), _jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: safeSeller })] }), storeHref && _jsx(Link, { href: storeHref, className: "shrink-0 rounded-lg bg-primary/10 dark:bg-primary/20 px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-xs)] font-semibold text-primary-700 dark:text-primary-300 hover:bg-primary/20 dark:hover:bg-primary/30 transition-colors", children: "Visit Store \u2192" })] }) }))] }));
|
|
@@ -7,7 +7,7 @@ import { getClientSessionAdapter } from "../../../contracts/client-session.js";
|
|
|
7
7
|
import { apiClient } from "../../../http/index.js";
|
|
8
8
|
import { useAuthEvent } from "./useAuthEvent.js";
|
|
9
9
|
import { RealtimeEventStatus } from "../../../react/hooks/useRealtimeEvent.js";
|
|
10
|
-
import { AUTH_ENDPOINTS
|
|
10
|
+
import { AUTH_ENDPOINTS } from "../../../constants/api-endpoints.js";
|
|
11
11
|
export function useCurrentUser(opts) {
|
|
12
12
|
const query = useQuery({
|
|
13
13
|
queryKey: ["auth", "me"],
|
|
@@ -11,11 +11,7 @@
|
|
|
11
11
|
import { NextResponse } from "next/server.js";
|
|
12
12
|
import { getProviders } from "../../../contracts/index.js";
|
|
13
13
|
import { normalizeError } from "../../../errors/normalize.js";
|
|
14
|
-
|
|
15
|
-
const v = url.searchParams.get(key);
|
|
16
|
-
const n = v !== null ? Number(v) : NaN;
|
|
17
|
-
return Number.isFinite(n) ? n : fallback;
|
|
18
|
-
}
|
|
14
|
+
import { numParam } from "../../../utils/search-params.js";
|
|
19
15
|
export async function GET(request) {
|
|
20
16
|
try {
|
|
21
17
|
const url = new URL(request.url);
|