@mohasinac/appkit 4.42.3 → 4.42.5

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/dist/client.d.ts CHANGED
@@ -229,6 +229,15 @@ export { PERMISSION_GROUPS, PERMISSION_DOMAINS, getPermissionsForDomain, formatP
229
229
  export { Details, Summary } from "./ui/components/Details";
230
230
  export { ADMIN_ENDPOINTS } from "./constants/index";
231
231
  export { SELLER_ENDPOINTS } from "./constants/index";
232
+ /**
233
+ * The buyer-portal endpoint map. Its admin and seller siblings were already
234
+ * here; this one was reachable only from `index.ts`, so a Client Component
235
+ * needing a /api/user/* URL had to either hardcode the path (blocked by
236
+ * audit-hardcoded-api-routes) or import the bare entry (the firebase-admin
237
+ * client-bundle leak audit-client-server-only-leak blocks). Added for
238
+ * BidDetailPageClient — checklist-page-wiring-detail-pages-detail-page-matches-list-modal.
239
+ */
240
+ export { ACCOUNT_ENDPOINTS } from "./constants/index";
232
241
  export { TourProvider, useTour } from "./_internal/client/features/tour/TourProvider";
233
242
  export type { TourContextValue, TourRole } from "./_internal/client/features/tour/TourProvider";
234
243
  export { apiClient, ApiClientError } from "./http/index";
package/dist/client.js CHANGED
@@ -280,6 +280,15 @@ export { PERMISSION_GROUPS, PERMISSION_DOMAINS, getPermissionsForDomain, formatP
280
280
  export { Details, Summary } from "./ui/components/Details";
281
281
  export { ADMIN_ENDPOINTS } from "./constants/index";
282
282
  export { SELLER_ENDPOINTS } from "./constants/index";
283
+ /**
284
+ * The buyer-portal endpoint map. Its admin and seller siblings were already
285
+ * here; this one was reachable only from `index.ts`, so a Client Component
286
+ * needing a /api/user/* URL had to either hardcode the path (blocked by
287
+ * audit-hardcoded-api-routes) or import the bare entry (the firebase-admin
288
+ * client-bundle leak audit-client-server-only-leak blocks). Added for
289
+ * BidDetailPageClient — checklist-page-wiring-detail-pages-detail-page-matches-list-modal.
290
+ */
291
+ export { ACCOUNT_ENDPOINTS } from "./constants/index";
283
292
  // P-16 — Tour system (driver.js-backed onboarding walkthrough).
284
293
  export { TourProvider, useTour } from "./_internal/client/features/tour/TourProvider";
285
294
  export { apiClient, ApiClientError } from "./http/index";
@@ -13,8 +13,8 @@ export declare const bundleFormSchema: z.ZodEffects<z.ZodObject<{
13
13
  priceRupees: z.ZodEffects<z.ZodString, string, string>;
14
14
  description: z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodLiteral<"">]>;
15
15
  ruleType: z.ZodEnum<["static", "dynamic"]>;
16
- productIds: z.ZodArray<z.ZodString, "many">;
17
- dynamicRule: z.ZodObject<{
16
+ productIds: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
17
+ dynamicRule: z.ZodOptional<z.ZodObject<{
18
18
  type: z.ZodLiteral<"dynamic">;
19
19
  filter: z.ZodObject<{
20
20
  categorySlug: z.ZodOptional<z.ZodString>;
@@ -54,17 +54,20 @@ export declare const bundleFormSchema: z.ZodEffects<z.ZodObject<{
54
54
  };
55
55
  limit: number;
56
56
  orderBy?: "price-asc" | "price-desc" | "createdAt-desc" | undefined;
57
- }>;
57
+ }>>;
58
58
  coverImage: z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodLiteral<"">]>;
59
59
  brandSlug: z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodLiteral<"">]>;
60
60
  isActive: z.ZodBoolean;
61
61
  }, "strip", z.ZodTypeAny, {
62
62
  name: string;
63
63
  isActive: boolean;
64
- productIds: string[];
65
64
  priceRupees: string;
66
65
  ruleType: "static" | "dynamic";
67
- dynamicRule: {
66
+ brandSlug?: string | undefined;
67
+ description?: string | undefined;
68
+ coverImage?: string | undefined;
69
+ productIds?: string[] | undefined;
70
+ dynamicRule?: {
68
71
  type: "dynamic";
69
72
  filter: {
70
73
  listingType?: "standard" | "pre-order" | "prize-draw" | undefined;
@@ -74,17 +77,17 @@ export declare const bundleFormSchema: z.ZodEffects<z.ZodObject<{
74
77
  };
75
78
  limit: number;
76
79
  orderBy?: "price-asc" | "price-desc" | "createdAt-desc" | undefined;
77
- };
78
- brandSlug?: string | undefined;
79
- description?: string | undefined;
80
- coverImage?: string | undefined;
80
+ } | undefined;
81
81
  }, {
82
82
  name: string;
83
83
  isActive: boolean;
84
- productIds: string[];
85
84
  priceRupees: string;
86
85
  ruleType: "static" | "dynamic";
87
- dynamicRule: {
86
+ brandSlug?: string | undefined;
87
+ description?: string | undefined;
88
+ coverImage?: string | undefined;
89
+ productIds?: string[] | undefined;
90
+ dynamicRule?: {
88
91
  type: "dynamic";
89
92
  filter: {
90
93
  listingType?: "standard" | "pre-order" | "prize-draw" | undefined;
@@ -94,17 +97,17 @@ export declare const bundleFormSchema: z.ZodEffects<z.ZodObject<{
94
97
  };
95
98
  limit: number;
96
99
  orderBy?: "price-asc" | "price-desc" | "createdAt-desc" | undefined;
97
- };
98
- brandSlug?: string | undefined;
99
- description?: string | undefined;
100
- coverImage?: string | undefined;
100
+ } | undefined;
101
101
  }>, {
102
102
  name: string;
103
103
  isActive: boolean;
104
- productIds: string[];
105
104
  priceRupees: string;
106
105
  ruleType: "static" | "dynamic";
107
- dynamicRule: {
106
+ brandSlug?: string | undefined;
107
+ description?: string | undefined;
108
+ coverImage?: string | undefined;
109
+ productIds?: string[] | undefined;
110
+ dynamicRule?: {
108
111
  type: "dynamic";
109
112
  filter: {
110
113
  listingType?: "standard" | "pre-order" | "prize-draw" | undefined;
@@ -114,17 +117,17 @@ export declare const bundleFormSchema: z.ZodEffects<z.ZodObject<{
114
117
  };
115
118
  limit: number;
116
119
  orderBy?: "price-asc" | "price-desc" | "createdAt-desc" | undefined;
117
- };
118
- brandSlug?: string | undefined;
119
- description?: string | undefined;
120
- coverImage?: string | undefined;
120
+ } | undefined;
121
121
  }, {
122
122
  name: string;
123
123
  isActive: boolean;
124
- productIds: string[];
125
124
  priceRupees: string;
126
125
  ruleType: "static" | "dynamic";
127
- dynamicRule: {
126
+ brandSlug?: string | undefined;
127
+ description?: string | undefined;
128
+ coverImage?: string | undefined;
129
+ productIds?: string[] | undefined;
130
+ dynamicRule?: {
128
131
  type: "dynamic";
129
132
  filter: {
130
133
  listingType?: "standard" | "pre-order" | "prize-draw" | undefined;
@@ -134,9 +137,6 @@ export declare const bundleFormSchema: z.ZodEffects<z.ZodObject<{
134
137
  };
135
138
  limit: number;
136
139
  orderBy?: "price-asc" | "price-desc" | "createdAt-desc" | undefined;
137
- };
138
- brandSlug?: string | undefined;
139
- description?: string | undefined;
140
- coverImage?: string | undefined;
140
+ } | undefined;
141
141
  }>;
142
142
  export type BundleFormValues = z.infer<typeof bundleFormSchema>;
@@ -95,12 +95,12 @@ export const bundleFormSchema = z
95
95
  * because an empty picker on a DYNAMIC bundle is correct — the resolver
96
96
  * fills the mirror. A flat `.min(3)` would block every dynamic save.
97
97
  */
98
- productIds: annotate(z.array(z.string()), {
98
+ productIds: annotate(z.array(z.string()).optional(), {
99
99
  section: "members", order: 2, row: "full", kind: "list",
100
100
  label: `${BUNDLE_COPY.adminEditor.ruleTypeStatic} (${BUNDLE_MIN_ITEMS}–${BUNDLE_MAX_ITEMS})`,
101
101
  when: (v) => v.ruleType !== "dynamic",
102
102
  }),
103
- dynamicRule: annotate(dynamicRuleSchema, {
103
+ dynamicRule: annotate(dynamicRuleSchema.optional(), {
104
104
  section: "members", order: 3, row: "full",
105
105
  label: BUNDLE_COPY.adminEditor.dynamic.title,
106
106
  help: BUNDLE_COPY.adminEditor.dynamic.hint,
@@ -130,20 +130,44 @@ export const bundleFormSchema = z
130
130
  * them — a two-member bundle saved, and `listBundleMembers` rendered it as
131
131
  * a "bundle" of two.
132
132
  */
133
+ /*
134
+ * 🛑 Both member sources are `.optional()` because a hidden control is not
135
+ * SENT, and the two are mutually hidden by `ruleType`. Declaring either as
136
+ * required made the branch that hides it impossible to save: a static
137
+ * bundle submitted no `dynamicRule`, zod raised `invalid_type`, and
138
+ * `zodErrorMap` rendered it as "Bundle members: This field is required" on
139
+ * a form whose picker visibly held three products. Admin bundle CREATION
140
+ * was impossible in both directions. Found by
141
+ * `money-flows/blockers--admin` → `cross-store-group-refused`, which could
142
+ * not reach the cross-store guard it exists to test.
143
+ *
144
+ * Requiredness therefore lives HERE, per branch, where `ruleType` is known.
145
+ */
146
+ if (v.ruleType === "dynamic") {
147
+ if (!v.dynamicRule) {
148
+ ctx.addIssue({
149
+ code: z.ZodIssueCode.custom,
150
+ path: ["dynamicRule"],
151
+ message: "Describe the query that resolves this bundle's members.",
152
+ });
153
+ }
154
+ return;
155
+ }
133
156
  if (v.ruleType !== "static")
134
157
  return;
135
- if (v.productIds.length < BUNDLE_MIN_ITEMS) {
158
+ const productIds = v.productIds ?? [];
159
+ if (productIds.length < BUNDLE_MIN_ITEMS) {
136
160
  ctx.addIssue({
137
161
  code: z.ZodIssueCode.custom,
138
162
  path: ["productIds"],
139
- message: `Select at least ${BUNDLE_MIN_ITEMS} products (currently ${v.productIds.length}).`,
163
+ message: `Select at least ${BUNDLE_MIN_ITEMS} products (currently ${productIds.length}).`,
140
164
  });
141
165
  }
142
- else if (v.productIds.length > BUNDLE_MAX_ITEMS) {
166
+ else if (productIds.length > BUNDLE_MAX_ITEMS) {
143
167
  ctx.addIssue({
144
168
  code: z.ZodIssueCode.custom,
145
169
  path: ["productIds"],
146
- message: `Maximum ${BUNDLE_MAX_ITEMS} products allowed (currently ${v.productIds.length}).`,
170
+ message: `Maximum ${BUNDLE_MAX_ITEMS} products allowed (currently ${productIds.length}).`,
147
171
  });
148
172
  }
149
173
  });
@@ -114,7 +114,31 @@ class EventRepository extends BaseRepository {
114
114
  createdAt: now,
115
115
  updatedAt: now,
116
116
  });
117
- const ref = await this.getCollection().add(data);
117
+ /*
118
+ * 🛑 This used to be `.add(data)` — a raw write that ran NEITHER half of
119
+ * the repository's own contract, and broke two things at once:
120
+ *
121
+ * 1. `applyWriteHooks` never fired, so `buildSearchTxtFor` never wrote
122
+ * SEARCH_TXT — and `list()` filters on
123
+ * `where(SEARCH_TXT, "array-contains", head)`. Every event created
124
+ * through the UI was therefore invisible to admin event search,
125
+ * permanently, while seeded events (whose tokens the seeder writes)
126
+ * searched fine. The hook's docstring above claims it is derived on
127
+ * every write path; it was not derived on this one.
128
+ * 2. `.add()` mints a Firestore auto-id, so the document id was e.g.
129
+ * `SJHlHFAs5BHMLQwH8MdM` while every seeded event uses its slug.
130
+ * Events are "pure slugs (id === slug)" per the slug table, and the
131
+ * public URL and any slug lookup depend on it.
132
+ *
133
+ * `.create()` rather than `.set()`: it REJECTS an existing id, so a
134
+ * duplicate title surfaces as a conflict instead of silently
135
+ * overwriting someone else's event.
136
+ *
137
+ * Found by tester run-3 batch 98, page-wiring/reachability--admin →
138
+ * lottery-can-be-created-without-seeding. Root Cause #9's shape.
139
+ */
140
+ const ref = this.getCollection().doc(slug);
141
+ await ref.create(this.applyWriteHooks(data));
118
142
  const created = await ref.get();
119
143
  serverLogger.info("Event created", { eventId: ref.id, type: input.type });
120
144
  return this.mapDoc(created);
@@ -553,4 +553,45 @@ export const usersSeedData = [
553
553
  createdAt: daysAgo(60),
554
554
  updatedAt: daysAgo(2),
555
555
  },
556
+ /*
557
+ * ── Disabled buyer: the ONLY account with `disabled: true` ──
558
+ *
559
+ * 🛑 This persona exists so that cases about a banned/disabled login are
560
+ * testable WITHOUT disabling a real one. Measured 2026-10-01: of the 19
561
+ * seeded users, `disabled: true` occurred ZERO times, so every such case was
562
+ * permanently unrunnable — `users` is PRESERVE-tier (never wiped, never
563
+ * restored, merge-write on reseed), making "disable someone to test it" the
564
+ * one piece of permanent damage a test run can do.
565
+ *
566
+ * Nothing but a sign-in attempt should ever touch it: no orders, no store,
567
+ * no public profile. Keep `disabled: true` — flipping it to false silently
568
+ * removes the only fixture these cases have.
569
+ *
570
+ * Needed by money-flows/blockers--guest → banned-account-blocked, and the
571
+ * soft-ban cases declined for the same reason. Same class as Root Cause #90:
572
+ * a case reports "nothing here" because its fixture was never seeded.
573
+ */
574
+ {
575
+ uid: "user-qa-disabled",
576
+ email: "qa.disabled@letitrip.in",
577
+ phoneNumber: `${_ph}9999900099`,
578
+ phoneVerified: false,
579
+ displayName: "QA Disabled Account",
580
+ role: USER_FIELDS.ROLE_VALUES.USER,
581
+ emailVerified: true,
582
+ disabled: true,
583
+ publicProfile: {
584
+ isPublic: false,
585
+ showEmail: false,
586
+ showPhone: false,
587
+ showOrders: false,
588
+ showWishlist: false,
589
+ bio: "Fixture account — deliberately disabled so ban/disabled-login cases are testable.",
590
+ location: "Indore, Madhya Pradesh",
591
+ },
592
+ stats: { totalOrders: 0, auctionsWon: 0, itemsSold: 0, reviewsCount: 0 },
593
+ metadata: { lastSignInTime: daysAgo(30), creationTime: daysAgo(90).toISOString(), loginCount: 1 },
594
+ createdAt: daysAgo(90),
595
+ updatedAt: daysAgo(30),
596
+ },
556
597
  ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mohasinac/appkit",
3
- "version": "4.42.3",
3
+ "version": "4.42.5",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"