@replohq/sdk 1.10.1 → 1.10.2
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/cart/cart-actions.d.ts +8 -0
- package/cart/cart-actions.js +20 -9
- package/cart/cart-loader.d.ts +2 -1
- package/cart/cart-loader.js +4 -2
- package/cart/cart-provider.js +363 -263
- package/cart/supplied-cart-reconciliation.d.ts +5 -0
- package/cart/supplied-cart-reconciliation.js +17 -0
- package/lib/buildMetadata.js +3 -3
- package/package.json +4 -4
- package/providers/replo-provider.js +8 -1
package/cart/cart-actions.d.ts
CHANGED
|
@@ -11,6 +11,14 @@ import type { AddToCartPayload, Cart, CartLinePayload, RemoveFromCartPayload, Up
|
|
|
11
11
|
export declare function getOrCreateCartAction(options?: {
|
|
12
12
|
country?: StorefrontCountryCode;
|
|
13
13
|
}): Promise<Cart | null>;
|
|
14
|
+
/**
|
|
15
|
+
* Reconciles an existing persisted cart without creating one when none exists.
|
|
16
|
+
*
|
|
17
|
+
* @returns The reconciled cart, or null when no persisted cart exists
|
|
18
|
+
*/
|
|
19
|
+
export declare function reconcileCartAction(options?: {
|
|
20
|
+
country?: StorefrontCountryCode;
|
|
21
|
+
}): Promise<Cart | null>;
|
|
14
22
|
/**
|
|
15
23
|
* Retrieves the current cart from cookies. Returns null if no cart exists or
|
|
16
24
|
* if the cart has expired/been deleted.
|
package/cart/cart-actions.js
CHANGED
|
@@ -11,21 +11,31 @@ import {
|
|
|
11
11
|
saveCartToCookie
|
|
12
12
|
} from "./utils/cookie-cart-persistence";
|
|
13
13
|
const SHOPIFY_CART_COOKIE_NAME = "replo_cart_id";
|
|
14
|
+
async function reconcileExistingCart(cart, country) {
|
|
15
|
+
const cartGateway = await getCartGateway();
|
|
16
|
+
const reconciledCart = await cartGateway.reconcileStoredCart(cart);
|
|
17
|
+
if (country && reconciledCart.buyerIdentity?.country !== country) {
|
|
18
|
+
return await cartGateway.updateCartBuyerIdentity({
|
|
19
|
+
cartId: reconciledCart.id,
|
|
20
|
+
buyerIdentity: { country }
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
return reconciledCart;
|
|
24
|
+
}
|
|
14
25
|
async function getOrCreateCartAction(options) {
|
|
15
26
|
const existingCart = await getCartAction();
|
|
16
27
|
if (existingCart) {
|
|
17
|
-
|
|
18
|
-
const reconciledCart = await cartGateway.reconcileStoredCart(existingCart);
|
|
19
|
-
if (options?.country && reconciledCart.buyerIdentity?.country !== options.country) {
|
|
20
|
-
return await cartGateway.updateCartBuyerIdentity({
|
|
21
|
-
cartId: reconciledCart.id,
|
|
22
|
-
buyerIdentity: { country: options.country }
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
return reconciledCart;
|
|
28
|
+
return await reconcileExistingCart(existingCart, options?.country);
|
|
26
29
|
}
|
|
27
30
|
return await createCartAction({ country: options?.country });
|
|
28
31
|
}
|
|
32
|
+
async function reconcileCartAction(options) {
|
|
33
|
+
const existingCart = await getCartAction();
|
|
34
|
+
if (!existingCart) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
return await reconcileExistingCart(existingCart, options?.country);
|
|
38
|
+
}
|
|
29
39
|
async function getCartAction() {
|
|
30
40
|
const checkoutProvider = await getCheckoutProvider();
|
|
31
41
|
if (checkoutProvider === "stripe") {
|
|
@@ -103,6 +113,7 @@ export {
|
|
|
103
113
|
createCartAction,
|
|
104
114
|
getCartAction,
|
|
105
115
|
getOrCreateCartAction,
|
|
116
|
+
reconcileCartAction,
|
|
106
117
|
removeCartLineItemsAction,
|
|
107
118
|
updateCartDiscountCodesAction,
|
|
108
119
|
updateCartLineItemAction
|
package/cart/cart-loader.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type { StorefrontCountryCode } from "../loaders/markets";
|
|
|
7
7
|
* @param children - Child components that will have access to the cart
|
|
8
8
|
* @returns A CartProvider component wrapping the children with cart data
|
|
9
9
|
*/
|
|
10
|
-
export declare function CartLoader({ children, country, }: React.PropsWithChildren<{
|
|
10
|
+
export declare function CartLoader({ children, country, shouldReconcileSuppliedCart, }: React.PropsWithChildren<{
|
|
11
11
|
country?: StorefrontCountryCode;
|
|
12
|
+
shouldReconcileSuppliedCart: boolean;
|
|
12
13
|
}>): Promise<import("react/jsx-runtime").JSX.Element>;
|
package/cart/cart-loader.js
CHANGED
|
@@ -2,16 +2,18 @@ import { jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { unstable_rethrow } from "next/navigation";
|
|
3
3
|
import { getCartAction } from "./cart-actions";
|
|
4
4
|
import { CartProvider } from "./cart-provider";
|
|
5
|
+
import { SuppliedCartReconciliationBoundary } from "./supplied-cart-reconciliation";
|
|
5
6
|
async function CartLoader({
|
|
6
7
|
children,
|
|
7
|
-
country
|
|
8
|
+
country,
|
|
9
|
+
shouldReconcileSuppliedCart
|
|
8
10
|
}) {
|
|
9
11
|
const cart = false ? null : await getCartAction().catch((error) => {
|
|
10
12
|
unstable_rethrow(error);
|
|
11
13
|
console.warn("[Replo] Failed to load cart:", error);
|
|
12
14
|
return null;
|
|
13
15
|
});
|
|
14
|
-
return /* @__PURE__ */ jsx(CartProvider, { cart, country, children });
|
|
16
|
+
return /* @__PURE__ */ jsx(SuppliedCartReconciliationBoundary, { enabled: shouldReconcileSuppliedCart, children: /* @__PURE__ */ jsx(CartProvider, { cart, country, children }) });
|
|
15
17
|
}
|
|
16
18
|
export {
|
|
17
19
|
CartLoader
|
package/cart/cart-provider.js
CHANGED
|
@@ -8,11 +8,13 @@ import {
|
|
|
8
8
|
createCartAction,
|
|
9
9
|
getCartAction,
|
|
10
10
|
getOrCreateCartAction,
|
|
11
|
+
reconcileCartAction,
|
|
11
12
|
removeCartLineItemsAction,
|
|
12
13
|
updateCartDiscountCodesAction,
|
|
13
14
|
updateCartLineItemAction
|
|
14
15
|
} from "./cart-actions";
|
|
15
16
|
import { CartCountryContext } from "./cart-country-context";
|
|
17
|
+
import { useShouldReconcileSuppliedCart } from "./supplied-cart-reconciliation";
|
|
16
18
|
import {
|
|
17
19
|
deriveAddToCartAdjustments,
|
|
18
20
|
deriveUpdateCartAdjustments
|
|
@@ -89,6 +91,25 @@ function removeLineFromCart(cart, lineId) {
|
|
|
89
91
|
const updatedLines = cart.lines.filter((line) => line.id !== lineId);
|
|
90
92
|
return recalculateCartCost({ ...cart, lines: updatedLines });
|
|
91
93
|
}
|
|
94
|
+
async function createBuyNowCart({
|
|
95
|
+
lines,
|
|
96
|
+
discountCodes,
|
|
97
|
+
country
|
|
98
|
+
}) {
|
|
99
|
+
const cart = await createCartAction({
|
|
100
|
+
lines,
|
|
101
|
+
skipStoringCart: true,
|
|
102
|
+
discountCodes,
|
|
103
|
+
country
|
|
104
|
+
});
|
|
105
|
+
if (!cart) {
|
|
106
|
+
console.warn(
|
|
107
|
+
"[Replo] Failed to create cart for buy now. Reach out to support@replo.app if this persists."
|
|
108
|
+
);
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
return cart;
|
|
112
|
+
}
|
|
92
113
|
const CartContext = React.createContext(
|
|
93
114
|
void 0
|
|
94
115
|
);
|
|
@@ -97,6 +118,9 @@ function CartProvider({
|
|
|
97
118
|
cart,
|
|
98
119
|
country
|
|
99
120
|
}) {
|
|
121
|
+
const isSuppliedCart = Boolean(cart);
|
|
122
|
+
const isInternalSuppliedCartReconciliationEnabled = useShouldReconcileSuppliedCart();
|
|
123
|
+
const shouldReconcileSuppliedCart = isSuppliedCart && (isInternalSuppliedCartReconciliationEnabled || Boolean(country));
|
|
100
124
|
const [cartData, setCartData] = React.useState(cart);
|
|
101
125
|
const optimisticCart = React.useRef(cart);
|
|
102
126
|
const updateCartData = React.useCallback(
|
|
@@ -116,79 +140,110 @@ function CartProvider({
|
|
|
116
140
|
if (marketSelection.current.country !== country) {
|
|
117
141
|
marketSelection.current = { country };
|
|
118
142
|
}
|
|
119
|
-
const runWithErrorState =
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
143
|
+
const runWithErrorState = React.useCallback(
|
|
144
|
+
async (operation, mutate) => {
|
|
145
|
+
setError(null);
|
|
146
|
+
try {
|
|
147
|
+
return await mutate();
|
|
148
|
+
} catch (caught) {
|
|
149
|
+
console.error(`[Replo] Cart ${operation} failed:`, caught);
|
|
150
|
+
setError(toCartError(operation, caught));
|
|
151
|
+
throw caught;
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
[]
|
|
155
|
+
);
|
|
129
156
|
const analytics = useAnalyticsOptional();
|
|
130
157
|
const serverCart = React.useRef(cart);
|
|
131
158
|
const needsRefresh = React.useRef(false);
|
|
132
159
|
const mutationQueue = React.useRef(Promise.resolve());
|
|
133
160
|
const pendingMutations = React.useRef(0);
|
|
134
161
|
const optimisticLines = React.useRef(/* @__PURE__ */ new Map());
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
throw new CartContextError({
|
|
143
|
-
message: "Your cart could not be refreshed. Please reload before changing it."
|
|
144
|
-
});
|
|
145
|
-
serverCart.current = restoredCart;
|
|
146
|
-
needsRefresh.current = false;
|
|
147
|
-
}
|
|
148
|
-
if (!serverCart.current) {
|
|
149
|
-
throw new CartContextError({
|
|
150
|
-
message: "Your cart could not be loaded. Please try again."
|
|
151
|
-
});
|
|
152
|
-
}
|
|
162
|
+
const lastReconciliationCountry = React.useRef(false);
|
|
163
|
+
const startSuppliedCartReconciliation = React.useCallback(() => {
|
|
164
|
+
if (!shouldReconcileSuppliedCart || lastReconciliationCountry.current === country) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
lastReconciliationCountry.current = country;
|
|
168
|
+
mutationQueue.current = mutationQueue.current.then(async () => {
|
|
153
169
|
try {
|
|
154
|
-
const
|
|
155
|
-
if (!
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
170
|
+
const reconciledCart = await reconcileCartAction({ country });
|
|
171
|
+
if (!reconciledCart) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
serverCart.current = reconciledCart;
|
|
175
|
+
if (isMounted.current && pendingMutations.current === 0) {
|
|
176
|
+
updateCartData(reconciledCart);
|
|
177
|
+
}
|
|
160
178
|
} catch (error2) {
|
|
161
|
-
|
|
162
|
-
needsRefresh.current = !restoredCart;
|
|
163
|
-
if (restoredCart) serverCart.current = restoredCart;
|
|
164
|
-
throw error2;
|
|
179
|
+
console.warn("[Replo] Failed to reconcile supplied cart:", error2);
|
|
165
180
|
}
|
|
166
181
|
});
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
182
|
+
}, [country, shouldReconcileSuppliedCart, updateCartData]);
|
|
183
|
+
const enqueueMutation = React.useCallback(
|
|
184
|
+
async (save) => {
|
|
185
|
+
startSuppliedCartReconciliation();
|
|
186
|
+
pendingMutations.current += 1;
|
|
187
|
+
const request = mutationQueue.current.then(async () => {
|
|
188
|
+
await cartCreationPromise?.catch(() => null);
|
|
189
|
+
if (needsRefresh.current) {
|
|
190
|
+
const restoredCart = await getCartAction();
|
|
191
|
+
if (!restoredCart)
|
|
192
|
+
throw new CartContextError({
|
|
193
|
+
message: "Your cart could not be refreshed. Please reload before changing it."
|
|
194
|
+
});
|
|
195
|
+
serverCart.current = restoredCart;
|
|
196
|
+
needsRefresh.current = false;
|
|
197
|
+
}
|
|
198
|
+
if (!serverCart.current) {
|
|
199
|
+
throw new CartContextError({
|
|
200
|
+
message: "Your cart could not be loaded. Please try again."
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
try {
|
|
204
|
+
const savedCart = await save(serverCart.current);
|
|
205
|
+
if (!savedCart)
|
|
206
|
+
throw new CartContextError({
|
|
207
|
+
message: "Your cart could not be saved. Please try again."
|
|
208
|
+
});
|
|
209
|
+
serverCart.current = savedCart;
|
|
210
|
+
} catch (error2) {
|
|
211
|
+
const restoredCart = await getCartAction().catch(() => null);
|
|
212
|
+
needsRefresh.current = !restoredCart;
|
|
213
|
+
if (restoredCart) serverCart.current = restoredCart;
|
|
214
|
+
throw error2;
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
mutationQueue.current = request.catch(() => {
|
|
218
|
+
}).then(() => {
|
|
219
|
+
pendingMutations.current -= 1;
|
|
220
|
+
if (pendingMutations.current === 0) {
|
|
221
|
+
reportedLineQuantities.current.clear();
|
|
222
|
+
optimisticLines.current.clear();
|
|
223
|
+
updateCartData(serverCart.current);
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
await request;
|
|
227
|
+
},
|
|
228
|
+
[startSuppliedCartReconciliation, updateCartData]
|
|
229
|
+
);
|
|
230
|
+
const findLine = React.useCallback(
|
|
231
|
+
({
|
|
232
|
+
currentCart,
|
|
233
|
+
lineId,
|
|
234
|
+
originalLine
|
|
235
|
+
}) => {
|
|
236
|
+
const existingLine = currentCart.lines.find((line) => line.id === lineId);
|
|
237
|
+
if (existingLine) return existingLine;
|
|
238
|
+
const optimisticLine = optimisticLines.current.get(lineId);
|
|
239
|
+
const merchandiseId = optimisticLine?.id ?? originalLine?.merchandise?.id;
|
|
240
|
+
const sellingPlanId = optimisticLine?.sellingPlanId ?? originalLine?.sellingPlanAllocation?.sellingPlan.id ?? null;
|
|
241
|
+
return currentCart.lines.find((line) => {
|
|
242
|
+
return line.merchandise?.id === merchandiseId && (line.sellingPlanAllocation?.sellingPlan.id ?? null) === sellingPlanId;
|
|
243
|
+
});
|
|
244
|
+
},
|
|
245
|
+
[]
|
|
246
|
+
);
|
|
192
247
|
const initializeCart = React.useCallback(async () => {
|
|
193
248
|
const selection = marketSelection.current;
|
|
194
249
|
const loadedCart = await getOrCreateSharedCart(country);
|
|
@@ -204,16 +259,19 @@ function CartProvider({
|
|
|
204
259
|
}, [country, updateCartData]);
|
|
205
260
|
React.useEffect(() => {
|
|
206
261
|
isMounted.current = true;
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
262
|
+
if (isSuppliedCart) {
|
|
263
|
+
startSuppliedCartReconciliation();
|
|
264
|
+
} else {
|
|
265
|
+
void initializeCart().catch((error2) => {
|
|
266
|
+
if (isMounted.current && !(error2 instanceof DOMException && error2.name === "AbortError")) {
|
|
267
|
+
console.error("[Replo] Error getting or creating cart:", error2);
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
}
|
|
212
271
|
return () => {
|
|
213
272
|
isMounted.current = false;
|
|
214
273
|
};
|
|
215
|
-
}, [initializeCart]);
|
|
216
|
-
const lineItems = cartData?.lines ?? [];
|
|
274
|
+
}, [initializeCart, isSuppliedCart, startSuppliedCartReconciliation]);
|
|
217
275
|
const subtotal = cartData?.lines.reduce((sum, lineItem) => {
|
|
218
276
|
if (lineItem.quantity > 0 && lineItem.merchandise) {
|
|
219
277
|
sum += lineItem.merchandise.price * lineItem.quantity;
|
|
@@ -250,213 +308,255 @@ function CartProvider({
|
|
|
250
308
|
updateCartData(refreshedCart);
|
|
251
309
|
}, [updateCartData]);
|
|
252
310
|
const reportedLineQuantities = React.useRef(/* @__PURE__ */ new Map());
|
|
253
|
-
const addToCart = (
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
311
|
+
const addToCart = React.useCallback(
|
|
312
|
+
(lines, openCartAfterAdd = true) => {
|
|
313
|
+
return runWithErrorState("add", async () => {
|
|
314
|
+
const readyCart = cartData ?? serverCart.current ?? await initializeCart().catch((caught) => {
|
|
315
|
+
if (openCartAfterAdd) setIsCartOpen(true);
|
|
316
|
+
throw caught;
|
|
317
|
+
});
|
|
318
|
+
reportedLineQuantities.current.clear();
|
|
319
|
+
const additions = lines.map((line) => {
|
|
320
|
+
const lineId = `temp-${uuidv4()}`;
|
|
321
|
+
optimisticLines.current.set(lineId, line);
|
|
322
|
+
return { line, lineId };
|
|
323
|
+
});
|
|
324
|
+
const applyLines = (cart2) => {
|
|
325
|
+
let updatedCart2 = { ...cart2 };
|
|
326
|
+
for (const { line, lineId } of additions) {
|
|
327
|
+
updatedCart2 = addLineToCart({ cart: updatedCart2, line, lineId });
|
|
328
|
+
}
|
|
329
|
+
return updatedCart2;
|
|
330
|
+
};
|
|
331
|
+
const updatedCart = updateCartData((currentCart) => {
|
|
332
|
+
return applyLines(currentCart ?? readyCart);
|
|
333
|
+
});
|
|
334
|
+
if (analytics) {
|
|
335
|
+
for (const line of lines) {
|
|
336
|
+
if (!line.merchandise) {
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
void analytics.addToCart({
|
|
340
|
+
data: buildCartLineAnalyticsData({
|
|
341
|
+
merchandise: line.merchandise,
|
|
342
|
+
quantity: line.quantity,
|
|
343
|
+
currencyCode: readyCart.cost.currencyCode
|
|
344
|
+
})
|
|
345
|
+
});
|
|
346
|
+
}
|
|
269
347
|
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
if (
|
|
278
|
-
|
|
348
|
+
if (openCartAfterAdd) {
|
|
349
|
+
setIsCartOpen(true);
|
|
350
|
+
emitCartView(updatedCart);
|
|
351
|
+
}
|
|
352
|
+
const adjustments = [];
|
|
353
|
+
await enqueueMutation(async (currentCart) => {
|
|
354
|
+
const savedCart = await addToCartAction(lines, currentCart.id);
|
|
355
|
+
if (savedCart) {
|
|
356
|
+
adjustments.push(
|
|
357
|
+
...deriveAddToCartAdjustments({
|
|
358
|
+
cartBeforeAdd: currentCart,
|
|
359
|
+
requestedLines: lines,
|
|
360
|
+
savedCart
|
|
361
|
+
})
|
|
362
|
+
);
|
|
279
363
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
364
|
+
return savedCart;
|
|
365
|
+
});
|
|
366
|
+
return { adjustments };
|
|
367
|
+
});
|
|
368
|
+
},
|
|
369
|
+
[
|
|
370
|
+
analytics,
|
|
371
|
+
cartData,
|
|
372
|
+
emitCartView,
|
|
373
|
+
enqueueMutation,
|
|
374
|
+
initializeCart,
|
|
375
|
+
runWithErrorState,
|
|
376
|
+
updateCartData
|
|
377
|
+
]
|
|
378
|
+
);
|
|
379
|
+
const updateCartItem = React.useCallback(
|
|
380
|
+
(line) => {
|
|
381
|
+
return runWithErrorState("update", async () => {
|
|
382
|
+
if (!cartData) {
|
|
383
|
+
console.warn(
|
|
384
|
+
"[Replo] Cannot update cart item: no cart exists. Reach out to support@replo.app if this persists."
|
|
385
|
+
);
|
|
386
|
+
return { adjustments: [] };
|
|
387
|
+
}
|
|
388
|
+
const currentLine = cartData.lines.find((item) => item.id === line.id);
|
|
389
|
+
const merchandise = currentLine?.merchandise ?? line.merchandise;
|
|
390
|
+
const quantityDelta = line.quantity - (reportedLineQuantities.current.get(line.id) ?? currentLine?.quantity ?? 0);
|
|
391
|
+
reportedLineQuantities.current.set(line.id, line.quantity);
|
|
392
|
+
if (analytics && merchandise && quantityDelta !== 0) {
|
|
393
|
+
const data = buildCartLineAnalyticsData({
|
|
394
|
+
merchandise,
|
|
395
|
+
quantity: Math.abs(quantityDelta),
|
|
396
|
+
currencyCode: cartData.cost.currencyCode
|
|
286
397
|
});
|
|
398
|
+
void (quantityDelta > 0 ? analytics.addToCart({ data }) : analytics.removeFromCart({ data }));
|
|
287
399
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
400
|
+
updateCartData((currentCart) => {
|
|
401
|
+
if (!currentCart) {
|
|
402
|
+
return null;
|
|
403
|
+
}
|
|
404
|
+
return updateLineInCart(currentCart, line);
|
|
405
|
+
});
|
|
406
|
+
const adjustments = [];
|
|
407
|
+
await enqueueMutation(async (currentCart) => {
|
|
408
|
+
const savedLine = findLine({
|
|
409
|
+
currentCart,
|
|
410
|
+
lineId: line.id,
|
|
411
|
+
originalLine: currentLine
|
|
412
|
+
});
|
|
413
|
+
if (!savedLine) {
|
|
414
|
+
return currentCart;
|
|
415
|
+
}
|
|
416
|
+
const savedCart = await updateCartLineItemAction(
|
|
417
|
+
{ ...line, id: savedLine.id },
|
|
418
|
+
currentCart.id
|
|
303
419
|
);
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
return { adjustments: [] };
|
|
317
|
-
}
|
|
318
|
-
const currentLine = cartData.lines.find((item) => item.id === line.id);
|
|
319
|
-
const merchandise = currentLine?.merchandise ?? line.merchandise;
|
|
320
|
-
const quantityDelta = line.quantity - (reportedLineQuantities.current.get(line.id) ?? currentLine?.quantity ?? 0);
|
|
321
|
-
reportedLineQuantities.current.set(line.id, line.quantity);
|
|
322
|
-
if (analytics && merchandise && quantityDelta !== 0) {
|
|
323
|
-
const data = buildCartLineAnalyticsData({
|
|
324
|
-
merchandise,
|
|
325
|
-
quantity: Math.abs(quantityDelta),
|
|
326
|
-
currencyCode: cartData.cost.currencyCode
|
|
420
|
+
if (savedCart) {
|
|
421
|
+
adjustments.push(
|
|
422
|
+
...deriveUpdateCartAdjustments({
|
|
423
|
+
cartBeforeUpdate: currentCart,
|
|
424
|
+
editedLine: savedLine,
|
|
425
|
+
requestedQuantity: line.quantity,
|
|
426
|
+
requestedSellingPlanId: line.sellingPlanId,
|
|
427
|
+
savedCart
|
|
428
|
+
})
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
return savedCart;
|
|
327
432
|
});
|
|
328
|
-
|
|
329
|
-
}
|
|
330
|
-
updateCartData((currentCart) => {
|
|
331
|
-
if (!currentCart) {
|
|
332
|
-
return null;
|
|
333
|
-
}
|
|
334
|
-
return updateLineInCart(currentCart, line);
|
|
433
|
+
return { adjustments };
|
|
335
434
|
});
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
435
|
+
},
|
|
436
|
+
[
|
|
437
|
+
analytics,
|
|
438
|
+
cartData,
|
|
439
|
+
enqueueMutation,
|
|
440
|
+
findLine,
|
|
441
|
+
runWithErrorState,
|
|
442
|
+
updateCartData
|
|
443
|
+
]
|
|
444
|
+
);
|
|
445
|
+
const removeCartItem = React.useCallback(
|
|
446
|
+
(lineId) => {
|
|
447
|
+
return runWithErrorState("remove", async () => {
|
|
448
|
+
if (!cartData) {
|
|
449
|
+
console.warn(
|
|
450
|
+
"[Replo] Cannot remove cart item: no cart exists. Reach out to support@replo.app if this persists."
|
|
451
|
+
);
|
|
452
|
+
return;
|
|
345
453
|
}
|
|
346
|
-
const
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
editedLine: savedLine,
|
|
355
|
-
requestedQuantity: line.quantity,
|
|
356
|
-
requestedSellingPlanId: line.sellingPlanId,
|
|
357
|
-
savedCart
|
|
454
|
+
const itemToRemove = cartData.lines.find((item) => item.id === lineId);
|
|
455
|
+
reportedLineQuantities.current.delete(lineId);
|
|
456
|
+
if (analytics && itemToRemove && itemToRemove.merchandise) {
|
|
457
|
+
void analytics.removeFromCart({
|
|
458
|
+
data: buildCartLineAnalyticsData({
|
|
459
|
+
merchandise: itemToRemove.merchandise,
|
|
460
|
+
quantity: itemToRemove.quantity,
|
|
461
|
+
currencyCode: cartData.cost.currencyCode
|
|
358
462
|
})
|
|
359
|
-
);
|
|
463
|
+
});
|
|
360
464
|
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
const removeCartItem = (lineId) => {
|
|
367
|
-
return runWithErrorState("remove", async () => {
|
|
368
|
-
if (!cartData) {
|
|
369
|
-
console.warn(
|
|
370
|
-
"[Replo] Cannot remove cart item: no cart exists. Reach out to support@replo.app if this persists."
|
|
371
|
-
);
|
|
372
|
-
return;
|
|
373
|
-
}
|
|
374
|
-
const itemToRemove = cartData.lines.find((item) => item.id === lineId);
|
|
375
|
-
reportedLineQuantities.current.delete(lineId);
|
|
376
|
-
if (analytics && itemToRemove && itemToRemove.merchandise) {
|
|
377
|
-
void analytics.removeFromCart({
|
|
378
|
-
data: buildCartLineAnalyticsData({
|
|
379
|
-
merchandise: itemToRemove.merchandise,
|
|
380
|
-
quantity: itemToRemove.quantity,
|
|
381
|
-
currencyCode: cartData.cost.currencyCode
|
|
382
|
-
})
|
|
465
|
+
updateCartData((currentCart) => {
|
|
466
|
+
if (!currentCart) {
|
|
467
|
+
return null;
|
|
468
|
+
}
|
|
469
|
+
return removeLineFromCart(currentCart, lineId);
|
|
383
470
|
});
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
await enqueueMutation(async (currentCart) => {
|
|
392
|
-
const savedLine = findLine({
|
|
393
|
-
currentCart,
|
|
394
|
-
lineId,
|
|
395
|
-
originalLine: itemToRemove
|
|
471
|
+
await enqueueMutation(async (currentCart) => {
|
|
472
|
+
const savedLine = findLine({
|
|
473
|
+
currentCart,
|
|
474
|
+
lineId,
|
|
475
|
+
originalLine: itemToRemove
|
|
476
|
+
});
|
|
477
|
+
return savedLine ? await removeCartLineItemsAction([savedLine.id], currentCart.id) : currentCart;
|
|
396
478
|
});
|
|
397
|
-
return savedLine ? await removeCartLineItemsAction([savedLine.id], currentCart.id) : currentCart;
|
|
398
|
-
});
|
|
399
|
-
});
|
|
400
|
-
};
|
|
401
|
-
const updateDiscountCodes = (discountCodes) => {
|
|
402
|
-
return runWithErrorState("discount", async () => {
|
|
403
|
-
const readyCart = cartData ?? serverCart.current ?? await initializeCart();
|
|
404
|
-
updateCartData((currentCart) => {
|
|
405
|
-
return {
|
|
406
|
-
...currentCart ?? readyCart,
|
|
407
|
-
discountCodes: discountCodes.map((code) => ({
|
|
408
|
-
code,
|
|
409
|
-
applicable: true
|
|
410
|
-
}))
|
|
411
|
-
};
|
|
412
479
|
});
|
|
413
|
-
|
|
414
|
-
|
|
480
|
+
},
|
|
481
|
+
[
|
|
482
|
+
analytics,
|
|
483
|
+
cartData,
|
|
484
|
+
enqueueMutation,
|
|
485
|
+
findLine,
|
|
486
|
+
runWithErrorState,
|
|
487
|
+
updateCartData
|
|
488
|
+
]
|
|
489
|
+
);
|
|
490
|
+
const updateDiscountCodes = React.useCallback(
|
|
491
|
+
(discountCodes) => {
|
|
492
|
+
return runWithErrorState("discount", async () => {
|
|
493
|
+
const readyCart = cartData ?? serverCart.current ?? await initializeCart();
|
|
494
|
+
updateCartData((currentCart) => {
|
|
495
|
+
return {
|
|
496
|
+
...currentCart ?? readyCart,
|
|
497
|
+
discountCodes: discountCodes.map((code) => ({
|
|
498
|
+
code,
|
|
499
|
+
applicable: true
|
|
500
|
+
}))
|
|
501
|
+
};
|
|
502
|
+
});
|
|
503
|
+
await enqueueMutation((currentCart) => {
|
|
504
|
+
return updateCartDiscountCodesAction(discountCodes, currentCart.id);
|
|
505
|
+
});
|
|
415
506
|
});
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
507
|
+
},
|
|
508
|
+
[
|
|
509
|
+
cartData,
|
|
510
|
+
enqueueMutation,
|
|
511
|
+
initializeCart,
|
|
512
|
+
runWithErrorState,
|
|
513
|
+
updateCartData
|
|
514
|
+
]
|
|
515
|
+
);
|
|
516
|
+
const buyNow = React.useCallback(
|
|
517
|
+
(lines, discountCodes) => {
|
|
518
|
+
return createBuyNowCart({ lines, discountCodes, country });
|
|
519
|
+
},
|
|
520
|
+
[country]
|
|
521
|
+
);
|
|
522
|
+
const contextValue = React.useMemo(
|
|
523
|
+
() => ({
|
|
524
|
+
cartData,
|
|
525
|
+
lineItems: cartData?.lines ?? [],
|
|
526
|
+
subtotal,
|
|
527
|
+
itemsCount,
|
|
528
|
+
currencyCode: cartData?.cost.currencyCode ?? "USD",
|
|
529
|
+
addToCart,
|
|
530
|
+
updateCartItem,
|
|
531
|
+
removeCartItem,
|
|
532
|
+
updateDiscountCodes,
|
|
533
|
+
refreshCart,
|
|
534
|
+
buyNow,
|
|
535
|
+
checkoutUrl: cartData?.checkoutUrl ?? "/",
|
|
536
|
+
isCartOpen,
|
|
537
|
+
openCart,
|
|
538
|
+
closeCart,
|
|
539
|
+
error,
|
|
540
|
+
clearError
|
|
541
|
+
}),
|
|
542
|
+
[
|
|
543
|
+
addToCart,
|
|
544
|
+
buyNow,
|
|
545
|
+
cartData,
|
|
546
|
+
clearError,
|
|
547
|
+
closeCart,
|
|
548
|
+
error,
|
|
549
|
+
isCartOpen,
|
|
550
|
+
itemsCount,
|
|
551
|
+
openCart,
|
|
552
|
+
refreshCart,
|
|
553
|
+
removeCartItem,
|
|
554
|
+
subtotal,
|
|
555
|
+
updateCartItem,
|
|
556
|
+
updateDiscountCodes
|
|
557
|
+
]
|
|
558
|
+
);
|
|
559
|
+
return /* @__PURE__ */ jsx(CartCountryContext.Provider, { value: country, children: /* @__PURE__ */ jsx(CartContext.Provider, { value: contextValue, children }) });
|
|
460
560
|
}
|
|
461
561
|
function useCartInternal() {
|
|
462
562
|
const context = React.useContext(CartContext);
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
export declare function SuppliedCartReconciliationBoundary({ children, enabled, }: React.PropsWithChildren<{
|
|
3
|
+
enabled: boolean;
|
|
4
|
+
}>): import("react/jsx-runtime").JSX.Element;
|
|
5
|
+
export declare function useShouldReconcileSuppliedCart(): boolean;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import React from "react";
|
|
4
|
+
const ShouldReconcileSuppliedCartContext = React.createContext(true);
|
|
5
|
+
function SuppliedCartReconciliationBoundary({
|
|
6
|
+
children,
|
|
7
|
+
enabled
|
|
8
|
+
}) {
|
|
9
|
+
return /* @__PURE__ */ jsx(ShouldReconcileSuppliedCartContext.Provider, { value: enabled, children });
|
|
10
|
+
}
|
|
11
|
+
function useShouldReconcileSuppliedCart() {
|
|
12
|
+
return React.useContext(ShouldReconcileSuppliedCartContext);
|
|
13
|
+
}
|
|
14
|
+
export {
|
|
15
|
+
SuppliedCartReconciliationBoundary,
|
|
16
|
+
useShouldReconcileSuppliedCart
|
|
17
|
+
};
|
package/lib/buildMetadata.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const BUILD_METADATA = {
|
|
2
2
|
"packageName": "@replohq/sdk",
|
|
3
|
-
"version": "1.10.
|
|
3
|
+
"version": "1.10.2",
|
|
4
4
|
"branch": "HEAD",
|
|
5
|
-
"commit": "
|
|
6
|
-
"builtAt": "2026-09-20T21:
|
|
5
|
+
"commit": "5ee9af27c9ec5545655f225b5029ef7572f61344",
|
|
6
|
+
"builtAt": "2026-09-20T21:55:46.106Z"
|
|
7
7
|
};
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@replohq/sdk",
|
|
3
|
-
"version": "1.10.
|
|
3
|
+
"version": "1.10.2",
|
|
4
4
|
"reploBuild": {
|
|
5
5
|
"packageName": "@replohq/sdk",
|
|
6
|
-
"version": "1.10.
|
|
6
|
+
"version": "1.10.2",
|
|
7
7
|
"branch": "HEAD",
|
|
8
|
-
"commit": "
|
|
9
|
-
"builtAt": "2026-09-20T21:
|
|
8
|
+
"commit": "5ee9af27c9ec5545655f225b5029ef7572f61344",
|
|
9
|
+
"builtAt": "2026-09-20T21:55:46.106Z"
|
|
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",
|
|
@@ -18,7 +18,14 @@ async function ReploProvider({
|
|
|
18
18
|
}
|
|
19
19
|
return /* @__PURE__ */ jsx(QueryProvider, { children: /* @__PURE__ */ jsx(LoaderErrorDetailsProvider, { enabled: shouldShowLoaderErrorDetails(), children: /* @__PURE__ */ jsxs(AnalyticsProvider, { checkoutProvider, children: [
|
|
20
20
|
/* @__PURE__ */ jsx(ReploFirstPartyPixel, {}),
|
|
21
|
-
/* @__PURE__ */ jsx(
|
|
21
|
+
/* @__PURE__ */ jsx(
|
|
22
|
+
CartLoader,
|
|
23
|
+
{
|
|
24
|
+
country: markets?.shopify?.country,
|
|
25
|
+
shouldReconcileSuppliedCart: checkoutProvider === "stripe",
|
|
26
|
+
children
|
|
27
|
+
}
|
|
28
|
+
)
|
|
22
29
|
] }) }) });
|
|
23
30
|
}
|
|
24
31
|
export {
|