@mohasinac/appkit 4.32.2 → 4.32.4
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/server/features/bundles/data.js +5 -2
- package/dist/_internal/server/features/categories/data.js +17 -7
- package/dist/_internal/server/features/products/list-public.js +41 -1
- package/dist/_internal/server/features/stores/data.js +7 -1
- package/dist/_internal/server/features/tester/visibility.d.ts +22 -0
- package/dist/_internal/server/features/tester/visibility.js +26 -0
- package/dist/features/blog/components/BlogIndexPageView.js +12 -7
- package/dist/features/categories/components/BrandDetailPageView.js +5 -2
- package/dist/features/categories/components/BundlesListView.js +3 -1
- package/dist/features/categories/components/CategoriesIndexPageView.js +10 -5
- package/dist/features/categories/components/CategoryDetailPageView.js +4 -1
- package/dist/features/events/components/EventsListPageView.js +21 -7
- package/dist/features/reviews/components/ReviewsIndexPageView.js +4 -0
- package/dist/features/stores/components/StoreBundlesPageView.js +2 -1
- package/dist/features/stores/components/StoreDetailLayoutView.js +2 -0
- package/dist/features/stores/components/StoresIndexPageView.js +10 -1
- package/package.json +1 -1
|
@@ -9,6 +9,7 @@ import { cache } from "react";
|
|
|
9
9
|
import { categoriesRepository } from "../../../../repositories";
|
|
10
10
|
import { productRepository } from "../../../../repositories";
|
|
11
11
|
import { safeRead } from "../../../../errors/safe-read";
|
|
12
|
+
import { hidePublicTestData } from "../tester/visibility";
|
|
12
13
|
/**
|
|
13
14
|
* Fetch a single bundle by slug for the public detail page. Returns null
|
|
14
15
|
* when the slug is empty or no matching `categoryType:"bundle"` row exists.
|
|
@@ -154,7 +155,7 @@ export const listFeaturedBundles = cache(async (limit = 8, _opts) => {
|
|
|
154
155
|
key: "categories.listByType(bundle)",
|
|
155
156
|
fallback: [],
|
|
156
157
|
});
|
|
157
|
-
return all;
|
|
158
|
+
return hidePublicTestData(all);
|
|
158
159
|
});
|
|
159
160
|
/**
|
|
160
161
|
* "Related Bundles" for the bundle detail page — every other active bundle,
|
|
@@ -173,5 +174,7 @@ export const getRelatedBundles = cache(async (bundle, limit = 8) => {
|
|
|
173
174
|
key: "categories.listByType(bundle)",
|
|
174
175
|
fallback: [],
|
|
175
176
|
});
|
|
176
|
-
return all
|
|
177
|
+
return hidePublicTestData(all)
|
|
178
|
+
.filter((b) => b.id !== bundle.id)
|
|
179
|
+
.slice(0, limit);
|
|
177
180
|
});
|
|
@@ -3,6 +3,7 @@ import { cache } from "react";
|
|
|
3
3
|
import { categoriesRepository } from "../../../../repositories";
|
|
4
4
|
import { safeRead } from "../../../../errors/safe-read";
|
|
5
5
|
import { CATEGORIES_FEATURED_LIMIT, CATEGORIES_MENU_LIMIT, CATEGORIES_SITEMAP_LIMIT } from "../../../shared/features/categories/config";
|
|
6
|
+
import { hidePublicTestData } from "../tester/visibility";
|
|
6
7
|
/** Full category document by slug — deduped per request via React.cache(). */
|
|
7
8
|
export const getCategoryForDetail = cache(async (slug) => {
|
|
8
9
|
/*
|
|
@@ -23,25 +24,34 @@ export const getCategoryForDetail = cache(async (slug) => {
|
|
|
23
24
|
});
|
|
24
25
|
/** Flat list of all categories at tier 1 (roots) — for nav/sitemap. */
|
|
25
26
|
export const listRootCategories = cache(async () => {
|
|
26
|
-
|
|
27
|
+
const rows = await safeRead(() => categoriesRepository.getCategoriesByTier(1), {
|
|
27
28
|
route: "/categories",
|
|
28
29
|
key: "categories.listRootCategories",
|
|
29
30
|
fallback: [],
|
|
30
31
|
});
|
|
32
|
+
return hidePublicTestData(rows);
|
|
31
33
|
});
|
|
32
34
|
/** Featured categories for homepage display (showOnHomepage: true). */
|
|
33
35
|
export const listFeaturedCategories = cache(async () => {
|
|
34
|
-
|
|
36
|
+
const rows = await safeRead(() => categoriesRepository
|
|
35
37
|
.list({ filters: "isActive==true,showOnHomepage==true", sorts: "order", page: 1, pageSize: CATEGORIES_FEATURED_LIMIT })
|
|
36
|
-
.then((r) => r.data ?? [])
|
|
37
|
-
|
|
38
|
+
.then((r) => r.data ?? []), {
|
|
39
|
+
route: "/",
|
|
40
|
+
key: "categories.listFeaturedCategories",
|
|
41
|
+
fallback: [],
|
|
42
|
+
});
|
|
43
|
+
return hidePublicTestData(rows);
|
|
38
44
|
});
|
|
39
45
|
/** Nav-menu categories (showInMenu: true, tier 1 + 2). */
|
|
40
46
|
export const listMenuCategories = cache(async () => {
|
|
41
|
-
|
|
47
|
+
const rows = await safeRead(() => categoriesRepository
|
|
42
48
|
.list({ filters: "isActive==true,display.showInMenu==true", sorts: "order", page: 1, pageSize: CATEGORIES_MENU_LIMIT })
|
|
43
|
-
.then((r) => r.data ?? [])
|
|
44
|
-
|
|
49
|
+
.then((r) => r.data ?? []), {
|
|
50
|
+
route: "nav/menu",
|
|
51
|
+
key: "categories.listMenuCategories",
|
|
52
|
+
fallback: [],
|
|
53
|
+
});
|
|
54
|
+
return hidePublicTestData(rows);
|
|
45
55
|
});
|
|
46
56
|
/** Full category tree rooted at a given rootId — for sidebar navigation. */
|
|
47
57
|
export const getCategoryTree = cache(async (rootId) => {
|
|
@@ -442,6 +442,21 @@ export async function listPublicProducts(input, opts) {
|
|
|
442
442
|
? unavailableClausesFor(types).find((c) => c.op === "lt" && c.sortField === requestedSortField)
|
|
443
443
|
: undefined;
|
|
444
444
|
if (pushdown) {
|
|
445
|
+
/*
|
|
446
|
+
* 🛑 This branch keeps a POST-pagination sandbox filter, knowingly.
|
|
447
|
+
*
|
|
448
|
+
* It exists to give the ended-listing archive real, unbounded-depth
|
|
449
|
+
* Firestore pagination, which is incompatible with the over-fetch the
|
|
450
|
+
* window path uses. So an anonymous page here can come back a row or two
|
|
451
|
+
* SHORT when a sandbox fixture lands in it — bounded by the ~25 fixtures
|
|
452
|
+
* that exist, and nothing like the empty page the default view produced,
|
|
453
|
+
* because real ended listings dominate this scope.
|
|
454
|
+
*
|
|
455
|
+
* Falling through to Path C instead would fix the short page and cost every
|
|
456
|
+
* anonymous visitor the deep pagination this branch was written for. The
|
|
457
|
+
* short page is the better trade; it is recorded here so the next reader
|
|
458
|
+
* knows it was weighed rather than missed.
|
|
459
|
+
*/
|
|
445
460
|
const filters = sieveAnd(safeFilters, clauseToSieve(pushdown, now));
|
|
446
461
|
const result = await runQuery(executor, {
|
|
447
462
|
filters,
|
|
@@ -558,7 +573,28 @@ export async function listPublicProducts(input, opts) {
|
|
|
558
573
|
const hasTypeFacet = Object.keys(input.typeFacets ?? {}).length > 0;
|
|
559
574
|
/** Multi-value `tags`/`features` — no `array-contains-any`, so per-row. */
|
|
560
575
|
const hasArrayMultiSelect = (input.tags?.length ?? 0) > 1 || (input.features?.length ?? 0) > 1;
|
|
561
|
-
|
|
576
|
+
/*
|
|
577
|
+
* 🛑 The tester-sandbox filter must run BEFORE the page is sliced.
|
|
578
|
+
*
|
|
579
|
+
* It used to run only in `finish()`, which receives an already-paginated
|
|
580
|
+
* page — so a page whose rows were ALL sandbox fixtures came back empty with
|
|
581
|
+
* a non-zero `total`. That is not hypothetical: the fixtures carry
|
|
582
|
+
* `Date.now()`-relative dates by design (they have to re-arm on every
|
|
583
|
+
* reseed), so immediately after a seed load they are the newest rows in the
|
|
584
|
+
* collection and fill the whole first page of the default `-createdAt` sort.
|
|
585
|
+
* The public catalogue returned `items: []` alongside `total: 79`.
|
|
586
|
+
*
|
|
587
|
+
* So an anonymous viewer forces the bounded-window path, where the filter is
|
|
588
|
+
* one more per-row predicate applied before the slice. That is already the
|
|
589
|
+
* default for public browsing (`wantAvailable`), so this widens the window
|
|
590
|
+
* path only for the non-default scopes.
|
|
591
|
+
*/
|
|
592
|
+
const hidesTestData = filterTestDataForViewer([{ isTestData: true }], opts?.viewer).length === 0;
|
|
593
|
+
const inMemory = wantAvailable ||
|
|
594
|
+
hasTypeFacet ||
|
|
595
|
+
hasArrayMultiSelect ||
|
|
596
|
+
hidesTestData ||
|
|
597
|
+
(hasDateRange && !canPushDate);
|
|
562
598
|
// When a date range stays in memory, fetch in whichever direction front-loads
|
|
563
599
|
// the rows that will PASS it: `dateFrom` (">=", still live) wants the most
|
|
564
600
|
// future dates first; `dateTo` ("<=", ends by) wants the earliest first.
|
|
@@ -597,6 +633,10 @@ export async function listPublicProducts(input, opts) {
|
|
|
597
633
|
return wanted.some((w) => raw.includes(w));
|
|
598
634
|
};
|
|
599
635
|
const filtered = items.filter((item) => {
|
|
636
|
+
// Before the slice — see the note on `hidesTestData` above.
|
|
637
|
+
if (hidesTestData && item.isTestData === true) {
|
|
638
|
+
return false;
|
|
639
|
+
}
|
|
600
640
|
if (wantAvailable && !isListingRowAvailable(item, now))
|
|
601
641
|
return false;
|
|
602
642
|
if (hasArrayMultiSelect) {
|
|
@@ -4,6 +4,7 @@ import { storeRepository } from "../../../../repositories";
|
|
|
4
4
|
import { STORES_PRODUCTS_PAGE_SIZE, STORES_SITEMAP_LIMIT, } from "../../../shared/features/stores/config";
|
|
5
5
|
import { makeGetStoreListingsInitial } from "../shared/listing-data-factory";
|
|
6
6
|
import { safeRead } from "../../../../errors/safe-read";
|
|
7
|
+
import { hidePublicTestData } from "../tester/visibility";
|
|
7
8
|
/** Full store document by slug — deduped per request via React.cache(). */
|
|
8
9
|
export const getStoreForDetail = cache(async (slug) => {
|
|
9
10
|
return (await storeRepository.findBySlug(slug)) ?? null;
|
|
@@ -19,5 +20,10 @@ export const listSitemapStores = cache(async () => {
|
|
|
19
20
|
* store — which is not an error to any crawler, just a smaller site.
|
|
20
21
|
*/
|
|
21
22
|
const result = await safeRead(() => storeRepository.listStores({ filters: "status==active", sorts: "-updatedAt", page: 1, pageSize: STORES_SITEMAP_LIMIT }, true), { route: "sitemap/stores", key: "stores.listStores", fallback: null });
|
|
22
|
-
|
|
23
|
+
// A sandbox store in the SITEMAP is the longest-lived form of this leak:
|
|
24
|
+
// a crawler keeps the URL long after the fixture is pruned.
|
|
25
|
+
return hidePublicTestData(result?.items ?? []).map(({ storeSlug, updatedAt }) => ({
|
|
26
|
+
storeSlug,
|
|
27
|
+
updatedAt,
|
|
28
|
+
}));
|
|
23
29
|
});
|
|
@@ -12,3 +12,25 @@ export declare function canViewTestData(viewer: ViewerLike | null | undefined):
|
|
|
12
12
|
export declare function filterTestDataForViewer<T extends TestDataFlagged>(items: T[], viewer: ViewerLike | null | undefined): T[];
|
|
13
13
|
/** Returns null (as if not found) for an isTestData:true single doc when the viewer can't see it. */
|
|
14
14
|
export declare function filterSingleTestData<T extends TestDataFlagged>(item: T | null, viewer: ViewerLike | null | undefined): T | null;
|
|
15
|
+
/**
|
|
16
|
+
* The PUBLIC projection of a list: sandbox rows removed, unconditionally.
|
|
17
|
+
*
|
|
18
|
+
* 🛑 Use this in any server read that feeds a public page. It is
|
|
19
|
+
* `filterTestDataForViewer(items, undefined)` under a name that says what it is
|
|
20
|
+
* for, because the two-argument form invites a call site to pass a viewer it
|
|
21
|
+
* does not actually have — and `undefined` there reads like an oversight rather
|
|
22
|
+
* than the fail-closed default it is.
|
|
23
|
+
*
|
|
24
|
+
* A signed-in tester still sees sandbox rows: the public pages hydrate from the
|
|
25
|
+
* APIs, which DO thread the real viewer. This covers the server-rendered first
|
|
26
|
+
* paint, which has no session in hand and must not publish the sandbox to
|
|
27
|
+
* everyone on the strength of that.
|
|
28
|
+
*
|
|
29
|
+
* Added 2026-09-01 after a reseed made the leak visible: the homepage carried
|
|
30
|
+
* 10 sandbox bundles, 8 events, 5 brands and 4 categories; /categories carried
|
|
31
|
+
* 9; /stores rendered a card for `store-tester-sandbox`. Only the products path
|
|
32
|
+
* had ever been filtered.
|
|
33
|
+
*/
|
|
34
|
+
export declare function hidePublicTestData<T extends TestDataFlagged>(items: T[]): T[];
|
|
35
|
+
/** The single-document form of {@link hidePublicTestData}. */
|
|
36
|
+
export declare function hidePublicTestDoc<T extends TestDataFlagged>(item: T | null): T | null;
|
|
@@ -26,3 +26,29 @@ export function filterSingleTestData(item, viewer) {
|
|
|
26
26
|
return null;
|
|
27
27
|
return item;
|
|
28
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* The PUBLIC projection of a list: sandbox rows removed, unconditionally.
|
|
31
|
+
*
|
|
32
|
+
* 🛑 Use this in any server read that feeds a public page. It is
|
|
33
|
+
* `filterTestDataForViewer(items, undefined)` under a name that says what it is
|
|
34
|
+
* for, because the two-argument form invites a call site to pass a viewer it
|
|
35
|
+
* does not actually have — and `undefined` there reads like an oversight rather
|
|
36
|
+
* than the fail-closed default it is.
|
|
37
|
+
*
|
|
38
|
+
* A signed-in tester still sees sandbox rows: the public pages hydrate from the
|
|
39
|
+
* APIs, which DO thread the real viewer. This covers the server-rendered first
|
|
40
|
+
* paint, which has no session in hand and must not publish the sandbox to
|
|
41
|
+
* everyone on the strength of that.
|
|
42
|
+
*
|
|
43
|
+
* Added 2026-09-01 after a reseed made the leak visible: the homepage carried
|
|
44
|
+
* 10 sandbox bundles, 8 events, 5 brands and 4 categories; /categories carried
|
|
45
|
+
* 9; /stores rendered a card for `store-tester-sandbox`. Only the products path
|
|
46
|
+
* had ever been filtered.
|
|
47
|
+
*/
|
|
48
|
+
export function hidePublicTestData(items) {
|
|
49
|
+
return filterTestDataForViewer(items, undefined);
|
|
50
|
+
}
|
|
51
|
+
/** The single-document form of {@link hidePublicTestData}. */
|
|
52
|
+
export function hidePublicTestDoc(item) {
|
|
53
|
+
return filterSingleTestData(item, undefined);
|
|
54
|
+
}
|
|
@@ -4,6 +4,7 @@ import { Container, Heading, Main, Section } from "../../../ui";
|
|
|
4
4
|
import { AdSlot } from "../../homepage/components/AdSlot";
|
|
5
5
|
import { BlogIndexListing } from "./BlogIndexListing";
|
|
6
6
|
import { safeRead } from "../../../errors/safe-read";
|
|
7
|
+
import { hidePublicTestData } from "../../../_internal/server/features/tester/visibility";
|
|
7
8
|
function sp(params, key) {
|
|
8
9
|
const v = params[key];
|
|
9
10
|
return Array.isArray(v) ? v[0] ?? "" : v ?? "";
|
|
@@ -14,15 +15,19 @@ export async function BlogIndexPageView({ searchParams = {} }) {
|
|
|
14
15
|
const pageSize = Number(sp(searchParams, "pageSize")) || 24;
|
|
15
16
|
const category = sp(searchParams, "category");
|
|
16
17
|
const sieveResult = await safeRead(() => blogRepository.listPublished({ ...(category ? { category } : {}) }, { sorts: sort, page, pageSize }), { route: "/blog", key: "blogPosts.listPublished", fallback: null });
|
|
17
|
-
|
|
18
|
+
// Sandbox posts are public rows by design — not on someone else's first paint.
|
|
19
|
+
const publicSieve = sieveResult
|
|
20
|
+
? { ...sieveResult, items: hidePublicTestData(sieveResult.items) }
|
|
21
|
+
: sieveResult;
|
|
22
|
+
const initialData = publicSieve
|
|
18
23
|
? {
|
|
19
|
-
posts:
|
|
24
|
+
posts: publicSieve.items,
|
|
20
25
|
meta: {
|
|
21
|
-
total:
|
|
22
|
-
page:
|
|
23
|
-
pageSize:
|
|
24
|
-
totalPages:
|
|
25
|
-
hasMore:
|
|
26
|
+
total: publicSieve.total,
|
|
27
|
+
page: publicSieve.page,
|
|
28
|
+
pageSize: publicSieve.pageSize,
|
|
29
|
+
totalPages: publicSieve.totalPages,
|
|
30
|
+
hasMore: publicSieve.hasMore,
|
|
26
31
|
},
|
|
27
32
|
}
|
|
28
33
|
: undefined;
|
|
@@ -17,6 +17,7 @@ import { CATEGORY_PAGE_TABS } from "../../products/constants/listing-tabs";
|
|
|
17
17
|
import { enabledCategoryTypes, enabledListingTypes } from "../../../_internal/shared/listing-types/feature-flags";
|
|
18
18
|
import { siteSettingsRepository } from "../../../repositories";
|
|
19
19
|
import { safeRead } from "../../../errors/safe-read";
|
|
20
|
+
import { hidePublicTestData } from "../../../_internal/server/features/tester/visibility";
|
|
20
21
|
const __O = {
|
|
21
22
|
hidden: "overflow-hidden",
|
|
22
23
|
};
|
|
@@ -48,13 +49,15 @@ export async function BrandDetailPageView({ slug, initialBrand }) {
|
|
|
48
49
|
// We pull all bundle categories and filter client-side by brandSlug —
|
|
49
50
|
// no dedicated repository query needed at this catalog's bundle volume.
|
|
50
51
|
brand?.slug
|
|
51
|
-
? safeRead(() => categoriesRepository
|
|
52
|
+
? safeRead(() => categoriesRepository
|
|
53
|
+
.listByType("bundle", { activeOnly: true, limit: 50 })
|
|
54
|
+
.then(hidePublicTestData), { route: "/brands/[slug]", key: "brand.bundles", fallback: [] })
|
|
52
55
|
: Promise.resolve([]),
|
|
53
56
|
// Related brands — every other active brand row, excluding this one.
|
|
54
57
|
// `as unknown` is load-bearing only because CategoryDocument.createdAt is a
|
|
55
58
|
// Date while CategoryItem.createdAt is a string — a pre-existing mismatch
|
|
56
59
|
// the old `.catch(() => [])` union happened to paper over.
|
|
57
|
-
safeRead(() => categoriesRepository.findActiveBrands(), {
|
|
60
|
+
safeRead(() => categoriesRepository.findActiveBrands().then(hidePublicTestData), {
|
|
58
61
|
route: "/brands/[slug]",
|
|
59
62
|
key: "brand.relatedBrands",
|
|
60
63
|
fallback: [],
|
|
@@ -4,6 +4,7 @@ import { Container, Heading, Main, Section, Text } from "../../../ui";
|
|
|
4
4
|
import { AdSlot } from "../../homepage/components/AdSlot";
|
|
5
5
|
import { CategoryBundlesListing } from "./CategoryBundlesListing";
|
|
6
6
|
import { safeRead } from "../../../errors/safe-read";
|
|
7
|
+
import { hidePublicTestData } from "../../../_internal/server/features/tester/visibility";
|
|
7
8
|
const DEFAULT_LIMIT = 200;
|
|
8
9
|
function sp(params, key) {
|
|
9
10
|
const v = params[key];
|
|
@@ -22,8 +23,9 @@ export async function BundlesListView({ searchParams = {}, onBuyNow, }) {
|
|
|
22
23
|
activeOnly: true,
|
|
23
24
|
limit: DEFAULT_LIMIT,
|
|
24
25
|
}), { route: "/bundles", key: "categories.listByType(bundle)", fallback: [] });
|
|
26
|
+
const visible = hidePublicTestData(all);
|
|
25
27
|
const bundles = storeId
|
|
26
|
-
?
|
|
28
|
+
? visible.filter((c) => c.createdByStoreId === storeId)
|
|
27
29
|
: all;
|
|
28
30
|
return (_jsx(Main, { children: _jsx(Section, { padding: "y-2xl", children: _jsxs(Container, { size: "xl", children: [_jsx(Heading, { level: 1, className: "mb-2", color: "primary", size: "3xl", weight: "semibold", children: "Collectible Bundles" }), _jsx(Text, { className: "mb-6 text-[var(--appkit-color-text-muted)]", size: "sm", children: "Curated multi-product bundles \u2014 Pok\u00E9mon starter sets, Beyblade tournament packs, Hot Wheels collector boxes and more. One price, one checkout, one shipment." }), _jsx(AdSlot, { id: "listing-sidebar-top", className: "mb-6" }), _jsx(CategoryBundlesListing, { initialBundles: bundles, onBuyNow: onBuyNow }), _jsx(AdSlot, { id: "listing-sidebar-bottom", className: "mt-8" })] }) }) }));
|
|
29
31
|
}
|
|
@@ -4,6 +4,8 @@ import { categoriesRepository } from "../../../repositories";
|
|
|
4
4
|
import { Container, Heading, Main, Section } from "../../../ui";
|
|
5
5
|
import { AdSlot } from "../../homepage/components/AdSlot";
|
|
6
6
|
import { CategoriesIndexListing } from "./CategoriesIndexListing";
|
|
7
|
+
import { safeRead } from "../../../errors/safe-read";
|
|
8
|
+
import { hidePublicTestData } from "../../../_internal/server/features/tester/visibility";
|
|
7
9
|
function sp(params, key) {
|
|
8
10
|
const v = params[key];
|
|
9
11
|
return Array.isArray(v) ? v[0] ?? "" : v ?? "";
|
|
@@ -39,14 +41,17 @@ export async function CategoriesIndexPageView({ searchParams = {} }) {
|
|
|
39
41
|
const sort = sp(searchParams, "sort") || "name";
|
|
40
42
|
const page = Number(sp(searchParams, "page")) || 1;
|
|
41
43
|
const filters = buildCategoryFilters(searchParams);
|
|
42
|
-
const result = await categoriesRepository
|
|
43
|
-
.list({
|
|
44
|
+
const result = await safeRead(() => categoriesRepository.list({
|
|
44
45
|
page,
|
|
45
46
|
pageSize: 200,
|
|
46
47
|
sorts: sort,
|
|
47
48
|
...(filters ? { filters } : {}),
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
}), { route: "/categories", key: "categories.list", fallback: null });
|
|
50
|
+
/*
|
|
51
|
+
* This one page carries THREE entity kinds — categories, brands and bundles
|
|
52
|
+
* are all `CategoryDocument` rows discriminated by `categoryType` — so a
|
|
53
|
+
* single unfiltered read published sandbox fixtures of all three.
|
|
54
|
+
*/
|
|
55
|
+
const initialData = hidePublicTestData((result?.items ?? []));
|
|
51
56
|
return (_jsx(Main, { children: _jsx(Section, { padding: "y-2xl", children: _jsxs(Container, { size: "xl", children: [_jsx(Heading, { level: 1, className: "mb-8", color: "primary", size: "3xl", weight: "semibold", children: "Categories" }), _jsx(AdSlot, { id: "listing-sidebar-top", className: "mb-6" }), _jsx(CategoriesIndexListing, { initialData: initialData }), _jsx(AdSlot, { id: "listing-sidebar-bottom", className: "mt-8" })] }) }) }));
|
|
52
57
|
}
|
|
@@ -16,6 +16,7 @@ import { CATEGORY_PAGE_TABS } from "../../products/constants/listing-tabs";
|
|
|
16
16
|
import { enabledCategoryTypes, enabledListingTypes } from "../../../_internal/shared/listing-types/feature-flags";
|
|
17
17
|
import { siteSettingsRepository } from "../../../repositories";
|
|
18
18
|
import { safeRead } from "../../../errors/safe-read";
|
|
19
|
+
import { hidePublicTestData } from "../../../_internal/server/features/tester/visibility";
|
|
19
20
|
const __O = {
|
|
20
21
|
hidden: "overflow-hidden",
|
|
21
22
|
xAuto: "overflow-x-auto",
|
|
@@ -52,7 +53,9 @@ export async function CategoryDetailPageView({ slug }) {
|
|
|
52
53
|
// SB-UNI-D — bundles fetched from the categories collection. We pull
|
|
53
54
|
// all active bundle rows; the carousel filters by category affinity.
|
|
54
55
|
categoriesIn
|
|
55
|
-
? safeRead(() => categoriesRepository
|
|
56
|
+
? safeRead(() => categoriesRepository
|
|
57
|
+
.listByType("bundle", { activeOnly: true, limit: 50 })
|
|
58
|
+
.then(hidePublicTestData), { route: "/categories/[slug]", key: "category.bundles", fallback: [] })
|
|
56
59
|
: Promise.resolve([]),
|
|
57
60
|
category?.id
|
|
58
61
|
? safeRead(() => categoriesRepository.getChildren(category.id), {
|
|
@@ -6,6 +6,8 @@ import { EventsIndexListing } from "./EventsIndexListing";
|
|
|
6
6
|
import { EVENT_FIELDS } from "../../../constants/field-names";
|
|
7
7
|
import { sieveFilter, sieveMultiEq, sieveAnd, SIEVE_OP } from "../../../utils/sieve-builder";
|
|
8
8
|
import { sortBy } from "../../../constants/sort";
|
|
9
|
+
import { safeRead } from "../../../errors/safe-read";
|
|
10
|
+
import { hidePublicTestData } from "../../../_internal/server/features/tester/visibility";
|
|
9
11
|
function sp(params, key) {
|
|
10
12
|
const v = params[key];
|
|
11
13
|
return Array.isArray(v) ? v[0] ?? "" : v ?? "";
|
|
@@ -46,16 +48,28 @@ export async function EventsListPageView({ searchParams = {} }) {
|
|
|
46
48
|
const pageSize = Number(sp(searchParams, "pageSize")) || 24;
|
|
47
49
|
const filters = buildEventFilters(searchParams);
|
|
48
50
|
const [result, facetCounts] = await Promise.all([
|
|
49
|
-
eventRepository
|
|
50
|
-
|
|
51
|
-
.
|
|
51
|
+
safeRead(() => eventRepository.list({ filters, sorts: sort, page, pageSize }), {
|
|
52
|
+
route: "/events",
|
|
53
|
+
key: "events.list",
|
|
54
|
+
fallback: null,
|
|
55
|
+
}),
|
|
52
56
|
// Facet counts are deliberately NOT narrowed by the shopper's own type or
|
|
53
57
|
// status selection: a count that shrank to zero as you ticked boxes would
|
|
54
58
|
// hide the very facet you'd need to untick. They answer "does this kind of
|
|
55
59
|
// event exist at all", which is the question that decides visibility.
|
|
56
|
-
eventRepository
|
|
57
|
-
|
|
58
|
-
.
|
|
60
|
+
safeRead(() => eventRepository.facetCountsForTypesAndStatuses(), {
|
|
61
|
+
route: "/events",
|
|
62
|
+
key: "events.facetCounts",
|
|
63
|
+
fallback: {},
|
|
64
|
+
}),
|
|
59
65
|
]);
|
|
60
|
-
|
|
66
|
+
/*
|
|
67
|
+
* Sandbox events are seeded as ordinary public rows so testers exercise the
|
|
68
|
+
* real browse path; they must not reach anyone else's first paint. The client
|
|
69
|
+
* refetch threads the real viewer, so a signed-in tester still sees them.
|
|
70
|
+
*/
|
|
71
|
+
const publicResult = result
|
|
72
|
+
? { ...result, items: hidePublicTestData(result.items) }
|
|
73
|
+
: result;
|
|
74
|
+
return (_jsx(Main, { children: _jsx(Section, { padding: "y-2xl", children: _jsxs(Container, { size: "xl", children: [_jsx(Heading, { level: 1, className: "mb-8", color: "primary", size: "3xl", weight: "semibold", children: "Events" }), _jsx(AdSlot, { id: "listing-sidebar-top", className: "mb-6" }), _jsx(EventsIndexListing, { initialData: publicResult, facetCounts: facetCounts }), _jsx(AdSlot, { id: "listing-sidebar-bottom", className: "mt-8" })] }) }) }));
|
|
61
75
|
}
|
|
@@ -40,6 +40,10 @@ export async function ReviewsIndexPageView({ searchParams = {} }) {
|
|
|
40
40
|
const sort = sp(searchParams, "sort") || "-createdAt";
|
|
41
41
|
const page = Number(sp(searchParams, "page")) || 1;
|
|
42
42
|
const filters = buildReviewFilters(searchParams);
|
|
43
|
+
// audit-public-test-data-ok: `ReviewDocument` carries no `isTestData` field —
|
|
44
|
+
// tsc rejects the filter outright, which is the honest signal that reviews
|
|
45
|
+
// have no sandbox concept to strip. A review OF a sandbox product is not
|
|
46
|
+
// flagged today; that is a seed-schema gap, not something a cast should hide.
|
|
43
47
|
const result = await safeRead(() => reviewRepository.listAll({ filters, sorts: sort, page, pageSize: 12 }), { route: "/reviews", key: "reviews.listAll", fallback: null });
|
|
44
48
|
const initialData = result
|
|
45
49
|
? {
|
|
@@ -3,6 +3,7 @@ import { categoriesRepository } from "../../../repositories";
|
|
|
3
3
|
import { getStoreBySlug } from "./StoreDetailLayoutView";
|
|
4
4
|
import { CategoryBundlesListing } from "../../categories/components/CategoryBundlesListing";
|
|
5
5
|
import { safeRead } from "../../../errors/safe-read";
|
|
6
|
+
import { hidePublicTestData } from "../../../_internal/server/features/tester/visibility";
|
|
6
7
|
/**
|
|
7
8
|
* Public store → Bundles tab.
|
|
8
9
|
*
|
|
@@ -24,5 +25,5 @@ export async function StoreBundlesPageView({ storeSlug, onBuyNow, }) {
|
|
|
24
25
|
key: "categories.listByType(bundle)",
|
|
25
26
|
fallback: [],
|
|
26
27
|
});
|
|
27
|
-
return _jsx(CategoryBundlesListing, { initialBundles: bundles, onBuyNow: onBuyNow });
|
|
28
|
+
return (_jsx(CategoryBundlesListing, { initialBundles: hidePublicTestData(bundles), onBuyNow: onBuyNow }));
|
|
28
29
|
}
|
|
@@ -61,6 +61,8 @@ export async function StoreDetailLayoutView({ storeSlug, activeTab, children, sc
|
|
|
61
61
|
const [listingCounts, couponsCount, reviewsCount] = storeId
|
|
62
62
|
? await Promise.all([
|
|
63
63
|
listingTabCounts(STORE_PAGE_TABS, { storeId }),
|
|
64
|
+
// audit-public-test-data-ok: a COUNT of the store's own coupons, and
|
|
65
|
+
// `CouponDocument` carries no isTestData field — there is nothing to strip.
|
|
64
66
|
couponsRepository
|
|
65
67
|
.list({ filters: sieveAnd(sieveFilter("storeId", SIEVE_OP.EQ, storeId), sieveFilter("validity.isActive", SIEVE_OP.EQ, "true")), page: 1, pageSize: 1 })
|
|
66
68
|
.then((r) => r.total)
|
|
@@ -4,6 +4,7 @@ import { Container, Heading, Main, Section } from "../../../ui";
|
|
|
4
4
|
import { AdSlot } from "../../homepage/components/AdSlot";
|
|
5
5
|
import { StoresIndexListing } from "./StoresIndexListing";
|
|
6
6
|
import { safeRead } from "../../../errors/safe-read";
|
|
7
|
+
import { hidePublicTestData } from "../../../_internal/server/features/tester/visibility";
|
|
7
8
|
function sp(params, key) {
|
|
8
9
|
const v = params[key];
|
|
9
10
|
return Array.isArray(v) ? v[0] ?? "" : v ?? "";
|
|
@@ -13,5 +14,13 @@ export async function StoresIndexPageView({ searchParams = {} }) {
|
|
|
13
14
|
const page = Number(sp(searchParams, "page")) || 1;
|
|
14
15
|
const pageSize = Number(sp(searchParams, "pageSize")) || 24;
|
|
15
16
|
const result = await safeRead(() => storeRepository.listStores({ page, pageSize, sorts: sort }, true), { route: "/stores", key: "stores.listStores", fallback: null });
|
|
16
|
-
|
|
17
|
+
/*
|
|
18
|
+
* The API already filters this; the SSR page called the repository directly
|
|
19
|
+
* and did not, so the first paint rendered a card for `store-tester-sandbox`
|
|
20
|
+
* while the client refetch dropped it.
|
|
21
|
+
*/
|
|
22
|
+
const publicResult = result
|
|
23
|
+
? { ...result, items: hidePublicTestData(result.items) }
|
|
24
|
+
: result;
|
|
25
|
+
return (_jsx(Main, { children: _jsx(Section, { padding: "y-2xl", children: _jsxs(Container, { size: "xl", children: [_jsx(Heading, { level: 1, className: "mb-8", color: "primary", size: "3xl", weight: "semibold", children: "Stores" }), _jsx(AdSlot, { id: "listing-sidebar-top", className: "mb-6" }), _jsx(StoresIndexListing, { initialData: publicResult ?? undefined }), _jsx(AdSlot, { id: "listing-sidebar-bottom", className: "mt-8" })] }) }) }));
|
|
17
26
|
}
|