@osimart/create-storefront 1.0.20 → 1.0.22

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.
@@ -17,39 +17,112 @@ const persistCartToken = (responseData) => {
17
17
  };
18
18
 
19
19
  /**
20
- * Add to / remove from a cart line (a line is a variant id).
20
+ * A cart response's lines as a list. Lines are keyed by their line id
21
+ * (order_item_id); one variant can sit on several lines (different
22
+ * customizations, or as an add-on of another product), so nothing may look a
23
+ * line up by variant id.
24
+ */
25
+ export function cartLines(response) {
26
+ return Object.entries(response?.cart || {}).map(([key, line]) => ({
27
+ ...line,
28
+ order_item_id: line?.order_item_id ?? key,
29
+ }));
30
+ }
31
+
32
+ /** A variant's own top-level lines summed: what "this product in the cart" means now. */
33
+ function variantSnapshot(lines, variantId) {
34
+ const own = lines.filter((l) => String(l.id) === String(variantId) && !l.parent_item);
35
+ if (!own.length) return null;
36
+ return { ...own[0], quantity: own.reduce((n, l) => n + (Number(l.quantity) || 0), 0) };
37
+ }
38
+
39
+ async function post(body) {
40
+ const baseUrl = getBaseUrl();
41
+ const responseData = await apiService(`${baseUrl}store/apis/cart/update-item/`, {
42
+ method: "POST",
43
+ headers: cartHeaders(),
44
+ body: JSON.stringify(body),
45
+ });
46
+ return persistCartToken(responseData);
47
+ }
48
+
49
+ /**
50
+ * Add a variant to the cart (the only action that takes a variant id).
21
51
  *
22
52
  * `context` is optional: callers that hold the product (product page, card,
23
53
  * quick buy) pass `{ product, variant }` so the analytics event carries its
24
- * brand and category, which the cart payload doesn't repeat. Cart-side callers
25
- * (drawer, checkout) omit it and get it from what was remembered at add time.
54
+ * brand and category, which the cart payload doesn't repeat.
55
+ *
56
+ * `properties` are the customization answers, keyed by field key
57
+ * ({ cover_name: "Lara", cover_color: "<option id>", gift_wrap: true }). The
58
+ * backend validates and prices them; a bad answer comes back as
59
+ * 400 { properties: { key: message } } and nothing is added. Same variant +
60
+ * same answers merges into the existing line, different answers make a new one.
26
61
  *
27
- * This is the single place add_to_cart / remove_from_cart are reported: every
28
- * cart edit in the storefront goes through here, once.
62
+ * Signature kept from before (item_id, quantity, action, context, properties)
63
+ * so existing callers don't change. Any action other than "add" is forwarded
64
+ * to editLine with `item_id` treated as the line id.
29
65
  */
30
- const EditCart = async (item_id, quantity = 1, action, context, properties = null) => {
66
+ const EditCart = async (item_id, quantity = 1, action = "add", context, properties = null) => {
67
+ if (action !== "add") return editLine({ order_item_id: item_id }, action, quantity);
31
68
  try {
32
- const baseUrl = getBaseUrl();
33
- const before = analytics.peekCartLine(item_id);
34
- const body = { quantity, item_id, action };
69
+ const before = variantSnapshot(analytics.peekCartLines(), item_id);
70
+ const body = { item_id, quantity, action: "add" };
35
71
  if (properties && Object.keys(properties).length) body.properties = properties;
36
- const responseData = await apiService(`${baseUrl}store/apis/cart/update-item/`, {
37
- method: "POST",
38
- headers: cartHeaders(),
39
- body: JSON.stringify(body),
40
- });
41
- persistCartToken(responseData);
72
+ const responseData = await post(body);
42
73
  analytics.cartEdited({
43
74
  itemId: item_id,
75
+ variantId: item_id,
44
76
  quantity,
45
- action,
77
+ action: "add",
46
78
  before,
47
- after: responseData?.cart?.[item_id] ?? null,
79
+ after: variantSnapshot(cartLines(responseData), item_id),
48
80
  context,
49
81
  });
50
82
  return responseData;
51
83
  } catch (error) {
52
- console.error('Cart fetch error:', error);
84
+ console.error('Cart update error:', error);
85
+ throw error;
86
+ }
87
+ };
88
+
89
+ /**
90
+ * Change one exact cart line.
91
+ *
92
+ * edit set the line's quantity to `quantity`
93
+ * remove take `quantity` off the line (0 deletes it)
94
+ * remove_all delete the line
95
+ *
96
+ * Add-on lines (parent_item set) follow their parent and can't be edited on
97
+ * their own — the backend rejects it, so callers never offer it.
98
+ */
99
+ const editLine = async (line, action, quantity) => {
100
+ const orderItemId = line?.order_item_id;
101
+ if (!orderItemId) throw new Error("Missing cart line id.");
102
+ try {
103
+ const before = analytics.peekCartLine(orderItemId);
104
+ const body = { order_item_id: orderItemId, action };
105
+ if (action !== "remove_all") body.quantity = quantity;
106
+ const responseData = await post(body);
107
+ const after = responseData?.cart?.[orderItemId]
108
+ ?? cartLines(responseData).find((l) => String(l.order_item_id) === String(orderItemId))
109
+ ?? null;
110
+ const prev = Number(before?.quantity ?? line?.quantity) || 0;
111
+ const next = Number(after?.quantity) || 0;
112
+ // Analytics speaks add/remove; "edit" is whichever way the quantity moved.
113
+ if (next !== prev) {
114
+ analytics.cartEdited({
115
+ itemId: orderItemId,
116
+ variantId: before?.id ?? line?.id,
117
+ quantity: Math.abs(next - prev),
118
+ action: next > prev ? "add" : "remove",
119
+ before: before ?? line,
120
+ after,
121
+ });
122
+ }
123
+ return responseData;
124
+ } catch (error) {
125
+ console.error('Cart update error:', error);
53
126
  throw error;
54
127
  }
55
128
  };
@@ -88,6 +161,7 @@ const mergeCart = async () => {
88
161
 
89
162
  export default {
90
163
  EditCart,
164
+ editLine,
91
165
  fetchCart,
92
166
  mergeCart,
93
167
  };
@@ -72,9 +72,9 @@ const state = {
72
72
  pixelIds: [],
73
73
  currency: "",
74
74
  consent: true,
75
- /** Last cart the server sent, keyed by line (variant) id. */
75
+ /** Last cart the server sent, keyed by line id (order_item_id). */
76
76
  cart: {},
77
- /** What we know about a line beyond the cart payload: brand, category… */
77
+ /** What we know about a variant beyond the cart payload: brand, category… (keyed by variant id) */
78
78
  lineContext: new Map(),
79
79
  /** Once-per-page guards (view_item, view_item_list). Cleared on navigation. */
80
80
  seen: new Set(),
@@ -240,12 +240,14 @@ export function productToItem(product, opts = {}) {
240
240
  }
241
241
 
242
242
  /**
243
- * A cart line (the server's `cart[id]` entry, keyed by variant id) -> item.
244
- * The payload doesn't repeat brand / category, so what the add-to-cart call
245
- * site knew is remembered per line and laid back over it here.
243
+ * A cart line -> item. `line.id` is the variant id (one variant can sit on
244
+ * several lines now, so lines are keyed by order_item_id); `id` is only the
245
+ * fallback. The payload doesn't repeat brand / category, so what the
246
+ * add-to-cart call site knew is remembered per variant and laid back over it.
246
247
  */
247
248
  function cartLineToItem(id, line, quantity) {
248
- const known = state.lineContext.get(String(id)) ?? {};
249
+ const variantId = line?.id ?? id;
250
+ const known = state.lineContext.get(String(variantId)) ?? {};
249
251
  const productId =
250
252
  known.item_id ?? line?.product_id ?? line?.product?.id ?? line?.product?.product?.id ?? id;
251
253
  return clean({
@@ -255,7 +257,7 @@ function cartLineToItem(id, line, quantity) {
255
257
  item_category: known.item_category,
256
258
  item_category2: known.item_category2,
257
259
  item_variant: variantLabel(line) || known.item_variant,
258
- variant_id: String(id),
260
+ variant_id: String(variantId),
259
261
  sku: line?.sku ?? known.sku,
260
262
  price: money(line?.price ?? known.price),
261
263
  quantity: quantity ?? Math.max(1, num(line?.quantity)),
@@ -499,11 +501,16 @@ function selectItem(product, override = {}) {
499
501
  log("select_item", ga);
500
502
  }
501
503
 
502
- /** What we last heard the cart contained for this line, before an edit. */
504
+ /** What we last heard the cart contained for this line (order_item_id), before an edit. */
503
505
  function peekCartLine(id) {
504
506
  return state.cart[String(id)] ?? null;
505
507
  }
506
508
 
509
+ /** Every line we last heard about, as a list (to sum a variant across lines). */
510
+ function peekCartLines() {
511
+ return Object.entries(state.cart).map(([key, line]) => ({ ...line, order_item_id: line?.order_item_id ?? key }));
512
+ }
513
+
507
514
  /** Remembers the server's cart. Called with every cart response. */
508
515
  function rememberCart(response) {
509
516
  if (!isClient() || !response || typeof response !== "object") return;
@@ -524,13 +531,16 @@ function resetCart() {
524
531
  * through, from product pages, cards, quick buy, the drawer and checkout — so
525
532
  * nothing is missed and nothing is counted twice.
526
533
  *
527
- * @param {{ itemId: string|number, quantity: number, action: "add"|"remove",
534
+ * `variantId` keys what the call site knew (brand, category); `itemId` is the
535
+ * line or variant the edit targeted.
536
+ *
537
+ * @param {{ itemId: string|number, variantId?: string|number, quantity: number, action: "add"|"remove",
528
538
  * before: object|null, after: object|null,
529
539
  * context?: { product?: object, variant?: object } }} edit
530
540
  */
531
- function cartEdited({ itemId, quantity, action, before, after, context }) {
541
+ function cartEdited({ itemId, variantId, quantity, action, before, after, context }) {
532
542
  if (!isClient()) return;
533
- const id = String(itemId);
543
+ const id = String(variantId ?? before?.id ?? after?.id ?? itemId);
534
544
 
535
545
  // Remember what the call site knew, for later edits made from the drawer.
536
546
  if (context?.product) {
@@ -832,6 +842,7 @@ const api = {
832
842
  viewItemList,
833
843
  selectItem,
834
844
  peekCartLine,
845
+ peekCartLines,
835
846
  rememberCart,
836
847
  resetCart,
837
848
  cartEdited,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@osimart/create-storefront",
3
- "version": "1.0.20",
3
+ "version": "1.0.22",
4
4
  "description": "Osimart storefront platform — logic, platform pages and CLI; extended by every theme repo",
5
5
  "type": "module",
6
6
  "main": "./nuxt.config.ts",