@mohasinac/appkit 2.7.45 → 2.7.47

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.
Files changed (88) hide show
  1. package/dist/client.d.ts +2 -2
  2. package/dist/client.js +1 -1
  3. package/dist/constants/field-names.d.ts +1 -0
  4. package/dist/constants/field-names.js +1 -0
  5. package/dist/core/unit-of-work.js +3 -4
  6. package/dist/features/account/components/AddressForm.js +23 -1
  7. package/dist/features/admin/components/AdminBlogEditorView.js +18 -11
  8. package/dist/features/admin/components/AdminBrandEditorView.js +18 -11
  9. package/dist/features/admin/components/AdminCarouselEditorView.js +18 -8
  10. package/dist/features/admin/components/AdminCategoryEditorView.js +18 -11
  11. package/dist/features/admin/components/AdminCouponEditorView.js +18 -11
  12. package/dist/features/admin/components/AdminFaqEditorView.js +18 -11
  13. package/dist/features/admin/components/AdminSublistingCategoryEditorView.js +18 -11
  14. package/dist/features/auth/actions/profile-actions.js +4 -1
  15. package/dist/features/auth/repository/user.repository.d.ts +1 -0
  16. package/dist/features/auth/repository/user.repository.js +3 -0
  17. package/dist/features/blog/api/route.js +3 -0
  18. package/dist/features/blog/components/BlogIndexListing.js +2 -0
  19. package/dist/features/blog/components/BlogPostView.js +2 -1
  20. package/dist/features/categories/api/route.js +1 -1
  21. package/dist/features/categories/components/BundleDetailView.js +5 -3
  22. package/dist/features/categories/components/CategoryBundlesListing.js +77 -41
  23. package/dist/features/categories/components/CategoryDetailPageView.js +12 -9
  24. package/dist/features/categories/components/CategoryProductsListing.js +3 -5
  25. package/dist/features/classified/components/ClassifiedIndexListing.js +3 -4
  26. package/dist/features/digital-codes/components/DigitalCodesIndexListing.js +3 -4
  27. package/dist/features/events/actions/event-actions.d.ts +2 -3
  28. package/dist/features/events/actions/event-actions.js +3 -5
  29. package/dist/features/events/api/route.js +7 -7
  30. package/dist/features/events/components/AdminEventEditorView.js +80 -6
  31. package/dist/features/events/components/AdminEventEntriesView.js +33 -4
  32. package/dist/features/events/components/EventFilters.d.ts +3 -0
  33. package/dist/features/events/components/EventFilters.js +1 -0
  34. package/dist/features/events/components/EventLeaderboard.js +1 -1
  35. package/dist/features/events/components/EventsIndexListing.js +10 -7
  36. package/dist/features/events/repository/event-entry.repository.d.ts +2 -2
  37. package/dist/features/events/repository/event-entry.repository.js +32 -7
  38. package/dist/features/events/types/index.d.ts +3 -1
  39. package/dist/features/homepage/components/BrandsSection.js +2 -2
  40. package/dist/features/homepage/components/CustomCardsSection.js +1 -1
  41. package/dist/features/homepage/components/CustomerReviewsSection.js +1 -1
  42. package/dist/features/homepage/components/FeaturedResultsSection.js +1 -1
  43. package/dist/features/homepage/components/SectionCarousel.js +1 -1
  44. package/dist/features/homepage/components/ShopByCategorySection.js +1 -1
  45. package/dist/features/live/components/LiveItemsIndexListing.js +3 -4
  46. package/dist/features/pre-orders/components/PreOrdersIndexListing.js +3 -4
  47. package/dist/features/products/components/AuctionsIndexListing.js +3 -4
  48. package/dist/features/products/components/PrizeDrawsIndexListing.js +3 -8
  49. package/dist/features/products/components/ProductDetailPageView.js +17 -2
  50. package/dist/features/products/components/ProductsIndexListing.js +10 -6
  51. package/dist/features/products/schemas/firestore.d.ts +2 -0
  52. package/dist/features/reviews/actions/review-actions.js +2 -0
  53. package/dist/features/reviews/components/ReviewDetailShell.js +3 -2
  54. package/dist/features/reviews/components/ReviewFilters.js +1 -1
  55. package/dist/features/reviews/components/ReviewsIndexListing.js +9 -2
  56. package/dist/features/reviews/hooks/useReviews.js +2 -0
  57. package/dist/features/reviews/schemas/firestore.d.ts +4 -0
  58. package/dist/features/reviews/types/index.d.ts +3 -0
  59. package/dist/features/scams/components/ScamRegistryView.js +41 -8
  60. package/dist/features/seller/components/SellerProductShell.js +27 -3
  61. package/dist/features/shell/StepForm.d.ts +6 -2
  62. package/dist/features/shell/StepForm.js +13 -10
  63. package/dist/features/stores/components/InteractiveStoreCard.js +1 -1
  64. package/dist/features/stores/components/StoreAuctionsListing.js +3 -4
  65. package/dist/features/stores/components/StoreDetailLayoutView.js +13 -5
  66. package/dist/features/stores/components/StorePreOrdersListing.js +3 -4
  67. package/dist/features/stores/components/StoreProductsListing.js +3 -4
  68. package/dist/index.d.ts +1 -0
  69. package/dist/providers/db-firebase/helpers.d.ts +4 -6
  70. package/dist/providers/db-firebase/helpers.js +18 -5
  71. package/dist/providers/db-firebase/sieve.js +26 -1
  72. package/dist/react/hooks/usePendingFilters.d.ts +2 -0
  73. package/dist/react/hooks/usePendingFilters.js +9 -0
  74. package/dist/react/hooks/usePendingTable.d.ts +5 -0
  75. package/dist/react/hooks/usePendingTable.js +1 -0
  76. package/dist/seed/homepage-sections-seed-data.js +2 -2
  77. package/dist/seed/products-standard-seed-data.js +1 -0
  78. package/dist/tailwind-utilities.css +1 -1
  79. package/dist/ui/components/Drawer.style.css +13 -0
  80. package/dist/ui/components/FilterDrawer.d.ts +4 -2
  81. package/dist/ui/components/FilterDrawer.js +2 -2
  82. package/dist/ui/components/Toast.d.ts +4 -1
  83. package/dist/ui/components/Toast.js +3 -3
  84. package/dist/ui/forms/FormShell.d.ts +19 -1
  85. package/dist/ui/forms/FormShell.js +92 -0
  86. package/dist/ui/forms/index.d.ts +2 -2
  87. package/dist/ui/forms/index.js +1 -1
  88. package/package.json +2 -1
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { cache } from "react";
3
- import { storeRepository, productRepository, categoriesRepository, siteSettingsRepository } from "../../../repositories";
3
+ import { storeRepository, productRepository, categoriesRepository, siteSettingsRepository, reviewRepository, couponsRepository } from "../../../repositories";
4
4
  import { ROUTES } from "../../../next";
5
5
  import { Container, Main, Section, Text } from "../../../ui";
6
6
  import { STORE_PAGE_TABS } from "../../products/constants/listing-tabs";
@@ -27,7 +27,7 @@ export async function StoreDetailLayoutView({ storeSlug, activeTab, children, })
27
27
  }
28
28
  const storeId = store?.id;
29
29
  const settings = await siteSettingsRepository.findById("global").catch(() => null);
30
- const [productsCount, auctionsCount, preOrdersCount, prizeDrawsCount, bundlesCount] = storeId
30
+ const [productsCount, auctionsCount, preOrdersCount, prizeDrawsCount, bundlesCount, couponsCount, reviewsCount] = storeId
31
31
  ? await Promise.all([
32
32
  productRepository
33
33
  .list({ filters: `storeId==${storeId},status==published,listingType==standard`, page: 1, pageSize: 1 })
@@ -51,8 +51,16 @@ export async function StoreDetailLayoutView({ storeSlug, activeTab, children, })
51
51
  .listByType("bundle", { activeOnly: true, limit: 100 })
52
52
  .then((rows) => rows.filter((c) => c.createdByStoreId === storeId).length)
53
53
  .catch(() => 0),
54
+ couponsRepository
55
+ .list({ filters: `sellerId==${storeId},validity.isActive==true`, page: 1, pageSize: 1 })
56
+ .then((r) => r.total)
57
+ .catch(() => 0),
58
+ reviewRepository
59
+ .listAll({ filters: `storeId==${storeId},status==approved`, page: 1, pageSize: 1 })
60
+ .then((r) => r.total)
61
+ .catch(() => 0),
54
62
  ])
55
- : [0, 0, 0, 0, 0];
63
+ : [0, 0, 0, 0, 0, 0, 0];
56
64
  const listingCounts = {
57
65
  products: productsCount,
58
66
  auctions: auctionsCount,
@@ -78,8 +86,8 @@ export async function StoreDetailLayoutView({ storeSlug, activeTab, children, })
78
86
  label: tabLabel(tab.label, listingCounts[tab.id]),
79
87
  href: STORE_LISTING_HREF[tab.id](storeSlug),
80
88
  })),
81
- { value: "coupons", label: "Coupons", href: String(ROUTES.PUBLIC.STORE_COUPONS(storeSlug)) },
82
- { value: "reviews", label: "Reviews", href: String(ROUTES.PUBLIC.STORE_REVIEWS(storeSlug)) },
89
+ { value: "coupons", label: tabLabel("Coupons", couponsCount), href: String(ROUTES.PUBLIC.STORE_COUPONS(storeSlug)) },
90
+ { value: "reviews", label: tabLabel("Reviews", reviewsCount), href: String(ROUTES.PUBLIC.STORE_REVIEWS(storeSlug)) },
83
91
  { value: "about", label: "About", href: String(ROUTES.PUBLIC.STORE_ABOUT(storeSlug)) },
84
92
  ];
85
93
  return (_jsxs(Main, { children: [_jsx(StoreHeader, { store: store }), _jsxs(Container, { size: "xl", className: "mt-6", children: [_jsx(StoreNavTabs, { tabs: tabs, activeValue: activeTab }), _jsx(Section, { className: "pt-6", children: children })] })] }));
@@ -24,7 +24,7 @@ export function StorePreOrdersListing({ storeId, initialData }) {
24
24
  const [searchInput, setSearchInput] = useState(table.get("q") || "");
25
25
  const [filterOpen, setFilterOpen] = useState(false);
26
26
  const [view, setView] = useState(table.get("view") || "grid");
27
- const { pendingTable, filterActiveCount, onFilterApply, onFilterClear, onFilterReset } = usePendingTable(table, FILTER_KEYS);
27
+ const { pendingTable, filterActiveCount, onFilterApply, onFilterClear, onResetAll, onFilterReset } = usePendingTable(table, FILTER_KEYS);
28
28
  const openFilters = useCallback(() => {
29
29
  onFilterReset();
30
30
  setFilterOpen(true);
@@ -34,10 +34,9 @@ export function StorePreOrdersListing({ storeId, initialData }) {
34
34
  setFilterOpen(false);
35
35
  }, [onFilterApply]);
36
36
  const resetAll = useCallback(() => {
37
- table.setMany({ q: "", sort: "" });
38
- onFilterClear();
37
+ onResetAll({ q: "", sort: "" });
39
38
  setSearchInput("");
40
- }, [table, onFilterClear]);
39
+ }, [onResetAll]);
41
40
  const hasActiveState = !!table.get("q") ||
42
41
  table.get("sort") !== DEFAULT_SORT ||
43
42
  filterActiveCount > 0;
@@ -24,7 +24,7 @@ export function StoreProductsListing({ storeId, initialData }) {
24
24
  const localCart = useGuestCart();
25
25
  const localWishlist = useGuestWishlist();
26
26
  const wishlistedIds = new Set(localWishlist.items.filter((i) => i.type === "product").map((i) => i.itemId));
27
- const { pendingTable, filterActiveCount, onFilterApply, onFilterClear, onFilterReset } = usePendingTable(table, FILTER_KEYS);
27
+ const { pendingTable, filterActiveCount, onFilterApply, onFilterClear, onResetAll, onFilterReset } = usePendingTable(table, FILTER_KEYS);
28
28
  const openFilters = useCallback(() => {
29
29
  onFilterReset();
30
30
  setFilterOpen(true);
@@ -34,10 +34,9 @@ export function StoreProductsListing({ storeId, initialData }) {
34
34
  setFilterOpen(false);
35
35
  }, [onFilterApply]);
36
36
  const resetAll = useCallback(() => {
37
- table.setMany({ q: "", sort: "" });
38
- onFilterClear();
37
+ onResetAll({ q: "", sort: "" });
39
38
  setSearchInput("");
40
- }, [table, onFilterClear]);
39
+ }, [onResetAll]);
41
40
  const hasActiveState = !!table.get("q") ||
42
41
  table.get("sort") !== "-createdAt" ||
43
42
  filterActiveCount > 0;
package/dist/index.d.ts CHANGED
@@ -1761,6 +1761,7 @@ export { EventRaffleWinnerView } from "./features/events/index";
1761
1761
  export type { EventRaffleWinnerViewProps } from "./features/events/index";
1762
1762
  export { SpinWheelView } from "./features/events/index";
1763
1763
  export type { SpinWheelViewProps } from "./features/events/index";
1764
+ export type { SpinPrize } from "./features/events/index";
1764
1765
  export { EventsListView } from "./features/events/index";
1765
1766
  export { EventsIndexListing } from "./features/events/index";
1766
1767
  export type { EventsIndexListingProps } from "./features/events/index";
@@ -1,13 +1,11 @@
1
1
  import type { CollectionReference, DocumentData, Query } from "firebase-admin/firestore";
2
- /**
3
- * Firestore serialisation helpers.
4
- *
5
- * These utilities are the single adapter boundary between raw Firestore
6
- * documents and application-level objects.
7
- */
8
2
  /**
9
3
  * Recursively remove `undefined` values from an object.
10
4
  * Firestore rejects writes that contain `undefined` fields.
5
+ *
6
+ * Non-plain values (Date, FieldValue sentinels, class instances, Arrays)
7
+ * are passed through as-is so that Firestore transform operations
8
+ * (increment, arrayUnion, deleteField, serverTimestamp) survive the strip.
11
9
  */
12
10
  export declare function removeUndefined<T extends Record<string, unknown>>(obj: T): Partial<T>;
13
11
  /**
@@ -5,9 +5,26 @@
5
5
  * documents and application-level objects.
6
6
  */
7
7
  // --- Undefined removal --------------------------------------------------------
8
+ /**
9
+ * Returns true only for plain objects ({}) — NOT class instances, Dates,
10
+ * Arrays, FieldValue sentinels, or Timestamps. Only plain objects are safe
11
+ * to recurse into; everything else must pass through unchanged so Firestore
12
+ * FieldValue sentinels (increment, arrayUnion, deleteField, etc.) are not
13
+ * stripped.
14
+ */
15
+ function isPlainObject(v) {
16
+ if (v === null || typeof v !== "object" || Array.isArray(v))
17
+ return false;
18
+ const proto = Object.getPrototypeOf(v);
19
+ return proto === Object.prototype || proto === null;
20
+ }
8
21
  /**
9
22
  * Recursively remove `undefined` values from an object.
10
23
  * Firestore rejects writes that contain `undefined` fields.
24
+ *
25
+ * Non-plain values (Date, FieldValue sentinels, class instances, Arrays)
26
+ * are passed through as-is so that Firestore transform operations
27
+ * (increment, arrayUnion, deleteField, serverTimestamp) survive the strip.
11
28
  */
12
29
  export function removeUndefined(obj) {
13
30
  const result = {};
@@ -15,11 +32,7 @@ export function removeUndefined(obj) {
15
32
  const value = obj[key];
16
33
  if (value === undefined)
17
34
  continue;
18
- if (value !== null &&
19
- typeof value === "object" &&
20
- !Array.isArray(value) &&
21
- !(value instanceof Date) &&
22
- !value.constructor?.name?.includes("Timestamp")) {
35
+ if (isPlainObject(value)) {
23
36
  const cleaned = removeUndefined(value);
24
37
  if (Object.keys(cleaned).length > 0) {
25
38
  result[key] = cleaned;
@@ -19,6 +19,31 @@
19
19
  */
20
20
  import { SieveProcessorBase } from "@mohasinac/sievejs/services";
21
21
  import { createFirebaseAdapter } from "@mohasinac/sievejs/adapters/firebase";
22
+ /**
23
+ * Wraps the stock Firebase adapter and upgrades OR groups where all conditions
24
+ * are equality checks on the same field into a Firestore `in` query.
25
+ *
26
+ * Background: Sieve parses `field==v1|v2` as a single term with two values
27
+ * (OR semantics). The stock adapter tries `whereOr` which Firestore Admin SDK
28
+ * doesn't support. Firestore's `.where(field, "in", [v1, v2])` is the correct
29
+ * equivalent and supports up to 30 values.
30
+ */
31
+ function createEnhancedFirebaseAdapter() {
32
+ const base = createFirebaseAdapter();
33
+ return {
34
+ ...base,
35
+ applyFilterGroup(query, group) {
36
+ if (group.length > 1 &&
37
+ group.every((c) => c.parsedOperator === "equals" &&
38
+ !c.operatorIsNegated &&
39
+ !c.operatorIsCaseInsensitive &&
40
+ c.field === group[0].field)) {
41
+ return query.where(group[0].field, "in", group.map((c) => c.value));
42
+ }
43
+ return base.applyFilterGroup(query, group);
44
+ },
45
+ };
46
+ }
22
47
  import { FirebaseRepository } from "./base";
23
48
  import { deserializeTimestamps, getFirestoreCount } from "./helpers";
24
49
  import { expandFilterAliases as _expandFilterAliases, } from "./filter-aliases";
@@ -90,7 +115,7 @@ export class FirebaseSieveRepository extends FirebaseRepository {
90
115
  const base = baseQuery ?? this.getCollection();
91
116
  const effective = withAliasesExpanded(model, aliases);
92
117
  const processor = new SieveProcessorBase({
93
- adapter: createFirebaseAdapter(),
118
+ adapter: createEnhancedFirebaseAdapter(),
94
119
  autoLoadConfig: false,
95
120
  options: merged,
96
121
  fields,
@@ -25,6 +25,8 @@ export interface UsePendingFiltersReturn {
25
25
  reset: () => void;
26
26
  /** Clear all keys in both pending state and the URL */
27
27
  clear: () => void;
28
+ /** Clear filter keys + extra URL keys in a single router.replace — avoids the double-replace race */
29
+ clearAll: (extras?: Record<string, string>) => void;
28
30
  }
29
31
  /**
30
32
  * usePendingFilters
@@ -65,6 +65,14 @@ export function usePendingFilters({ table, keys, }) {
65
65
  updates[k] = "";
66
66
  table.setMany(updates);
67
67
  }, [keys, table]);
68
+ const clearAll = useCallback((extras) => {
69
+ const empty = Object.fromEntries(keys.map((k) => [k, []]));
70
+ setPending(empty);
71
+ const updates = { page: "1", ...extras };
72
+ for (const k of keys)
73
+ updates[k] = "";
74
+ table.setMany(updates);
75
+ }, [keys, table]);
68
76
  return {
69
77
  pending,
70
78
  applied,
@@ -75,5 +83,6 @@ export function usePendingFilters({ table, keys, }) {
75
83
  apply,
76
84
  reset,
77
85
  clear,
86
+ clearAll,
78
87
  };
79
88
  }
@@ -17,6 +17,11 @@ export interface UsePendingTableReturn {
17
17
  onFilterApply: () => void;
18
18
  /** Clear all filter keys from pending state and URL */
19
19
  onFilterClear: () => void;
20
+ /**
21
+ * Clear filter keys + extra URL keys in a SINGLE router.replace.
22
+ * Use this in resetAll handlers to avoid the double-replace race condition.
23
+ */
24
+ onResetAll: (extras?: Record<string, string>) => void;
20
25
  /**
21
26
  * Revert pending state to the current URL values without clearing them.
22
27
  * Call this when opening a filter drawer to ensure it shows the last
@@ -40,6 +40,7 @@ export function usePendingTable(table, keys) {
40
40
  filterActiveCount: filters.appliedCount,
41
41
  onFilterApply: filters.apply,
42
42
  onFilterClear: filters.clear,
43
+ onResetAll: filters.clearAll,
43
44
  onFilterReset: filters.reset,
44
45
  };
45
46
  }
@@ -327,9 +327,9 @@ export const homepageSectionsSeedData = [
327
327
  /** Which tabs to surface in display order (leave empty [] to auto-derive from categories) */
328
328
  visibleTabs: ["general", "orders_payment", "shipping_delivery", "returns_refunds"],
329
329
  /** Allow multiple accordion panels open at once */
330
- allowMultipleOpen: false,
330
+ allowMultipleOpen: true,
331
331
  /** Number of items expanded on first render (0 = all closed) */
332
- defaultOpenCount: 1,
332
+ defaultOpenCount: 100,
333
333
  categories: ["general", "orders_payment", "shipping_delivery", "returns_refunds", "product_information"],
334
334
  },
335
335
  createdAt: daysAgo(90),
@@ -4134,6 +4134,7 @@ export const productsStandardSeedData = [
4134
4134
  ..._rawProductsStandardSeedData.map((p) => ({
4135
4135
  ...p,
4136
4136
  listingType: "standard",
4137
+ isPartOfBundle: Array.isArray(p.partOfBundleIds) && p.partOfBundleIds.length > 0,
4137
4138
  })),
4138
4139
  // ── SB-UNI-M classified seed (OLX-style meetup) ──────────────────────────
4139
4140
  {