@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.
- package/dist/_internal/client/features/filters/filter-load-options.d.ts +29 -0
- package/dist/_internal/client/features/filters/filter-load-options.js +98 -0
- package/dist/_internal/client/features/layout/DashboardLayoutClient.js +1 -1
- package/dist/_internal/server/features/classified/adapters.js +1 -1
- package/dist/_internal/server/features/digital-code/adapters.js +1 -1
- package/dist/_internal/server/features/live/adapters.js +1 -1
- package/dist/_internal/shared/actions/action-registry.js +194 -1
- package/dist/client.d.ts +2 -0
- package/dist/client.js +1 -0
- package/dist/constants/api-endpoints.d.ts +6 -0
- package/dist/constants/api-endpoints.js +2 -0
- package/dist/constants/field-names.d.ts +1 -0
- package/dist/constants/field-names.js +1 -0
- package/dist/features/about/components/FAQPageView.js +1 -1
- package/dist/features/about/components/PublicProfileView.js +2 -2
- package/dist/features/account/components/UserSidebar.js +1 -1
- package/dist/features/admin/components/AdminAddressEditorView.d.ts +7 -0
- package/dist/features/admin/components/AdminAddressEditorView.js +117 -0
- package/dist/features/admin/components/AdminCategoryEditorView.js +12 -9
- package/dist/features/admin/components/AdminFeatureFlagsView.js +58 -13
- package/dist/features/admin/components/AdminProductEditorView.d.ts +3 -1
- package/dist/features/admin/components/AdminProductEditorView.js +12 -9
- package/dist/features/admin/components/AdminReturnRequestsView.js +5 -4
- package/dist/features/admin/components/AdminReviewsView.js +5 -4
- package/dist/features/admin/components/AdminSidebar.js +1 -1
- package/dist/features/admin/components/index.d.ts +2 -0
- package/dist/features/admin/components/index.js +1 -0
- package/dist/features/auctions/components/AuctionBidsTable.d.ts +13 -0
- package/dist/features/auctions/components/AuctionBidsTable.js +62 -0
- package/dist/features/auctions/components/AuctionDetailPageView.js +2 -2
- package/dist/features/auctions/components/AuctionFilters.d.ts +7 -1
- package/dist/features/auctions/components/AuctionFilters.js +3 -2
- package/dist/features/auctions/components/index.d.ts +2 -0
- package/dist/features/auctions/components/index.js +1 -0
- package/dist/features/blog/components/BlogPostView.js +1 -1
- package/dist/features/cart/components/CartView.js +2 -1
- package/dist/features/cart/components/CheckoutView.js +2 -1
- package/dist/features/categories/components/BrandDetailTabs.d.ts +5 -1
- package/dist/features/categories/components/BrandDetailTabs.js +29 -15
- package/dist/features/categories/components/CategoryDetailTabs.d.ts +5 -1
- package/dist/features/categories/components/CategoryDetailTabs.js +30 -15
- package/dist/features/classified/components/ClassifiedFilters.d.ts +5 -1
- package/dist/features/classified/components/ClassifiedFilters.js +3 -2
- package/dist/features/digital-codes/components/DigitalCodeFilters.d.ts +5 -1
- package/dist/features/digital-codes/components/DigitalCodeFilters.js +3 -2
- package/dist/features/events/repository/event-entry.repository.d.ts +1 -0
- package/dist/features/events/repository/event-entry.repository.js +5 -0
- package/dist/features/filters/index.d.ts +2 -0
- package/dist/features/filters/index.js +1 -0
- package/dist/features/homepage/components/FAQSection.js +1 -1
- package/dist/features/homepage/components/FeaturedProductsSection.js +6 -1
- package/dist/features/homepage/components/HeroCarousel.js +21 -5
- package/dist/features/homepage/components/SectionCarousel.js +3 -1
- package/dist/features/homepage/components/ShopByCategorySection.js +2 -2
- package/dist/features/layout/AppLayoutShell.d.ts +1 -1
- package/dist/features/layout/AppLayoutShell.js +1 -1
- package/dist/features/live/components/LiveItemFilters.d.ts +5 -1
- package/dist/features/live/components/LiveItemFilters.js +3 -2
- package/dist/features/pre-orders/components/PreOrderDetailPageView.js +2 -2
- package/dist/features/pre-orders/components/PreOrderFilters.d.ts +7 -1
- package/dist/features/pre-orders/components/PreOrderFilters.js +3 -2
- package/dist/features/products/columns/productTableColumns.js +1 -1
- package/dist/features/products/components/CompareOverlay.d.ts +2 -0
- package/dist/features/products/components/CompareOverlay.js +1 -1
- package/dist/features/products/components/ProductFilters.d.ts +7 -1
- package/dist/features/products/components/ProductFilters.js +3 -2
- package/dist/features/products/components/ProductForm.js +6 -6
- package/dist/features/products/components/ProductGrid.js +1 -1
- package/dist/features/products/repository/products.repository.d.ts +4 -0
- package/dist/features/products/repository/products.repository.js +22 -3
- package/dist/features/products/schemas/firestore.d.ts +7 -1
- package/dist/features/products/types/index.d.ts +4 -0
- package/dist/features/reviews/repository/reviews.repository.d.ts +1 -0
- package/dist/features/reviews/repository/reviews.repository.js +14 -3
- package/dist/features/seller/components/SellerSidebar.js +1 -1
- package/dist/features/stores/components/StoreAboutView.js +1 -1
- package/dist/features/stores/components/StoreDetailLayoutView.js +16 -2
- package/dist/features/stores/components/StoreHeader.js +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- package/dist/next/routing/route-map.d.ts +14 -0
- package/dist/next/routing/route-map.js +7 -0
- package/dist/seed/bids-seed-data.js +7 -4
- package/dist/seed/events-seed-data.js +23 -0
- package/dist/seed/history-seed-data.js +13 -0
- package/dist/seed/orders-seed-data.js +108 -1
- package/dist/seed/products-auctions-seed-data.js +40 -40
- package/dist/seed/products-preorders-seed-data.js +16 -16
- package/dist/seed/products-prize-draws-seed-data.js +4 -4
- package/dist/seed/products-standard-seed-data.js +202 -202
- package/dist/tailwind-utilities.css +1 -1
- 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
|
|
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({
|
|
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({
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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)
|
|
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
|
-
...
|
|
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: "
|
|
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 —
|
|
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,
|
|
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:
|
|
595
|
-
endsActive:
|
|
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
|
];
|