@blamejs/blamejs-shop 0.3.38 → 0.3.39
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/CHANGELOG.md +2 -0
- package/lib/asset-manifest.json +1 -1
- package/lib/customers.js +11 -4
- package/lib/loyalty.js +9 -3
- package/lib/order.js +14 -5
- package/lib/store-credit.js +9 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,8 @@ upgrading across more than a few patches at a time.
|
|
|
8
8
|
|
|
9
9
|
## v0.3.x
|
|
10
10
|
|
|
11
|
+
- v0.3.39 (2026-05-31) — **Paginated lists no longer show an empty extra page at exact page boundaries.** Several paginated lists offered a next page even when the current page was the last one, if the list's length happened to be an exact multiple of the page size — following that next link landed on an empty page. This affected a customer's order history, loyalty history, and store-credit history, and the admin customer list. Each now checks whether a further item actually exists before offering a next page, so the next link appears only when there is genuinely more to show. **Fixed:** *No phantom next page on exactly-full lists* — A customer's order history, loyalty history, and store-credit history, and the admin customer list, offered a next page whenever the current page was full — so a list whose total was an exact multiple of the page size showed a next link that led to an empty page. These lists now look one item past the page before offering a next link, so it appears only when a further item exists. This matches the fix already in place for collection pages.
|
|
12
|
+
|
|
11
13
|
- v0.3.38 (2026-05-31) — **Pre-order campaigns — reserve a product before it is released.** Products that are not yet released could not be sold or reserved. Operators can now run a pre-order campaign for a product from the admin console — setting its release date, price, and an optional reservation cap. While the campaign is open, the product page shows a Pre-order control with the release date and how many reservations remain, and a signed-in customer can reserve their quantity without paying upfront. Customers see and can cancel their reservations from their account, and when the operator launches the campaign on its release date, each reservation is turned into a pending order the customer pays for through normal checkout. **Added:** *Pre-order campaigns* — A Pre-orders screen in the admin console runs a campaign for a product's SKU — release date, price, an optional deposit, and a reservation cap — with launch and close controls (launch is available once the release date arrives). While a campaign is open, the product page shows a Pre-order control with the release date and remaining availability instead of the usual add-to-cart, and a signed-in customer can reserve their quantity (no charge at reservation time; reserving past the cap or on a closed campaign is refused). Customers manage their reservations under Pre-orders in their account and can cancel one, which frees that capacity. Launching the campaign converts each open reservation into a pending order the customer completes through normal checkout. A customer can only see and cancel their own reservations.
|
|
12
14
|
|
|
13
15
|
- v0.3.37 (2026-05-31) — **Gift registries — create one, share it, and let people mark gifts as bought.** A customer can now create a gift registry for an occasion — a wedding, baby, or birthday — add the products they want with the quantity desired, and share a public link. Anyone with the link can open the registry without an account, see what is still needed, add an item to their own cart to buy it through normal checkout, or mark an item as already purchased so it stops showing as needed. Registries can be public, unlisted, or private, and a private registry is not viewable from its link. The shared page never reveals who owns the registry or who bought what, and is not indexed by search engines. **Added:** *Gift registries* — A new Registry section in the account area lets a customer create a registry (title, occasion, event date, and a public/unlisted/private visibility), add and remove the items they want with a desired quantity, edit its details, and close it. Each registry has a shareable public page at its own link where anyone — no account needed — can see the items and how many of each are still needed, add an item to their cart to buy it through the normal checkout, or mark an item as already purchased so the registry's progress updates. A private registry is not viewable from its link, the public page shows only the items (never the owner's identity, address, or who purchased what), and registry pages are excluded from search indexing.
|
package/lib/asset-manifest.json
CHANGED
package/lib/customers.js
CHANGED
|
@@ -255,20 +255,27 @@ function create(opts) {
|
|
|
255
255
|
}
|
|
256
256
|
}
|
|
257
257
|
var sql, params;
|
|
258
|
+
// Fetch one row beyond the page so the next cursor is emitted ONLY
|
|
259
|
+
// when a customer past this page actually exists. Keying the cursor
|
|
260
|
+
// off a full page alone (rows.length === limit) advertises a
|
|
261
|
+
// phantom next page when the roster size is an exact multiple of
|
|
262
|
+
// the limit — the console's "Next page" link then lands empty.
|
|
258
263
|
if (cursorVals) {
|
|
259
264
|
sql = "SELECT id, email_hash, display_name, created_at, updated_at FROM customers " +
|
|
260
265
|
"WHERE (created_at < ?1 OR (created_at = ?1 AND id < ?2)) " +
|
|
261
266
|
"ORDER BY created_at DESC, id DESC LIMIT ?3";
|
|
262
|
-
params = [cursorVals[0], cursorVals[1], limit];
|
|
267
|
+
params = [cursorVals[0], cursorVals[1], limit + 1];
|
|
263
268
|
} else {
|
|
264
269
|
sql = "SELECT id, email_hash, display_name, created_at, updated_at FROM customers " +
|
|
265
270
|
"ORDER BY created_at DESC, id DESC LIMIT ?1";
|
|
266
|
-
params = [limit];
|
|
271
|
+
params = [limit + 1];
|
|
267
272
|
}
|
|
268
|
-
var
|
|
273
|
+
var fetched = (await query(sql, params)).rows;
|
|
274
|
+
var hasMore = fetched.length > limit;
|
|
275
|
+
var rows = hasMore ? fetched.slice(0, limit) : fetched;
|
|
269
276
|
var last = rows[rows.length - 1];
|
|
270
277
|
var next = null;
|
|
271
|
-
if (last &&
|
|
278
|
+
if (last && hasMore) {
|
|
272
279
|
next = b.pagination.encodeCursor({
|
|
273
280
|
orderKey: CUSTOMERS_ORDER_KEY,
|
|
274
281
|
vals: [last.created_at, last.id],
|
package/lib/loyalty.js
CHANGED
|
@@ -450,11 +450,17 @@ function create(opts) {
|
|
|
450
450
|
sql += " AND occurred_at < ?2";
|
|
451
451
|
params.push(cursor);
|
|
452
452
|
}
|
|
453
|
+
// Fetch one row beyond the page so the next cursor is emitted ONLY
|
|
454
|
+
// when an older transaction actually exists. Keying the cursor off
|
|
455
|
+
// a full page alone (rows.length === limit) advertises a phantom
|
|
456
|
+
// next page when the history length is an exact multiple of the
|
|
457
|
+
// limit — the "Older activity" link then lands on an empty page.
|
|
453
458
|
sql += " ORDER BY occurred_at DESC LIMIT ?" + (params.length + 1);
|
|
454
|
-
params.push(limit);
|
|
459
|
+
params.push(limit + 1);
|
|
455
460
|
var r = await query(sql, params);
|
|
456
|
-
var
|
|
457
|
-
var
|
|
461
|
+
var hasMore = r.rows.length > limit;
|
|
462
|
+
var rows = hasMore ? r.rows.slice(0, limit) : r.rows;
|
|
463
|
+
var nextCursor = hasMore ? rows[rows.length - 1].occurred_at : null;
|
|
458
464
|
return { rows: rows, next_cursor: nextCursor };
|
|
459
465
|
},
|
|
460
466
|
|
package/lib/order.js
CHANGED
|
@@ -412,19 +412,28 @@ function create(opts) {
|
|
|
412
412
|
}
|
|
413
413
|
}
|
|
414
414
|
var sql, params;
|
|
415
|
+
// Fetch one row beyond the page so the next cursor is emitted ONLY
|
|
416
|
+
// when an order past this page actually exists. Keying the cursor
|
|
417
|
+
// off a full page alone (rows.length === limit) advertises a
|
|
418
|
+
// phantom next page when the customer's order count is an exact
|
|
419
|
+
// multiple of the limit — the "Load more" link then lands on an
|
|
420
|
+
// empty page.
|
|
415
421
|
if (cursorVals) {
|
|
416
422
|
sql = "SELECT * FROM orders WHERE customer_id = ?1 AND " +
|
|
417
423
|
"(updated_at < ?2 OR (updated_at = ?2 AND id < ?3)) " +
|
|
418
424
|
"ORDER BY updated_at DESC, id DESC LIMIT ?4";
|
|
419
|
-
params = [customerId, cursorVals[0], cursorVals[1], limit];
|
|
425
|
+
params = [customerId, cursorVals[0], cursorVals[1], limit + 1];
|
|
420
426
|
} else {
|
|
421
427
|
sql = "SELECT * FROM orders WHERE customer_id = ?1 " +
|
|
422
428
|
"ORDER BY updated_at DESC, id DESC LIMIT ?2";
|
|
423
|
-
params = [customerId, limit];
|
|
429
|
+
params = [customerId, limit + 1];
|
|
424
430
|
}
|
|
425
|
-
var
|
|
431
|
+
var fetched = (await query(sql, params)).rows;
|
|
432
|
+
var hasMore = fetched.length > limit;
|
|
433
|
+
var rows = hasMore ? fetched.slice(0, limit) : fetched;
|
|
426
434
|
// Hydrate ship_to_json + lines on each row so the renderer
|
|
427
|
-
// doesn't need a separate trip per order.
|
|
435
|
+
// doesn't need a separate trip per order. The peeked row is sliced
|
|
436
|
+
// off first so it never costs a hydration trip.
|
|
428
437
|
for (var i = 0; i < rows.length; i += 1) {
|
|
429
438
|
rows[i].ship_to = JSON.parse(rows[i].ship_to_json);
|
|
430
439
|
rows[i].lines = (await query(
|
|
@@ -434,7 +443,7 @@ function create(opts) {
|
|
|
434
443
|
}
|
|
435
444
|
var last = rows[rows.length - 1];
|
|
436
445
|
var next = null;
|
|
437
|
-
if (last &&
|
|
446
|
+
if (last && hasMore) {
|
|
438
447
|
next = b.pagination.encodeCursor({
|
|
439
448
|
orderKey: ORDER_ORDER_KEY,
|
|
440
449
|
vals: [last.updated_at, last.id],
|
package/lib/store-credit.js
CHANGED
|
@@ -331,11 +331,17 @@ function create(opts) {
|
|
|
331
331
|
sql += " AND occurred_at < ?2";
|
|
332
332
|
params.push(cursor);
|
|
333
333
|
}
|
|
334
|
+
// Fetch one row beyond the page so the next cursor is emitted ONLY
|
|
335
|
+
// when an older ledger entry actually exists. Keying the cursor off
|
|
336
|
+
// a full page alone (rows.length === limit) advertises a phantom
|
|
337
|
+
// next page when the ledger length is an exact multiple of the
|
|
338
|
+
// limit — the "Older activity" link then lands on an empty page.
|
|
334
339
|
sql += " ORDER BY occurred_at DESC, id DESC LIMIT ?" + (params.length + 1);
|
|
335
|
-
params.push(limit);
|
|
340
|
+
params.push(limit + 1);
|
|
336
341
|
var r = await query(sql, params);
|
|
337
|
-
var
|
|
338
|
-
var
|
|
342
|
+
var hasMore = r.rows.length > limit;
|
|
343
|
+
var rows = hasMore ? r.rows.slice(0, limit) : r.rows;
|
|
344
|
+
var nextCursor = hasMore ? rows[rows.length - 1].occurred_at : null;
|
|
339
345
|
return { rows: rows, next_cursor: nextCursor };
|
|
340
346
|
},
|
|
341
347
|
|
package/package.json
CHANGED