@jay-framework/wix-cart 0.15.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/dist/index.js ADDED
@@ -0,0 +1,445 @@
1
+ import { registerService, getService } from "@jay-framework/stack-server-runtime";
2
+ import { currentCart } from "@wix/ecom";
3
+ import { createJayService, makeJayStackComponent, RenderPipeline, makeJayInit } from "@jay-framework/fullstack-component";
4
+ import { createJayContext, useGlobalContext } from "@jay-framework/runtime";
5
+ import { registerReactiveGlobalContext, createSignal, useReactive } from "@jay-framework/component";
6
+ import { WIX_CLIENT_CONTEXT, WIX_CLIENT_SERVICE } from "@jay-framework/wix-server-client";
7
+ let currentCartInstance;
8
+ function getCurrentCartClient(wixClient) {
9
+ if (!currentCartInstance) {
10
+ currentCartInstance = wixClient.use(currentCart);
11
+ }
12
+ return currentCartInstance;
13
+ }
14
+ const WIX_CART_SERVICE = createJayService("Wix Cart Service");
15
+ function provideWixCartService(wixClient) {
16
+ const service = {
17
+ cart: getCurrentCartClient(wixClient)
18
+ };
19
+ registerService(WIX_CART_SERVICE, service);
20
+ return service;
21
+ }
22
+ function parseWixMediaUrl(url) {
23
+ if (!url) return null;
24
+ const match = url.match(
25
+ /^wix:(image|video|document|audio):\/\/v1\/([^/]+)\/([^#]+)(?:#(.*))?$/
26
+ );
27
+ if (!match) return null;
28
+ const [, type, mediaId, fileName, hashParams] = match;
29
+ const result = {
30
+ type,
31
+ mediaId,
32
+ fileName: decodeURIComponent(fileName)
33
+ };
34
+ if (hashParams) {
35
+ const params = new URLSearchParams(hashParams);
36
+ const originWidth = params.get("originWidth");
37
+ const originHeight = params.get("originHeight");
38
+ if (originWidth) result.originWidth = parseInt(originWidth, 10);
39
+ if (originHeight) result.originHeight = parseInt(originHeight, 10);
40
+ const posterUri = params.get("posterUri");
41
+ const posterWidth = params.get("posterWidth");
42
+ const posterHeight = params.get("posterHeight");
43
+ if (posterUri) result.posterUri = decodeURIComponent(posterUri);
44
+ if (posterWidth) result.posterWidth = parseInt(posterWidth, 10);
45
+ if (posterHeight) result.posterHeight = parseInt(posterHeight, 10);
46
+ }
47
+ return result;
48
+ }
49
+ function formatWixMediaUrl(_id, url, resize) {
50
+ const resizeFragment = "";
51
+ if (url == null ? void 0 : url.startsWith("wix:")) {
52
+ const parsed = parseWixMediaUrl(url);
53
+ if (parsed) {
54
+ return `https://static.wixstatic.com/media/${parsed.mediaId}${resizeFragment}`;
55
+ }
56
+ }
57
+ if ((url == null ? void 0 : url.startsWith("http://")) || (url == null ? void 0 : url.startsWith("https://"))) {
58
+ return url;
59
+ }
60
+ return "";
61
+ }
62
+ function mapLineItem(item) {
63
+ const catalogRef = item.catalogReference;
64
+ const physicalProperties = item.physicalProperties;
65
+ const priceData = item.price;
66
+ const descriptionLines = item.descriptionLines || [];
67
+ const variantParts = descriptionLines.filter((line) => line.name?.translated).map(
68
+ (line) => `${line.name?.translated}: ${line.colorInfo?.translated || line.plainText?.translated || ""}`
69
+ );
70
+ const slug = item.url?.split("/").pop() || "";
71
+ return {
72
+ lineItemId: item._id || "",
73
+ productId: catalogRef?.catalogItemId || "",
74
+ productName: item.productName?.translated || item.productName?.original || "",
75
+ productUrl: slug ? `/products/${slug}` : "",
76
+ variantName: variantParts.join(" / "),
77
+ sku: physicalProperties?.sku || "",
78
+ image: {
79
+ url: formatWixMediaUrl("", item.image || ""),
80
+ altText: item.productName?.translated || ""
81
+ },
82
+ quantity: item.quantity || 1,
83
+ unitPrice: {
84
+ amount: priceData?.amount || "0",
85
+ formattedAmount: priceData?.formattedAmount || ""
86
+ },
87
+ lineTotal: {
88
+ amount: item.lineItemPrice?.amount || "0",
89
+ formattedAmount: item.lineItemPrice?.formattedAmount || ""
90
+ },
91
+ lineDiscount: {
92
+ amount: "0",
93
+ formattedAmount: ""
94
+ },
95
+ hasDiscount: false
96
+ };
97
+ }
98
+ function calculateTotalDiscount(appliedDiscounts) {
99
+ if (!appliedDiscounts) return 0;
100
+ return appliedDiscounts.reduce(
101
+ (sum, d) => sum + parseFloat(d.merchantDiscount?.amount?.amount || "0"),
102
+ 0
103
+ );
104
+ }
105
+ function calculateItemCount(lineItems) {
106
+ if (!lineItems) return 0;
107
+ return lineItems.reduce((sum, item) => sum + (item.quantity || 0), 0);
108
+ }
109
+ function getEmptySummary() {
110
+ return {
111
+ itemCount: 0,
112
+ subtotal: { amount: "0", formattedAmount: "$0.00" },
113
+ discount: { amount: "0", formattedAmount: "" },
114
+ hasDiscount: false,
115
+ estimatedTax: { amount: "0", formattedAmount: "" },
116
+ showTax: false,
117
+ estimatedTotal: { amount: "0", formattedAmount: "$0.00" },
118
+ currency: "USD"
119
+ };
120
+ }
121
+ function mapCartSummary(cart) {
122
+ if (!cart) {
123
+ return getEmptySummary();
124
+ }
125
+ const totalDiscount = calculateTotalDiscount(cart.appliedDiscounts);
126
+ const itemCount = calculateItemCount(cart.lineItems);
127
+ return {
128
+ itemCount,
129
+ subtotal: {
130
+ amount: "0",
131
+ formattedAmount: "$0.00"
132
+ },
133
+ discount: {
134
+ amount: totalDiscount.toString(),
135
+ formattedAmount: totalDiscount > 0 ? `-$${totalDiscount.toFixed(2)}` : ""
136
+ },
137
+ hasDiscount: totalDiscount > 0,
138
+ estimatedTax: {
139
+ amount: "0",
140
+ formattedAmount: ""
141
+ },
142
+ showTax: false,
143
+ estimatedTotal: {
144
+ amount: "0",
145
+ formattedAmount: "$0.00"
146
+ },
147
+ currency: cart.currency || "USD"
148
+ };
149
+ }
150
+ function mapCartToState(cart) {
151
+ if (!cart) {
152
+ return getEmptyCartState();
153
+ }
154
+ const lineItems = (cart.lineItems || []).map(mapLineItem);
155
+ const appliedCoupon = cart.appliedDiscounts?.find((d) => d.coupon?.code)?.coupon?.code || "";
156
+ return {
157
+ cartId: cart._id || "",
158
+ isEmpty: lineItems.length === 0,
159
+ lineItems,
160
+ summary: mapCartSummary(cart),
161
+ appliedCoupon,
162
+ hasAppliedCoupon: !!appliedCoupon
163
+ };
164
+ }
165
+ function getEmptyCartState() {
166
+ return {
167
+ cartId: "",
168
+ isEmpty: true,
169
+ lineItems: [],
170
+ summary: getEmptySummary(),
171
+ appliedCoupon: "",
172
+ hasAppliedCoupon: false
173
+ };
174
+ }
175
+ function mapCartToIndicator(cart) {
176
+ if (!cart || !cart.lineItems?.length) {
177
+ return {
178
+ itemCount: 0,
179
+ hasItems: false
180
+ };
181
+ }
182
+ const itemCount = calculateItemCount(cart.lineItems);
183
+ return {
184
+ itemCount,
185
+ hasItems: itemCount > 0
186
+ };
187
+ }
188
+ function isCartNotFoundError(error) {
189
+ const err = error;
190
+ if (err?.response?.status === 404) return true;
191
+ if (err?.status === 404) return true;
192
+ if (err?.code === 404) return true;
193
+ const message = err?.message?.toLowerCase() || "";
194
+ if (message.includes("not found") && message.includes("cart")) return true;
195
+ return false;
196
+ }
197
+ async function getCurrentCartOrNull(cartClient) {
198
+ try {
199
+ const response = await cartClient.getCurrentCart();
200
+ return response ?? null;
201
+ } catch (error) {
202
+ if (isCartNotFoundError(error)) {
203
+ return null;
204
+ }
205
+ throw error;
206
+ }
207
+ }
208
+ async function estimateCurrentCartTotalsOrNull(cartClient) {
209
+ try {
210
+ const response = await cartClient.estimateCurrentCartTotals({});
211
+ return response ?? null;
212
+ } catch (error) {
213
+ if (isCartNotFoundError(error)) {
214
+ return null;
215
+ }
216
+ throw error;
217
+ }
218
+ }
219
+ function mapEstimateTotalsToState(estimate) {
220
+ if (!estimate?.cart) {
221
+ return getEmptyCartState();
222
+ }
223
+ const cart = estimate.cart;
224
+ const lineItems = (cart.lineItems || []).map(mapLineItem);
225
+ const appliedCoupon = cart.appliedDiscounts?.find((d) => d.coupon?.code)?.coupon?.code || "";
226
+ const itemCount = lineItems.reduce((sum, item) => sum + item.quantity, 0);
227
+ const priceSummary = estimate.priceSummary;
228
+ const taxSummary = estimate.taxSummary;
229
+ const discountAmount = parseFloat(priceSummary?.discount?.amount || "0");
230
+ const hasTax = parseFloat(taxSummary?.totalTax?.amount || "0") > 0;
231
+ return {
232
+ cartId: cart._id || "",
233
+ isEmpty: lineItems.length === 0,
234
+ lineItems,
235
+ summary: {
236
+ itemCount,
237
+ subtotal: {
238
+ amount: priceSummary?.subtotal?.amount || "0",
239
+ formattedAmount: priceSummary?.subtotal?.formattedAmount || "$0.00"
240
+ },
241
+ discount: {
242
+ amount: discountAmount.toString(),
243
+ formattedAmount: priceSummary?.discount?.formattedAmount || ""
244
+ },
245
+ hasDiscount: discountAmount > 0,
246
+ estimatedTax: {
247
+ amount: taxSummary?.totalTax?.amount || "0",
248
+ formattedAmount: taxSummary?.totalTax?.formattedAmount || ""
249
+ },
250
+ showTax: hasTax,
251
+ estimatedTotal: {
252
+ amount: priceSummary?.total?.amount || priceSummary?.subtotal?.amount || "0",
253
+ formattedAmount: priceSummary?.total?.formattedAmount || priceSummary?.subtotal?.formattedAmount || "$0.00"
254
+ },
255
+ currency: cart.currency || "USD"
256
+ },
257
+ appliedCoupon,
258
+ hasAppliedCoupon: !!appliedCoupon
259
+ };
260
+ }
261
+ const WIX_STORES_APP_ID = "215238eb-22a5-4c36-9e7b-e7c08025e04e";
262
+ const WIX_CART_CONTEXT = createJayContext();
263
+ function provideWixCartContext() {
264
+ const wixClientContext = useGlobalContext(WIX_CLIENT_CONTEXT);
265
+ const wixClient = wixClientContext.client;
266
+ const cartClient = getCurrentCartClient(wixClient);
267
+ const cartContext = registerReactiveGlobalContext(WIX_CART_CONTEXT, () => {
268
+ const [itemCount, setItemCount] = createSignal(0);
269
+ const [hasItems, setHasItems] = createSignal(false);
270
+ const reactive = useReactive();
271
+ function updateIndicatorFromCart(cart) {
272
+ const indicator = mapCartToIndicator(cart);
273
+ reactive.batchReactions(() => {
274
+ setItemCount(indicator.itemCount);
275
+ setHasItems(indicator.hasItems);
276
+ });
277
+ }
278
+ async function refreshCartIndicator() {
279
+ const cart = await getCurrentCartOrNull(cartClient);
280
+ updateIndicatorFromCart(cart);
281
+ }
282
+ async function getEstimatedCart() {
283
+ const estimate = await estimateCurrentCartTotalsOrNull(cartClient);
284
+ return mapEstimateTotalsToState(estimate);
285
+ }
286
+ async function addToCart(productId, quantity = 1, options) {
287
+ console.log(`[WixCart] Adding to cart: ${productId} x ${quantity}`, options);
288
+ const lineItem = {
289
+ catalogReference: {
290
+ catalogItemId: productId,
291
+ appId: WIX_STORES_APP_ID,
292
+ options: {
293
+ variantId: options?.variantId,
294
+ options: options?.modifiers,
295
+ customTextFields: options?.customTextFields
296
+ }
297
+ },
298
+ quantity
299
+ };
300
+ const result = await cartClient.addToCurrentCart({
301
+ lineItems: [lineItem]
302
+ });
303
+ updateIndicatorFromCart(result.cart ?? null);
304
+ return { cartState: mapCartToState(result.cart ?? null) };
305
+ }
306
+ async function removeLineItems(lineItemIds) {
307
+ const result = await cartClient.removeLineItemsFromCurrentCart(lineItemIds);
308
+ updateIndicatorFromCart(result.cart ?? null);
309
+ return { cartState: mapCartToState(result.cart ?? null) };
310
+ }
311
+ async function updateLineItemQuantity(lineItemId, quantity) {
312
+ let result;
313
+ if (quantity === 0) {
314
+ result = await cartClient.removeLineItemsFromCurrentCart([lineItemId]);
315
+ } else {
316
+ result = await cartClient.updateCurrentCartLineItemQuantity([
317
+ { _id: lineItemId, quantity }
318
+ ]);
319
+ }
320
+ updateIndicatorFromCart(result.cart ?? null);
321
+ return { cartState: mapCartToState(result.cart ?? null) };
322
+ }
323
+ async function clearCart() {
324
+ const cart = await getCurrentCartOrNull(cartClient);
325
+ if (cart?.lineItems?.length) {
326
+ const lineItemIds = cart.lineItems.map((item) => item._id || "").filter(Boolean);
327
+ if (lineItemIds.length > 0) {
328
+ await cartClient.removeLineItemsFromCurrentCart(lineItemIds);
329
+ }
330
+ }
331
+ setItemCount(0);
332
+ setHasItems(false);
333
+ }
334
+ async function applyCoupon(couponCode) {
335
+ const result = await cartClient.updateCurrentCart({ couponCode });
336
+ updateIndicatorFromCart(result ?? null);
337
+ return { cartState: mapCartToState(result ?? null) };
338
+ }
339
+ async function removeCoupon() {
340
+ const result = await cartClient.removeCouponFromCurrentCart();
341
+ updateIndicatorFromCart(result.cart ?? null);
342
+ return { cartState: mapCartToState(result.cart ?? null) };
343
+ }
344
+ return {
345
+ cartIndicator: {
346
+ itemCount,
347
+ hasItems
348
+ },
349
+ refreshCartIndicator,
350
+ getEstimatedCart,
351
+ addToCart,
352
+ removeLineItems,
353
+ updateLineItemQuantity,
354
+ clearCart,
355
+ applyCoupon,
356
+ removeCoupon
357
+ };
358
+ });
359
+ console.log("[wix-cart] Client cart context initialized (reactive)");
360
+ return cartContext;
361
+ }
362
+ async function renderFastChanging$1(_props, _wixCart) {
363
+ const Pipeline = RenderPipeline.for();
364
+ return Pipeline.ok(null).toPhaseOutput(() => ({
365
+ viewState: {
366
+ itemCount: 0,
367
+ hasItems: false,
368
+ isLoading: true,
369
+ justAdded: false
370
+ },
371
+ carryForward: {}
372
+ }));
373
+ }
374
+ const cartIndicator = makeJayStackComponent().withProps().withServices(WIX_CART_SERVICE).withFastRender(renderFastChanging$1);
375
+ function createEmptyCartViewState() {
376
+ return {
377
+ isEmpty: true,
378
+ isLoading: true,
379
+ isCheckingOut: false,
380
+ lineItems: [],
381
+ summary: {
382
+ itemCount: 0,
383
+ subtotal: { amount: "0", formattedAmount: "$0.00" },
384
+ discount: { amount: "0", formattedAmount: "" },
385
+ hasDiscount: false,
386
+ estimatedTax: { amount: "0", formattedAmount: "" },
387
+ showTax: false,
388
+ estimatedTotal: { amount: "0", formattedAmount: "$0.00" },
389
+ currency: "USD"
390
+ },
391
+ coupon: {
392
+ code: "",
393
+ isApplying: false,
394
+ appliedCode: "",
395
+ hasAppliedCoupon: false,
396
+ errorMessage: "",
397
+ hasError: false
398
+ }
399
+ };
400
+ }
401
+ async function renderSlowlyChanging(_props, _wixCart) {
402
+ const Pipeline = RenderPipeline.for();
403
+ return Pipeline.ok(null).toPhaseOutput(() => ({
404
+ viewState: {
405
+ cartId: "",
406
+ emptyCartMessage: "Your cart is empty"
407
+ },
408
+ carryForward: {}
409
+ }));
410
+ }
411
+ async function renderFastChanging(_props, _slowCarryForward, _wixCart) {
412
+ const Pipeline = RenderPipeline.for();
413
+ return Pipeline.ok(null).toPhaseOutput(() => ({
414
+ viewState: createEmptyCartViewState(),
415
+ carryForward: {}
416
+ }));
417
+ }
418
+ const cartPage = makeJayStackComponent().withProps().withServices(WIX_CART_SERVICE).withSlowlyRender(renderSlowlyChanging).withFastRender(renderFastChanging);
419
+ const init = makeJayInit().withServer(async () => {
420
+ console.log("[wix-cart] Initializing Wix Cart service...");
421
+ const wixClient = getService(WIX_CLIENT_SERVICE);
422
+ provideWixCartService(wixClient);
423
+ console.log("[wix-cart] Server initialization complete");
424
+ return {
425
+ enableClientCart: true
426
+ };
427
+ });
428
+ export {
429
+ WIX_CART_CONTEXT,
430
+ WIX_CART_SERVICE,
431
+ cartIndicator,
432
+ cartPage,
433
+ estimateCurrentCartTotalsOrNull,
434
+ getCurrentCartClient,
435
+ getCurrentCartOrNull,
436
+ getEmptyCartState,
437
+ init,
438
+ mapCartSummary,
439
+ mapCartToIndicator,
440
+ mapCartToState,
441
+ mapEstimateTotalsToState,
442
+ mapLineItem,
443
+ provideWixCartContext,
444
+ provideWixCartService
445
+ };
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@jay-framework/wix-cart",
3
+ "version": "0.15.0",
4
+ "type": "module",
5
+ "description": "Wix Cart/Ecom shared package for Jay Framework",
6
+ "license": "Apache-2.0",
7
+ "main": "dist/index.js",
8
+ "files": [
9
+ "dist",
10
+ "plugin.yaml"
11
+ ],
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "default": "./dist/index.js"
16
+ },
17
+ "./client": {
18
+ "types": "./dist/index.client.d.ts",
19
+ "default": "./dist/index.client.js"
20
+ },
21
+ "./plugin.yaml": "./plugin.yaml",
22
+ "./cart-indicator.jay-contract": "./dist/contracts/cart-indicator.jay-contract",
23
+ "./cart-page.jay-contract": "./dist/contracts/cart-page.jay-contract"
24
+ },
25
+ "scripts": {
26
+ "build": "npm run clean && npm run definitions && npm run build:client && npm run build:server && npm run build:copy-contract && npm run build:types",
27
+ "definitions": "jay-cli definitions lib",
28
+ "build:client": "vite build",
29
+ "build:server": "vite build --ssr",
30
+ "build:copy-contract": "mkdir -p dist/contracts && cp lib/contracts/*.jay-contract* dist/contracts/",
31
+ "build:types": "tsup lib/index.ts lib/index.client.ts --dts-only --format esm",
32
+ "build:check-types": "tsc",
33
+ "clean": "rimraf dist",
34
+ "confirm": "npm run clean && npm run build && npm run test",
35
+ "test": ":"
36
+ },
37
+ "dependencies": {
38
+ "@jay-framework/component": "^0.14.0",
39
+ "@jay-framework/fullstack-component": "^0.14.0",
40
+ "@jay-framework/reactive": "^0.14.0",
41
+ "@jay-framework/runtime": "^0.14.0",
42
+ "@jay-framework/secure": "^0.14.0",
43
+ "@jay-framework/stack-client-runtime": "^0.14.0",
44
+ "@jay-framework/stack-server-runtime": "^0.14.0",
45
+ "@jay-framework/wix-server-client": "^0.15.0",
46
+ "@jay-framework/wix-utils": "^0.15.0",
47
+ "@wix/ecom": "^1.0.1978",
48
+ "@wix/sdk": "^1.21.5"
49
+ },
50
+ "devDependencies": {
51
+ "@babel/core": "^7.23.7",
52
+ "@babel/preset-env": "^7.23.8",
53
+ "@babel/preset-typescript": "^7.23.3",
54
+ "@jay-framework/compiler-jay-stack": "^0.14.0",
55
+ "@jay-framework/jay-cli": "^0.14.0",
56
+ "@jay-framework/vite-plugin": "^0.14.0",
57
+ "@jay-framework/wix-dev-environment": "^0.6.10",
58
+ "nodemon": "^3.0.3",
59
+ "rimraf": "^5.0.5",
60
+ "tslib": "^2.6.2",
61
+ "tsup": "^8.5.1",
62
+ "typescript": "^5.3.3",
63
+ "vite": "^5.0.11",
64
+ "vitest": "^1.2.0"
65
+ }
66
+ }
package/plugin.yaml ADDED
@@ -0,0 +1,14 @@
1
+ name: wix-cart
2
+ # Shared Wix Cart/Ecom package for Jay Framework
3
+
4
+ contracts:
5
+ - name: cart-indicator
6
+ contract: cart-indicator.jay-contract
7
+ component: cartIndicator
8
+ description: Lightweight cart indicator for site headers showing item count
9
+ - name: cart-page
10
+ contract: cart-page.jay-contract
11
+ component: cartPage
12
+ description: Full cart page with line item management and checkout
13
+
14
+ # Plugin initialization uses makeJayInit pattern in lib/init.ts