@mohasinac/appkit 4.41.12 → 4.41.14

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.
@@ -8,7 +8,7 @@ import { assertAuctionActive, assertBidAmount, assertNotAuctionOwner, shouldAuto
8
8
  import { ValidationError } from "../../../shared/errors/index";
9
9
  export async function placeBidAction(input) {
10
10
  return wrapAction(async () => {
11
- const user = await requireRoleUser(["buyer", "seller", "admin"]);
11
+ const user = await requireRoleUser(["user", "buyer", "seller", "admin"]);
12
12
  const parsed = placeBidActionSchema.safeParse(input);
13
13
  if (!parsed.success)
14
14
  throw new ValidationError(parsed.error.issues[0]?.message ?? "Invalid bid input");
@@ -7,7 +7,7 @@ import { upsertCartItem, mergeGuestItems } from "./service";
7
7
  import { ValidationError } from "../../../shared/errors/index";
8
8
  export async function addToCartAction(input) {
9
9
  return wrapAction(async () => {
10
- const user = await requireRoleUser(["buyer", "seller", "admin"]);
10
+ const user = await requireRoleUser(["user", "buyer", "seller", "admin"]);
11
11
  const parsed = addToCartSchema.safeParse(input);
12
12
  if (!parsed.success)
13
13
  throw new ValidationError(parsed.error.issues[0]?.message ?? "Invalid cart input");
@@ -17,7 +17,7 @@ export async function addToCartAction(input) {
17
17
  }
18
18
  export async function removeFromCartAction(input) {
19
19
  return wrapAction(async () => {
20
- const user = await requireRoleUser(["buyer", "seller", "admin"]);
20
+ const user = await requireRoleUser(["user", "buyer", "seller", "admin"]);
21
21
  const parsed = removeFromCartSchema.safeParse(input);
22
22
  if (!parsed.success)
23
23
  throw new ValidationError(parsed.error.issues[0]?.message ?? "Invalid input");
@@ -27,14 +27,14 @@ export async function removeFromCartAction(input) {
27
27
  }
28
28
  export async function clearCartAction() {
29
29
  return wrapAction(async () => {
30
- const user = await requireRoleUser(["buyer", "seller", "admin"]);
30
+ const user = await requireRoleUser(["user", "buyer", "seller", "admin"]);
31
31
  await cartRepository.clearCart(user.uid);
32
32
  return { success: true };
33
33
  });
34
34
  }
35
35
  export async function mergeGuestCartAction(input) {
36
36
  return wrapAction(async () => {
37
- const user = await requireRoleUser(["buyer", "seller", "admin"]);
37
+ const user = await requireRoleUser(["user", "buyer", "seller", "admin"]);
38
38
  const parsed = mergeGuestCartSchema.safeParse(input);
39
39
  if (!parsed.success)
40
40
  throw new ValidationError(parsed.error.issues[0]?.message ?? "Invalid cart data");
@@ -36,7 +36,7 @@ export async function updateEventAction(eventId, input) {
36
36
  }
37
37
  export async function registerForEventAction(input) {
38
38
  return wrapAction(async () => {
39
- const user = await requireRoleUser(["buyer", "seller", "admin"]);
39
+ const user = await requireRoleUser(["user", "buyer", "seller", "admin"]);
40
40
  const parsed = registerForEventSchema.safeParse(input);
41
41
  if (!parsed.success)
42
42
  throw new ValidationError(parsed.error.issues[0]?.message ?? "Invalid registration input");
@@ -72,7 +72,7 @@ export async function registerForEventAction(input) {
72
72
  }
73
73
  export async function cancelEventRegistrationAction(entryId) {
74
74
  return wrapAction(async () => {
75
- const user = await requireRoleUser(["buyer", "seller", "admin"]);
75
+ const user = await requireRoleUser(["user", "buyer", "seller", "admin"]);
76
76
  const entry = await eventEntryRepository.findById(entryId);
77
77
  if (!entry || entry.userId !== user.uid) {
78
78
  throw new ValidationError("Registration not found or does not belong to you");
@@ -4,7 +4,7 @@ import { requireRoleUser } from "../../../../providers/auth-firebase/helpers";
4
4
  import { historyRepository, } from "../../../../repositories";
5
5
  export async function trackProductViewAction(productId, productType, productSnapshot) {
6
6
  return wrapAction(async () => {
7
- const user = await requireRoleUser(["buyer", "seller", "admin"]);
7
+ const user = await requireRoleUser(["user", "buyer", "seller", "admin"]);
8
8
  await historyRepository.track(user.uid, {
9
9
  productId,
10
10
  productType,
@@ -14,7 +14,7 @@ export async function trackProductViewAction(productId, productType, productSnap
14
14
  }
15
15
  export async function mergeGuestHistoryAction(guestItems) {
16
16
  return wrapAction(async () => {
17
- const user = await requireRoleUser(["buyer", "seller", "admin"]);
17
+ const user = await requireRoleUser(["user", "buyer", "seller", "admin"]);
18
18
  return historyRepository.merge(user.uid, guestItems);
19
19
  });
20
20
  }
@@ -28,7 +28,7 @@ function isDrawWindowOpen(config) {
28
28
  export async function submitLotteryPullAction(input) {
29
29
  return wrapAction(async () => {
30
30
  // Auth — must be logged in
31
- const user = await requireRoleUser(["buyer", "seller", "admin", "moderator", "employee"]);
31
+ const user = await requireRoleUser(["user", "buyer", "seller", "admin", "moderator", "employee"]);
32
32
  const parsed = submitLotteryPullSchema.safeParse(input);
33
33
  if (!parsed.success) {
34
34
  throw new ValidationError(parsed.error.issues[0]?.message ?? "Invalid lottery pull input");
@@ -18,7 +18,7 @@ import { normalizeError } from "../../../../errors/normalize";
18
18
  import { serverLogger } from "../../../../monitoring";
19
19
  export async function createOrderAction(input) {
20
20
  return wrapAction(async () => {
21
- const user = await requireRoleUser(["buyer", "seller", "admin"]);
21
+ const user = await requireRoleUser(["user", "buyer", "seller", "admin"]);
22
22
  const parsed = createOrderSchema.safeParse(input);
23
23
  if (!parsed.success)
24
24
  throw new ValidationError(parsed.error.issues[0]?.message ?? "Invalid order input");
@@ -31,7 +31,7 @@ export async function createOrderAction(input) {
31
31
  }
32
32
  export async function cancelOrderAction(input) {
33
33
  return wrapAction(async () => {
34
- const user = await requireRoleUser(["buyer", "seller", "admin"]);
34
+ const user = await requireRoleUser(["user", "buyer", "seller", "admin"]);
35
35
  const parsed = cancelOrderSchema.safeParse(input);
36
36
  if (!parsed.success)
37
37
  throw new ValidationError(parsed.error.issues[0]?.message ?? "Invalid input");
@@ -50,7 +50,7 @@ export async function cancelOrderAction(input) {
50
50
  */
51
51
  export async function requestReturnAction(input) {
52
52
  return wrapAction(async () => {
53
- const user = await requireRoleUser(["buyer", "seller", "admin"]);
53
+ const user = await requireRoleUser(["user", "buyer", "seller", "admin"]);
54
54
  const parsed = returnRequestSchema.safeParse(input);
55
55
  if (!parsed.success)
56
56
  throw new ValidationError(parsed.error.issues[0]?.message ?? "Invalid input");
@@ -161,7 +161,7 @@ function notifyAdminsOfPaymentProof(order) {
161
161
  }
162
162
  export async function attachPaymentProofAction(orderId, proof) {
163
163
  return wrapAction(async () => {
164
- const user = await requireRoleUser(["buyer", "seller", "admin"]);
164
+ const user = await requireRoleUser(["user", "buyer", "seller", "admin"]);
165
165
  const order = await orderRepository.findById(orderId);
166
166
  if (!order)
167
167
  throw new OrderNotFoundError(orderId);
@@ -350,7 +350,7 @@ export async function adminRejectPaymentAsFraudAction(orderId, note) {
350
350
  */
351
351
  export async function raiseOrderDisputeAction(orderId, reason) {
352
352
  return wrapAction(async () => {
353
- const user = await requireRoleUser(["buyer", "seller", "admin"]);
353
+ const user = await requireRoleUser(["user", "buyer", "seller", "admin"]);
354
354
  if (!reason.trim())
355
355
  throw new ValidationError(REASON_REQUIRED_MSG);
356
356
  const order = await orderRepository.findById(orderId);
@@ -7,7 +7,7 @@ import { assertPreOrderAvailable, computeDeposit } from "./service";
7
7
  import { ValidationError } from "../../../shared/errors/index";
8
8
  export async function reservePreOrderAction(input) {
9
9
  return wrapAction(async () => {
10
- const user = await requireRoleUser(["buyer", "seller", "admin"]);
10
+ const user = await requireRoleUser(["user", "buyer", "seller", "admin"]);
11
11
  const parsed = reservePreOrderSchema.safeParse(input);
12
12
  if (!parsed.success)
13
13
  throw new ValidationError(parsed.error.issues[0]?.message ?? "Invalid reservation input");
@@ -9,7 +9,7 @@ import { ValidationError } from "../../../shared/errors/index";
9
9
  const ERR_INVALID_COUPON_INPUT = "Invalid coupon input";
10
10
  export async function applyCouponAction(input) {
11
11
  return wrapAction(async () => {
12
- const user = await requireRoleUser(["buyer", "seller", "admin"]);
12
+ const user = await requireRoleUser(["user", "buyer", "seller", "admin"]);
13
13
  const parsed = applyCouponSchema.safeParse(input);
14
14
  if (!parsed.success)
15
15
  throw new ValidationError(parsed.error.issues[0]?.message ?? ERR_INVALID_COUPON_INPUT);
@@ -8,7 +8,7 @@ import { createReviewSchema, replyToReviewSchema, deleteReviewSchema, } from "..
8
8
  import { assertNotDuplicateReview, assertReviewOwnership, getReviewOrThrow, } from "./service";
9
9
  export async function createReviewAction(input) {
10
10
  return wrapAction(async () => {
11
- const user = await requireRoleUser(["buyer", "seller", "admin"]);
11
+ const user = await requireRoleUser(["user", "buyer", "seller", "admin"]);
12
12
  const parsed = createReviewSchema.safeParse(input);
13
13
  if (!parsed.success) {
14
14
  throw new ValidationError(parsed.error.issues[0]?.message ?? "Invalid input");
@@ -54,7 +54,7 @@ export async function replyToReviewAction(input) {
54
54
  }
55
55
  export async function deleteReviewAction(input) {
56
56
  return wrapAction(async () => {
57
- const user = await requireRoleUser(["buyer", "seller", "admin"]);
57
+ const user = await requireRoleUser(["user", "buyer", "seller", "admin"]);
58
58
  const parsed = deleteReviewSchema.safeParse(input);
59
59
  if (!parsed.success) {
60
60
  throw new ValidationError(parsed.error.issues[0]?.message ?? "Invalid input");
@@ -71,7 +71,7 @@ export async function deleteReviewAction(input) {
71
71
  }
72
72
  export async function markReviewHelpfulAction(reviewId) {
73
73
  return wrapAction(async () => {
74
- const user = await requireRoleUser(["buyer", "seller", "admin"]);
74
+ const user = await requireRoleUser(["user", "buyer", "seller", "admin"]);
75
75
  await getReviewOrThrow(reviewId);
76
76
  // Keyed on the voter so a repeated call is a no-op rather than another +1.
77
77
  return reviewRepository.voteHelpful(reviewId, user.uid);
@@ -7,7 +7,7 @@ import { WISHLIST_MAX } from "../../../shared/features/wishlist/config";
7
7
  import { WishlistCapError } from "../../../shared/features/wishlist/errors";
8
8
  export async function addToWishlistAction(productId, extras) {
9
9
  return wrapAction(async () => {
10
- const user = await requireRoleUser(["buyer", "seller", "admin"]);
10
+ const user = await requireRoleUser(["user", "buyer", "seller", "admin"]);
11
11
  try {
12
12
  return await wishlistRepository.addItem(user.uid, productId, extras);
13
13
  }
@@ -21,13 +21,13 @@ export async function addToWishlistAction(productId, extras) {
21
21
  }
22
22
  export async function removeFromWishlistAction(productId) {
23
23
  return wrapAction(async () => {
24
- const user = await requireRoleUser(["buyer", "seller", "admin"]);
24
+ const user = await requireRoleUser(["user", "buyer", "seller", "admin"]);
25
25
  await wishlistRepository.removeItem(user.uid, productId);
26
26
  });
27
27
  }
28
28
  export async function mergeGuestWishlistAction(guestItems) {
29
29
  return wrapAction(async () => {
30
- const user = await requireRoleUser(["buyer", "seller", "admin"]);
30
+ const user = await requireRoleUser(["user", "buyer", "seller", "admin"]);
31
31
  const merged = [];
32
32
  for (const item of guestItems) {
33
33
  try {
@@ -141,9 +141,35 @@ export function AdminOrdersView({ children, ...props }) {
141
141
  ? `${title}${extraCount > 0 ? ` +${extraCount} more` : ""}`
142
142
  : `Order ${orderId}`,
143
143
  secondary: [
144
- toStringValue(item.buyerName ?? item.customerName, "Unknown buyer"),
145
- toCurrency(item.totalAmount ?? item.total ?? item.amount),
146
- orderId.slice(0, 14),
144
+ /*
145
+ * 🛑 `userName` and `totalPrice` FIRST — they are the document's
146
+ * real field names (OrderDocument, and the live response of
147
+ * GET /api/admin/orders).
148
+ *
149
+ * This read `buyerName ?? customerName` and
150
+ * `totalAmount ?? total ?? amount`: five names, not one of which
151
+ * the order document has ever carried. So every row rendered
152
+ * "Unknown buyer · -", on all 25 rows, for as long as the view has
153
+ * existed — and the `??` chains made it look like considered
154
+ * defensive coding rather than a guess. A fallback spelled three
155
+ * ways is the tell that nobody checked which one was real.
156
+ *
157
+ * The aliases are GONE rather than demoted to fallbacks: nothing
158
+ * declares them (the client `Order` type has no such fields) and
159
+ * nothing emits them (no adapter renames to them). Keeping a
160
+ * fallback with no producer only preserves the ambiguity that
161
+ * hid the bug.
162
+ */
163
+ toStringValue(item.userName, "Unknown buyer"),
164
+ toCurrency(item.totalPrice),
165
+ /*
166
+ * Not `.slice(0, 14)` — that cut mid-date, so
167
+ * `order-1-20260822-aucwon` and `order-1-20260821-prizedr` both
168
+ * rendered as "order-1-202608". A truncation that destroys
169
+ * uniqueness is worse than a long string, because the id is the
170
+ * one thing on the row an admin searches by.
171
+ */
172
+ orderId,
147
173
  paymentReviewLabel(item),
148
174
  ]
149
175
  .filter(Boolean)
@@ -366,13 +366,37 @@ export function SellerOrdersView({ orderDetailApiBase = SELLER_ENDPOINTS.ORDERS,
366
366
  const loc = item.physicalLocation;
367
367
  return {
368
368
  id: toStringValue(item.id, `order-${index}`),
369
- primary: `Order ${toStringValue(item.id, "-").slice(0, 14)}`,
370
- secondary: toStringValue(item.buyerName ?? item.buyerDisplayName, "Unknown buyer"),
369
+ /*
370
+ * 🛑 The row says WHAT WAS SOLD, not the id repeated.
371
+ *
372
+ * This was `Order ${id.slice(0, 14)}` — a raw id, truncated
373
+ * mid-date, so two different orders rendered identically. The title
374
+ * was available the whole time: `items[0].productTitle` is
375
+ * denormalised onto every order precisely so a list never needs a
376
+ * second fetch (Root Cause #52), and it is already read three lines
377
+ * below as `itemTitle`.
378
+ */
379
+ primary: (() => {
380
+ const title = typeof firstItem.productTitle === "string" ? firstItem.productTitle : "";
381
+ if (!title)
382
+ return `Order ${toStringValue(item.id, "-")}`;
383
+ const extra = itemsArr.length - 1;
384
+ return extra > 0 ? `${title} +${extra} more` : title;
385
+ })(),
386
+ /*
387
+ * `userName` / `totalPrice` are the document's real field names.
388
+ * `buyerName`/`buyerDisplayName`/`totalAmount`/`total` are four
389
+ * spellings the order document has never carried, so every seller's
390
+ * order list showed "Unknown buyer" and ₹0 — see AdminOrdersView for
391
+ * the full writeup. They are removed rather than demoted: nothing
392
+ * declares them and nothing emits them.
393
+ */
394
+ secondary: toStringValue(item.userName, "Unknown buyer"),
371
395
  status: toStringValue(item.status, "PENDING"),
372
396
  updatedAt: toRelativeDate(item.updatedAt ?? item.orderDate ?? item.createdAt),
373
397
  itemCount: itemsArr.length,
374
- totalAmount: Number(item.totalAmount ?? item.total ?? 0),
375
- buyerName: toStringValue(item.buyerName ?? item.buyerDisplayName, "Unknown buyer"),
398
+ totalAmount: Number(item.totalPrice ?? 0),
399
+ buyerName: toStringValue(item.userName, "Unknown buyer"),
376
400
  itemImage: typeof firstItem.image === "string" ? firstItem.image : undefined,
377
401
  itemTitle: typeof firstItem.productTitle === "string" ? firstItem.productTitle : undefined,
378
402
  physicalLocation: loc && typeof loc.zone === "string"
@@ -73,15 +73,29 @@ export const authored = {
73
73
  roles: ["buyer"],
74
74
  startPage: "/user/orders",
75
75
  steps: [
76
- "Sign in as vivaan.kapoor@gmail.com / TempPass123! — this persona owns the seeded DELIVERED orders, and a return can only be requested on one.",
77
- "Open /user/orders and open order-1-20251107-11joon, which is delivered.",
76
+ /*
77
+ * 🛑 rehan, NOT vivaan — and the order changed with it.
78
+ *
79
+ * This said vivaan.kapoor@gmail.com (user-seto-kaiba), who does own
80
+ * order-1-20251107-11joon. But TWO other cases in this same batch use
81
+ * that order as a FOREIGN one, and a batch browses as exactly one
82
+ * identity — so the page could not be worked as authored: the same
83
+ * order had to be both owned and not-owned by the signed-in user.
84
+ *
85
+ * rehan.sheikh@gmail.com (user-yugi-muto) is the harness's `main`
86
+ * identity, and order-1-20260818-stdctx is one of its delivered orders.
87
+ * That makes the whole batch coherent under one session and leaves the
88
+ * access-control cases genuinely foreign.
89
+ */
90
+ "Sign in as rehan.sheikh@gmail.com / TempPass123! — this persona owns the order below, and a return can only be requested on a delivered one.",
91
+ "Open /user/orders and open order-1-20260818-stdctx, which is delivered.",
78
92
  "Use the Return action.",
79
93
  "Give the reason: item not as described.",
80
94
  "Submit the request.",
81
95
  "Reload the order.",
82
96
  "Open /user/returns and look for it.",
83
97
  ],
84
- inputs: { orderId: "order-1-20251107-11joon", reason: "item not as described" },
98
+ inputs: { orderId: "order-1-20260818-stdctx", reason: "item not as described" },
85
99
  expectedBehaviour: "A return request moves the order into Return Requested and appears on the returns surface. That status is deliberately an ACTIVE one — it is the seller's next action item, not a closed state — so the order must not disappear from the active list.",
86
100
  expectedUiState: "The order's status reads Return Requested, and the same order is listed under /user/returns.",
87
101
  expectedData: { statusAfterReturn: "return_requested" },
@@ -545,7 +545,25 @@ const acquisitionOrders = [
545
545
  shippingCarrier: "Delhivery",
546
546
  orderDate: daysAgo(12),
547
547
  shippingDate: daysAgo(9),
548
- deliveryDate: daysAgo(6),
548
+ /*
549
+ * 🛑 TWO DAYS, NOT SIX — THIS IS THE RETURN-WINDOW FIXTURE.
550
+ *
551
+ * A return can only be requested on a DELIVERED order inside the 7-day
552
+ * window, and you cannot reach "delivered" by buying in a test: it needs
553
+ * seller action. So the return flow is testable only if a seeded delivered
554
+ * order sits inside the window — and until now none did for the harness's
555
+ * own identity. Every candidate was 6, 7, 8, 20, 29 days old, so
556
+ * /user/orders/{id}/return answered 'The 7-day return window for this
557
+ * order has closed.' and the case could never be worked.
558
+ *
559
+ * Six days would have been inside the window today and OUTSIDE it
560
+ * tomorrow — a fixture that passes on Monday and fails on Tuesday is worse
561
+ * than one that always fails, because the failure looks like a regression.
562
+ * Two days leaves five days of margin.
563
+ *
564
+ * Do not raise this without checking RETURN_WINDOW_DAYS.
565
+ */
566
+ deliveryDate: daysAgo(2),
549
567
  createdAt: daysAgo(12),
550
568
  updatedAt: daysAgo(6),
551
569
  sourceContext: { path: "standard", listPrice: 999 },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mohasinac/appkit",
3
- "version": "4.41.12",
3
+ "version": "4.41.14",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"