@fluid-app/portal-sdk 0.1.405 → 0.1.406

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.
@@ -203,6 +203,7 @@ function mapToPortalTenantOrder(raw) {
203
203
  token: raw.token ?? "",
204
204
  order_number: typeof rawOrderNumber === "string" && rawOrderNumber.length > 0 ? rawOrderNumber : null,
205
205
  status: raw.status ?? "pending",
206
+ fulfillment_status: typeof raw.fulfillment_status === "string" ? raw.fulfillment_status : "unfulfilled",
206
207
  subtotal,
207
208
  subtotal_in_currency: raw.subtotal_in_currency ?? subtotal,
208
209
  discount,
@@ -359,127 +360,226 @@ function OrdersTranslationBridge({ children }) {
359
360
  });
360
361
  }
361
362
  //#endregion
362
- //#region src/screens/orders/portal-orders-list-helpers.ts
363
- const STATUS_LOOKUP = {
364
- fulfilled: {
365
- kind: "fulfilled",
366
- labelKey: "status_fulfilled"
367
- },
368
- completed: {
369
- kind: "fulfilled",
370
- labelKey: "status_fulfilled"
371
- },
372
- partially_fulfilled: {
373
- kind: "preparing",
374
- labelKey: "status_preparing"
375
- },
376
- in_progress: {
377
- kind: "preparing",
378
- labelKey: "status_preparing"
379
- },
380
- on_hold: {
381
- kind: "preparing",
382
- labelKey: "status_preparing"
383
- },
384
- scheduled: {
385
- kind: "preparing",
386
- labelKey: "status_preparing"
387
- },
388
- preparing: {
389
- kind: "preparing",
390
- labelKey: "status_preparing"
391
- },
392
- shipped: {
393
- kind: "shipped",
394
- labelKey: "status_shipped"
395
- },
396
- cancelled: { kind: "cancelled" },
397
- returned: { kind: "cancelled" },
398
- refunded: { kind: "cancelled" },
399
- pending: {
400
- kind: "pending",
401
- labelKey: "status_processing"
402
- },
403
- unfulfilled: {
404
- kind: "pending",
405
- labelKey: "status_processing"
406
- },
407
- processing: {
408
- kind: "pending",
409
- labelKey: "status_processing"
410
- }
363
+ //#region ../../orders/ui/src/components/status-badge.tsx
364
+ const colorStyles = {
365
+ green: "bg-green-500/15 text-foreground",
366
+ yellow: "bg-yellow-500/15 text-foreground",
367
+ red: "bg-red-500/15 text-foreground",
368
+ blue: "bg-blue-500/15 text-foreground",
369
+ orange: "bg-orange-500/15 text-foreground",
370
+ gray: "bg-foreground/10 text-foreground"
411
371
  };
412
- /**
413
- * Map a raw status string (typically `fulfillment_status`) from the BFF
414
- * onto our internal status vocabulary (`StatusKind`). Aliases are folded
415
- * together (`unfulfilled` → `pending`, `in_progress`/`scheduled` →
416
- * `preparing`, etc.). Unknown statuses pass through with `kind: "pending"`
417
- * and the raw label capitalized — never synthesize a colored pill that
418
- * pretends to be real status data.
419
- */
420
- function mapStatus(orderStatus, t) {
421
- const entry = STATUS_LOOKUP[(orderStatus || "").toLowerCase()];
422
- const rawLabel = orderStatus ? orderStatus.charAt(0).toUpperCase() + orderStatus.slice(1) : "—";
423
- if (!entry) return {
424
- kind: "pending",
425
- label: rawLabel
426
- };
427
- return {
428
- kind: entry.kind,
429
- label: entry.labelKey ? t(entry.labelKey) : rawLabel
430
- };
431
- }
432
- const STATUS_STYLES = {
433
- fulfilled: {
434
- bg: "bg-emerald-50 dark:bg-emerald-950/40",
435
- text: "text-emerald-700 dark:text-emerald-400",
436
- dot: "bg-emerald-500"
437
- },
438
- shipped: {
439
- bg: "bg-sky-50 dark:bg-sky-950/40",
440
- text: "text-sky-700 dark:text-sky-400",
441
- dot: "bg-sky-500"
442
- },
443
- preparing: {
444
- bg: "bg-amber-50 dark:bg-amber-950/40",
445
- text: "text-amber-700 dark:text-amber-400",
446
- dot: "bg-amber-500"
447
- },
448
- pending: {
449
- bg: "bg-muted",
450
- text: "text-muted-foreground",
451
- dot: "bg-muted-foreground"
452
- },
453
- cancelled: {
454
- bg: "bg-destructive/10",
455
- text: "text-destructive",
456
- dot: "bg-destructive"
457
- }
372
+ const dotColorStyles = {
373
+ green: "bg-green-500",
374
+ yellow: "bg-yellow-500",
375
+ red: "bg-red-500",
376
+ blue: "bg-blue-500",
377
+ orange: "bg-orange-500",
378
+ gray: "bg-gray-500"
458
379
  };
459
- const CANCELLED_FAMILY = new Set([
380
+ const sizeStyles = {
381
+ xs: "px-1.5 py-0.5 text-[10px]",
382
+ sm: "px-2 py-0.5 text-xs",
383
+ md: "px-2.5 py-1 text-sm"
384
+ };
385
+ function StatusBadge({ color, dot, size = "md", className, children }) {
386
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
387
+ className: require_src.cn("inline-flex items-center gap-1 rounded-full font-medium whitespace-nowrap capitalize", colorStyles[color], sizeStyles[size], className),
388
+ children: [dot && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: require_src.cn("h-1.5 w-1.5 rounded-full", dotColorStyles[color]) }), children]
389
+ });
390
+ }
391
+ //#endregion
392
+ //#region ../../orders/ui/src/lib/format.ts
393
+ function startCase(str) {
394
+ if (!str) return "";
395
+ return str.replace(/_/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
396
+ }
397
+ //#endregion
398
+ //#region ../../orders/ui/src/lib/translate-status.ts
399
+ /** Status values that have a `status_*` translation key in the orders dictionary. */
400
+ const translatableStatuses = new Set([
460
401
  "cancelled",
402
+ "completed",
403
+ "fulfilled",
404
+ "pending",
405
+ "preparing",
406
+ "processing",
461
407
  "returned",
462
- "refunded"
408
+ "shipped"
463
409
  ]);
464
410
  /**
411
+ * Translate an order status using the orders `t()` function.
412
+ * Falls back to `startCase` for statuses without a dedicated translation key.
413
+ */
414
+ function translateStatus(t, status) {
415
+ if (translatableStatuses.has(status)) return t(`status_${status}`);
416
+ return startCase(status);
417
+ }
418
+ //#endregion
419
+ //#region ../../orders/ui/src/lib/effective-order-date.ts
420
+ function effectiveOrderDate(order) {
421
+ return order.sale_date ?? order.created_at;
422
+ }
423
+ //#endregion
424
+ //#region ../../orders/ui/src/lib/fulfillment-status.ts
425
+ /**
426
+ * Canonical fulfillment-status → badge color mapping.
427
+ *
428
+ * This is a deliberate mirror of fluid-admin's fulfillment color config
429
+ * (`COLOR_CONFIG.fulfillment` + `getDefaultColorForUnknownStatus` in
430
+ * `apps/fluid-admin/components/Orders/utils/formatters.ts`) so the
431
+ * customer-facing portal renders order status tags with the SAME label
432
+ * and color as the admin — admin is the source of truth (CURRENT-1496).
433
+ *
434
+ * The `Commerce::Order#fulfillment_status` enum values are:
435
+ * `unfulfilled, in_progress, on_hold, partially_fulfilled, scheduled,
436
+ * fulfilled`.
437
+ */
438
+ const FULFILLMENT_STATUS_COLORS = {
439
+ fulfilled: "gray",
440
+ unfulfilled: "yellow",
441
+ in_progress: "yellow",
442
+ on_hold: "orange",
443
+ partially_fulfilled: "yellow",
444
+ scheduled: "blue",
445
+ processing: "yellow"
446
+ };
447
+ /**
448
+ * Cancelled-family lifecycle values. These come from `order_status` (not
449
+ * `fulfillment_status`), and the portal orders list surfaces them on the
450
+ * same pill. Admin renders these red, so we match.
451
+ */
452
+ const CANCELLED_FAMILY_COLORS = {
453
+ cancelled: "red",
454
+ returned: "red",
455
+ refunded: "red"
456
+ };
457
+ /**
458
+ * Fallback for statuses not in the explicit maps. Mirrors admin's
459
+ * `getDefaultColorForUnknownStatus` for the fulfillment type so unknown
460
+ * values still read consistently with admin rather than defaulting to a
461
+ * meaningless grey.
462
+ */
463
+ function defaultColor(status) {
464
+ if (status.includes("fulfilled") || status.includes("complete")) return "gray";
465
+ if (status.includes("hold")) return "orange";
466
+ return "yellow";
467
+ }
468
+ /**
469
+ * Cancelled-family lifecycle values, derived from the color map so there is
470
+ * a single source of truth. Membership is checked case-insensitively.
471
+ */
472
+ const CANCELLED_FAMILY = new Set(Object.keys(CANCELLED_FAMILY_COLORS));
473
+ /**
474
+ * Resolve which status string should drive an order's pill. Cancelled-family
475
+ * lifecycle values (`cancelled` / `returned` / `refunded`) come from the
476
+ * order's lifecycle status; everything else reads off `fulfillment_status`
477
+ * (the source of truth per `Commerce::Order#fulfillment_status`).
478
+ *
479
+ * Both the orders list and the order detail page use this so a cancelled
480
+ * order renders red "Cancelled" instead of a misleading yellow "Unfulfilled"
481
+ * — `fulfillment_status` has no cancelled-family value, so a cancelled order
482
+ * typically carries `fulfillment_status: "unfulfilled"` (CURRENT-1496).
483
+ *
484
+ * lifecycleStatus - the order's lifecycle status (`order_status` on a list
485
+ * order, `status` on a portal-tenant detail order).
486
+ * fulfillmentStatus - the order's `fulfillment_status`.
487
+ */
488
+ function resolveOrderStatus(lifecycleStatus, fulfillmentStatus) {
489
+ if (lifecycleStatus && CANCELLED_FAMILY.has(lifecycleStatus.toLowerCase())) return lifecycleStatus;
490
+ return fulfillmentStatus ?? lifecycleStatus ?? "";
491
+ }
492
+ /**
493
+ * Resolve the badge color for an order status tag, matching admin exactly
494
+ * for every `fulfillment_status` enum value and the cancelled family.
495
+ */
496
+ function fulfillmentStatusColor(status) {
497
+ if (!status) return "gray";
498
+ const normalized = status.toLowerCase();
499
+ return FULFILLMENT_STATUS_COLORS[normalized] ?? CANCELLED_FAMILY_COLORS[normalized] ?? defaultColor(normalized);
500
+ }
501
+ //#endregion
502
+ //#region ../../orders/ui/src/components/fulfillment-status-badge.tsx
503
+ /**
504
+ * Renders an order status tag whose label and color match the fluid-admin
505
+ * fulfillment pill exactly (CURRENT-1496). Admin is the source of truth:
506
+ * `fulfilled` is grey, `unfulfilled`/`in_progress`/`partially_fulfilled`
507
+ * are yellow, `on_hold` is orange, and `scheduled` is blue. Labels come
508
+ * from `translateStatus`, which yields the same start-cased text admin
509
+ * shows (`getStatusDisplayText`) while preserving the translated
510
+ * `status_fulfilled` copy where available.
511
+ */
512
+ function FulfillmentStatusBadge({ status, className }) {
513
+ const { t } = useOrdersTranslation();
514
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatusBadge, {
515
+ color: fulfillmentStatusColor(status),
516
+ dot: true,
517
+ size: "sm",
518
+ className,
519
+ children: translateStatus(t, status)
520
+ });
521
+ }
522
+ //#endregion
523
+ //#region ../../orders/ui/src/components/static-map.tsx
524
+ function StaticMap({ apiKey, address, options, alt, className }) {
525
+ const { t } = useOrdersTranslation();
526
+ const [errored, setErrored] = (0, react.useState)(false);
527
+ const url = buildStaticMapUrl(apiKey, address, options);
528
+ if (!url || errored) return null;
529
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
530
+ src: url,
531
+ alt: alt ?? t("shipping_address_map"),
532
+ width: options?.width ?? 600,
533
+ height: options?.height ?? 300,
534
+ className: className ?? "border-border w-full rounded border object-cover shadow-xs",
535
+ onError: () => setErrored(true)
536
+ });
537
+ }
538
+ //#endregion
539
+ //#region src/screens/orders/portal-orders-list-helpers.ts
540
+ const STATUS_KIND_LOOKUP = {
541
+ fulfilled: "fulfilled",
542
+ completed: "fulfilled",
543
+ shipped: "shipped",
544
+ partially_fulfilled: "preparing",
545
+ in_progress: "preparing",
546
+ on_hold: "preparing",
547
+ scheduled: "preparing",
548
+ preparing: "preparing",
549
+ cancelled: "cancelled",
550
+ returned: "cancelled",
551
+ refunded: "cancelled",
552
+ pending: "pending",
553
+ unfulfilled: "pending",
554
+ processing: "pending"
555
+ };
556
+ /**
557
+ * Classify a raw status string onto the internal `StatusKind` vocabulary
558
+ * used to drive the filter tabs. This is intentionally coarser than the
559
+ * rendered pill — the pill's label and color come from
560
+ * `FulfillmentStatusBadge`. Unknown statuses fall back to `pending`.
561
+ */
562
+ function statusKind(orderStatus) {
563
+ return STATUS_KIND_LOOKUP[(orderStatus || "").toLowerCase()] ?? "pending";
564
+ }
565
+ /**
465
566
  * Pick the right status string to drive the pill / filter for a list order.
466
567
  * Cancelled-family lifecycle states come from `order_status`; everything
467
568
  * else is read off `fulfillment_status` (the BFF source-of-truth per the
468
- * `Commerce::Order#fulfillment_status` enum).
569
+ * `Commerce::Order#fulfillment_status` enum). Delegates to the shared
570
+ * `resolveOrderStatus` so the list and the order detail page stay in lockstep.
469
571
  */
470
572
  function statusForOrder(order) {
471
- if (CANCELLED_FAMILY.has(order.order_status)) return order.order_status;
472
- return order.fulfillment_status ?? order.order_status;
573
+ return resolveOrderStatus(order.order_status, order.fulfillment_status);
473
574
  }
474
575
  function filterPredicate(filter, order) {
475
576
  if (filter === "all") return true;
476
577
  if (filter === "subscription") return order.subscription != null;
477
- const status = mapStatus(statusForOrder(order), identity).kind;
478
- if (filter === "fulfilled") return status === "fulfilled" || status === "shipped";
479
- if (filter === "preparing") return status === "preparing" || status === "pending";
578
+ const kind = statusKind(statusForOrder(order));
579
+ if (filter === "fulfilled") return kind === "fulfilled" || kind === "shipped";
580
+ if (filter === "preparing") return kind === "preparing" || kind === "pending";
480
581
  return true;
481
582
  }
482
- const identity = ((k) => k);
483
583
  function countByFilter(list) {
484
584
  return {
485
585
  all: list.length,
@@ -728,7 +828,6 @@ function FilterPills({ active, counts, onChange, t }) {
728
828
  });
729
829
  }
730
830
  function OrderRow({ order, onClick, t }) {
731
- const status = mapStatus(statusForOrder(order), t);
732
831
  const dateLabel = formatFriendlyDate(orderDisplayDate(order), t);
733
832
  const total = formatOrderTotal(order);
734
833
  const titleText = order.first_item?.title || `${t("order_number")}${order.order_number}`;
@@ -752,7 +851,7 @@ function OrderRow({ order, onClick, t }) {
752
851
  className: "text-muted-foreground font-medium",
753
852
  children: titleSuffix
754
853
  })]
755
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatusPill, { status })]
854
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FulfillmentStatusBadge, { status: statusForOrder(order) })]
756
855
  }),
757
856
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
758
857
  className: "text-muted-foreground mt-1 truncate text-xs",
@@ -808,13 +907,6 @@ function SubscriptionBadge({ t }) {
808
907
  })
809
908
  });
810
909
  }
811
- function StatusPill({ status }) {
812
- const styles = STATUS_STYLES[status.kind];
813
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
814
- className: `inline-flex shrink-0 items-center gap-1 rounded-md px-2 py-0.5 text-xs font-semibold ${styles.bg} ${styles.text}`,
815
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: `size-1.5 rounded-full ${styles.dot}` }), status.label]
816
- });
817
- }
818
910
  function OrdersListSkeleton() {
819
911
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: Array(4).fill(0).map((_, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
820
912
  className: `flex items-center gap-3 px-4 py-3.5 ${i < 3 ? "border-border border-b" : ""}`,
@@ -957,107 +1049,6 @@ function MaskedPaymentLabel({ label, className }) {
957
1049
  });
958
1050
  }
959
1051
  //#endregion
960
- //#region ../../orders/ui/src/components/status-badge.tsx
961
- const colorStyles = {
962
- green: "bg-green-500/15 text-foreground",
963
- yellow: "bg-yellow-500/15 text-foreground",
964
- red: "bg-red-500/15 text-foreground",
965
- blue: "bg-blue-500/15 text-foreground",
966
- gray: "bg-foreground/10 text-foreground"
967
- };
968
- const dotColorStyles = {
969
- green: "bg-green-500",
970
- yellow: "bg-yellow-500",
971
- red: "bg-red-500",
972
- blue: "bg-blue-500",
973
- gray: "bg-gray-500"
974
- };
975
- const sizeStyles = {
976
- xs: "px-1.5 py-0.5 text-[10px]",
977
- sm: "px-2 py-0.5 text-xs",
978
- md: "px-2.5 py-1 text-sm"
979
- };
980
- function StatusBadge({ color, dot, size = "md", className, children }) {
981
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
982
- className: require_src.cn("inline-flex items-center gap-1 rounded-full font-medium whitespace-nowrap capitalize", colorStyles[color], sizeStyles[size], className),
983
- children: [dot && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: require_src.cn("h-1.5 w-1.5 rounded-full", dotColorStyles[color]) }), children]
984
- });
985
- }
986
- //#endregion
987
- //#region ../../orders/ui/src/lib/format.ts
988
- function startCase(str) {
989
- if (!str) return "";
990
- return str.replace(/_/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
991
- }
992
- //#endregion
993
- //#region ../../orders/ui/src/lib/translate-status.ts
994
- /** Status values that have a `status_*` translation key in the orders dictionary. */
995
- const translatableStatuses = new Set([
996
- "cancelled",
997
- "completed",
998
- "fulfilled",
999
- "pending",
1000
- "preparing",
1001
- "processing",
1002
- "returned",
1003
- "shipped"
1004
- ]);
1005
- /**
1006
- * Translate an order status using the orders `t()` function.
1007
- * Falls back to `startCase` for statuses without a dedicated translation key.
1008
- */
1009
- function translateStatus(t, status) {
1010
- if (translatableStatuses.has(status)) return t(`status_${status}`);
1011
- return startCase(status);
1012
- }
1013
- //#endregion
1014
- //#region ../../orders/ui/src/components/order-status-badge.tsx
1015
- const statusColorMap = {
1016
- paid: "green",
1017
- fulfilled: "green",
1018
- delivered: "green",
1019
- complete: "green",
1020
- pending: "yellow",
1021
- unfulfilled: "yellow",
1022
- partially_fulfilled: "yellow",
1023
- processing: "yellow",
1024
- refunded: "red",
1025
- cancelled: "red",
1026
- failed: "red",
1027
- voided: "red"
1028
- };
1029
- function OrderStatusBadge({ status, className }) {
1030
- const { t } = useOrdersTranslation();
1031
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatusBadge, {
1032
- color: statusColorMap[status] ?? "gray",
1033
- dot: true,
1034
- size: "sm",
1035
- className,
1036
- children: translateStatus(t, status)
1037
- });
1038
- }
1039
- //#endregion
1040
- //#region ../../orders/ui/src/components/static-map.tsx
1041
- function StaticMap({ apiKey, address, options, alt, className }) {
1042
- const { t } = useOrdersTranslation();
1043
- const [errored, setErrored] = (0, react.useState)(false);
1044
- const url = buildStaticMapUrl(apiKey, address, options);
1045
- if (!url || errored) return null;
1046
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
1047
- src: url,
1048
- alt: alt ?? t("shipping_address_map"),
1049
- width: options?.width ?? 600,
1050
- height: options?.height ?? 300,
1051
- className: className ?? "border-border w-full rounded border object-cover shadow-xs",
1052
- onError: () => setErrored(true)
1053
- });
1054
- }
1055
- //#endregion
1056
- //#region ../../orders/ui/src/lib/effective-order-date.ts
1057
- function effectiveOrderDate(order) {
1058
- return order.sale_date ?? order.created_at;
1059
- }
1060
- //#endregion
1061
1052
  //#region ../../orders/ui/src/components/portal-order-detail.tsx
1062
1053
  /** Capitalize the first letter for display in headings / sentence-leading copy. */
1063
1054
  const capitalize = (value) => value.charAt(0).toUpperCase() + value.slice(1);
@@ -1441,7 +1432,7 @@ function DetailsSection({ order, googleMapsApiKey }) {
1441
1432
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1442
1433
  className: "text-muted-foreground text-sm",
1443
1434
  children: t("status_label")
1444
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OrderStatusBadge, { status: order.status })]
1435
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FulfillmentStatusBadge, { status: resolveOrderStatus(order.status, order.fulfillment_status) })]
1445
1436
  })
1446
1437
  })
1447
1438
  }),
@@ -1664,4 +1655,4 @@ Object.defineProperty(exports, "ordersScreenPropertySchema", {
1664
1655
  }
1665
1656
  });
1666
1657
 
1667
- //# sourceMappingURL=OrdersScreen-BOxI7p-f.cjs.map
1658
+ //# sourceMappingURL=OrdersScreen-BlNndMxv.cjs.map