@mohasinac/appkit 2.7.30 → 2.7.32

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.
Files changed (92) hide show
  1. package/dist/_internal/client/features/filters/filter-load-options.d.ts +29 -0
  2. package/dist/_internal/client/features/filters/filter-load-options.js +98 -0
  3. package/dist/_internal/client/features/layout/DashboardLayoutClient.js +1 -1
  4. package/dist/_internal/server/features/classified/adapters.js +1 -1
  5. package/dist/_internal/server/features/digital-code/adapters.js +1 -1
  6. package/dist/_internal/server/features/live/adapters.js +1 -1
  7. package/dist/_internal/shared/actions/action-registry.js +194 -1
  8. package/dist/client.d.ts +2 -0
  9. package/dist/client.js +1 -0
  10. package/dist/constants/api-endpoints.d.ts +6 -0
  11. package/dist/constants/api-endpoints.js +2 -0
  12. package/dist/constants/field-names.d.ts +1 -0
  13. package/dist/constants/field-names.js +1 -0
  14. package/dist/features/about/components/FAQPageView.js +1 -1
  15. package/dist/features/about/components/PublicProfileView.js +2 -2
  16. package/dist/features/account/components/UserSidebar.js +1 -1
  17. package/dist/features/admin/components/AdminAddressEditorView.d.ts +7 -0
  18. package/dist/features/admin/components/AdminAddressEditorView.js +117 -0
  19. package/dist/features/admin/components/AdminCategoryEditorView.js +12 -9
  20. package/dist/features/admin/components/AdminFeatureFlagsView.js +58 -13
  21. package/dist/features/admin/components/AdminProductEditorView.d.ts +3 -1
  22. package/dist/features/admin/components/AdminProductEditorView.js +12 -9
  23. package/dist/features/admin/components/AdminReturnRequestsView.js +5 -4
  24. package/dist/features/admin/components/AdminReviewsView.js +5 -4
  25. package/dist/features/admin/components/AdminSidebar.js +1 -1
  26. package/dist/features/admin/components/index.d.ts +2 -0
  27. package/dist/features/admin/components/index.js +1 -0
  28. package/dist/features/auctions/components/AuctionBidsTable.d.ts +13 -0
  29. package/dist/features/auctions/components/AuctionBidsTable.js +62 -0
  30. package/dist/features/auctions/components/AuctionDetailPageView.js +2 -2
  31. package/dist/features/auctions/components/AuctionFilters.d.ts +7 -1
  32. package/dist/features/auctions/components/AuctionFilters.js +3 -2
  33. package/dist/features/auctions/components/index.d.ts +2 -0
  34. package/dist/features/auctions/components/index.js +1 -0
  35. package/dist/features/blog/components/BlogPostView.js +1 -1
  36. package/dist/features/cart/components/CartView.js +2 -1
  37. package/dist/features/cart/components/CheckoutView.js +2 -1
  38. package/dist/features/categories/components/BrandDetailTabs.d.ts +5 -1
  39. package/dist/features/categories/components/BrandDetailTabs.js +29 -15
  40. package/dist/features/categories/components/CategoryDetailTabs.d.ts +5 -1
  41. package/dist/features/categories/components/CategoryDetailTabs.js +30 -15
  42. package/dist/features/classified/components/ClassifiedFilters.d.ts +5 -1
  43. package/dist/features/classified/components/ClassifiedFilters.js +3 -2
  44. package/dist/features/digital-codes/components/DigitalCodeFilters.d.ts +5 -1
  45. package/dist/features/digital-codes/components/DigitalCodeFilters.js +3 -2
  46. package/dist/features/events/repository/event-entry.repository.d.ts +1 -0
  47. package/dist/features/events/repository/event-entry.repository.js +5 -0
  48. package/dist/features/filters/index.d.ts +2 -0
  49. package/dist/features/filters/index.js +1 -0
  50. package/dist/features/homepage/components/FAQSection.js +1 -1
  51. package/dist/features/homepage/components/FeaturedProductsSection.js +6 -1
  52. package/dist/features/homepage/components/HeroCarousel.js +21 -5
  53. package/dist/features/homepage/components/SectionCarousel.js +3 -1
  54. package/dist/features/homepage/components/ShopByCategorySection.js +2 -2
  55. package/dist/features/layout/AppLayoutShell.d.ts +1 -1
  56. package/dist/features/layout/AppLayoutShell.js +1 -1
  57. package/dist/features/live/components/LiveItemFilters.d.ts +5 -1
  58. package/dist/features/live/components/LiveItemFilters.js +3 -2
  59. package/dist/features/pre-orders/components/PreOrderDetailPageView.js +2 -2
  60. package/dist/features/pre-orders/components/PreOrderFilters.d.ts +7 -1
  61. package/dist/features/pre-orders/components/PreOrderFilters.js +3 -2
  62. package/dist/features/products/columns/productTableColumns.js +1 -1
  63. package/dist/features/products/components/CompareOverlay.d.ts +2 -0
  64. package/dist/features/products/components/CompareOverlay.js +1 -1
  65. package/dist/features/products/components/ProductFilters.d.ts +7 -1
  66. package/dist/features/products/components/ProductFilters.js +3 -2
  67. package/dist/features/products/components/ProductForm.js +6 -6
  68. package/dist/features/products/components/ProductGrid.js +1 -1
  69. package/dist/features/products/repository/products.repository.d.ts +4 -0
  70. package/dist/features/products/repository/products.repository.js +22 -3
  71. package/dist/features/products/schemas/firestore.d.ts +7 -1
  72. package/dist/features/products/types/index.d.ts +4 -0
  73. package/dist/features/reviews/repository/reviews.repository.d.ts +1 -0
  74. package/dist/features/reviews/repository/reviews.repository.js +14 -3
  75. package/dist/features/seller/components/SellerSidebar.js +1 -1
  76. package/dist/features/stores/components/StoreAboutView.js +1 -1
  77. package/dist/features/stores/components/StoreDetailLayoutView.js +16 -2
  78. package/dist/features/stores/components/StoreHeader.js +1 -1
  79. package/dist/index.d.ts +6 -0
  80. package/dist/index.js +6 -0
  81. package/dist/next/routing/route-map.d.ts +14 -0
  82. package/dist/next/routing/route-map.js +7 -0
  83. package/dist/seed/bids-seed-data.js +7 -4
  84. package/dist/seed/events-seed-data.js +23 -0
  85. package/dist/seed/history-seed-data.js +13 -0
  86. package/dist/seed/orders-seed-data.js +108 -1
  87. package/dist/seed/products-auctions-seed-data.js +40 -40
  88. package/dist/seed/products-preorders-seed-data.js +16 -16
  89. package/dist/seed/products-prize-draws-seed-data.js +4 -4
  90. package/dist/seed/products-standard-seed-data.js +202 -202
  91. package/dist/tailwind-utilities.css +1 -1
  92. package/package.json +1 -1
@@ -2,6 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useTranslations } from "next-intl";
4
4
  import { FilterFacetSection } from "../../filters/FilterFacetSection";
5
+ import { AsyncFacetSection } from "../../filters/AsyncFacetSection";
5
6
  import { RangeFilter } from "../../filters/RangeFilter";
6
7
  import { SwitchFilter } from "../../filters/SwitchFilter";
7
8
  import { Div } from "../../../ui";
@@ -89,7 +90,7 @@ export function getProductSortOptions(variant) {
89
90
  return PRODUCT_PUBLIC_SORT_OPTIONS;
90
91
  }
91
92
  }
92
- export function ProductFilters({ table, categoryOptions = [], brandOptions = [], storeOptions = [], sellerOptions, tagOptions = [], sublistingCategoryOptions = [], featureOptions = [], showStatus = false, variant, statusOptions, currencyPrefix = "", showShipping = true, showBundleFilter = true, }) {
93
+ export function ProductFilters({ table, categoryOptions = [], brandOptions = [], storeOptions = [], sellerOptions, tagOptions = [], sublistingCategoryOptions = [], featureOptions = [], showStatus = false, variant, statusOptions, currencyPrefix = "", showShipping = true, showBundleFilter = true, loadCategoryOptions, loadBrandOptions, loadStoreOptions, }) {
93
94
  const resolvedStoreOptions = storeOptions.length > 0 ? storeOptions : (sellerOptions ?? []);
94
95
  const t = useTranslations("filters");
95
96
  const conditionOptions = [
@@ -114,5 +115,5 @@ export function ProductFilters({ table, categoryOptions = [], brandOptions = [],
114
115
  const selectedFeatures = splitPipe(TABLE_KEYS.FEATURES);
115
116
  const resolvedVariant = variant ?? (showStatus ? "admin" : "public");
116
117
  const shouldShowStatus = resolvedVariant !== "public" || showStatus;
117
- return (_jsxs(Div, { children: [categoryOptions.length > 0 && (_jsx(FilterFacetSection, { title: t("category"), options: categoryOptions, selected: selectedCategories, onChange: (vals) => table.set(TABLE_KEYS.CATEGORY, vals.join("|")), searchable: true, defaultCollapsed: categoryOptions.length > 6 })), _jsx(FilterFacetSection, { title: t("condition"), options: conditionOptions, selected: selectedConditions, onChange: (vals) => table.set(TABLE_KEYS.CONDITION, vals.join("|")), searchable: false, defaultCollapsed: false }), _jsx(RangeFilter, { title: t("priceRange"), minValue: table.get(TABLE_KEYS.MIN_PRICE), maxValue: table.get(TABLE_KEYS.MAX_PRICE), onMinChange: (v) => table.set(TABLE_KEYS.MIN_PRICE, v), onMaxChange: (v) => table.set(TABLE_KEYS.MAX_PRICE, v), prefix: currencyPrefix, showSlider: true, minBound: 0, maxBound: 500000, step: 500, minPlaceholder: t("minPrice"), maxPlaceholder: t("maxPrice"), defaultCollapsed: false }), brandOptions.length > 0 && (_jsx(FilterFacetSection, { title: t("brand"), options: brandOptions, selected: selectedBrands, onChange: (vals) => table.set(TABLE_KEYS.BRAND, vals.join("|")), searchable: brandOptions.length > 4, defaultCollapsed: brandOptions.length > 6 })), resolvedStoreOptions.length > 0 && (_jsx(FilterFacetSection, { title: t("store"), options: resolvedStoreOptions, selected: selectedSellers, onChange: (vals) => table.set(TABLE_KEYS.STORE_ID, vals.join("|")), searchable: resolvedStoreOptions.length > 4, defaultCollapsed: resolvedStoreOptions.length > 6 })), showShipping && (_jsx(SwitchFilter, { title: t("shipping"), label: t("freeShippingOnly"), checked: table.get(TABLE_KEYS.FREE_SHIPPING) === "true", onChange: (v) => table.set(TABLE_KEYS.FREE_SHIPPING, v ? "true" : ""), defaultCollapsed: false })), tagOptions.length > 0 && (_jsx(FilterFacetSection, { title: t("tags"), options: tagOptions, selected: selectedTags, onChange: (vals) => table.set(TABLE_KEYS.TAGS, vals.join("|")), searchable: tagOptions.length > 4, defaultCollapsed: tagOptions.length > 6 })), shouldShowStatus && (_jsx(FilterFacetSection, { title: t("status"), options: statusOptions ?? defaultStatusOptions, selected: selectedStatuses, onChange: (vals) => table.set(TABLE_KEYS.STATUS, vals.join("|")), searchable: false, defaultCollapsed: false })), sublistingCategoryOptions.length > 0 && (_jsx(FilterFacetSection, { title: "Sublisting Type", options: sublistingCategoryOptions, selected: selectedSublistings, onChange: (vals) => table.set(TABLE_KEYS.SUBLISTING_CATEGORY, vals.join("|")), searchable: sublistingCategoryOptions.length > 4, defaultCollapsed: false })), showBundleFilter && (_jsx(SwitchFilter, { title: "Bundles", label: "Part of a bundle only", checked: table.get(TABLE_KEYS.IS_PART_OF_BUNDLE) === "true", onChange: (v) => table.set(TABLE_KEYS.IS_PART_OF_BUNDLE, v ? "true" : ""), defaultCollapsed: false })), featureOptions.length > 0 && (_jsx(FilterFacetSection, { title: "Features", options: featureOptions, selected: selectedFeatures, onChange: (vals) => table.set(TABLE_KEYS.FEATURES, vals.join("|")), searchable: featureOptions.length > 4, defaultCollapsed: featureOptions.length > 4 }))] }));
118
+ return (_jsxs(Div, { children: [loadCategoryOptions ? (_jsx(AsyncFacetSection, { title: t("category"), loadOptions: loadCategoryOptions, selected: selectedCategories, onChange: (vals) => table.set(TABLE_KEYS.CATEGORY, vals.join("|")), defaultCollapsed: false })) : categoryOptions.length > 0 ? (_jsx(FilterFacetSection, { title: t("category"), options: categoryOptions, selected: selectedCategories, onChange: (vals) => table.set(TABLE_KEYS.CATEGORY, vals.join("|")), searchable: true, defaultCollapsed: categoryOptions.length > 6 })) : null, _jsx(FilterFacetSection, { title: t("condition"), options: conditionOptions, selected: selectedConditions, onChange: (vals) => table.set(TABLE_KEYS.CONDITION, vals.join("|")), searchable: false, defaultCollapsed: false }), _jsx(RangeFilter, { title: t("priceRange"), minValue: table.get(TABLE_KEYS.MIN_PRICE), maxValue: table.get(TABLE_KEYS.MAX_PRICE), onMinChange: (v) => table.set(TABLE_KEYS.MIN_PRICE, v), onMaxChange: (v) => table.set(TABLE_KEYS.MAX_PRICE, v), prefix: currencyPrefix, showSlider: true, minBound: 0, maxBound: 500000, step: 500, minPlaceholder: t("minPrice"), maxPlaceholder: t("maxPrice"), defaultCollapsed: false }), loadBrandOptions ? (_jsx(AsyncFacetSection, { title: t("brand"), loadOptions: loadBrandOptions, selected: selectedBrands, onChange: (vals) => table.set(TABLE_KEYS.BRAND, vals.join("|")), defaultCollapsed: false })) : brandOptions.length > 0 ? (_jsx(FilterFacetSection, { title: t("brand"), options: brandOptions, selected: selectedBrands, onChange: (vals) => table.set(TABLE_KEYS.BRAND, vals.join("|")), searchable: brandOptions.length > 4, defaultCollapsed: brandOptions.length > 6 })) : null, loadStoreOptions ? (_jsx(AsyncFacetSection, { title: t("store"), loadOptions: loadStoreOptions, selected: selectedSellers, onChange: (vals) => table.set(TABLE_KEYS.STORE_ID, vals.join("|")), defaultCollapsed: false })) : resolvedStoreOptions.length > 0 ? (_jsx(FilterFacetSection, { title: t("store"), options: resolvedStoreOptions, selected: selectedSellers, onChange: (vals) => table.set(TABLE_KEYS.STORE_ID, vals.join("|")), searchable: resolvedStoreOptions.length > 4, defaultCollapsed: resolvedStoreOptions.length > 6 })) : null, showShipping && (_jsx(SwitchFilter, { title: t("shipping"), label: t("freeShippingOnly"), checked: table.get(TABLE_KEYS.FREE_SHIPPING) === "true", onChange: (v) => table.set(TABLE_KEYS.FREE_SHIPPING, v ? "true" : ""), defaultCollapsed: false })), tagOptions.length > 0 && (_jsx(FilterFacetSection, { title: t("tags"), options: tagOptions, selected: selectedTags, onChange: (vals) => table.set(TABLE_KEYS.TAGS, vals.join("|")), searchable: tagOptions.length > 4, defaultCollapsed: tagOptions.length > 6 })), shouldShowStatus && (_jsx(FilterFacetSection, { title: t("status"), options: statusOptions ?? defaultStatusOptions, selected: selectedStatuses, onChange: (vals) => table.set(TABLE_KEYS.STATUS, vals.join("|")), searchable: false, defaultCollapsed: false })), sublistingCategoryOptions.length > 0 && (_jsx(FilterFacetSection, { title: "Sublisting Type", options: sublistingCategoryOptions, selected: selectedSublistings, onChange: (vals) => table.set(TABLE_KEYS.SUBLISTING_CATEGORY, vals.join("|")), searchable: sublistingCategoryOptions.length > 4, defaultCollapsed: false })), showBundleFilter && (_jsx(SwitchFilter, { title: "Bundles", label: "Part of a bundle only", checked: table.get(TABLE_KEYS.IS_PART_OF_BUNDLE) === "true", onChange: (v) => table.set(TABLE_KEYS.IS_PART_OF_BUNDLE, v ? "true" : ""), defaultCollapsed: false })), featureOptions.length > 0 && (_jsx(FilterFacetSection, { title: "Features", options: featureOptions, selected: selectedFeatures, onChange: (vals) => table.set(TABLE_KEYS.FEATURES, vals.join("|")), searchable: featureOptions.length > 4, defaultCollapsed: featureOptions.length > 4 }))] }));
118
119
  }
@@ -43,7 +43,7 @@ export function ProductForm({ product, onChange, isReadonly = false, renderDescr
43
43
  type: "product-image",
44
44
  index: galleryIndexRef.current,
45
45
  name: product.title || "product",
46
- category: product.category || "uncategorized",
46
+ category: (product.categorySlugs?.[0] ?? product.category) || "uncategorized",
47
47
  store: product.storeName || "store",
48
48
  });
49
49
  };
@@ -52,7 +52,7 @@ export function ProductForm({ product, onChange, isReadonly = false, renderDescr
52
52
  type: "product-video",
53
53
  index: 1,
54
54
  name: product.title || "product",
55
- category: product.category || "uncategorized",
55
+ category: (product.categorySlugs?.[0] ?? product.category) || "uncategorized",
56
56
  store: product.storeName || "store",
57
57
  });
58
58
  };
@@ -63,10 +63,10 @@ export function ProductForm({ product, onChange, isReadonly = false, renderDescr
63
63
  editable: !isReadonly,
64
64
  }) })) : (_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { className: "text-sm font-medium text-zinc-900 dark:text-zinc-100", children: t("formDescription") }), _jsx(RichTextEditor, { value: normalizeRichTextHtml(product.description || ""), onChange: (value) => update({ description: value }), disabled: isReadonly, placeholder: "Enter product description" }), _jsx(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400", children: "Rich text is supported for product descriptions." })] })), _jsxs(FormGroup, { columns: 2, children: [renderCategorySelector ? (_jsx(_Fragment, { children: renderCategorySelector({
65
65
  label: t("formCategory"),
66
- value: product.category || "",
67
- onChange: (value) => update({ category: value }),
66
+ value: (product.categorySlugs?.[0] ?? product.category) || "",
67
+ onChange: (value) => update({ categorySlugs: value ? [value] : [] }),
68
68
  disabled: isReadonly,
69
- }) })) : (_jsx(FormField, { name: "category", label: t("formCategory"), type: "text", value: product.category || "", onChange: (value) => update({ category: value }), disabled: isReadonly })), _jsx(FormField, { name: "subcategory", label: t("formSubcategory"), type: "text", value: product.subcategory || "", onChange: (value) => update({ subcategory: value }), disabled: isReadonly, placeholder: "e.g. Smartphones" })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { className: "text-sm font-medium text-zinc-900 dark:text-zinc-100", children: t("formBrand") }), _jsx("div", { className: "flex gap-3 flex-wrap", children: ["single", "unbranded", "mixed"].map((mode) => (_jsxs("label", { className: "flex items-center gap-1.5 cursor-pointer select-none text-sm text-zinc-700 dark:text-zinc-300", children: [_jsx("input", { type: "radio", name: "brandMode", value: mode, checked: (product.brandMode ?? "single") === mode, onChange: () => {
69
+ }) })) : (_jsx(FormField, { name: "category", label: t("formCategory"), type: "text", value: (product.categorySlugs?.[0] ?? product.category) || "", onChange: (value) => update({ categorySlugs: value ? [value] : [] }), disabled: isReadonly })), _jsx(FormField, { name: "subcategory", label: t("formSubcategory"), type: "text", value: product.subcategory || "", onChange: (value) => update({ subcategory: value }), disabled: isReadonly, placeholder: "e.g. Smartphones" })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { className: "text-sm font-medium text-zinc-900 dark:text-zinc-100", children: t("formBrand") }), _jsx("div", { className: "flex gap-3 flex-wrap", children: ["single", "unbranded", "mixed"].map((mode) => (_jsxs("label", { className: "flex items-center gap-1.5 cursor-pointer select-none text-sm text-zinc-700 dark:text-zinc-300", children: [_jsx("input", { type: "radio", name: "brandMode", value: mode, checked: (product.brandMode ?? "single") === mode, onChange: () => {
70
70
  if (mode === "unbranded") {
71
71
  update({ brandMode: "unbranded", brand: undefined, brands: [] });
72
72
  }
@@ -88,7 +88,7 @@ export function ProductForm({ product, onChange, isReadonly = false, renderDescr
88
88
  type: "product-image",
89
89
  index: 1,
90
90
  name: product.title || "product",
91
- category: product.category || "uncategorized",
91
+ category: (product.categorySlugs?.[0] ?? product.category) || "uncategorized",
92
92
  store: product.storeName || "store",
93
93
  }), onChange: (url) => update({ mainImage: url }), label: t("formMainImage"), helperText: "Recommended: 800x800px (1:1)" })), isReadonly && product.mainImage && (_jsx(FormField, { name: "mainImage", label: t("formMainImage"), type: "text", value: product.mainImage, onChange: () => { }, disabled: true })), !isReadonly && (_jsx(MediaUploadList, { label: t("formGalleryImages"), value: galleryImages, onChange: (fields) => update({ images: fields.map((f) => f.url) }), onUpload: handleGalleryUpload, accept: "image/*", maxItems: 5, maxSizeMB: 10, helperText: t("formGalleryImagesHelper"), onAbort: onMediaAbort })), !isReadonly && (_jsx(MediaUploadField, { label: t("formVideo"), value: product.video?.url || "", onChange: (url) => update({
94
94
  video: url
@@ -59,7 +59,7 @@ export function ProductCard({ product, href, onClick, onAddToWishlist, isWishlis
59
59
  isWishlisted
60
60
  ? "bg-rose-500 text-white hover:bg-rose-600"
61
61
  : "bg-white/90 dark:bg-slate-800/90 text-zinc-500 dark:text-zinc-400 hover:text-rose-500 hover:bg-white dark:hover:bg-slate-800",
62
- ].join(" "), children: _jsx("svg", { className: "h-4 w-4", fill: isWishlisted ? "currentColor" : "none", stroke: "currentColor", strokeWidth: 2, viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12z" }) }) }))] }), _jsxs(Div, { className: "flex flex-1 flex-col p-3 pt-2.5", children: [_jsx(Text, { className: `${THEME_CONSTANTS.utilities.textClamp2} text-sm font-semibold text-zinc-900 dark:text-white leading-snug`, children: product.title }), (product.categoryName || product.brand) && (_jsxs(Row, { className: "mt-1 gap-1 flex-wrap", children: [product.categoryName && (_jsxs(Span, { className: "inline-flex items-center gap-0.5 rounded-full bg-zinc-100 dark:bg-zinc-800 px-1.5 py-0.5 text-[10px] text-zinc-500 dark:text-zinc-400 truncate max-w-[100px]", children: [_jsx("svg", { className: "h-2.5 w-2.5 flex-shrink-0", fill: "none", stroke: "currentColor", strokeWidth: 2, viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M3 7a2 2 0 012-2h14a2 2 0 012 2v10a2 2 0 01-2 2H5a2 2 0 01-2-2V7z" }) }), product.categoryName] })), product.brand && (_jsxs(Span, { className: "inline-flex items-center gap-0.5 rounded-full bg-zinc-100 dark:bg-zinc-800 px-1.5 py-0.5 text-[10px] text-zinc-500 dark:text-zinc-400 truncate max-w-[90px]", children: [_jsx("svg", { className: "h-2.5 w-2.5 flex-shrink-0", fill: "none", stroke: "currentColor", strokeWidth: 2, viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" }) }), product.brand] }))] })), (() => {
62
+ ].join(" "), children: _jsx("svg", { className: "h-4 w-4", fill: isWishlisted ? "currentColor" : "none", stroke: "currentColor", strokeWidth: 2, viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12z" }) }) }))] }), _jsxs(Div, { className: "flex flex-1 flex-col p-3 pt-2.5", children: [_jsx(Text, { className: `${THEME_CONSTANTS.utilities.textClamp2} text-sm font-semibold text-zinc-900 dark:text-white leading-snug`, children: product.title }), ((Array.isArray(product.categoryNames) ? product.categoryNames[0] : product.categoryName) || product.brand) && (_jsxs(Row, { className: "mt-1 gap-1 flex-wrap", children: [(Array.isArray(product.categoryNames) ? product.categoryNames[0] : product.categoryName) && (_jsxs(Span, { className: "inline-flex items-center gap-0.5 rounded-full bg-zinc-100 dark:bg-zinc-800 px-1.5 py-0.5 text-[10px] text-zinc-500 dark:text-zinc-400 truncate max-w-[100px]", children: [_jsx("svg", { className: "h-2.5 w-2.5 flex-shrink-0", fill: "none", stroke: "currentColor", strokeWidth: 2, viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M3 7a2 2 0 012-2h14a2 2 0 012 2v10a2 2 0 01-2 2H5a2 2 0 01-2-2V7z" }) }), (Array.isArray(product.categoryNames) ? product.categoryNames[0] : product.categoryName)] })), product.brand && (_jsxs(Span, { className: "inline-flex items-center gap-0.5 rounded-full bg-zinc-100 dark:bg-zinc-800 px-1.5 py-0.5 text-[10px] text-zinc-500 dark:text-zinc-400 truncate max-w-[90px]", children: [_jsx("svg", { className: "h-2.5 w-2.5 flex-shrink-0", fill: "none", stroke: "currentColor", strokeWidth: 2, viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" }) }), product.brand] }))] })), (() => {
63
63
  const seller = safeDisplayName(product.storeName, "");
64
64
  return seller ? (_jsxs(Text, { className: "mt-0.5 text-[11px] text-zinc-400 dark:text-zinc-500 truncate", children: ["by ", seller] })) : null;
65
65
  })(), product.rating !== undefined && (_jsxs(Row, { className: "mt-1 gap-1 items-center", children: [_jsx(Span, { className: "text-[11px] text-amber-400", children: "\u2605" }), _jsxs(Span, { className: "text-[11px] text-zinc-500 dark:text-zinc-400", children: [product.rating.toFixed(1), product.reviewCount ? ` (${product.reviewCount})` : ""] })] })), _jsxs(Div, { className: "mt-auto pt-2", children: [_jsxs(Row, { className: "items-baseline gap-2", children: [_jsx(Span, { className: "text-base font-bold text-primary dark:text-primary-400", children: formatCurrency(product.price, getDefaultCurrency()) }), product.originalPrice && product.originalPrice > product.price && (_jsx(Span, { className: "text-xs text-zinc-400 line-through dark:text-zinc-500", children: formatCurrency(product.originalPrice, getDefaultCurrency()) }))] }), (() => {
@@ -62,6 +62,10 @@ export declare class ProductRepository extends BaseRepository<ProductDocument> {
62
62
  canFilter: boolean;
63
63
  canSort: boolean;
64
64
  };
65
+ categorySlugs: {
66
+ canFilter: boolean;
67
+ canSort: boolean;
68
+ };
65
69
  subcategory: {
66
70
  canFilter: boolean;
67
71
  canSort: boolean;
@@ -71,7 +71,12 @@ export class ProductRepository extends BaseRepository {
71
71
  return product;
72
72
  }
73
73
  mapDoc(snap) {
74
- return super.mapDoc(snap);
74
+ const doc = super.mapDoc(snap);
75
+ // Backward compat: normalise legacy `category` string into `categorySlugs[]`.
76
+ if (!doc.categorySlugs || doc.categorySlugs.length === 0) {
77
+ doc.categorySlugs = doc.category ? [doc.category] : [];
78
+ }
79
+ return doc;
75
80
  }
76
81
  async update(id, data) {
77
82
  this.cacheInvalidateForId(id);
@@ -116,7 +121,10 @@ export class ProductRepository extends BaseRepository {
116
121
  return this.findBy(PRODUCT_FIELDS.FEATURED, true);
117
122
  }
118
123
  async findByCategory(category) {
119
- return this.findBy(PRODUCT_FIELDS.CATEGORY, category);
124
+ const snap = await this.getCollection()
125
+ .where(PRODUCT_FIELDS.CATEGORY_SLUGS, "array-contains", category)
126
+ .get();
127
+ return snap.docs.map((d) => this.mapDoc(d));
120
128
  }
121
129
  async findBySlug(slug) {
122
130
  const key = this.cacheKeyBySlug(slug);
@@ -313,7 +321,7 @@ export class ProductRepository extends BaseRepository {
313
321
  baseQuery = baseQuery.where(PRODUCT_FIELDS.STORE_ID, "==", opts.storeId);
314
322
  }
315
323
  if (opts?.categoriesIn && opts.categoriesIn.length > 0) {
316
- baseQuery = baseQuery.where(PRODUCT_FIELDS.CATEGORY, "in", opts.categoriesIn);
324
+ baseQuery = baseQuery.where(PRODUCT_FIELDS.CATEGORY_SLUGS, "array-contains-any", opts.categoriesIn);
317
325
  }
318
326
  return this.sieveQuery(model, ProductRepository.SIEVE_FIELDS, {
319
327
  baseQuery,
@@ -410,6 +418,7 @@ ProductRepository.SIEVE_FIELDS = {
410
418
  title: { canFilter: true, canSort: true },
411
419
  slug: { canFilter: true, canSort: false },
412
420
  category: { canFilter: true, canSort: true },
421
+ categorySlugs: { canFilter: true, canSort: false },
413
422
  subcategory: { canFilter: true, canSort: true },
414
423
  brand: { canFilter: true, canSort: true },
415
424
  condition: { canFilter: true, canSort: false },
@@ -504,6 +513,16 @@ ProductRepository.FILTER_ALIASES = {
504
513
  return "";
505
514
  }
506
515
  },
516
+ /**
517
+ * Backward-compat: `category==slug` → `categorySlugs @= slug`
518
+ * Allows existing consumers using the old single-value filter to keep
519
+ * working after the category → categorySlugs[] migration (Track C).
520
+ */
521
+ category: (value, operator) => {
522
+ if (operator !== "==")
523
+ return "";
524
+ return `${PRODUCT_FIELDS.CATEGORY_SLUGS}@=${value}`;
525
+ },
507
526
  /** Shorthand for the homepage promoted strip. `promoted==true` only. */
508
527
  promoted: (value, operator) => {
509
528
  if (operator !== "==" || value !== "true")
@@ -111,7 +111,13 @@ export interface ProductDocument {
111
111
  seoTitle?: string;
112
112
  seoDescription?: string;
113
113
  seoKeywords?: string[];
114
- category: string;
114
+ /** Primary category FK slugs (1-to-many). Use categorySlugs[0] for display. */
115
+ categorySlugs: string[];
116
+ /** Display-name cache parallel to categorySlugs[]. */
117
+ categoryNames?: string[];
118
+ /** @deprecated Read-only alias — normalised from legacy docs on load. */
119
+ category?: string;
120
+ /** @deprecated Read-only alias. */
115
121
  categoryName?: string;
116
122
  subcategory?: string;
117
123
  brand?: string;
@@ -41,8 +41,12 @@ export interface ProductItem {
41
41
  isPromoted?: boolean;
42
42
  currentBid?: number;
43
43
  availableQuantity?: number;
44
+ /** @deprecated Use categorySlugs[0] */
44
45
  category?: string;
46
+ /** @deprecated Use categoryNames[0] */
45
47
  categoryName?: string;
48
+ categorySlugs?: string[];
49
+ categoryNames?: string[];
46
50
  categorySlug?: string;
47
51
  sellerAvatar?: string;
48
52
  status: ProductStatus;
@@ -8,6 +8,7 @@ export interface ReviewRatingAggregate {
8
8
  }
9
9
  declare class ReviewRepository extends BaseRepository<ReviewDocument> {
10
10
  constructor();
11
+ private decryptSieveResult;
11
12
  private encryptReviewData;
12
13
  protected mapDoc<D = ReviewDocument>(snap: import("../../../providers/db-firebase").DocumentSnapshot): D;
13
14
  create(input: ReviewCreateInput): Promise<ReviewDocument>;
@@ -13,6 +13,14 @@ class ReviewRepository extends BaseRepository {
13
13
  constructor() {
14
14
  super(REVIEW_COLLECTION);
15
15
  }
16
+ decryptSieveResult(result) {
17
+ return {
18
+ ...result,
19
+ items: result.items.map((item) => decryptPiiFields(item, [
20
+ ...REVIEW_PII_FIELDS,
21
+ ])),
22
+ };
23
+ }
16
24
  encryptReviewData(data) {
17
25
  let encrypted = encryptPiiFields(data, [...REVIEW_PII_FIELDS]);
18
26
  encrypted = addPiiIndices(data, REVIEW_PII_INDEX_MAP);
@@ -181,25 +189,28 @@ class ReviewRepository extends BaseRepository {
181
189
  }
182
190
  async listForProduct(productId, model) {
183
191
  const baseQuery = this.getCollection().where(REVIEW_FIELDS.PRODUCT_ID, "==", productId);
184
- return this.sieveQuery(model, ReviewRepository.SIEVE_FIELDS, {
192
+ const result = await this.sieveQuery(model, ReviewRepository.SIEVE_FIELDS, {
185
193
  baseQuery,
186
194
  defaultPageSize: 10,
187
195
  maxPageSize: 50,
188
196
  });
197
+ return this.decryptSieveResult(result);
189
198
  }
190
199
  async listForStore(storeId, model) {
191
200
  const baseQuery = this.getCollection().where(REVIEW_FIELDS.STORE_ID, "==", storeId);
192
- return this.sieveQuery(model, ReviewRepository.SIEVE_FIELDS, {
201
+ const result = await this.sieveQuery(model, ReviewRepository.SIEVE_FIELDS, {
193
202
  baseQuery,
194
203
  defaultPageSize: 10,
195
204
  maxPageSize: 100,
196
205
  });
206
+ return this.decryptSieveResult(result);
197
207
  }
198
208
  async listAll(model) {
199
- return this.sieveQuery(model, ReviewRepository.SIEVE_FIELDS, {
209
+ const result = await this.sieveQuery(model, ReviewRepository.SIEVE_FIELDS, {
200
210
  defaultPageSize: 50,
201
211
  maxPageSize: 200,
202
212
  });
213
+ return this.decryptSieveResult(result);
203
214
  }
204
215
  }
205
216
  ReviewRepository.SIEVE_FIELDS = {
@@ -56,7 +56,7 @@ export function StoreSidebar({ items, groups, activeHref, storeName, storeLogoUR
56
56
  height: "calc(100vh - var(--header-height, 3.5rem))",
57
57
  width: "18rem",
58
58
  transform: desktopOpen ? "translateX(0)" : "translateX(calc(-100% + 1.25rem))",
59
- }, children: [_jsxs("div", { className: "flex-1 bg-white dark:bg-slate-950 border-r border-zinc-200 dark:border-slate-700 flex flex-col overflow-hidden shadow-xl", children: [_jsx("div", { className: "px-4 py-3.5 border-b border-zinc-100 dark:border-slate-800 shrink-0", children: _jsxs("div", { className: "flex items-center gap-3 min-w-0", children: [storeLogoURL ? (_jsx(Div, { role: "img", "aria-label": storeName, className: CLS_STORE_AVATAR, style: { backgroundImage: `url(${storeLogoURL})` } })) : (_jsx(Div, { className: CLS_STORE_FALLBACK, children: storeName?.[0]?.toUpperCase() })), _jsx(Text, { className: CLS_STORE_NAME, children: storeName || panelTitle })] }) }), _jsx("div", { className: "flex-1 overflow-y-auto", children: navContent })] }), _jsx("button", { type: "button", onClick: handleToggle, "aria-label": desktopOpen ? "Collapse sidebar" : "Expand sidebar", className: "w-5 shrink-0 bg-primary dark:bg-secondary hover:bg-primary-600 dark:hover:bg-secondary-600 flex items-center justify-center transition-colors rounded-r-md shadow-md", children: _jsx("svg", { className: "w-3 h-3 text-white", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: desktopOpen ? (_jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2.5, d: "M11 19l-7-7 7-7m8 14l-7-7 7-7" })) : (_jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2.5, d: "M13 5l7 7-7 7M5 5l7 7-7 7" })) }) })] }), _jsx("div", { className: "md:hidden", children: _jsx(BottomSheet, { open: mobileOpen, onClose: close, title: panelTitle, children: groups ? (_jsx(GroupsContent, { groups: groups, activeHref: activeHref, storeName: storeName, storeLogoURL: storeLogoURL, onItemClick: close })) : (_jsx(FlatContent, { items: items, activeHref: activeHref, storeName: storeName, storeLogoURL: storeLogoURL, onItemClick: close })) }) })] }));
59
+ }, children: [_jsxs("div", { className: "flex-1 bg-white dark:bg-slate-950 border-r border-zinc-200 dark:border-slate-700 flex flex-col overflow-hidden shadow-xl", children: [_jsx("div", { className: "px-4 py-3.5 border-b border-zinc-100 dark:border-slate-800 shrink-0", children: _jsxs("div", { className: "flex items-center gap-3 min-w-0", children: [storeLogoURL ? (_jsx(Div, { role: "img", "aria-label": storeName, className: CLS_STORE_AVATAR, style: { backgroundImage: `url(${storeLogoURL})` } })) : (_jsx(Div, { className: CLS_STORE_FALLBACK, children: storeName?.[0]?.toUpperCase() })), _jsx(Text, { className: CLS_STORE_NAME, children: storeName || panelTitle })] }) }), _jsx("div", { className: "flex-1 overflow-y-auto", children: navContent })] }), _jsx("button", { type: "button", onClick: handleToggle, "aria-label": desktopOpen ? "Collapse sidebar" : "Expand sidebar", className: "w-5 shrink-0 flex items-center justify-center transition-opacity rounded-r-md shadow-md hover:opacity-85", style: { background: "linear-gradient(to bottom, var(--appkit-color-cobalt-700), var(--appkit-color-cobalt))" }, children: _jsx("svg", { className: "w-3 h-3 text-white", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: desktopOpen ? (_jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2.5, d: "M11 19l-7-7 7-7m8 14l-7-7 7-7" })) : (_jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2.5, d: "M13 5l7 7-7 7M5 5l7 7-7 7" })) }) })] }), _jsx("div", { className: "md:hidden", children: _jsx(BottomSheet, { open: mobileOpen, onClose: close, title: panelTitle, children: groups ? (_jsx(GroupsContent, { groups: groups, activeHref: activeHref, storeName: storeName, storeLogoURL: storeLogoURL, onItemClick: close })) : (_jsx(FlatContent, { items: items, activeHref: activeHref, storeName: storeName, storeLogoURL: storeLogoURL, onItemClick: close })) }) })] }));
60
60
  }
61
61
  return (_jsxs(_Fragment, { children: [mounted && mobileOpen &&
62
62
  createPortal(_jsx(DrawerPanel, { title: panelTitle, onClose: close, children: navContent }), document.body), _jsx("div", { className: "md:hidden", children: _jsx(BottomSheet, { open: mobileOpen, onClose: close, title: panelTitle, children: groups ? (_jsx(GroupsContent, { groups: groups, activeHref: activeHref, storeName: storeName, storeLogoURL: storeLogoURL, onItemClick: close })) : (_jsx(FlatContent, { items: items, activeHref: activeHref, storeName: storeName, storeLogoURL: storeLogoURL, onItemClick: close })) }) })] }));
@@ -10,5 +10,5 @@ export function StoreAboutView({ store, labels = {}, renderSocialLinks, renderSt
10
10
  month: "long",
11
11
  })
12
12
  : null;
13
- return (_jsxs(Div, { className: `space-y-6 py-6 ${className}`, children: [store.bio && (_jsxs(Div, { children: [_jsx(Heading, { level: 2, className: "text-lg font-semibold mb-2", children: labels.aboutTitle ?? "About" }), _jsx(RichText, { html: normalizeRichTextHtml(store.bio), className: "text-neutral-600" })] })), renderStats?.(store), _jsxs(Div, { className: "space-y-3 text-sm", children: [store.location && (_jsxs(Div, { className: "flex gap-2", children: [_jsx(Text, { className: CLS_LABEL, children: labels.locationLabel ?? "Location" }), _jsx(Text, { className: "text-neutral-600", children: store.location })] })), joinDate && (_jsxs(Div, { className: "flex gap-2", children: [_jsx(Text, { className: CLS_LABEL, children: labels.memberSinceLabel ?? "Member since" }), _jsx(Text, { className: "text-neutral-600", children: joinDate })] })), store.website && (_jsxs(Div, { className: "flex gap-2", children: [_jsx(Text, { className: CLS_LABEL, children: labels.websiteLabel ?? "Website" }), _jsx("a", { href: store.website, target: "_blank", rel: "noopener noreferrer", className: "text-primary hover:underline break-all", children: store.website })] }))] }), store.socialLinks && renderSocialLinks?.(store.socialLinks), store.returnPolicy && (_jsxs(Div, { children: [_jsx(Heading, { level: 3, className: "text-base font-semibold mb-2", children: labels.returnPolicyLabel ?? "Return Policy" }), _jsx(RichText, { html: normalizeRichTextHtml(store.returnPolicy), className: "text-neutral-600" })] })), store.shippingPolicy && (_jsxs(Div, { children: [_jsx(Heading, { level: 3, className: "text-base font-semibold mb-2", children: labels.shippingPolicyLabel ?? "Shipping Policy" }), _jsx(RichText, { html: normalizeRichTextHtml(store.shippingPolicy), className: "text-neutral-600" })] })), store.isVacationMode && (_jsxs(Div, { className: "bg-yellow-50 border border-yellow-200 rounded-lg p-4", children: [_jsx(Text, { className: "text-yellow-800 font-medium", children: labels.vacationModeLabel ?? "This store is on vacation mode" }), store.vacationMessage && (_jsx(Text, { className: "text-yellow-700 mt-1", children: store.vacationMessage }))] }))] }));
13
+ return (_jsxs(Div, { className: `space-y-6 py-6 ${className}`, children: [store.bio && (_jsxs(Div, { children: [_jsx(Heading, { level: 2, className: "text-lg font-semibold mb-2", children: labels.aboutTitle ?? "About" }), _jsx(RichText, { html: normalizeRichTextHtml(store.bio) })] })), renderStats?.(store), _jsxs(Div, { className: "space-y-3 text-sm", children: [store.location && (_jsxs(Div, { className: "flex gap-2", children: [_jsx(Text, { className: CLS_LABEL, children: labels.locationLabel ?? "Location" }), _jsx(Text, { className: "", children: store.location })] })), joinDate && (_jsxs(Div, { className: "flex gap-2", children: [_jsx(Text, { className: CLS_LABEL, children: labels.memberSinceLabel ?? "Member since" }), _jsx(Text, { className: "", children: joinDate })] })), store.website && (_jsxs(Div, { className: "flex gap-2", children: [_jsx(Text, { className: CLS_LABEL, children: labels.websiteLabel ?? "Website" }), _jsx("a", { href: store.website, target: "_blank", rel: "noopener noreferrer", className: "text-primary hover:underline break-all", children: store.website })] }))] }), store.socialLinks && renderSocialLinks?.(store.socialLinks), store.returnPolicy && (_jsxs(Div, { children: [_jsx(Heading, { level: 3, className: "text-base font-semibold mb-2", children: labels.returnPolicyLabel ?? "Return Policy" }), _jsx(RichText, { html: normalizeRichTextHtml(store.returnPolicy) })] })), store.shippingPolicy && (_jsxs(Div, { children: [_jsx(Heading, { level: 3, className: "text-base font-semibold mb-2", children: labels.shippingPolicyLabel ?? "Shipping Policy" }), _jsx(RichText, { html: normalizeRichTextHtml(store.shippingPolicy) })] })), store.isVacationMode && (_jsxs(Div, { className: "bg-yellow-50 border border-yellow-200 rounded-lg p-4", children: [_jsx(Text, { className: "text-yellow-800 font-medium", children: labels.vacationModeLabel ?? "This store is on vacation mode" }), store.vacationMessage && (_jsx(Text, { className: "text-yellow-700 mt-1", children: store.vacationMessage }))] }))] }));
14
14
  }
@@ -1,9 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { cache } from "react";
3
- import { storeRepository, productRepository, categoriesRepository } from "../../../repositories";
3
+ import { storeRepository, productRepository, categoriesRepository, siteSettingsRepository } from "../../../repositories";
4
4
  import { ROUTES } from "../../../next";
5
5
  import { Container, Main, Section, Text } from "../../../ui";
6
6
  import { STORE_PAGE_TABS } from "../../products/constants/listing-tabs";
7
+ import { isListingTypeEnabled, isCategoryTypeEnabled } from "../../../_internal/shared/listing-types/feature-flags";
7
8
  import { StoreHeader } from "./StoreHeader";
8
9
  import { StoreNavTabs } from "./StoreNavTabs";
9
10
  const STORE_LISTING_HREF = {
@@ -25,6 +26,7 @@ export async function StoreDetailLayoutView({ storeSlug, activeTab, children, })
25
26
  return (_jsx(Main, { children: _jsx(Section, { className: "py-20", children: _jsx(Container, { size: "md", children: _jsx(Text, { className: "text-center text-zinc-500", children: "Store not found." }) }) }) }));
26
27
  }
27
28
  const storeId = store?.id;
29
+ const settings = await siteSettingsRepository.findById("global").catch(() => null);
28
30
  const [productsCount, auctionsCount, preOrdersCount, prizeDrawsCount, bundlesCount] = storeId
29
31
  ? await Promise.all([
30
32
  productRepository
@@ -58,8 +60,20 @@ export async function StoreDetailLayoutView({ storeSlug, activeTab, children, })
58
60
  "prize-draws": prizeDrawsCount,
59
61
  bundles: bundlesCount,
60
62
  };
63
+ const TAB_LISTING_TYPE = {
64
+ products: "standard",
65
+ auctions: "auction",
66
+ "pre-orders": "pre-order",
67
+ "prize-draws": "prize-draw",
68
+ };
69
+ const visibleStoreTabs = STORE_PAGE_TABS.filter((tab) => {
70
+ if (tab.id === "bundles")
71
+ return isCategoryTypeEnabled("bundle", settings);
72
+ const lt = TAB_LISTING_TYPE[tab.id];
73
+ return lt ? isListingTypeEnabled(lt, settings) : true;
74
+ });
61
75
  const tabs = [
62
- ...STORE_PAGE_TABS.map((tab) => ({
76
+ ...visibleStoreTabs.map((tab) => ({
63
77
  value: tab.id,
64
78
  label: tabLabel(tab.label, listingCounts[tab.id]),
65
79
  href: STORE_LISTING_HREF[tab.id](storeSlug),
@@ -3,7 +3,7 @@ import { Button, Div, Heading, RichText, Row, Section, Span, Text, } from "../..
3
3
  import { normalizeRichTextHtml } from "../../../utils/string.formatter";
4
4
  import { ShareButton } from "../../products/components/ShareButton";
5
5
  export function StoreHeader({ store, labels = {}, onFollow, className = "", }) {
6
- return (_jsxs(Section, { className: `bg-white dark:bg-slate-900 border-b border-gray-200 dark:border-slate-700 ${className}`, children: [store.storeBannerURL && (_jsx(Div, { className: "h-40 md:h-56 overflow-hidden bg-gray-100 dark:bg-slate-800", children: _jsx(Div, { role: "img", "aria-label": `${store.storeName} banner`, className: "h-full w-full bg-center bg-cover", style: { backgroundImage: `url(${store.storeBannerURL})` } }) })), _jsxs(Div, { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4", children: [_jsxs(Div, { className: "flex items-end gap-4", children: [store.storeLogoURL ? (_jsx(Div, { role: "img", "aria-label": store.storeName, className: "-mt-8 h-16 w-16 rounded-xl border-2 border-white dark:border-slate-800 bg-center bg-cover shadow-sm", style: { backgroundImage: `url(${store.storeLogoURL})` } })) : (_jsx(Div, { className: "-mt-8 h-16 w-16 rounded-xl border-2 border-white dark:border-slate-800 bg-orange-100 dark:bg-orange-900/30 flex items-center justify-center text-orange-600 dark:text-orange-400 font-bold text-2xl shadow-sm", children: store.storeName[0]?.toUpperCase() })), _jsxs(Div, { className: "flex-1 min-w-0", children: [_jsxs(Div, { className: "flex flex-wrap items-center gap-2 mb-0.5", children: [_jsx(Heading, { level: 1, className: "text-xl font-bold text-gray-900 dark:text-zinc-100", children: store.storeName }), store.averageRating != null && store.averageRating > 0 && (_jsxs(Span, { className: "inline-flex items-center gap-1 text-sm font-medium text-amber-500", children: ["\u2605 ", store.averageRating.toFixed(1)] }))] }), _jsxs(Row, { className: "gap-3 text-xs text-gray-500 dark:text-zinc-400 mb-0.5", children: [store.category && _jsx(Span, { className: "capitalize", children: store.category }), store.totalProducts != null && store.totalProducts > 0 && (_jsxs(Span, { children: [store.totalProducts, " ", labels.products ?? "products"] })), store.totalReviews != null && store.totalReviews > 0 && (_jsxs(Span, { children: [store.totalReviews, " ", labels.reviews ?? "reviews"] })), store.itemsSold != null && store.itemsSold > 0 && (_jsxs(Span, { children: [store.itemsSold, " ", labels.sold ?? "sold"] }))] }), store.storeDescription && (_jsx(RichText, { html: normalizeRichTextHtml(store.storeDescription), copyableCode: true, className: "text-sm text-gray-500 dark:text-zinc-400 mt-0.5" }))] }), _jsxs(Row, { gap: "sm", align: "center", className: "shrink-0", children: [_jsx(ShareButton, { title: store.storeName }), onFollow && (_jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => onFollow(store.storeSlug), className: "rounded-lg border border-orange-500 px-4 py-2 text-sm font-medium text-orange-500 hover:bg-orange-50 transition-colors", children: labels.follow ?? "Follow" }))] })] }), store.isVacationMode && (_jsx(Text, { className: "mt-3 rounded-lg bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 px-3 py-2 text-sm text-yellow-700 dark:text-yellow-300", children: store.vacationMessage ??
6
+ return (_jsxs(Section, { className: `bg-white dark:bg-slate-900 border-b border-gray-200 dark:border-slate-700 ${className}`, children: [store.storeBannerURL && (_jsx(Div, { className: "h-40 md:h-56 overflow-hidden bg-gray-100 dark:bg-slate-800", children: _jsx(Div, { role: "img", "aria-label": `${store.storeName} banner`, className: "h-full w-full bg-center bg-cover", style: { backgroundImage: `url(${store.storeBannerURL})` } }) })), _jsxs(Div, { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4", children: [_jsxs(Div, { className: "flex items-end gap-4", children: [store.storeLogoURL ? (_jsx(Div, { role: "img", "aria-label": store.storeName, className: "-mt-8 h-16 w-16 rounded-xl border-2 border-white dark:border-slate-800 bg-center bg-cover shadow-sm", style: { backgroundImage: `url(${store.storeLogoURL})` } })) : (_jsx(Div, { className: "-mt-8 h-16 w-16 rounded-xl border-2 border-white dark:border-slate-800 bg-orange-100 dark:bg-orange-900/30 flex items-center justify-center text-orange-600 dark:text-orange-400 font-bold text-2xl shadow-sm", children: store.storeName[0]?.toUpperCase() })), _jsxs(Div, { className: "flex-1 min-w-0", children: [_jsxs(Div, { className: "flex flex-wrap items-center gap-2 mb-0.5", children: [_jsx(Heading, { level: 1, className: "text-xl font-bold text-gray-900 dark:text-zinc-100", children: store.storeName }), store.averageRating != null && store.averageRating > 0 && (_jsxs(Span, { className: "inline-flex items-center gap-1 text-sm font-medium text-amber-500", children: ["\u2605 ", store.averageRating.toFixed(1)] }))] }), _jsxs(Row, { className: "gap-3 text-xs text-gray-500 dark:text-zinc-400 mb-0.5", children: [store.category && _jsx(Span, { className: "capitalize", children: store.category }), store.totalProducts != null && store.totalProducts > 0 && (_jsxs(Span, { children: [store.totalProducts, " ", labels.products ?? "products"] })), store.totalReviews != null && store.totalReviews > 0 && (_jsxs(Span, { children: [store.totalReviews, " ", labels.reviews ?? "reviews"] })), store.itemsSold != null && store.itemsSold > 0 && (_jsxs(Span, { children: [store.itemsSold, " ", labels.sold ?? "sold"] }))] }), store.storeDescription && (_jsx(RichText, { html: normalizeRichTextHtml(store.storeDescription), copyableCode: true, className: "mt-0.5" }))] }), _jsxs(Row, { gap: "sm", align: "center", className: "shrink-0", children: [_jsx(ShareButton, { title: store.storeName }), onFollow && (_jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => onFollow(store.storeSlug), className: "rounded-lg border border-orange-500 px-4 py-2 text-sm font-medium text-orange-500 hover:bg-orange-50 transition-colors", children: labels.follow ?? "Follow" }))] })] }), store.isVacationMode && (_jsx(Text, { className: "mt-3 rounded-lg bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 px-3 py-2 text-sm text-yellow-700 dark:text-yellow-300", children: store.vacationMessage ??
7
7
  labels.vacationMode ??
8
8
  "Store is on vacation mode" }))] })] }));
9
9
  }
package/dist/index.d.ts CHANGED
@@ -1058,6 +1058,8 @@ export { AdminBrandsView } from "./features/admin/index";
1058
1058
  export type { AdminBrandsViewProps } from "./features/admin/index";
1059
1059
  export { AdminBrandEditorView } from "./features/admin/index";
1060
1060
  export type { AdminBrandEditorViewProps } from "./features/admin/index";
1061
+ export { AdminAddressEditorView } from "./features/admin/index";
1062
+ export type { AdminAddressEditorViewProps } from "./features/admin/index";
1061
1063
  export { AdminCategoriesView } from "./features/admin/index";
1062
1064
  export { AdminCategoryEditorView } from "./features/admin/index";
1063
1065
  export type { AdminCategoryEditorViewProps } from "./features/admin/index";
@@ -1297,6 +1299,8 @@ export { BID_PUBLIC_FIELDS } from "./features/auctions/index";
1297
1299
  export { BID_UPDATABLE_FIELDS } from "./features/auctions/index";
1298
1300
  export { BidStatusValues } from "./features/auctions/index";
1299
1301
  export { DEFAULT_BID_DATA } from "./features/auctions/index";
1302
+ export { AuctionBidsTable } from "./features/auctions/index";
1303
+ export type { AuctionBidsTableProps, AuctionWithBids } from "./features/auctions/index";
1300
1304
  export { MarketplaceAuctionCard } from "./features/auctions/index";
1301
1305
  export { MarketplaceAuctionGrid } from "./features/auctions/index";
1302
1306
  export { auctionAdminColumns } from "./features/auctions/index";
@@ -1886,6 +1890,8 @@ export type { FaqUpdateInput } from "./features/faq/server";
1886
1890
  export type { VoteFaqActionInput } from "./features/faq/server";
1887
1891
  export type { VoteFaqActionResult } from "./features/faq/server";
1888
1892
  export { FilterFacetSection } from "./features/filters/index";
1893
+ export { AsyncFacetSection } from "./features/filters/index";
1894
+ export type { AsyncFacetSectionProps } from "./features/filters/index";
1889
1895
  export { FilterPanel } from "./features/filters/index";
1890
1896
  export { RangeFilter } from "./features/filters/index";
1891
1897
  export { SwitchFilter } from "./features/filters/index";
package/dist/index.js CHANGED
@@ -2071,6 +2071,8 @@ export { AdminCarouselEditorView } from "./features/admin/index";
2071
2071
  export { AdminBrandsView } from "./features/admin/index";
2072
2072
  // AdminBrandEditorView - Component for admin brand create/edit form.
2073
2073
  export { AdminBrandEditorView } from "./features/admin/index";
2074
+ // AdminAddressEditorView - Component for admin address create/edit form.
2075
+ export { AdminAddressEditorView } from "./features/admin/index";
2074
2076
  // AdminCategoriesView - Component for admin categories view.
2075
2077
  export { AdminCategoriesView } from "./features/admin/index";
2076
2078
  // AdminCategoryEditorView - Component for admin category create/edit form.
@@ -2457,6 +2459,8 @@ export { BidStatusValues } from "./features/auctions/index";
2457
2459
  // DEFAULT_BID_DATA - Constant used across modules.
2458
2460
  export { DEFAULT_BID_DATA } from "./features/auctions/index";
2459
2461
  // [CLIENT-SSR]-Runs in both SSR and browser â€" React component or hook that does not depend on browser-only APIs.
2462
+ // AuctionBidsTable - Collapsible auction-grouped bids table for buyer/store/admin portals.
2463
+ export { AuctionBidsTable } from "./features/auctions/index";
2460
2464
  // MarketplaceAuctionCard - Component for marketplace auction card.
2461
2465
  export { MarketplaceAuctionCard } from "./features/auctions/index";
2462
2466
  // [CLIENT-SSR]-Runs in both SSR and browser â€" React component or hook that does not depend on browser-only APIs.
@@ -3614,6 +3618,8 @@ export { voteFaq } from "./features/faq/server";
3614
3618
  // [CLIENT-SSR]-Runs in both SSR and browser â€" React component or hook that does not depend on browser-only APIs.
3615
3619
  // FilterFacetSection - Shared export for filter facet section.
3616
3620
  export { FilterFacetSection } from "./features/filters/index";
3621
+ // AsyncFacetSection - Async paginated filter section (replaces FilterFacetSection for large datasets).
3622
+ export { AsyncFacetSection } from "./features/filters/index";
3617
3623
  // [CLIENT-SSR]-Runs in both SSR and browser â€" React component or hook that does not depend on browser-only APIs.
3618
3624
  // FilterPanel - Shared export for filter panel.
3619
3625
  export { FilterPanel } from "./features/filters/index";
@@ -126,6 +126,10 @@ export declare const DEFAULT_ROUTE_MAP: {
126
126
  readonly RETURNS: "/user/returns";
127
127
  readonly SUPPORT: "/user/support";
128
128
  readonly SUPPORT_TICKET: (id: string) => string;
129
+ readonly PRE_ORDERS: "/user/pre-orders";
130
+ readonly DIGITAL_CODES: "/user/digital-codes";
131
+ readonly PRIZE_DRAWS: "/user/prize-draws";
132
+ readonly EVENTS: "/user/events";
129
133
  };
130
134
  readonly STORE: {
131
135
  readonly DASHBOARD: "/store";
@@ -238,6 +242,9 @@ export declare const DEFAULT_ROUTE_MAP: {
238
242
  readonly HISTORY: "/admin/history";
239
243
  readonly RETURN_REQUESTS: "/admin/return-requests";
240
244
  readonly STORE_ADDRESSES: "/admin/store-addresses";
245
+ readonly ADDRESSES: "/admin/addresses";
246
+ readonly ADDRESSES_NEW: "/admin/addresses/new";
247
+ readonly ADDRESSES_EDIT: (id: string) => string;
241
248
  readonly SUBLISTING_CATEGORIES: "/admin/sublisting-categories";
242
249
  readonly SUBLISTING_CATEGORIES_NEW: "/admin/sublisting-categories/new";
243
250
  readonly SUBLISTING_CATEGORIES_EDIT: (id: string) => string;
@@ -381,6 +388,10 @@ export declare const ROUTES: {
381
388
  readonly RETURNS: "/user/returns";
382
389
  readonly SUPPORT: "/user/support";
383
390
  readonly SUPPORT_TICKET: (id: string) => string;
391
+ readonly PRE_ORDERS: "/user/pre-orders";
392
+ readonly DIGITAL_CODES: "/user/digital-codes";
393
+ readonly PRIZE_DRAWS: "/user/prize-draws";
394
+ readonly EVENTS: "/user/events";
384
395
  };
385
396
  readonly STORE: {
386
397
  readonly DASHBOARD: "/store";
@@ -493,6 +504,9 @@ export declare const ROUTES: {
493
504
  readonly HISTORY: "/admin/history";
494
505
  readonly RETURN_REQUESTS: "/admin/return-requests";
495
506
  readonly STORE_ADDRESSES: "/admin/store-addresses";
507
+ readonly ADDRESSES: "/admin/addresses";
508
+ readonly ADDRESSES_NEW: "/admin/addresses/new";
509
+ readonly ADDRESSES_EDIT: (id: string) => string;
496
510
  readonly SUBLISTING_CATEGORIES: "/admin/sublisting-categories";
497
511
  readonly SUBLISTING_CATEGORIES_NEW: "/admin/sublisting-categories/new";
498
512
  readonly SUBLISTING_CATEGORIES_EDIT: (id: string) => string;
@@ -114,6 +114,10 @@ export const DEFAULT_ROUTE_MAP = {
114
114
  RETURNS: "/user/returns",
115
115
  SUPPORT: "/user/support",
116
116
  SUPPORT_TICKET: (id) => `/user/support/${id}`,
117
+ PRE_ORDERS: "/user/pre-orders",
118
+ DIGITAL_CODES: "/user/digital-codes",
119
+ PRIZE_DRAWS: "/user/prize-draws",
120
+ EVENTS: "/user/events",
117
121
  },
118
122
  STORE: {
119
123
  DASHBOARD: "/store",
@@ -228,6 +232,9 @@ export const DEFAULT_ROUTE_MAP = {
228
232
  HISTORY: "/admin/history",
229
233
  RETURN_REQUESTS: "/admin/return-requests",
230
234
  STORE_ADDRESSES: "/admin/store-addresses",
235
+ ADDRESSES: "/admin/addresses",
236
+ ADDRESSES_NEW: "/admin/addresses/new",
237
+ ADDRESSES_EDIT: (id) => `/admin/addresses/${id}/edit`,
231
238
  SUBLISTING_CATEGORIES: "/admin/sublisting-categories",
232
239
  SUBLISTING_CATEGORIES_NEW: "/admin/sublisting-categories/new",
233
240
  SUBLISTING_CATEGORIES_EDIT: (id) => `/admin/sublisting-categories/${id}/edit`,
@@ -580,19 +580,22 @@ export const bidsSeedData = [
580
580
  slugPrefix: "bid-miku-100",
581
581
  }),
582
582
  // ═══════════════════════════════════════════════════════════════════════════
583
- // auction-pokemon-mew-1st-edition-psa10 — ACTIVE, ends in 24h, 3 bids → current ₹1,49,999
583
+ // auction-pokemon-mew-1st-edition-psa10 — ENDED (10d ago), 4 bids → admin WON ₹1,49,999
584
584
  // ═══════════════════════════════════════════════════════════════════════════
585
585
  ...buildBidLadder({
586
586
  productId: "auction-pokemon-mew-1st-edition-psa10",
587
587
  productTitle: "Pokémon Mew #151 1st Edition — PSA 10 GEM MINT (AUCTION)",
588
- amounts: [9999900, 12499900, 14999900],
588
+ amounts: [9999900, 11499900, 12999900, 14999900],
589
589
  bidders: [
590
590
  { id: "user-arjun-singh", name: "Arjun Singh", email: "arjun.singh@gmail.com" },
591
591
  { id: "user-rahul-sharma", name: "Rahul Sharma", email: "rahul.sharma@gmail.com" },
592
592
  { id: "user-siddharth-rao", name: "Siddharth Rao", email: "siddharth.rao@gmail.com" },
593
+ { id: "user-admin-letitrip", name: "LetItRip Admin", email: "admin@letitrip.in" },
593
594
  ],
594
- daysAgoForFirst: 5,
595
- endsActive: true,
595
+ daysAgoForFirst: 15,
596
+ endsActive: false,
597
+ closedDaysAgo: 10,
598
+ winningIndex: 3,
596
599
  slugPrefix: "bid-mew-1ed",
597
600
  }),
598
601
  // ═══════════════════════════════════════════════════════════════════════════
@@ -437,4 +437,27 @@ export const eventEntriesSeedData = [
437
437
  reviewStatus: EVENT_ENTRY_FIELDS.REVIEW_STATUS_VALUES.PENDING,
438
438
  submittedAt: daysAgo(1),
439
439
  },
440
+ // ── Admin raffle entry ───────────────────────────────────────────────────
441
+ {
442
+ id: "entry-mega-raffle-admin",
443
+ eventId: "event-collectibles-mega-raffle-2026",
444
+ userId: "user-admin-letitrip",
445
+ userDisplayName: "LetItRip Admin",
446
+ reviewStatus: EVENT_ENTRY_FIELDS.REVIEW_STATUS_VALUES.APPROVED,
447
+ raffleEligible: true,
448
+ submittedAt: daysAgo(1),
449
+ },
450
+ // ── Admin spin-wheel entry (won ₹100 off coupon) ─────────────────────────
451
+ {
452
+ id: "entry-spin-wheel-admin",
453
+ eventId: "event-spin-the-wheel-2026",
454
+ userId: "user-admin-letitrip",
455
+ userDisplayName: "LetItRip Admin",
456
+ reviewStatus: EVENT_ENTRY_FIELDS.REVIEW_STATUS_VALUES.APPROVED,
457
+ spinUsed: true,
458
+ spinPrizeId: "prize-100-off",
459
+ spinPrizeCouponCode: "SPIN100OFF-ADM",
460
+ spinWonAt: daysAgo(2),
461
+ submittedAt: daysAgo(2),
462
+ },
440
463
  ];
@@ -58,4 +58,17 @@ export const historySeedData = [
58
58
  { productId: "preorder-beyblade-x-bx10-booster", productType: "preorder", viewedAt: hoursAgo(3) },
59
59
  { productId: "preorder-shf-broly-super-hero", productType: "preorder", viewedAt: hoursAgo(20) },
60
60
  ]),
61
+ // Admin browses across all stores to review inventory + check recent purchases
62
+ makeDoc("user-admin-letitrip", [
63
+ { productId: "product-gsc-aqua-konosuba-scale", productType: "product", viewedAt: hoursAgo(1) },
64
+ { productId: "auction-pokemon-mew-1st-edition-psa10", productType: "auction", viewedAt: hoursAgo(4) },
65
+ { productId: "product-mafex-miles-morales-spiderman", productType: "product", viewedAt: hoursAgo(10) },
66
+ { productId: "product-figma-link-totk", productType: "product", viewedAt: hoursAgo(18) },
67
+ { productId: "product-pokemon-151-booster-box", productType: "product", viewedAt: daysAgo(1) },
68
+ { productId: "product-yugioh-25th-tin", productType: "product", viewedAt: daysAgo(2) },
69
+ { productId: "product-beyblade-x-bx04-knight-shield", productType: "product", viewedAt: daysAgo(3) },
70
+ { productId: "product-hot-wheels-rlc-bone-shaker", productType: "product", viewedAt: daysAgo(4) },
71
+ { productId: "product-gundam-rx78-mg", productType: "product", viewedAt: daysAgo(5) },
72
+ { productId: "preorder-pokemon-prismatic-evolutions", productType: "preorder", viewedAt: daysAgo(6) },
73
+ ]),
61
74
  ];