orionplace-react 0.7.7 → 0.7.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orionplace-react",
3
- "version": "0.7.7",
3
+ "version": "0.7.8",
4
4
  "description": "Next.js 16 storefront for your Orionplace marketplace: setup CLI, server API gateway, typed store and ready-made catalog, cart, checkout and account components in English, Russian and Spanish, plus an Android app build.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -404,8 +404,8 @@ export function OrderDetails({
404
404
  {step.current ? (
405
405
  <span className={styles.stepState}>{labels.stepStates.current}</span>
406
406
  ) : null}
407
- {step.key === 'placed' && order.time_submited?.date ? (
408
- <span className={styles.stepNote}>{order.time_submited.date}</span>
407
+ {step.date ? (
408
+ <span className={styles.stepNote}>{step.date}</span>
409
409
  ) : step.total > 1 ? (
410
410
  <span className={styles.stepNote}>
411
411
  {step.count} {labels.of} {step.total}
@@ -29,6 +29,17 @@ import { apiErrorMessage, OrionplaceApiError } from '@/lib/orionplace/api';
29
29
  import { useOrionplaceApi } from '@/hooks/useOrionplaceApi';
30
30
  import { productAvailability } from '@/lib/orionplace/productAvailability';
31
31
  import { productActionLabels } from '@/lib/orionplace/productActionLabels';
32
+
33
+ // What a finished product action did: a key of productActionLabels.
34
+ type ProductActionDone =
35
+ | 'cartAdded'
36
+ | 'cartUpdated'
37
+ | 'cartRemoved'
38
+ | 'favoriteAdded'
39
+ | 'favoriteRemoved'
40
+ | 'subscribed'
41
+ | 'unsubscribed'
42
+ | 'preorderSaved';
32
43
  import styles from '@/app/storefront.module.css';
33
44
  import { signInPageLocation } from '@/lib/orionplace/signInReturn';
34
45
 
@@ -236,7 +247,11 @@ export function OrionplaceStorefrontProvider({
236
247
  }
237
248
  }
238
249
 
239
- async function action(work: () => Promise<void>) {
250
+ // done names what the action did, for the notice («Added to the cart.»).
251
+ async function action(
252
+ work: () => Promise<void>,
253
+ done: ProductActionDone,
254
+ ) {
240
255
  if (actionPending.current) {
241
256
  return;
242
257
  }
@@ -248,7 +263,7 @@ export function OrionplaceStorefrontProvider({
248
263
  setMessageSignIn(false);
249
264
  try {
250
265
  await work();
251
- setMessage(copy.saved);
266
+ setMessage(copy[done]);
252
267
  } catch (reason) {
253
268
  if (
254
269
  reason instanceof OrionplaceApiError &&
@@ -399,9 +414,12 @@ export function OrionplaceStorefrontProvider({
399
414
  } finally {
400
415
  products.getState().setPendingFavorite(selection.product);
401
416
  }
402
- });
417
+ }, selection.selected ? 'favoriteAdded' : 'favoriteRemoved');
403
418
  },
404
419
  onAdd(selection) {
420
+ // Adding a product already in the cart sets its new quantity.
421
+ const inCart = Boolean(product(selection.product)?.cart_counts?.cart_id);
422
+
405
423
  void action(async () => {
406
424
  const data = await api.request<{
407
425
  cart_item_id?: number;
@@ -431,7 +449,7 @@ export function OrionplaceStorefrontProvider({
431
449
  c_cart_positions: data.c_cart_positions,
432
450
  });
433
451
  }
434
- });
452
+ }, inCart ? 'cartUpdated' : 'cartAdded');
435
453
  },
436
454
  onRemove(selection) {
437
455
  void action(async () => {
@@ -446,7 +464,7 @@ export function OrionplaceStorefrontProvider({
446
464
  c_cart_positions: data.c_cart_positions,
447
465
  });
448
466
  }
449
- });
467
+ }, 'cartRemoved');
450
468
  },
451
469
  onSubscribe(selection) {
452
470
  if (!init?.is_authenticated) {
@@ -465,7 +483,7 @@ export function OrionplaceStorefrontProvider({
465
483
  });
466
484
  await refreshNotificationState();
467
485
  await refreshProduct(selection.product);
468
- });
486
+ }, 'subscribed');
469
487
  },
470
488
  onUnsubscribe(selection) {
471
489
  if (!init?.is_authenticated) {
@@ -477,7 +495,7 @@ export function OrionplaceStorefrontProvider({
477
495
  product_id: selection.product,
478
496
  });
479
497
  await refreshProduct(selection.product);
480
- });
498
+ }, 'unsubscribed');
481
499
  },
482
500
  onPreorder(selection) {
483
501
  const current = product(selection.product);
@@ -511,7 +529,7 @@ export function OrionplaceStorefrontProvider({
511
529
  }
512
530
  await api.request('preorders_create', body);
513
531
  setPreorder(undefined);
514
- });
532
+ }, 'preorderSaved');
515
533
  },
516
534
  closePreorder() {
517
535
  setPreorder(undefined);
@@ -59,6 +59,8 @@ export interface OrderStep {
59
59
  current: boolean;
60
60
  count: number;
61
61
  total: number;
62
+ /** The date the step was reached ('' until then); see orderStepDate. */
63
+ date?: string;
62
64
  }
63
65
 
64
66
  export type OrderShipmentStateKey =
@@ -228,7 +230,39 @@ export function orderCanCancel(
228
230
  }
229
231
 
230
232
  // Canceled and returned orders have no delivery progress to show.
231
- export function orderSteps(order: OrderShipmentCounters): OrderStep[] {
233
+ // The date a step was reached: the order's own date for Placed; for Shipped
234
+ // and Delivered, once every shipment got there, the latest of their sent
235
+ // (or delivery) dates; '' while a shipment has not reached the step.
236
+ export function orderStepDate(
237
+ order: OrderShipmentCounters & Partial<Pick<IOrder, 'time_submited'>>,
238
+ key: OrderStepKey,
239
+ ): string {
240
+ if (key === 'placed') {
241
+ return order.time_submited?.date ?? '';
242
+ }
243
+
244
+ const shipments = order.delivery_froms ?? [];
245
+ const reached = shipments.map((shipment) =>
246
+ key === 'shipped'
247
+ ? shipment.dates?.sent || shipment.dates?.delivery_date_real
248
+ : shipment.dates?.delivery_date_real,
249
+ );
250
+
251
+ if (!reached.length || reached.some((date) => !date?.date)) {
252
+ return '';
253
+ }
254
+
255
+ // The latest is the one the fewest minutes ago (any date format).
256
+ const latest = reached.reduce((best, date) =>
257
+ Number(date?.minutes_ago ?? 0) < Number(best?.minutes_ago ?? 0) ? date : best,
258
+ );
259
+
260
+ return latest?.date ?? '';
261
+ }
262
+
263
+ export function orderSteps(
264
+ order: OrderShipmentCounters & Partial<Pick<IOrder, 'time_submited'>>,
265
+ ): OrderStep[] {
232
266
  const { total, sent, delivered } = orderShipmentProgress(order);
233
267
 
234
268
  if (
@@ -239,13 +273,21 @@ export function orderSteps(order: OrderShipmentCounters): OrderStep[] {
239
273
  }
240
274
 
241
275
  const steps = [
242
- { key: 'placed', state: 'done', current: false, count: 1, total: 1 },
276
+ {
277
+ key: 'placed',
278
+ state: 'done',
279
+ current: false,
280
+ count: 1,
281
+ total: 1,
282
+ date: orderStepDate(order, 'placed'),
283
+ },
243
284
  {
244
285
  key: 'shipped',
245
286
  state: stepState(sent, total),
246
287
  current: false,
247
288
  count: sent,
248
289
  total,
290
+ date: orderStepDate(order, 'shipped'),
249
291
  },
250
292
  {
251
293
  key: 'delivered',
@@ -253,6 +295,7 @@ export function orderSteps(order: OrderShipmentCounters): OrderStep[] {
253
295
  current: false,
254
296
  count: delivered,
255
297
  total,
298
+ date: orderStepDate(order, 'delivered'),
256
299
  },
257
300
  ] satisfies OrderStep[];
258
301
 
@@ -4,6 +4,13 @@ const labels = {
4
4
  en: {
5
5
  pending: 'Updating product…',
6
6
  saved: 'Product updated.',
7
+ cartAdded: 'Added to the cart.',
8
+ cartUpdated: 'Cart quantity updated.',
9
+ cartRemoved: 'Removed from the cart.',
10
+ favoriteAdded: 'Saved to favorites.',
11
+ favoriteRemoved: 'Removed from favorites.',
12
+ subscribed: 'We will let you know when it is back in stock.',
13
+ unsubscribed: 'Availability notification turned off.',
7
14
  failed: 'The request failed. Your confirmed product data is unchanged.',
8
15
  unavailable: 'This action is unavailable.',
9
16
  login: 'Sign in to save favorites or availability notifications.',
@@ -31,6 +38,13 @@ const labels = {
31
38
  ru: {
32
39
  pending: 'Обновляем товар…',
33
40
  saved: 'Товар обновлён.',
41
+ cartAdded: 'Товар добавлен в корзину.',
42
+ cartUpdated: 'Количество в корзине изменено.',
43
+ cartRemoved: 'Товар удалён из корзины.',
44
+ favoriteAdded: 'Товар добавлен в избранное.',
45
+ favoriteRemoved: 'Товар удалён из избранного.',
46
+ subscribed: 'Сообщим, когда товар появится в наличии.',
47
+ unsubscribed: 'Уведомление о наличии отключено.',
34
48
  failed: 'Запрос не выполнен. Подтверждённые данные товара сохранены.',
35
49
  unavailable: 'Это действие недоступно.',
36
50
  login: 'Войдите, чтобы сохранять избранное и уведомления о наличии.',
@@ -56,6 +70,13 @@ const labels = {
56
70
  es: {
57
71
  pending: 'Actualizando el producto…',
58
72
  saved: 'Producto actualizado.',
73
+ cartAdded: 'Añadido al carrito.',
74
+ cartUpdated: 'Cantidad del carrito actualizada.',
75
+ cartRemoved: 'Eliminado del carrito.',
76
+ favoriteAdded: 'Guardado en favoritos.',
77
+ favoriteRemoved: 'Eliminado de favoritos.',
78
+ subscribed: 'Te avisaremos cuando vuelva a estar disponible.',
79
+ unsubscribed: 'Aviso de disponibilidad desactivado.',
59
80
  failed: 'La solicitud ha fallado. Los datos confirmados del producto no han cambiado.',
60
81
  unavailable: 'Esta acción no está disponible.',
61
82
  login: 'Inicia sesión para guardar favoritos o avisos de disponibilidad.',