@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.
- package/app/composables/UseThemeGroup.js +4 -0
- package/app/composables/useAddToCart.js +16 -6
- package/app/composables/useCart.js +66 -25
- package/app/composables/useCheckout.js +47 -6
- package/app/composables/useContentTree.js +28 -2
- package/app/composables/useProductCustomization.js +107 -15
- package/app/composables/useProductDetail.js +29 -8
- package/app/composables/useProductItems.js +5 -3
- package/app/composables/useProductVariants.js +32 -1
- package/app/composables/useQuickShop.js +20 -0
- package/app/logic/components/CartDrawer.js +14 -19
- package/app/logic/components/CartItem.js +18 -1
- package/app/logic/components/ProductVariantPicker.js +7 -2
- package/app/logic/components/QuickBuy.js +14 -6
- package/app/logic/components/SiteHeader.js +2 -0
- package/app/logic/sections/MainClassificationSection.js +2 -0
- package/app/pages/checkout.vue +29 -10
- package/app/pages/order-detail.vue +25 -2
- package/app/pages/user-profile/components/orders.vue +23 -2
- package/app/services/CartServices.js +92 -18
- package/app/utils/analytics.js +22 -11
- package/package.json +1 -1
|
@@ -17,39 +17,112 @@ const persistCartToken = (responseData) => {
|
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
*
|
|
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.
|
|
25
|
-
*
|
|
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
|
-
*
|
|
28
|
-
*
|
|
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
|
|
33
|
-
const
|
|
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
|
|
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
|
|
79
|
+
after: variantSnapshot(cartLines(responseData), item_id),
|
|
48
80
|
context,
|
|
49
81
|
});
|
|
50
82
|
return responseData;
|
|
51
83
|
} catch (error) {
|
|
52
|
-
console.error('Cart
|
|
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
|
};
|
package/app/utils/analytics.js
CHANGED
|
@@ -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 (
|
|
75
|
+
/** Last cart the server sent, keyed by line id (order_item_id). */
|
|
76
76
|
cart: {},
|
|
77
|
-
/** What we know about a
|
|
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
|
|
244
|
-
*
|
|
245
|
-
*
|
|
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
|
|
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(
|
|
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
|
-
*
|
|
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