@decocms/apps-wake 7.66.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +58 -0
- package/src/actions/cart/addCoupon.ts +43 -0
- package/src/actions/cart/addItem.ts +10 -0
- package/src/actions/cart/addItems.ts +50 -0
- package/src/actions/cart/addKit.ts +53 -0
- package/src/actions/cart/partnerAssociate.ts +48 -0
- package/src/actions/cart/partnerDisassociate.ts +43 -0
- package/src/actions/cart/removeCoupon.ts +39 -0
- package/src/actions/cart/removeKit.ts +56 -0
- package/src/actions/cart/updateItemQuantity.ts +69 -0
- package/src/actions/newsletter/register.ts +32 -0
- package/src/actions/notifyme.ts +28 -0
- package/src/actions/review/create.ts +30 -0
- package/src/actions/shippingSimulation.ts +60 -0
- package/src/actions/submmitForm.ts +25 -0
- package/src/actions/wishlist/addProduct.ts +43 -0
- package/src/actions/wishlist/removeProduct.ts +45 -0
- package/src/client.ts +123 -0
- package/src/commerceLoaders.ts +137 -0
- package/src/handlers/sitemap.ts +61 -0
- package/src/index.ts +58 -0
- package/src/loaders/cart.ts +46 -0
- package/src/loaders/partners.ts +58 -0
- package/src/loaders/productDetailsPage.ts +174 -0
- package/src/loaders/productList.ts +262 -0
- package/src/loaders/productListingPage.ts +333 -0
- package/src/loaders/proxy.ts +79 -0
- package/src/loaders/recommendations.ts +84 -0
- package/src/loaders/shop.ts +37 -0
- package/src/loaders/suggestion.ts +67 -0
- package/src/loaders/user.ts +48 -0
- package/src/loaders/wishlist.ts +40 -0
- package/src/manifest.gen.ts +69 -0
- package/src/mod.ts +88 -0
- package/src/registry.ts +9 -0
- package/src/utils/authError.ts +107 -0
- package/src/utils/authenticate.ts +42 -0
- package/src/utils/capitalize.ts +9 -0
- package/src/utils/cart.ts +33 -0
- package/src/utils/client.ts +11 -0
- package/src/utils/cookies.ts +82 -0
- package/src/utils/getVariations.ts +47 -0
- package/src/utils/graphql/queries.ts +1363 -0
- package/src/utils/graphql/storefront.graphql.gen.ts +10233 -0
- package/src/utils/graphql.ts +88 -0
- package/src/utils/graphqlOperationName.ts +67 -0
- package/src/utils/httpError.ts +15 -0
- package/src/utils/instrumentedFetch.ts +46 -0
- package/src/utils/operationRouter.ts +51 -0
- package/src/utils/parseHeaders.ts +19 -0
- package/src/utils/partner.ts +34 -0
- package/src/utils/requestCtx.ts +41 -0
- package/src/utils/transform.ts +460 -0
- package/src/utils/types.ts +6 -0
- package/src/utils/user.ts +9 -0
- package/tsconfig.json +7 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import type { Product, ProductDetailsPage } from "@decocms/apps-commerce/types";
|
|
2
|
+
import { getWakeClient } from "../client";
|
|
3
|
+
import { handleAuthError } from "../utils/authError";
|
|
4
|
+
import { MAXIMUM_REQUEST_QUANTITY } from "../utils/getVariations";
|
|
5
|
+
import { GetBuyList, GetProduct } from "../utils/graphql/queries";
|
|
6
|
+
import type { BuyListQuery, GetProductQuery } from "../utils/graphql/storefront.graphql.gen";
|
|
7
|
+
import { getPartnerCookie } from "../utils/partner";
|
|
8
|
+
import { currentRequestHeaders, currentUrl, forwardedHeaders } from "../utils/requestCtx";
|
|
9
|
+
import { parseSlug, toBreadcrumbList, toProduct } from "../utils/transform";
|
|
10
|
+
import type { RequestURLParam } from "../utils/types";
|
|
11
|
+
import productListLoader from "./productList";
|
|
12
|
+
|
|
13
|
+
export interface Props {
|
|
14
|
+
slug: RequestURLParam;
|
|
15
|
+
buyTogether?: boolean;
|
|
16
|
+
includeSameParent?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @title Wake Integration
|
|
21
|
+
* @description Product Details Page loader
|
|
22
|
+
*/
|
|
23
|
+
async function loader(props: Props): Promise<ProductDetailsPage | null> {
|
|
24
|
+
const url = currentUrl();
|
|
25
|
+
const { slug, buyTogether, includeSameParent } = props;
|
|
26
|
+
const storefront = getWakeClient();
|
|
27
|
+
|
|
28
|
+
const partnerAccessToken = getPartnerCookie(currentRequestHeaders());
|
|
29
|
+
|
|
30
|
+
const headers = forwardedHeaders();
|
|
31
|
+
|
|
32
|
+
if (!slug) return null;
|
|
33
|
+
|
|
34
|
+
const variantId = Number(url.searchParams.get("skuId")) || null;
|
|
35
|
+
const { id: productId } = parseSlug(slug);
|
|
36
|
+
|
|
37
|
+
if (!productId) {
|
|
38
|
+
throw new Error("Missing product id");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let wakeBuyList: BuyListQuery["buyList"] | undefined;
|
|
42
|
+
try {
|
|
43
|
+
const buyListResult = await storefront.query<BuyListQuery>(
|
|
44
|
+
GetBuyList,
|
|
45
|
+
{ id: productId, partnerAccessToken },
|
|
46
|
+
headers,
|
|
47
|
+
);
|
|
48
|
+
wakeBuyList = buyListResult.buyList;
|
|
49
|
+
} catch (error: unknown) {
|
|
50
|
+
handleAuthError(error, "load buy list");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const buyListProducts = await Promise.all(
|
|
54
|
+
wakeBuyList?.buyListProducts?.map(async (buyListProduct) => {
|
|
55
|
+
if (!buyListProduct) return;
|
|
56
|
+
|
|
57
|
+
const { productId, includeSameParent, quantity } = buyListProduct;
|
|
58
|
+
|
|
59
|
+
const buyListProductPage = await loader({
|
|
60
|
+
// 'slug' its just to fit the parse function of loader
|
|
61
|
+
slug: `slug-${productId}`,
|
|
62
|
+
includeSameParent,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
if (!buyListProductPage) return;
|
|
66
|
+
|
|
67
|
+
buyListProductPage.product.additionalProperty?.push({
|
|
68
|
+
"@type": "PropertyValue",
|
|
69
|
+
name: "SuggestedQuantity",
|
|
70
|
+
value: String(quantity),
|
|
71
|
+
alternateName: "Buy List Suggested Quantity",
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
return buyListProductPage.product;
|
|
75
|
+
}) ?? [],
|
|
76
|
+
).then((maybeProductList) => maybeProductList.filter((node): node is Product => Boolean(node)));
|
|
77
|
+
|
|
78
|
+
let wakeProduct: GetProductQuery["product"] | undefined;
|
|
79
|
+
try {
|
|
80
|
+
const productResult = await storefront.query<GetProductQuery>(
|
|
81
|
+
GetProduct,
|
|
82
|
+
{
|
|
83
|
+
productId,
|
|
84
|
+
includeParentIdVariants: includeSameParent,
|
|
85
|
+
partnerAccessToken,
|
|
86
|
+
},
|
|
87
|
+
headers,
|
|
88
|
+
);
|
|
89
|
+
wakeProduct = productResult.product;
|
|
90
|
+
} catch (error: unknown) {
|
|
91
|
+
handleAuthError(error, "load product details");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const wakeProductOrBuyList = wakeProduct || wakeBuyList;
|
|
95
|
+
|
|
96
|
+
if (!wakeProductOrBuyList) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const variantsItems =
|
|
101
|
+
(await productListLoader({
|
|
102
|
+
first: MAXIMUM_REQUEST_QUANTITY,
|
|
103
|
+
sortDirection: "ASC",
|
|
104
|
+
sortKey: "RANDOM",
|
|
105
|
+
filters: { productId: [productId] },
|
|
106
|
+
})) ?? [];
|
|
107
|
+
|
|
108
|
+
const buyTogetherItens =
|
|
109
|
+
buyTogether && wakeProductOrBuyList.buyTogether?.length
|
|
110
|
+
? ((await productListLoader({
|
|
111
|
+
first: MAXIMUM_REQUEST_QUANTITY,
|
|
112
|
+
sortDirection: "ASC",
|
|
113
|
+
sortKey: "RANDOM",
|
|
114
|
+
filters: {
|
|
115
|
+
productId: wakeProductOrBuyList.buyTogether
|
|
116
|
+
?.map((bt) => bt?.productId)
|
|
117
|
+
.filter((id): id is number => id != null),
|
|
118
|
+
mainVariant: true,
|
|
119
|
+
},
|
|
120
|
+
getVariations: true,
|
|
121
|
+
})) ?? [])
|
|
122
|
+
: [];
|
|
123
|
+
|
|
124
|
+
const product = toProduct(wakeProductOrBuyList, { base: url }, variantsItems, variantId);
|
|
125
|
+
return {
|
|
126
|
+
"@type": "ProductDetailsPage",
|
|
127
|
+
breadcrumbList: toBreadcrumbList(
|
|
128
|
+
wakeProductOrBuyList.breadcrumbs ?? [],
|
|
129
|
+
{
|
|
130
|
+
base: url,
|
|
131
|
+
},
|
|
132
|
+
product,
|
|
133
|
+
),
|
|
134
|
+
product: {
|
|
135
|
+
...product,
|
|
136
|
+
isAccessoryOrSparePartFor: buyListProducts,
|
|
137
|
+
isRelatedTo:
|
|
138
|
+
buyTogetherItens?.map((buyItem) => {
|
|
139
|
+
return {
|
|
140
|
+
...buyItem,
|
|
141
|
+
additionalType: "BuyTogether",
|
|
142
|
+
};
|
|
143
|
+
}) ?? [],
|
|
144
|
+
},
|
|
145
|
+
seo: {
|
|
146
|
+
canonical: product.isVariantOf?.url ?? "",
|
|
147
|
+
title: wakeProductOrBuyList.productName ?? "",
|
|
148
|
+
description: wakeProductOrBuyList.seo?.find((m) => m?.name === "description")?.content ?? "",
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export const cache = "stale-while-revalidate";
|
|
154
|
+
|
|
155
|
+
export const cacheKey = (props: Props): string | null => {
|
|
156
|
+
const url = currentUrl();
|
|
157
|
+
const skuId = url.searchParams.get("skuId") ?? "";
|
|
158
|
+
|
|
159
|
+
// Avoid cross-tenant cache bleed when a partner token is present
|
|
160
|
+
if (getPartnerCookie(currentRequestHeaders())) {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const params = new URLSearchParams([
|
|
165
|
+
["slug", String(props.slug)],
|
|
166
|
+
["buyTogether", String(props.buyTogether ?? false)],
|
|
167
|
+
["includeSameParent", String(props.includeSameParent ?? false)],
|
|
168
|
+
["skuId", skuId],
|
|
169
|
+
]);
|
|
170
|
+
|
|
171
|
+
return `wake:pdp?${params.toString()}`;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
export default loader;
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import type { Product } from "@decocms/apps-commerce/types";
|
|
2
|
+
import { getWakeClient } from "../client";
|
|
3
|
+
import { handleAuthError } from "../utils/authError";
|
|
4
|
+
import { getVariations } from "../utils/getVariations";
|
|
5
|
+
import { GetProducts } from "../utils/graphql/queries";
|
|
6
|
+
import type { GetProductsQuery, ProductFragment } from "../utils/graphql/storefront.graphql.gen";
|
|
7
|
+
import { getPartnerCookie } from "../utils/partner";
|
|
8
|
+
import { currentRequestHeaders, currentUrl, forwardedHeaders } from "../utils/requestCtx";
|
|
9
|
+
import { toProduct } from "../utils/transform";
|
|
10
|
+
|
|
11
|
+
export interface StockFilter {
|
|
12
|
+
dcId?: number[];
|
|
13
|
+
/** @description The distribution center names to match. */
|
|
14
|
+
dcName?: string[];
|
|
15
|
+
/**
|
|
16
|
+
* @title Stock greater than or equal
|
|
17
|
+
* @description The product stock must be greater than or equal to.
|
|
18
|
+
*/
|
|
19
|
+
stock_gte?: number;
|
|
20
|
+
/**
|
|
21
|
+
* @title Stock less than or equal
|
|
22
|
+
* @description The product stock must be lesser than or equal to.
|
|
23
|
+
*/
|
|
24
|
+
stock_lte?: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface PriceFilter {
|
|
28
|
+
/**
|
|
29
|
+
* @title Discount greater than
|
|
30
|
+
* @description The product discount must be greater than or equal to.
|
|
31
|
+
*/
|
|
32
|
+
discount_gte?: number;
|
|
33
|
+
/**
|
|
34
|
+
* @title Discount lesser than
|
|
35
|
+
* @description The product discount must be lesser than or equal to.
|
|
36
|
+
*/
|
|
37
|
+
discount_lte?: number;
|
|
38
|
+
/** @description Return only products where the listed price is more than the price. */
|
|
39
|
+
discounted?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* @title Price greater than
|
|
42
|
+
* @description The product price must be greater than or equal to.
|
|
43
|
+
*/
|
|
44
|
+
price_gte?: number;
|
|
45
|
+
/**
|
|
46
|
+
* @title Price lesser than
|
|
47
|
+
* @description The product price must be lesser than or equal to. */
|
|
48
|
+
price_lte?: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface Filters {
|
|
52
|
+
/** @description The set of attributes to filter. */
|
|
53
|
+
attributes?: {
|
|
54
|
+
id?: string[];
|
|
55
|
+
name?: string[];
|
|
56
|
+
type?: string[];
|
|
57
|
+
value?: string[];
|
|
58
|
+
};
|
|
59
|
+
/** @description Choose if you want to retrieve only the available products in stock. */
|
|
60
|
+
available?: boolean;
|
|
61
|
+
/** @description The set of brand IDs which the result item brand ID must be included in. */
|
|
62
|
+
brandId?: number[];
|
|
63
|
+
/** @description The set of category IDs which the result item category ID must be included in. */
|
|
64
|
+
categoryId?: number[];
|
|
65
|
+
/** @description The set of EANs which the result item EAN must be included. */
|
|
66
|
+
ean?: string[];
|
|
67
|
+
/** @description Retrieve the product variant only if it contains images. */
|
|
68
|
+
hasImages?: boolean;
|
|
69
|
+
/** @description Retrieve the product variant only if it is the main product variant. */
|
|
70
|
+
mainVariant?: boolean;
|
|
71
|
+
|
|
72
|
+
/** @description The set of prices to filter. */
|
|
73
|
+
prices?: PriceFilter;
|
|
74
|
+
|
|
75
|
+
/** @description The product unique identifier (you may provide a list of IDs if needed). */
|
|
76
|
+
productId?: number[];
|
|
77
|
+
/** @description The product variant unique identifier (you may provide a list of IDs if needed). */
|
|
78
|
+
productVariantId?: number[];
|
|
79
|
+
/** @description A product ID or a list of IDs to search for other products with the same parent ID. */
|
|
80
|
+
sameParentAs?: number[];
|
|
81
|
+
/** @description The set of SKUs which the result item SKU must be included. */
|
|
82
|
+
sku?: string[];
|
|
83
|
+
/**
|
|
84
|
+
* @title Stock greater than
|
|
85
|
+
* @description Show products with a quantity of available products in stock greater than or equal to the given number. */
|
|
86
|
+
stock_gte?: number;
|
|
87
|
+
/**
|
|
88
|
+
* @title Stock lesser than
|
|
89
|
+
* @description Show products with a quantity of available products in stock less than or equal to the given number. */
|
|
90
|
+
stock_lte?: number;
|
|
91
|
+
/** @description The set of stocks to filter. */
|
|
92
|
+
stocks?: StockFilter;
|
|
93
|
+
/**
|
|
94
|
+
* @title Upated after
|
|
95
|
+
* @format date
|
|
96
|
+
* @description Retrieve products which the last update date is greater than or equal to the given date.
|
|
97
|
+
*/
|
|
98
|
+
updatedAt_gte?: string;
|
|
99
|
+
/**
|
|
100
|
+
* @title Upated before
|
|
101
|
+
* @format date
|
|
102
|
+
* @description Retrieve products which the last update date is less than or equal to the given date.
|
|
103
|
+
*/
|
|
104
|
+
updatedAt_lte?: string;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export interface Props {
|
|
108
|
+
/**
|
|
109
|
+
* @title Count
|
|
110
|
+
* @description Number of products to return
|
|
111
|
+
* @maximum 50
|
|
112
|
+
* @default 12
|
|
113
|
+
*/
|
|
114
|
+
first: number;
|
|
115
|
+
sortDirection: "ASC" | "DESC";
|
|
116
|
+
sortKey: "DISCOUNT" | "NAME" | "PRICE" | "RANDOM" | "RELEASE_DATE" | "SALES" | "STOCK";
|
|
117
|
+
|
|
118
|
+
filters: Filters;
|
|
119
|
+
|
|
120
|
+
/** @description Retrieve variantions for each product. */
|
|
121
|
+
getVariations?: boolean;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Valid keys of Wake's `ProductExplicitFiltersInput`. Migrated blocks may carry
|
|
125
|
+
// other platforms' filter shapes (e.g. Shopify's `tags`/`productTypes`/
|
|
126
|
+
// `productVendors`/`variantOptions`), which Wake rejects with a 500. Keep only
|
|
127
|
+
// the recognized keys.
|
|
128
|
+
const WAKE_FILTER_KEYS = new Set<keyof Filters>([
|
|
129
|
+
"attributes",
|
|
130
|
+
"available",
|
|
131
|
+
"brandId",
|
|
132
|
+
"categoryId",
|
|
133
|
+
"ean",
|
|
134
|
+
"hasImages",
|
|
135
|
+
"mainVariant",
|
|
136
|
+
"prices",
|
|
137
|
+
"productId",
|
|
138
|
+
"productVariantId",
|
|
139
|
+
"sameParentAs",
|
|
140
|
+
"sku",
|
|
141
|
+
"stock_gte",
|
|
142
|
+
"stock_lte",
|
|
143
|
+
"stocks",
|
|
144
|
+
"updatedAt_gte",
|
|
145
|
+
"updatedAt_lte",
|
|
146
|
+
]);
|
|
147
|
+
|
|
148
|
+
const sanitizeFilters = (filters: unknown): Filters => {
|
|
149
|
+
if (!filters || typeof filters !== "object") return {};
|
|
150
|
+
const out: Record<string, unknown> = {};
|
|
151
|
+
for (const [key, value] of Object.entries(filters as Record<string, unknown>)) {
|
|
152
|
+
if (WAKE_FILTER_KEYS.has(key as keyof Filters)) out[key] = value;
|
|
153
|
+
}
|
|
154
|
+
return out as Filters;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* @title Wake Integration
|
|
159
|
+
* @description Product List loader
|
|
160
|
+
*/
|
|
161
|
+
const productListLoader = async (props: Props): Promise<Product[] | null> => {
|
|
162
|
+
const url = currentUrl();
|
|
163
|
+
const storefront = getWakeClient();
|
|
164
|
+
const partnerAccessToken = getPartnerCookie(currentRequestHeaders());
|
|
165
|
+
|
|
166
|
+
const headers = forwardedHeaders();
|
|
167
|
+
|
|
168
|
+
let data: GetProductsQuery | undefined;
|
|
169
|
+
try {
|
|
170
|
+
// Pass ONLY the declared GraphQL variables. The framework injects extra
|
|
171
|
+
// props (e.g. `__pageUrl`, `__pagePath`) that Wake rejects (500) if spread
|
|
172
|
+
// into the operation's variables, so we pick explicitly.
|
|
173
|
+
const variables = {
|
|
174
|
+
// Tolerate legacy/other-platform props (e.g. `count`) and default the
|
|
175
|
+
// required GraphQL variables so shelf blocks that don't set them still
|
|
176
|
+
// resolve. `first` is `Int!` — an undefined here 500s the Wake API.
|
|
177
|
+
first: props.first ?? (props as { count?: number }).count ?? 12,
|
|
178
|
+
sortDirection: props.sortDirection ?? "DESC",
|
|
179
|
+
sortKey: props.sortKey ?? "SALES",
|
|
180
|
+
filters: sanitizeFilters(props.filters),
|
|
181
|
+
partnerAccessToken,
|
|
182
|
+
};
|
|
183
|
+
data = await storefront.query<GetProductsQuery>(GetProducts, variables, headers);
|
|
184
|
+
} catch (error: unknown) {
|
|
185
|
+
handleAuthError(error, "load product list");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const products = data?.products?.nodes;
|
|
189
|
+
|
|
190
|
+
if (!Array.isArray(products)) {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const productIDs = products.map((i) => i?.productId).filter((id): id is number => id != null);
|
|
195
|
+
|
|
196
|
+
const variations = props.getVariations ? await getVariations(productIDs, url, headers) : [];
|
|
197
|
+
|
|
198
|
+
return products
|
|
199
|
+
.filter((node): node is ProductFragment => Boolean(node))
|
|
200
|
+
.map((node) => {
|
|
201
|
+
const productVariations = variations?.filter(
|
|
202
|
+
(v) => v.inProductGroupWithID === node.productId,
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
return toProduct(node, { base: url }, productVariations);
|
|
206
|
+
});
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
export const cache = "stale-while-revalidate";
|
|
210
|
+
|
|
211
|
+
// Helper function for deterministic filter serialization
|
|
212
|
+
function stableStringify(value: unknown): string {
|
|
213
|
+
const seen = new WeakSet();
|
|
214
|
+
const replacer = (_k: string, v: unknown) => {
|
|
215
|
+
if (v && typeof v === "object") {
|
|
216
|
+
if (seen.has(v as object)) return "[Circular]";
|
|
217
|
+
seen.add(v as object);
|
|
218
|
+
if (Array.isArray(v)) return v; // keep array order
|
|
219
|
+
const obj = v as Record<string, unknown>;
|
|
220
|
+
return Object.keys(obj)
|
|
221
|
+
.sort()
|
|
222
|
+
.reduce(
|
|
223
|
+
(acc, key) => {
|
|
224
|
+
acc[key] = obj[key];
|
|
225
|
+
return acc;
|
|
226
|
+
},
|
|
227
|
+
{} as Record<string, unknown>,
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
return v;
|
|
231
|
+
};
|
|
232
|
+
return JSON.stringify(value, replacer);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export const cacheKey = (props: Props): string | null => {
|
|
236
|
+
// Avoid cross-tenant cache bleed when a partner token is present
|
|
237
|
+
if (getPartnerCookie(currentRequestHeaders())) {
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Don't cache dynamic/random sorts
|
|
242
|
+
if (props.sortKey === "RANDOM") {
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const params = new URLSearchParams([
|
|
247
|
+
["first", String(props.first ?? 12)],
|
|
248
|
+
["sortKey", props.sortKey ?? "NAME"],
|
|
249
|
+
["sortDirection", props.sortDirection ?? "ASC"],
|
|
250
|
+
["getVariations", String(Boolean(props.getVariations ?? false))],
|
|
251
|
+
]);
|
|
252
|
+
|
|
253
|
+
// Add filters to cache key with deterministic serialization
|
|
254
|
+
if (props.filters) {
|
|
255
|
+
params.append("filters", stableStringify(props.filters));
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
params.sort();
|
|
259
|
+
return `wake:productList?${params.toString()}`;
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
export default productListLoader;
|