@replohq/sdk 2.15.0 → 2.16.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/_vendor/schemas/placeholderProductsProtocol.d.ts +7 -0
- package/_vendor/schemas/placeholderProductsProtocol.mjs +8 -0
- package/cart/hooks/use-add-to-cart.js +8 -1
- package/lib/buildMetadata.js +3 -3
- package/loaders/placeholder-product.d.ts +24 -0
- package/loaders/placeholder-product.js +39 -0
- package/loaders/product-loader.js +48 -1
- package/package.json +4 -4
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const PLACEHOLDER_PRODUCTS_IFRAME_RPC_CHANNEL_ID: "replo:placeholder-products-iframe-rpc";
|
|
2
|
+
export declare const PLACEHOLDER_PRODUCTS_IFRAME_RPC_CHANNEL_VERSION: 3;
|
|
3
|
+
export declare const PLACEHOLDER_HERO_PRODUCT_ID = "f0921037-e530-4e67-a612-d573b7c40b19";
|
|
4
|
+
export declare const PLACEHOLDER_PRODUCT_ID = "c53f98cd-0989-4661-a7c2-a0304d184676";
|
|
5
|
+
export type PlaceholderProductId = typeof PLACEHOLDER_HERO_PRODUCT_ID | typeof PLACEHOLDER_PRODUCT_ID;
|
|
6
|
+
export declare const PLACEHOLDER_PRODUCTS_SOURCE_KEY = "__REPLO_PLACEHOLDER_PRODUCTS__";
|
|
7
|
+
export declare const PLACEHOLDER_PRODUCTS_CHANGE_EVENT = "replo:placeholder-products";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
var PLACEHOLDER_PRODUCTS_IFRAME_RPC_CHANNEL_ID = "replo:placeholder-products-iframe-rpc";
|
|
2
|
+
var PLACEHOLDER_PRODUCTS_IFRAME_RPC_CHANNEL_VERSION = 3;
|
|
3
|
+
var PLACEHOLDER_HERO_PRODUCT_ID = "f0921037-e530-4e67-a612-d573b7c40b19";
|
|
4
|
+
var PLACEHOLDER_PRODUCT_ID = "c53f98cd-0989-4661-a7c2-a0304d184676";
|
|
5
|
+
var PLACEHOLDER_PRODUCTS_SOURCE_KEY = "__REPLO_PLACEHOLDER_PRODUCTS__";
|
|
6
|
+
var PLACEHOLDER_PRODUCTS_CHANGE_EVENT = "replo:placeholder-products";
|
|
7
|
+
|
|
8
|
+
export { PLACEHOLDER_HERO_PRODUCT_ID, PLACEHOLDER_PRODUCTS_CHANGE_EVENT, PLACEHOLDER_PRODUCTS_IFRAME_RPC_CHANNEL_ID, PLACEHOLDER_PRODUCTS_IFRAME_RPC_CHANNEL_VERSION, PLACEHOLDER_PRODUCTS_SOURCE_KEY, PLACEHOLDER_PRODUCT_ID };
|
|
@@ -3,6 +3,7 @@ import React from "react";
|
|
|
3
3
|
import { useQueryClient } from "@tanstack/react-query";
|
|
4
4
|
import { fromMajorUnitsToMinorUnits } from "../../_vendor/schemas/moneyFormat.mjs";
|
|
5
5
|
import { DATA_LOADER_KEYS } from "../../loaders/loader-keys";
|
|
6
|
+
import { isPlaceholderMerchandiseId } from "../../loaders/placeholder-product";
|
|
6
7
|
import { useCartInternal } from "../cart-provider";
|
|
7
8
|
import { toCartError } from "../utils/cart-error";
|
|
8
9
|
const PRODUCT_LOADER_KEYS = [
|
|
@@ -86,9 +87,15 @@ function useAddToCart() {
|
|
|
86
87
|
const [isAdding, setIsAdding] = React.useState(false);
|
|
87
88
|
const addToCart = React.useCallback(
|
|
88
89
|
async (items, openCartAfterAdd = true) => {
|
|
90
|
+
const buyableItems = items.filter((item) => {
|
|
91
|
+
return !isPlaceholderMerchandiseId(item.merchandiseId);
|
|
92
|
+
});
|
|
93
|
+
if (buyableItems.length === 0) {
|
|
94
|
+
return { adjustments: [], error: null };
|
|
95
|
+
}
|
|
89
96
|
setIsAdding(true);
|
|
90
97
|
try {
|
|
91
|
-
const cartLines =
|
|
98
|
+
const cartLines = buyableItems.map((item) => {
|
|
92
99
|
const merchandise = item.merchandise ?? findMerchandiseInQueryCache({
|
|
93
100
|
queryClient,
|
|
94
101
|
merchandiseId: item.merchandiseId
|
package/lib/buildMetadata.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const BUILD_METADATA = {
|
|
2
2
|
"packageName": "@replohq/sdk",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.16.0",
|
|
4
4
|
"branch": "HEAD",
|
|
5
|
-
"commit": "
|
|
6
|
-
"builtAt": "2026-10-
|
|
5
|
+
"commit": "ace9e163575351a4ad987d0f0427807eaec836ee",
|
|
6
|
+
"builtAt": "2026-10-31T09:51:00.149Z"
|
|
7
7
|
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { PlaceholderProductId } from "../_vendor/schemas/placeholderProductsProtocol";
|
|
2
|
+
import type { FullProduct } from "./loader-schemas";
|
|
3
|
+
import { PLACEHOLDER_PRODUCTS_SOURCE_KEY } from "../_vendor/schemas/placeholderProductsProtocol";
|
|
4
|
+
declare global {
|
|
5
|
+
interface Window {
|
|
6
|
+
__REPLO_PLACEHOLDER_PRODUCTS__?: (card: {
|
|
7
|
+
productId: PlaceholderProductId;
|
|
8
|
+
cardId: string;
|
|
9
|
+
}) => FullProduct | null;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/** Placeholder IDs name no product, so loaders never fetch them. */
|
|
13
|
+
export declare function isPlaceholderProductId(productId: string | null | undefined): productId is PlaceholderProductId;
|
|
14
|
+
/** A placeholder product's variant IDs start with its placeholder ID, so they never reach a cart. */
|
|
15
|
+
export declare function isPlaceholderMerchandiseId(merchandiseId: string): boolean;
|
|
16
|
+
export type PlaceholderProductSource = NonNullable<Window[typeof PLACEHOLDER_PRODUCTS_SOURCE_KEY]>;
|
|
17
|
+
/** sandbox-runtime's product source (null on the server and on a published site), numbered so each new one is a new key. */
|
|
18
|
+
export declare function usePlaceholderProductSource(): {
|
|
19
|
+
source: ((card: {
|
|
20
|
+
productId: PlaceholderProductId;
|
|
21
|
+
cardId: string;
|
|
22
|
+
}) => FullProduct | null) | null;
|
|
23
|
+
sourceNumber: number;
|
|
24
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { useSyncExternalStore } from "react";
|
|
2
|
+
import {
|
|
3
|
+
PLACEHOLDER_HERO_PRODUCT_ID,
|
|
4
|
+
PLACEHOLDER_PRODUCT_ID,
|
|
5
|
+
PLACEHOLDER_PRODUCTS_CHANGE_EVENT,
|
|
6
|
+
PLACEHOLDER_PRODUCTS_SOURCE_KEY
|
|
7
|
+
} from "../_vendor/schemas/placeholderProductsProtocol.mjs";
|
|
8
|
+
function isPlaceholderProductId(productId) {
|
|
9
|
+
return productId === PLACEHOLDER_HERO_PRODUCT_ID || productId === PLACEHOLDER_PRODUCT_ID;
|
|
10
|
+
}
|
|
11
|
+
function isPlaceholderMerchandiseId(merchandiseId) {
|
|
12
|
+
return merchandiseId.startsWith(PLACEHOLDER_HERO_PRODUCT_ID) || merchandiseId.startsWith(PLACEHOLDER_PRODUCT_ID);
|
|
13
|
+
}
|
|
14
|
+
function subscribe(listener) {
|
|
15
|
+
window.addEventListener(PLACEHOLDER_PRODUCTS_CHANGE_EVENT, listener);
|
|
16
|
+
return () => {
|
|
17
|
+
window.removeEventListener(PLACEHOLDER_PRODUCTS_CHANGE_EVENT, listener);
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
let sourceCount = 0;
|
|
21
|
+
const sourceNumbers = /* @__PURE__ */ new WeakMap();
|
|
22
|
+
function getSourceNumber(source) {
|
|
23
|
+
const sourceNumber = sourceNumbers.get(source) ?? ++sourceCount;
|
|
24
|
+
sourceNumbers.set(source, sourceNumber);
|
|
25
|
+
return sourceNumber;
|
|
26
|
+
}
|
|
27
|
+
function usePlaceholderProductSource() {
|
|
28
|
+
const source = useSyncExternalStore(
|
|
29
|
+
subscribe,
|
|
30
|
+
() => window[PLACEHOLDER_PRODUCTS_SOURCE_KEY] ?? null,
|
|
31
|
+
() => null
|
|
32
|
+
);
|
|
33
|
+
return { source, sourceNumber: source ? getSourceNumber(source) : 0 };
|
|
34
|
+
}
|
|
35
|
+
export {
|
|
36
|
+
isPlaceholderMerchandiseId,
|
|
37
|
+
isPlaceholderProductId,
|
|
38
|
+
usePlaceholderProductSource
|
|
39
|
+
};
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx } from "react/jsx-runtime";
|
|
3
|
-
import { Suspense } from "react";
|
|
3
|
+
import { Suspense, useId } from "react";
|
|
4
4
|
import { useSuspenseQuery } from "@tanstack/react-query";
|
|
5
5
|
import { invokeLoader } from "./invoke-loader";
|
|
6
6
|
import { LoaderErrorBoundary } from "./loader-error-boundary";
|
|
7
|
+
import {
|
|
8
|
+
isPlaceholderProductId,
|
|
9
|
+
usePlaceholderProductSource
|
|
10
|
+
} from "./placeholder-product";
|
|
7
11
|
import { useReportPreviewPlaceholderRender } from "./preview-placeholder";
|
|
8
12
|
import { buildProductLoaderArgs } from "./product-loader-args";
|
|
9
13
|
function ProductLoaderInner({
|
|
@@ -20,6 +24,39 @@ function ProductLoaderInner({
|
|
|
20
24
|
useReportPreviewPlaceholderRender(queryResult.data);
|
|
21
25
|
return /* @__PURE__ */ jsx(Fragment, { children: children(queryResult.data) });
|
|
22
26
|
}
|
|
27
|
+
function PlaceholderProductCard({
|
|
28
|
+
getProduct,
|
|
29
|
+
card,
|
|
30
|
+
fallback,
|
|
31
|
+
children
|
|
32
|
+
}) {
|
|
33
|
+
const product = getProduct?.(card) ?? null;
|
|
34
|
+
return /* @__PURE__ */ jsx(Fragment, { children: product ? children(product) : fallback });
|
|
35
|
+
}
|
|
36
|
+
function PlaceholderProductLoader({
|
|
37
|
+
productId,
|
|
38
|
+
fallback,
|
|
39
|
+
children
|
|
40
|
+
}) {
|
|
41
|
+
const cardId = useId();
|
|
42
|
+
const { source, sourceNumber } = usePlaceholderProductSource();
|
|
43
|
+
return /* @__PURE__ */ jsx(
|
|
44
|
+
LoaderErrorBoundary,
|
|
45
|
+
{
|
|
46
|
+
fallback,
|
|
47
|
+
resetKey: `placeholder:${productId}:${sourceNumber}`,
|
|
48
|
+
children: /* @__PURE__ */ jsx(
|
|
49
|
+
PlaceholderProductCard,
|
|
50
|
+
{
|
|
51
|
+
getProduct: source,
|
|
52
|
+
card: { productId, cardId },
|
|
53
|
+
fallback,
|
|
54
|
+
children
|
|
55
|
+
}
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
);
|
|
59
|
+
}
|
|
23
60
|
function ProductLoader({
|
|
24
61
|
loaderKey,
|
|
25
62
|
handle,
|
|
@@ -32,6 +69,16 @@ function ProductLoader({
|
|
|
32
69
|
fallback,
|
|
33
70
|
loadingFallback = fallback
|
|
34
71
|
}) {
|
|
72
|
+
if (isPlaceholderProductId(productId)) {
|
|
73
|
+
return /* @__PURE__ */ jsx(
|
|
74
|
+
PlaceholderProductLoader,
|
|
75
|
+
{
|
|
76
|
+
productId,
|
|
77
|
+
fallback: fallback ?? null,
|
|
78
|
+
children
|
|
79
|
+
}
|
|
80
|
+
);
|
|
81
|
+
}
|
|
35
82
|
const args = buildProductLoaderArgs({
|
|
36
83
|
handle,
|
|
37
84
|
productId,
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@replohq/sdk",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.16.0",
|
|
4
4
|
"reploBuild": {
|
|
5
5
|
"packageName": "@replohq/sdk",
|
|
6
|
-
"version": "2.
|
|
6
|
+
"version": "2.16.0",
|
|
7
7
|
"branch": "HEAD",
|
|
8
|
-
"commit": "
|
|
9
|
-
"builtAt": "2026-10-
|
|
8
|
+
"commit": "ace9e163575351a4ad987d0f0427807eaec836ee",
|
|
9
|
+
"builtAt": "2026-10-31T09:51:00.149Z"
|
|
10
10
|
},
|
|
11
11
|
"description": "Replo SDK — cart, analytics, and data loaders for agent-built Next.js sites.",
|
|
12
12
|
"license": "SEE LICENSE IN LICENSE",
|