@pradip1995/segment-login-template 0.5.14 → 0.5.16

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": "@pradip1995/segment-login-template",
3
- "version": "0.5.14",
3
+ "version": "0.5.16",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -23,11 +23,6 @@
23
23
  "./google-auth-section": "./src/google-auth-section.tsx",
24
24
  "./otp-input": "./src/otp-input.tsx"
25
25
  },
26
- "scripts": {
27
- "typecheck": "tsc --noEmit",
28
- "lint": "tsc --noEmit",
29
- "test": "node --experimental-strip-types --test src/*.test.ts"
30
- },
31
26
  "peerDependencies": {
32
27
  "@pradip1995/commerce-auth": "^4.0.0",
33
28
  "@pradip1995/commerce-core": "^4.0.0",
@@ -40,12 +35,17 @@
40
35
  "@pradip1995/commerce-auth": "^4.0.0",
41
36
  "@pradip1995/commerce-core": "^4.0.0",
42
37
  "@pradip1995/segment-primitives": "^0.4.11",
43
- "@pradip1995/segment-tokens": "^0.3.7"
38
+ "@pradip1995/segment-tokens": "^0.3.14"
44
39
  },
45
40
  "devDependencies": {
46
41
  "@pradip1995/plugin-sdk": "^0.2.0",
47
42
  "@types/react": "^19",
48
43
  "react": "19.0.3",
49
44
  "typescript": "^5.7.2"
45
+ },
46
+ "scripts": {
47
+ "typecheck": "tsc --noEmit",
48
+ "lint": "tsc --noEmit",
49
+ "test": "node --experimental-strip-types --test src/*.test.ts"
50
50
  }
51
- }
51
+ }
@@ -5,6 +5,10 @@ import LocalizedLink from "@pradip1995/segment-primitives/localized-link"
5
5
  import { formatPrice } from "@pradip1995/segment-primitives/format-price"
6
6
  import { logoutGuest } from "@pradip1995/commerce-core/data/guest"
7
7
  import { acct } from "./account-theme"
8
+ import {
9
+ resolveOrderHistoryStatus,
10
+ type OrderHistoryStatusInput,
11
+ } from "./order-history-status"
8
12
 
9
13
  export default function AccountGuestOrders({
10
14
  orders,
@@ -79,7 +83,7 @@ export default function AccountGuestOrders({
79
83
  Order #{order.display_id}
80
84
  </p>
81
85
  <p className="text-xs text-muted mt-1 capitalize">
82
- {order.status?.replace(/_/g, " ")}
86
+ {resolveOrderHistoryStatus(order as OrderHistoryStatusInput).label}
83
87
  </p>
84
88
  </div>
85
89
  <p className="text-sm font-semibold text-brand-accent shrink-0">
@@ -8,6 +8,11 @@ import LocalizedLink from "@pradip1995/segment-primitives/localized-link"
8
8
  import { formatPrice } from "@pradip1995/segment-primitives/format-price"
9
9
  import { presentOrderLines } from "@pradip1995/segment-primitives/order-lines"
10
10
  import { acct } from "./account-theme"
11
+ import {
12
+ resolveOrderHistoryStatus,
13
+ type OrderHistoryStatusInput,
14
+ type OrderHistoryStatusTone,
15
+ } from "./order-history-status"
11
16
 
12
17
  function isCancelled(order: HttpTypes.StoreOrder) {
13
18
  const status = (order.status || "").toLowerCase()
@@ -20,28 +25,26 @@ function canReorder(order: HttpTypes.StoreOrder) {
20
25
  return ["delivered", "partially_delivered"].includes(fulfillment)
21
26
  }
22
27
 
23
- function statusStyle(order: HttpTypes.StoreOrder) {
24
- if (isCancelled(order)) {
28
+ function historyStatus(order: HttpTypes.StoreOrder) {
29
+ return resolveOrderHistoryStatus(order as OrderHistoryStatusInput)
30
+ }
31
+
32
+ function statusStyle(tone: OrderHistoryStatusTone) {
33
+ if (tone === "cancel") {
25
34
  return "bg-[#FFF1F2] text-[#BE123C] border-[#BE123C]/20"
26
35
  }
27
- const s = (order.fulfillment_status || order.status || "").toLowerCase()
28
- if (s === "delivered" || s === "complete" || s === "completed") {
36
+ if (tone === "return") {
37
+ return "bg-[#FFF7ED] text-[#C2410C] border-[#C2410C]/20"
38
+ }
39
+ if (tone === "done") {
29
40
  return "bg-[#E6F4F0] text-[#008A5D] border-[#008A5D]/20"
30
41
  }
31
- if (s.includes("ship")) {
42
+ if (tone === "shipped") {
32
43
  return "bg-[#EBF5FF] text-[#0068D2] border-[#0068D2]/20"
33
44
  }
34
- if (s.includes("fulfill")) {
35
- return "bg-surface-muted text-brand-accent border-brand-accent"
36
- }
37
45
  return "bg-surface-muted text-muted border-cart-border"
38
46
  }
39
47
 
40
- function statusLabel(order: HttpTypes.StoreOrder) {
41
- if (isCancelled(order)) return "Cancelled"
42
- return (order.fulfillment_status || order.status || "").replace(/_/g, " ")
43
- }
44
-
45
48
  export default function AccountOrders({
46
49
  orders,
47
50
  }: {
@@ -66,7 +69,7 @@ export default function AccountOrders({
66
69
  if (!q) return safeOrders
67
70
  return safeOrders.filter((order) => {
68
71
  const id = String(order.display_id ?? order.id)
69
- const status = statusLabel(order)
72
+ const status = historyStatus(order).label
70
73
  return id.includes(q) || status.toLowerCase().includes(q)
71
74
  })
72
75
  }, [safeOrders, query])
@@ -125,7 +128,7 @@ export default function AccountOrders({
125
128
  ) : (
126
129
  <ul className="space-y-4">
127
130
  {filtered.map((order) => {
128
- const label = statusLabel(order)
131
+ const resolved = historyStatus(order)
129
132
  const showReorder = canReorder(order)
130
133
  const presentation = presentOrderLines(order)
131
134
  return (
@@ -141,8 +144,8 @@ export default function AccountOrders({
141
144
  <p className={`font-bold ${acct.heading}`}>
142
145
  Order #{order.display_id}
143
146
  </p>
144
- <span className={`${acct.badge} capitalize ${statusStyle(order)}`}>
145
- {label}
147
+ <span className={`${acct.badge} capitalize ${statusStyle(resolved.tone)}`}>
148
+ {resolved.label}
146
149
  </span>
147
150
  </div>
148
151
  <p className={`text-xs ${acct.muted}`}>
@@ -0,0 +1,180 @@
1
+ import assert from "node:assert/strict"
2
+ import { describe, it } from "node:test"
3
+ import {
4
+ resolveOrderHistoryStatus,
5
+ type OrderHistoryStatus,
6
+ type OrderHistoryStatusInput,
7
+ } from "./order-history-status.ts"
8
+
9
+ const CASES: Array<{
10
+ name: string
11
+ order: OrderHistoryStatusInput
12
+ want: OrderHistoryStatus
13
+ }> = [
14
+ {
15
+ name: "stays delivered when there is no return",
16
+ order: { status: "completed", fulfillment_status: "delivered" },
17
+ want: { label: "Delivered", tone: "done" },
18
+ },
19
+ {
20
+ name: "shows return requested after a return is submitted on a delivered order",
21
+ order: {
22
+ status: "completed",
23
+ fulfillment_status: "delivered",
24
+ returns: [{ status: "requested" }],
25
+ },
26
+ want: { label: "Return Requested", tone: "return" },
27
+ },
28
+ {
29
+ name: "treats a return with no status as requested",
30
+ order: {
31
+ fulfillment_status: "delivered",
32
+ returns: [{ status: null }],
33
+ },
34
+ want: { label: "Return Requested", tone: "return" },
35
+ },
36
+ {
37
+ name: "maps open returns to return approved",
38
+ order: {
39
+ fulfillment_status: "delivered",
40
+ returns: [{ status: "open" }],
41
+ },
42
+ want: { label: "Return Approved", tone: "return" },
43
+ },
44
+ {
45
+ name: "maps approved returns to return approved",
46
+ order: {
47
+ fulfillment_status: "delivered",
48
+ returns: [{ status: "approved" }],
49
+ },
50
+ want: { label: "Return Approved", tone: "return" },
51
+ },
52
+ {
53
+ name: "maps partially received returns to return in progress",
54
+ order: {
55
+ fulfillment_status: "delivered",
56
+ returns: [{ status: "partially_received" }],
57
+ },
58
+ want: { label: "Return In Progress", tone: "return" },
59
+ },
60
+ {
61
+ name: "maps received returns to returned",
62
+ order: {
63
+ status: "completed",
64
+ fulfillment_status: "delivered",
65
+ returns: [{ status: "received" }],
66
+ },
67
+ want: { label: "Returned", tone: "return" },
68
+ },
69
+ {
70
+ name: "maps fulfillment returned when returns are missing from the payload",
71
+ order: { fulfillment_status: "returned" },
72
+ want: { label: "Returned", tone: "return" },
73
+ },
74
+ {
75
+ name: "maps fulfillment partially returned to return in progress",
76
+ order: { fulfillment_status: "partially_returned" },
77
+ want: { label: "Return In Progress", tone: "return" },
78
+ },
79
+ {
80
+ name: "shows refunded when payment is refunded after a return",
81
+ order: {
82
+ fulfillment_status: "returned",
83
+ payment_status: "refunded",
84
+ returns: [{ status: "received" }],
85
+ },
86
+ want: { label: "Refunded", tone: "return" },
87
+ },
88
+ {
89
+ name: "shows refunded for a partial refund",
90
+ order: {
91
+ fulfillment_status: "delivered",
92
+ payment_status: "partially_refunded",
93
+ returns: [{ status: "requested" }],
94
+ },
95
+ want: { label: "Refunded", tone: "return" },
96
+ },
97
+ {
98
+ name: "ignores canceled returns and keeps delivered",
99
+ order: {
100
+ fulfillment_status: "delivered",
101
+ returns: [{ status: "canceled" }, { status: "cancelled" }],
102
+ },
103
+ want: { label: "Delivered", tone: "done" },
104
+ },
105
+ {
106
+ name: "ignores exchange and claim returns",
107
+ order: {
108
+ fulfillment_status: "delivered",
109
+ returns: [
110
+ { status: "requested", exchange_id: "exch_1" },
111
+ { status: "requested", claim_id: "claim_1" },
112
+ ],
113
+ },
114
+ want: { label: "Delivered", tone: "done" },
115
+ },
116
+ {
117
+ name: "uses the furthest return lifecycle when several returns exist",
118
+ order: {
119
+ fulfillment_status: "delivered",
120
+ returns: [{ status: "requested" }, { status: "received" }],
121
+ },
122
+ want: { label: "Returned", tone: "return" },
123
+ },
124
+ {
125
+ name: "cancelled order wins over a return",
126
+ order: {
127
+ status: "canceled",
128
+ fulfillment_status: "delivered",
129
+ returns: [{ status: "requested" }],
130
+ },
131
+ want: { label: "Cancelled", tone: "cancel" },
132
+ },
133
+ {
134
+ name: "shows shipped when not delivered and no return",
135
+ order: { fulfillment_status: "shipped" },
136
+ want: { label: "Shipped", tone: "shipped" },
137
+ },
138
+ {
139
+ name: "shows order confirmed for fulfilled orders",
140
+ order: { fulfillment_status: "fulfilled" },
141
+ want: { label: "Order Confirmed", tone: "done" },
142
+ },
143
+ {
144
+ name: "shows return requested from item return quantities when returns are omitted",
145
+ order: {
146
+ fulfillment_status: "delivered",
147
+ items: [{ detail: { return_requested_quantity: 1 } }],
148
+ },
149
+ want: { label: "Return Requested", tone: "return" },
150
+ },
151
+ {
152
+ name: "shows returned from received item quantities",
153
+ order: {
154
+ fulfillment_status: "delivered",
155
+ items: [{ detail: { return_requested_quantity: 1, return_received_quantity: 1 } }],
156
+ },
157
+ want: { label: "Returned", tone: "return" },
158
+ },
159
+ {
160
+ name: "shows return in progress when only some quantity is received",
161
+ order: {
162
+ fulfillment_status: "delivered",
163
+ items: [{ detail: { return_requested_quantity: 2, return_received_quantity: 1 } }],
164
+ },
165
+ want: { label: "Return In Progress", tone: "return" },
166
+ },
167
+ {
168
+ name: "shows processing as the default",
169
+ order: { status: "pending" },
170
+ want: { label: "Processing", tone: "processing" },
171
+ },
172
+ ]
173
+
174
+ describe("resolveOrderHistoryStatus", () => {
175
+ for (const row of CASES) {
176
+ it(row.name, () => {
177
+ assert.deepEqual(resolveOrderHistoryStatus(row.order), row.want)
178
+ })
179
+ }
180
+ })
@@ -0,0 +1,170 @@
1
+ export type OrderHistoryStatusTone =
2
+ | "cancel"
3
+ | "shipped"
4
+ | "done"
5
+ | "return"
6
+ | "processing"
7
+
8
+ export type OrderHistoryStatus = {
9
+ label: string
10
+ tone: OrderHistoryStatusTone
11
+ }
12
+
13
+ export type OrderHistoryReturnLike = {
14
+ status?: string | null
15
+ exchange_id?: string | null
16
+ claim_id?: string | null
17
+ }
18
+
19
+ export type OrderHistoryStatusInput = {
20
+ status?: string | null
21
+ fulfillment_status?: string | null
22
+ payment_status?: string | null
23
+ returns?: OrderHistoryReturnLike[] | null
24
+ items?: Array<{
25
+ detail?: {
26
+ return_requested_quantity?: number | null
27
+ return_received_quantity?: number | null
28
+ } | null
29
+ }> | null
30
+ }
31
+
32
+ type ReturnLifecycle =
33
+ | "requested"
34
+ | "approved"
35
+ | "in_progress"
36
+ | "returned"
37
+ | "refunded"
38
+
39
+ const CLOSED_RETURN = new Set(["canceled", "cancelled", "rejected"])
40
+
41
+ const RETURN_RANK: Record<ReturnLifecycle, number> = {
42
+ requested: 1,
43
+ approved: 2,
44
+ in_progress: 3,
45
+ returned: 4,
46
+ refunded: 5,
47
+ }
48
+
49
+ const RETURN_LABEL: Record<ReturnLifecycle, string> = {
50
+ requested: "Return Requested",
51
+ approved: "Return Approved",
52
+ in_progress: "Return In Progress",
53
+ returned: "Returned",
54
+ refunded: "Refunded",
55
+ }
56
+
57
+ function normalize(value?: string | null) {
58
+ return (value || "").toLowerCase().trim()
59
+ }
60
+
61
+ function isStandaloneReturn(ret: OrderHistoryReturnLike) {
62
+ return !ret.exchange_id && !ret.claim_id
63
+ }
64
+
65
+ function lifecycleFromReturnStatus(status: string): ReturnLifecycle | null {
66
+ switch (status) {
67
+ case "requested":
68
+ return "requested"
69
+ case "open":
70
+ case "approved":
71
+ return "approved"
72
+ case "partially_received":
73
+ case "in_progress":
74
+ return "in_progress"
75
+ case "received":
76
+ case "returned":
77
+ case "complete":
78
+ case "completed":
79
+ return "returned"
80
+ default:
81
+ return null
82
+ }
83
+ }
84
+
85
+ function pickHigher(
86
+ current: ReturnLifecycle | null,
87
+ next: ReturnLifecycle
88
+ ): ReturnLifecycle {
89
+ if (!current) return next
90
+ return RETURN_RANK[next] > RETURN_RANK[current] ? next : current
91
+ }
92
+
93
+ function returnLifecycle(order: OrderHistoryStatusInput): ReturnLifecycle | null {
94
+ const payment = normalize(order.payment_status)
95
+ let best: ReturnLifecycle | null = null
96
+
97
+ if (payment === "refunded" || payment === "partially_refunded") {
98
+ best = "refunded"
99
+ }
100
+
101
+ for (const ret of order.returns || []) {
102
+ if (!isStandaloneReturn(ret)) continue
103
+ const status = normalize(ret.status)
104
+ if (CLOSED_RETURN.has(status)) continue
105
+ const life = status
106
+ ? lifecycleFromReturnStatus(status) ?? "requested"
107
+ : "requested"
108
+ best = pickHigher(best, life)
109
+ }
110
+
111
+ let requestedQty = 0
112
+ let receivedQty = 0
113
+ for (const item of order.items || []) {
114
+ requestedQty += Number(item.detail?.return_requested_quantity) || 0
115
+ receivedQty += Number(item.detail?.return_received_quantity) || 0
116
+ }
117
+ if (receivedQty > 0 && requestedQty > receivedQty) {
118
+ best = pickHigher(best, "in_progress")
119
+ } else if (receivedQty > 0) {
120
+ best = pickHigher(best, "returned")
121
+ } else if (requestedQty > 0) {
122
+ best = pickHigher(best, "requested")
123
+ }
124
+
125
+ const fulfillment = normalize(order.fulfillment_status)
126
+ if (fulfillment === "returned") {
127
+ best = pickHigher(best, "returned")
128
+ } else if (fulfillment === "partially_returned") {
129
+ best = pickHigher(best, "in_progress")
130
+ }
131
+
132
+ return best
133
+ }
134
+
135
+ export function resolveOrderHistoryStatus(
136
+ order: OrderHistoryStatusInput
137
+ ): OrderHistoryStatus {
138
+ const orderStatus = normalize(order.status)
139
+ if (orderStatus === "canceled" || orderStatus === "cancelled") {
140
+ return { label: "Cancelled", tone: "cancel" }
141
+ }
142
+
143
+ const life = returnLifecycle(order)
144
+ if (life) {
145
+ return { label: RETURN_LABEL[life], tone: "return" }
146
+ }
147
+
148
+ const fulfillment = normalize(order.fulfillment_status)
149
+ const combined = fulfillment || orderStatus
150
+
151
+ if (combined.includes("cancel")) {
152
+ return { label: "Cancelled", tone: "cancel" }
153
+ }
154
+ if (combined.includes("ship")) {
155
+ return { label: "Shipped", tone: "shipped" }
156
+ }
157
+ if (
158
+ combined === "delivered" ||
159
+ combined === "complete" ||
160
+ combined === "completed" ||
161
+ combined.includes("fulfill")
162
+ ) {
163
+ return {
164
+ label: combined === "delivered" ? "Delivered" : "Order Confirmed",
165
+ tone: "done",
166
+ }
167
+ }
168
+
169
+ return { label: "Processing", tone: "processing" }
170
+ }