@mohasinac/appkit 4.30.11 → 4.30.12

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.
@@ -21,7 +21,9 @@ export function AdminBlogView({ children, ...props }) {
21
21
  const config = {
22
22
  portal: "admin",
23
23
  title: "Blog Posts",
24
- // Search intentionally absent: this endpoint does not read `q`, so the box accepted typing and changed nothing. Restore it when the collection gains searchTxt — audit-listing-search-capability tracks it.
24
+ // buildBlogSearchTxt indexes title + excerpt + category + tags — NOT the post
25
+ // body, which is not tokenised.
26
+ searchPlaceholder: "Search by title, excerpt, category or tag…",
25
27
  emptyLabel: "No blog posts found",
26
28
  filterKeys: ["status", "isFeatured"],
27
29
  defaultSort: sortBy("publishedAt", "DESC"),
@@ -52,7 +52,8 @@ export function AdminCouponsView({ children, onBulkArchive, onBulkDelete, ...pro
52
52
  const config = {
53
53
  portal: "admin",
54
54
  title: "Coupons",
55
- // Search intentionally absent: this endpoint does not read `q`, so the box accepted typing and changed nothing. Restore it when the collection gains searchTxt — audit-listing-search-capability tracks it.
55
+ // buildCouponSearchTxt indexes code + name + description — nothing else.
56
+ searchPlaceholder: "Search by code, name or description…",
56
57
  emptyLabel: "No coupons found",
57
58
  filterKeys: ["type", "showExpired"],
58
59
  defaultSort: sortBy("createdAt", "DESC"),
@@ -103,7 +103,10 @@ export function AdminOffersView({ children, ...props }) {
103
103
  const config = {
104
104
  portal: "admin",
105
105
  title: "Offers",
106
- // Search intentionally absent: this endpoint does not read `q`, so the box accepted typing and changed nothing. Restore it when the collection gains searchTxt — audit-listing-search-capability tracks it.
106
+ // buildOfferSearchTxt indexes productTitle + productSlug + storeName. It does
107
+ // NOT index buyerName or buyerEmail — those are PII, and the placeholder
108
+ // must not promise a match it will never make.
109
+ searchPlaceholder: "Search by product or store…",
107
110
  emptyLabel: "No offers found",
108
111
  filterKeys: ["status"],
109
112
  defaultSort: sortBy("createdAt", "DESC"),
@@ -33,7 +33,8 @@ export function AdminEventsView({ children, ...props }) {
33
33
  const config = {
34
34
  portal: "admin",
35
35
  title: "Events",
36
- // Search intentionally absent: this endpoint does not read `q`, so the box accepted typing and changed nothing. Restore it when the collection gains searchTxt — audit-listing-search-capability tracks it.
36
+ // buildEventSearchTxt indexes title + description + type + tags.
37
+ searchPlaceholder: "Search by title, description, type or tag…",
37
38
  emptyLabel: "No events found",
38
39
  filterKeys: ["status", "type"],
39
40
  defaultSort: sortBy("startsAt", "DESC"),
@@ -151,7 +151,9 @@ declare class OrderRepository extends BaseRepository<OrderDocument> {
151
151
  canSort: boolean;
152
152
  };
153
153
  };
154
- listForSeller(productIds: string[], model: SieveModel): Promise<FirebaseSieveResult<OrderDocument>>;
154
+ listForSeller(productIds: string[], model: SieveModel, opts?: {
155
+ search?: string;
156
+ }): Promise<FirebaseSieveResult<OrderDocument>>;
155
157
  listForUser(userId: string, model: SieveModel): Promise<FirebaseSieveResult<OrderDocument>>;
156
158
  static readonly ADMIN_SIEVE_FIELDS: {
157
159
  searchTxt: {
@@ -260,7 +260,12 @@ class OrderRepository extends BaseRepository {
260
260
  throw new DatabaseError(`Failed to delete orders for user: ${userId}`, error);
261
261
  }
262
262
  }
263
- async listForSeller(productIds, model) {
263
+ async listForSeller(productIds, model, opts) {
264
+ const plan = planSearchTxt(opts?.search);
265
+ // A search that yielded no usable term must return NOTHING, not the
266
+ // seller's whole order list.
267
+ if (plan.empty)
268
+ return emptySearchResult();
264
269
  if (productIds.length === 0) {
265
270
  const page = Math.max(1, Number(model.page ?? 1));
266
271
  const pageSize = Math.max(1, Number(model.pageSize ?? 20));
@@ -273,12 +278,22 @@ class OrderRepository extends BaseRepository {
273
278
  hasMore: false,
274
279
  };
275
280
  }
276
- const baseQuery = this.getCollection().where(ORDER_FIELDS.PRODUCT_ID, "in", productIds);
277
- return this.sieveQuery(model, OrderRepository.SELLER_SIEVE_FIELDS, {
281
+ let baseQuery = this.getCollection().where(ORDER_FIELDS.PRODUCT_ID, "in", productIds);
282
+ if (plan.head) {
283
+ // `array-contains` alongside `in` is legal — Firestore's one-per-query
284
+ // limits are on `array-contains` and on the `in`/`not-in`/
285
+ // `array-contains-any` family separately, so one of each is fine.
286
+ // Verified against production data before this shipped, and the
287
+ // matching composite index is declared in firestore.indexes.json;
288
+ // the query happened to plan without one, which is not a guarantee.
289
+ baseQuery = baseQuery.where(ORDER_FIELDS.SEARCH_TXT, "array-contains", plan.head);
290
+ }
291
+ const result = await this.sieveQuery(model, OrderRepository.SELLER_SIEVE_FIELDS, {
278
292
  baseQuery,
279
293
  defaultPageSize: 20,
280
294
  maxPageSize: 100,
281
295
  });
296
+ return refineSearchTxt(result, plan.rest);
282
297
  }
283
298
  async listForUser(userId, model) {
284
299
  const baseQuery = this.getCollection().where(ORDER_FIELDS.USER_ID, "==", userId);
@@ -49,7 +49,8 @@ export function SellerCouponsView({ onCreateClick, onEditClick, onToggle, onDele
49
49
  const config = {
50
50
  portal: "seller",
51
51
  title: "Coupons",
52
- // Search intentionally absent: this endpoint does not read `q`, so the box accepted typing and changed nothing. Restore it when the collection gains searchTxt — audit-listing-search-capability tracks it.
52
+ // buildCouponSearchTxt indexes code + name + description — nothing else.
53
+ searchPlaceholder: "Search by code, name or description…",
53
54
  emptyLabel: "No coupons found — create your first coupon",
54
55
  filterKeys: ["isActive"],
55
56
  defaultSort: sortBy("createdAt", "DESC"),
@@ -22,7 +22,9 @@ export function SellerOffersView({ children, onAcceptOffer, onRejectOffer, onCou
22
22
  const config = {
23
23
  portal: "seller",
24
24
  title: "Offers",
25
- // Search intentionally absent: this endpoint does not read `q`, so the box accepted typing and changed nothing. Restore it when the collection gains searchTxt — audit-listing-search-capability tracks it.
25
+ // buildOfferSearchTxt indexes productTitle + productSlug + storeName — never
26
+ // the buyer's name or email.
27
+ searchPlaceholder: "Search by product or store…",
26
28
  emptyLabel: "No offers received",
27
29
  filterKeys: ["status"],
28
30
  defaultSort: sortBy("createdAt", "DESC"),
@@ -294,7 +294,10 @@ export function SellerOrdersView({ orderDetailApiBase = SELLER_ENDPOINTS.ORDERS,
294
294
  const config = {
295
295
  portal: "seller",
296
296
  title: "Orders",
297
- // Search intentionally absent: this endpoint does not read `q`, so the box accepted typing and changed nothing. Restore it when the collection gains searchTxt — audit-listing-search-capability tracks it.
297
+ // buildOrderSearchTxt indexes productTitle, storeName and trackingNumber.
298
+ // It does NOT index the buyer's name, email or address — those are PII,
299
+ // and the placeholder must not promise a match it will never make.
300
+ searchPlaceholder: "Search by product, store or tracking number…",
298
301
  emptyLabel: "No orders yet",
299
302
  filterKeys: ["status"],
300
303
  defaultSort: DEFAULT_SORT,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mohasinac/appkit",
3
- "version": "4.30.11",
3
+ "version": "4.30.12",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"