@mohasinac/appkit 2.7.17 → 2.7.19

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 (91) hide show
  1. package/dist/_internal/server/features/bundles/actions.d.ts +15 -0
  2. package/dist/_internal/server/features/bundles/actions.js +48 -0
  3. package/dist/_internal/server/features/bundles/index.d.ts +1 -0
  4. package/dist/_internal/server/features/bundles/index.js +1 -0
  5. package/dist/_internal/server/features/checkout/actions.d.ts +4 -0
  6. package/dist/_internal/server/features/checkout/actions.js +23 -12
  7. package/dist/_internal/server/features/payments/data.d.ts +5 -3
  8. package/dist/_internal/server/features/payments/data.js +13 -8
  9. package/dist/_internal/server/features/payments/index.d.ts +1 -1
  10. package/dist/_internal/server/features/payments/index.js +1 -1
  11. package/dist/_internal/server/features/seo/manifest.js +9 -3
  12. package/dist/_internal/shared/features/categories/bundle-config.d.ts +24 -0
  13. package/dist/_internal/shared/features/categories/bundle-config.js +28 -0
  14. package/dist/_internal/shared/features/categories/bundle-copy.d.ts +7 -0
  15. package/dist/_internal/shared/features/categories/bundle-copy.js +9 -0
  16. package/dist/_internal/shared/features/categories/bundle-schemas.d.ts +69 -19
  17. package/dist/_internal/shared/features/categories/bundle-schemas.js +20 -2
  18. package/dist/_internal/shared/features/checkout/config.d.ts +1 -1
  19. package/dist/_internal/shared/features/checkout/config.js +1 -1
  20. package/dist/_internal/shared/features/payments/config.d.ts +2 -1
  21. package/dist/_internal/shared/features/payments/config.js +2 -1
  22. package/dist/_internal/shared/features/products/schema.d.ts +8 -8
  23. package/dist/constants/field-names.d.ts +8 -1
  24. package/dist/constants/field-names.js +9 -2
  25. package/dist/constants/table-keys.d.ts +3 -0
  26. package/dist/constants/table-keys.js +3 -0
  27. package/dist/features/admin/components/AdminSiteSettingsView.js +17 -15
  28. package/dist/features/admin/components/DashboardStats.js +20 -12
  29. package/dist/features/admin/components/analytics/AdminAnalyticsCharts.js +18 -2
  30. package/dist/features/admin/components/analytics/AdminStatCard.d.ts +7 -1
  31. package/dist/features/admin/components/analytics/AdminStatCard.js +13 -2
  32. package/dist/features/admin/schemas/firestore.d.ts +39 -4
  33. package/dist/features/admin/schemas/firestore.js +14 -1
  34. package/dist/features/categories/components/BundleBuyNowCta.d.ts +3 -1
  35. package/dist/features/categories/components/BundleBuyNowCta.js +3 -3
  36. package/dist/features/categories/components/BundleDynamicRuleEditor.d.ts +1 -1
  37. package/dist/features/categories/components/BundleDynamicRuleEditor.js +2 -1
  38. package/dist/features/categories/components/BundlesListView.d.ts +4 -1
  39. package/dist/features/categories/components/BundlesListView.js +2 -2
  40. package/dist/features/categories/components/CategoryBundlesListing.d.ts +4 -1
  41. package/dist/features/categories/components/CategoryBundlesListing.js +9 -8
  42. package/dist/features/categories/schemas/firestore.d.ts +29 -1
  43. package/dist/features/checkout/actions/checkout-actions.d.ts +11 -0
  44. package/dist/features/checkout/actions/checkout-actions.js +26 -0
  45. package/dist/features/filters/FilterFacetSection.d.ts +6 -1
  46. package/dist/features/filters/FilterFacetSection.js +25 -2
  47. package/dist/features/homepage/components/FeaturedBundlesSection.d.ts +4 -1
  48. package/dist/features/homepage/components/FeaturedBundlesSection.js +9 -8
  49. package/dist/features/orders/actions/refund-actions.js +10 -7
  50. package/dist/features/orders/schemas/firestore.d.ts +16 -13
  51. package/dist/features/orders/schemas/firestore.js +6 -5
  52. package/dist/features/pre-orders/components/MarketplacePreorderCard.js +1 -1
  53. package/dist/features/pre-orders/components/PreOrdersIndexListing.js +2 -1
  54. package/dist/features/products/components/AuctionsIndexListing.js +2 -1
  55. package/dist/features/products/components/ProductDetailPageView.js +3 -1
  56. package/dist/features/products/components/ProductDetailView.d.ts +3 -1
  57. package/dist/features/products/components/ProductDetailView.js +2 -2
  58. package/dist/features/products/components/ProductFilters.d.ts +10 -4
  59. package/dist/features/products/components/ProductFilters.js +20 -16
  60. package/dist/features/products/components/ProductGrid.js +2 -2
  61. package/dist/features/products/components/ProductsIndexListing.js +2 -1
  62. package/dist/features/products/constants/action-defs.d.ts +64 -22
  63. package/dist/features/products/constants/action-defs.js +101 -64
  64. package/dist/features/products/constants/product-features.constants.js +5 -1
  65. package/dist/features/products/schemas/index.d.ts +2 -2
  66. package/dist/features/products/schemas/product-features.d.ts +1 -1
  67. package/dist/features/products/schemas/product-features.validators.d.ts +10 -10
  68. package/dist/features/products/schemas/product-features.validators.js +4 -0
  69. package/dist/features/seller/components/SellerCouponEditorView.js +2 -2
  70. package/dist/features/seller/components/analytics/SellerAnalyticsStats.js +10 -4
  71. package/dist/features/stores/components/StoreBundlesPageView.d.ts +4 -1
  72. package/dist/features/stores/components/StoreBundlesPageView.js +2 -2
  73. package/dist/features/stores/components/StoresIndexListing.js +2 -1
  74. package/dist/providers/db-firebase/admin-app-lite.js +8 -2
  75. package/dist/providers/db-firebase/admin.js +8 -2
  76. package/dist/seed/addresses-seed-data.js +24 -7
  77. package/dist/seed/cart-seed-data.d.ts +10 -10
  78. package/dist/seed/cart-seed-data.js +39 -2
  79. package/dist/seed/grouped-listings-seed-data.js +47 -0
  80. package/dist/seed/orders-seed-data.js +150 -0
  81. package/dist/seed/products-standard-seed-data.js +42 -0
  82. package/dist/seed/site-settings-seed-data.js +14 -4
  83. package/dist/seed/users-seed-data.js +1 -1
  84. package/dist/seed/wishlists-seed-data.js +7 -0
  85. package/dist/server-entry.d.ts +2 -1
  86. package/dist/server-entry.js +2 -1
  87. package/dist/server.d.ts +2 -1
  88. package/dist/server.js +3 -1
  89. package/dist/tailwind-utilities.css +1 -1
  90. package/dist/ui/components/SideDrawer.style.css +49 -11
  91. package/package.json +1 -1
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Bundle server actions — appkit internal.
3
+ *
4
+ * `addBundleToCartAction` is the shared business logic called by the
5
+ * consumer's `"use server"` wrapper. The consumer is responsible for:
6
+ * 1. Resolving the authenticated userId (throw AuthError when unauthenticated
7
+ * so BundleBuyNowCta's isAuthError guard shows the login modal).
8
+ * 2. Calling redirect(ROUTES.PUBLIC.CHECKOUT) after this returns.
9
+ *
10
+ * Bundles are categoryType:"bundle" rows — they bypass the regular
11
+ * addItemToCart capability gate and write directly to cartRepository with
12
+ * bundleCategorySlug + bundleProductIds so checkout's bundle-expansion
13
+ * helpers can fan out stock decrements to member products.
14
+ */
15
+ export declare function addBundleToCartAction(userId: string, bundleSlug: string): Promise<void>;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Bundle server actions — appkit internal.
3
+ *
4
+ * `addBundleToCartAction` is the shared business logic called by the
5
+ * consumer's `"use server"` wrapper. The consumer is responsible for:
6
+ * 1. Resolving the authenticated userId (throw AuthError when unauthenticated
7
+ * so BundleBuyNowCta's isAuthError guard shows the login modal).
8
+ * 2. Calling redirect(ROUTES.PUBLIC.CHECKOUT) after this returns.
9
+ *
10
+ * Bundles are categoryType:"bundle" rows — they bypass the regular
11
+ * addItemToCart capability gate and write directly to cartRepository with
12
+ * bundleCategorySlug + bundleProductIds so checkout's bundle-expansion
13
+ * helpers can fan out stock decrements to member products.
14
+ */
15
+ import { NotFoundError, ValidationError } from "../../../../errors";
16
+ import { categoriesRepository } from "../../../../repositories";
17
+ import { cartRepository } from "../../../../features/cart/repository/cart.repository";
18
+ import { getDefaultCurrency } from "../../../../core/baseline-resolver";
19
+ export async function addBundleToCartAction(userId, bundleSlug) {
20
+ if (!userId)
21
+ throw new ValidationError("User id is required");
22
+ if (!bundleSlug)
23
+ throw new ValidationError("Bundle slug is required");
24
+ const bundle = await categoriesRepository.findBySlugAndType(bundleSlug, "bundle");
25
+ if (!bundle)
26
+ throw new NotFoundError("Bundle not found");
27
+ if (!bundle.isActive)
28
+ throw new ValidationError("Bundle is not available");
29
+ if (bundle.bundleStockStatus === "out_of_stock") {
30
+ throw new ValidationError("Bundle is currently out of stock");
31
+ }
32
+ if (!bundle.bundlePriceInPaise || bundle.bundlePriceInPaise < 1) {
33
+ throw new ValidationError("Bundle price is not configured");
34
+ }
35
+ await cartRepository.addItem(userId, {
36
+ productId: bundle.id,
37
+ productTitle: bundle.name,
38
+ productImage: bundle.display?.coverImage ?? "",
39
+ price: bundle.bundlePriceInPaise,
40
+ currency: getDefaultCurrency(),
41
+ quantity: 1,
42
+ storeId: bundle.createdByStoreId ?? "",
43
+ storeName: bundle.createdByStoreName ?? "",
44
+ listingType: "standard",
45
+ bundleCategorySlug: bundle.slug,
46
+ bundleProductIds: bundle.bundleProductIds ?? [],
47
+ });
48
+ }
@@ -1,3 +1,4 @@
1
1
  export { getBundleForDetail, listBundleMembers, listFeaturedBundles, type BundleDataOptions, } from "./data";
2
2
  export { buildBundleMetadata, type BundleMetadataOptions } from "./metadata";
3
3
  export { renderBundleOg, renderBundleOgImage, type BundleOgData, } from "./og";
4
+ export { addBundleToCartAction } from "./actions";
@@ -2,3 +2,4 @@ export { getBundleForDetail, listBundleMembers, listFeaturedBundles, } from "./d
2
2
  export { buildBundleMetadata } from "./metadata";
3
3
  // S-SBUNI-4 2026-05-13 — bundle OG renderer.
4
4
  export { renderBundleOg, renderBundleOgImage, } from "./og";
5
+ export { addBundleToCartAction } from "./actions";
@@ -16,6 +16,10 @@ export interface CreateCheckoutOrderInput {
16
16
  paymentMethod: CheckoutPaymentMethod;
17
17
  notes?: string;
18
18
  excludedProductIds?: string[];
19
+ /** When true this is an admin test order — OTP consent is pre-granted and the order is created paid/processing. */
20
+ adminBypass?: boolean;
21
+ /** UID of the admin who triggered the bypass. Set when adminBypass is true. */
22
+ adminBypassBy?: string;
19
23
  }
20
24
  /**
21
25
  * Place order(s) from the user's cart in a single Firestore transaction.
@@ -76,7 +76,7 @@ function emitOrderPlacedNotifications(args) {
76
76
  * auth + rate-limiting before calling this and supply the resolved user fields.
77
77
  */
78
78
  export async function createCheckoutOrderAction(input) {
79
- const { userId: uid, userName, userEmail, addressId, paymentMethod, notes, excludedProductIds = [], } = input;
79
+ const { userId: uid, userName, userEmail, addressId, paymentMethod, notes, excludedProductIds = [], adminBypass = false, adminBypassBy, } = input;
80
80
  const siteSettings = await siteSettingsRepository.getSingleton();
81
81
  const commissions = siteSettings?.commissions ?? CHECKOUT_DEFAULT_COMMISSIONS;
82
82
  const cart = await unitOfWork.carts.getOrCreate(uid);
@@ -147,7 +147,7 @@ export async function createCheckoutOrderAction(input) {
147
147
  const reason = !otpData ? "otp_not_verified" : "consent_expired";
148
148
  throw Object.assign(new ApiError(403, "Order verification required. Please complete OTP verification before placing this order."), { _failReason: reason });
149
149
  }
150
- const emailOtpUsed = otpData.verifiedVia !== "sms";
150
+ const emailOtpUsed = otpData.verifiedVia !== "sms" && otpData.verifiedVia !== "admin_bypass";
151
151
  // SB-UNI-5 2026-05-13 — bundle-aware stock fan-out. Build the unique
152
152
  // product-id set across the whole cart (regular items + each bundle's
153
153
  // members) and fetch each one ONCE. Validation walks per cart-line and
@@ -249,6 +249,11 @@ export async function createCheckoutOrderAction(input) {
249
249
  }
250
250
  const appliedCoupons = cart.appliedCoupons ?? [];
251
251
  const orderGroups = splitCartIntoOrderGroups(available);
252
+ // Admin-bypass orders are immediately paid and processing; generate a shared
253
+ // transaction reference for all orders in this checkout batch.
254
+ const adminBatchId = adminBypass
255
+ ? `ADMIN-${Date.now().toString(36).toUpperCase()}-${Math.random().toString(36).slice(2, 6).toUpperCase()}`
256
+ : undefined;
252
257
  const orderIds = [];
253
258
  let total = 0;
254
259
  const emailsToSend = [];
@@ -389,14 +394,16 @@ export async function createCheckoutOrderAction(input) {
389
394
  items: orderItems,
390
395
  orderType,
391
396
  offerId: firstItem.offerId ?? undefined,
392
- status: OrderStatusValues.PENDING,
393
- paymentStatus: PaymentStatusValues.PENDING,
394
- paymentMethod,
397
+ status: adminBypass ? OrderStatusValues.PROCESSING : OrderStatusValues.PENDING,
398
+ paymentStatus: adminBypass ? PaymentStatusValues.PAID : PaymentStatusValues.PENDING,
399
+ paymentMethod: adminBypass ? PaymentMethodValues.ADMIN_BYPASS : paymentMethod,
400
+ paymentId: adminBatchId,
401
+ adminBypassBy: adminBypassBy,
395
402
  shippingAddress,
396
403
  notes,
397
404
  shippingFee: shippingFee > 0 ? shippingFee : undefined,
398
- depositAmount,
399
- codRemainingAmount,
405
+ depositAmount: adminBypass ? undefined : depositAmount,
406
+ codRemainingAmount: adminBypass ? undefined : codRemainingAmount,
400
407
  couponCode: appliedDiscounts[0]?.code,
401
408
  couponDiscount: couponDiscount > 0 ? couponDiscount : undefined,
402
409
  appliedDiscounts: appliedDiscounts.length > 0 ? appliedDiscounts : undefined,
@@ -429,7 +436,7 @@ export async function createCheckoutOrderAction(input) {
429
436
  buyerName: userName,
430
437
  storeOwnerId,
431
438
  productLabel: orderItems.length > 1 ? `${orderItems.length} items` : firstItem.productTitle,
432
- paid: false,
439
+ paid: adminBypass,
433
440
  });
434
441
  }
435
442
  if (couponUsageAccumulator.size > 0) {
@@ -487,7 +494,8 @@ export async function attachPaymentAction(input) {
487
494
  export async function verifyAndPlaceRazorpayOrderAction(input) {
488
495
  const { userId: uid, userName, userEmail, razorpay_order_id, razorpay_payment_id, razorpay_signature, addressId, notes, } = input;
489
496
  const siteSettings = await siteSettingsRepository.getSingleton();
490
- const razorpayFeePercent = siteSettings?.commissions?.razorpayFeePercent ?? 5;
497
+ const platformFeePercent = siteSettings?.commissions?.platformFeePercent ?? 5;
498
+ const gstPercent = siteSettings?.commissions?.gstPercent ?? 18;
491
499
  const commissions = siteSettings?.commissions ?? CHECKOUT_DEFAULT_COMMISSIONS;
492
500
  const isValid = await verifyPaymentSignatureWithKeys({
493
501
  razorpay_order_id,
@@ -596,8 +604,9 @@ export async function verifyAndPlaceRazorpayOrderAction(input) {
596
604
  const unit = isBundle ? item.price : product.price;
597
605
  return sum + unit * item.quantity;
598
606
  }, 0);
599
- const expectedPlatformFee = Math.round(cartSubtotalRs * (razorpayFeePercent / 100) * 100) / 100;
600
- const expectedPaymentAmountRs = cartSubtotalRs + expectedPlatformFee;
607
+ const expectedPlatformFee = Math.round(cartSubtotalRs * (platformFeePercent / 100) * 100) / 100;
608
+ const expectedGstOnFee = Math.round(expectedPlatformFee * (gstPercent / 100) * 100) / 100;
609
+ const expectedPaymentAmountRs = cartSubtotalRs + expectedPlatformFee + expectedGstOnFee;
601
610
  const rzpOrderRecord = await fetchRazorpayOrder(razorpay_order_id);
602
611
  const paidAmountRs = paiseToRupees(rzpOrderRecord.amount);
603
612
  if (paidAmountRs < expectedPaymentAmountRs - 1) {
@@ -684,7 +693,9 @@ export async function verifyAndPlaceRazorpayOrderAction(input) {
684
693
  }
685
694
  }
686
695
  couponDiscount = Math.min(couponDiscount, groupTotal);
687
- const platformFee = Math.round(groupTotal * (razorpayFeePercent / 100) * 100) / 100;
696
+ const rawPlatformFee = Math.round(groupTotal * (platformFeePercent / 100) * 100) / 100;
697
+ const gstOnFee = Math.round(rawPlatformFee * (gstPercent / 100) * 100) / 100;
698
+ const platformFee = rawPlatformFee + gstOnFee;
688
699
  const orderTotal = Math.max(0, groupTotal - couponDiscount) + shippingFee;
689
700
  total += orderTotal;
690
701
  // S-SBUNI-RULES 2026-05-13 — order-item decoration via rule registry.
@@ -1,11 +1,13 @@
1
1
  export interface ResolvedPaymentFee {
2
2
  baseAmount: number;
3
3
  platformFee: number;
4
+ gstOnFee: number;
4
5
  totalAmount: number;
5
6
  }
6
7
  /**
7
- * Returns the cart total inflated by the configured Razorpay processing fee.
8
- * The percent comes from siteSettings.commissions.razorpayFeePercent and
9
- * falls back to the default if unset.
8
+ * Inflates the cart total by our platform fee + GST on that fee.
9
+ * platformFeePercent = siteSettings.commissions.platformFeePercent (our cut %).
10
+ * gstPercent = siteSettings.commissions.gstPercent (18% GST on our fee, not on full order).
11
+ * minimumTransactionFee = per-gateway-transaction floor in rupees.
10
12
  */
11
13
  export declare function resolvePaymentFee(baseAmount: number): Promise<ResolvedPaymentFee>;
@@ -1,14 +1,19 @@
1
1
  import { siteSettingsRepository } from "../../../../repositories";
2
- import { PAYMENTS_DEFAULT_RAZORPAY_FEE_PERCENT } from "../../../shared/features/payments/config";
2
+ import { PAYMENTS_DEFAULT_PLATFORM_FEE_PERCENT, PAYMENTS_DEFAULT_GST_PERCENT, } from "../../../shared/features/payments/config";
3
3
  /**
4
- * Returns the cart total inflated by the configured Razorpay processing fee.
5
- * The percent comes from siteSettings.commissions.razorpayFeePercent and
6
- * falls back to the default if unset.
4
+ * Inflates the cart total by our platform fee + GST on that fee.
5
+ * platformFeePercent = siteSettings.commissions.platformFeePercent (our cut %).
6
+ * gstPercent = siteSettings.commissions.gstPercent (18% GST on our fee, not on full order).
7
+ * minimumTransactionFee = per-gateway-transaction floor in rupees.
7
8
  */
8
9
  export async function resolvePaymentFee(baseAmount) {
9
10
  const siteSettings = await siteSettingsRepository.getSingleton();
10
- const razorpayFeePercent = siteSettings?.commissions?.razorpayFeePercent ?? PAYMENTS_DEFAULT_RAZORPAY_FEE_PERCENT;
11
- const platformFee = Math.round(baseAmount * (razorpayFeePercent / 100) * 100) / 100;
12
- const totalAmount = baseAmount + platformFee;
13
- return { baseAmount, platformFee, totalAmount };
11
+ const platformFeePercent = siteSettings?.commissions?.platformFeePercent ?? PAYMENTS_DEFAULT_PLATFORM_FEE_PERCENT;
12
+ const gstPercent = siteSettings?.commissions?.gstPercent ?? PAYMENTS_DEFAULT_GST_PERCENT;
13
+ const minimumTransactionFee = Math.max(0, siteSettings?.commissions?.minimumTransactionFee ?? 0);
14
+ const platformFee = Math.round(baseAmount * (platformFeePercent / 100) * 100) / 100;
15
+ const gstOnFee = Math.round(platformFee * (gstPercent / 100) * 100) / 100;
16
+ const rawTotal = baseAmount + platformFee + gstOnFee;
17
+ const totalAmount = Math.max(rawTotal, baseAmount + minimumTransactionFee);
18
+ return { baseAmount, platformFee, gstOnFee, totalAmount };
14
19
  }
@@ -1,3 +1,3 @@
1
1
  export { createPaymentIntentAction, verifyPaymentSignatureAction, type CreatePaymentIntentInput, type CreatePaymentIntentResult, type VerifyPaymentSignatureInput, } from "./actions";
2
2
  export { resolvePaymentFee, type ResolvedPaymentFee } from "./data";
3
- export { PAYMENTS_DEFAULT_RAZORPAY_FEE_PERCENT, PAYMENTS_RECEIPT_PREFIX, } from "../../../shared/features/payments/config";
3
+ export { PAYMENTS_DEFAULT_PLATFORM_FEE_PERCENT, PAYMENTS_DEFAULT_GST_PERCENT, PAYMENTS_RECEIPT_PREFIX, } from "../../../shared/features/payments/config";
@@ -1,3 +1,3 @@
1
1
  export { createPaymentIntentAction, verifyPaymentSignatureAction, } from "./actions";
2
2
  export { resolvePaymentFee } from "./data";
3
- export { PAYMENTS_DEFAULT_RAZORPAY_FEE_PERCENT, PAYMENTS_RECEIPT_PREFIX, } from "../../../shared/features/payments/config";
3
+ export { PAYMENTS_DEFAULT_PLATFORM_FEE_PERCENT, PAYMENTS_DEFAULT_GST_PERCENT, PAYMENTS_RECEIPT_PREFIX, } from "../../../shared/features/payments/config";
@@ -15,9 +15,15 @@ export function buildManifest({ name, shortName, description }) {
15
15
  prefer_related_applications: false,
16
16
  icons: [
17
17
  {
18
- src: "/favicon.svg",
19
- sizes: "any",
20
- type: "image/svg+xml",
18
+ src: "/favicon/android-chrome-192x192.png",
19
+ sizes: "192x192",
20
+ type: "image/png",
21
+ purpose: "any maskable",
22
+ },
23
+ {
24
+ src: "/favicon/android-chrome-512x512.png",
25
+ sizes: "512x512",
26
+ type: "image/png",
21
27
  purpose: "any maskable",
22
28
  },
23
29
  ],
@@ -15,3 +15,27 @@ export declare const BUNDLE_MAX_PER_USER_DEFAULT: 3;
15
15
  export declare const BUNDLES_PAGE_SIZE: 24;
16
16
  /** Cap on homepage featured-bundles strip. */
17
17
  export declare const BUNDLES_FEATURED_LIMIT: 12;
18
+ /**
19
+ * Listing types that may be added to a bundle.
20
+ * - standard — regular product
21
+ * - pre-order — pre-order product
22
+ * - prize-draw — added as N draws/entries (see BundleItemDetail.drawCount)
23
+ *
24
+ * Auctions and bundles themselves are never eligible.
25
+ */
26
+ export declare const BUNDLE_ELIGIBLE_LISTING_TYPES: readonly ["standard", "pre-order", "prize-draw"];
27
+ export type BundleEligibleListingType = (typeof BUNDLE_ELIGIBLE_LISTING_TYPES)[number];
28
+ /**
29
+ * "special" — explicitly curated admin bundle. Only special bundles write
30
+ * reverse `partOfBundleIds` pointers on member products and are displayed in
31
+ * the "Bundled" badge on product cards / detail pages.
32
+ *
33
+ * "brand" — auto-generated collection driven by a dynamic brand query rule.
34
+ * Does NOT update `partOfBundleIds` on products; treated as a discovery
35
+ * surface, not a priced product grouping.
36
+ */
37
+ export declare const BUNDLE_KIND_SPECIAL: "special";
38
+ export declare const BUNDLE_KIND_BRAND: "brand";
39
+ export type BundleKind = typeof BUNDLE_KIND_SPECIAL | typeof BUNDLE_KIND_BRAND;
40
+ /** Max draw entries a prize-draw item contributes when added to a bundle. */
41
+ export declare const BUNDLE_DRAW_COUNT_MAX: 500;
@@ -15,3 +15,31 @@ export const BUNDLE_MAX_PER_USER_DEFAULT = 3;
15
15
  export const BUNDLES_PAGE_SIZE = 24;
16
16
  /** Cap on homepage featured-bundles strip. */
17
17
  export const BUNDLES_FEATURED_LIMIT = 12;
18
+ // ── Bundle eligibility ───────────────────────────────────────────────────────
19
+ /**
20
+ * Listing types that may be added to a bundle.
21
+ * - standard — regular product
22
+ * - pre-order — pre-order product
23
+ * - prize-draw — added as N draws/entries (see BundleItemDetail.drawCount)
24
+ *
25
+ * Auctions and bundles themselves are never eligible.
26
+ */
27
+ export const BUNDLE_ELIGIBLE_LISTING_TYPES = [
28
+ "standard",
29
+ "pre-order",
30
+ "prize-draw",
31
+ ];
32
+ // ── Bundle kinds ─────────────────────────────────────────────────────────────
33
+ /**
34
+ * "special" — explicitly curated admin bundle. Only special bundles write
35
+ * reverse `partOfBundleIds` pointers on member products and are displayed in
36
+ * the "Bundled" badge on product cards / detail pages.
37
+ *
38
+ * "brand" — auto-generated collection driven by a dynamic brand query rule.
39
+ * Does NOT update `partOfBundleIds` on products; treated as a discovery
40
+ * surface, not a priced product grouping.
41
+ */
42
+ export const BUNDLE_KIND_SPECIAL = "special";
43
+ export const BUNDLE_KIND_BRAND = "brand";
44
+ /** Max draw entries a prize-draw item contributes when added to a bundle. */
45
+ export const BUNDLE_DRAW_COUNT_MAX = 500;
@@ -86,6 +86,13 @@ export declare const BUNDLE_COPY: {
86
86
  readonly ruleTypeLabel: "Bundle members source";
87
87
  readonly ruleTypeStatic: "Hand-picked products";
88
88
  readonly ruleTypeDynamic: "Dynamic query (auto-resolves)";
89
+ readonly bundleKindLabel: "Bundle kind";
90
+ readonly bundleKindHint: "Special bundles show the 'Bundled' badge on product cards and update member product metadata. Brand bundles are discovery collections only.";
91
+ readonly bundleKindSpecial: "Special bundle";
92
+ readonly bundleKindBrand: "Brand collection";
93
+ readonly drawCountLabel: "Draw entries included";
94
+ readonly drawCountHint: "How many raffle entries the buyer receives for this prize-draw when purchasing the bundle.";
95
+ readonly drawCountPlaceholder: "e.g. 5";
89
96
  readonly dynamic: {
90
97
  readonly title: "Dynamic query rule";
91
98
  readonly hint: "onProductStockChange resolves bundleProductIds; bundleQueryResolvedAt tracks the last refresh.";
@@ -96,6 +96,15 @@ export const BUNDLE_COPY = {
96
96
  ruleTypeLabel: "Bundle members source",
97
97
  ruleTypeStatic: "Hand-picked products",
98
98
  ruleTypeDynamic: "Dynamic query (auto-resolves)",
99
+ // Bundle kind
100
+ bundleKindLabel: "Bundle kind",
101
+ bundleKindHint: "Special bundles show the 'Bundled' badge on product cards and update member product metadata. Brand bundles are discovery collections only.",
102
+ bundleKindSpecial: "Special bundle",
103
+ bundleKindBrand: "Brand collection",
104
+ // Prize-draw draw count
105
+ drawCountLabel: "Draw entries included",
106
+ drawCountHint: "How many raffle entries the buyer receives for this prize-draw when purchasing the bundle.",
107
+ drawCountPlaceholder: "e.g. 5",
99
108
  dynamic: {
100
109
  title: "Dynamic query rule",
101
110
  hint: "onProductStockChange resolves bundleProductIds; bundleQueryResolvedAt tracks the last refresh.",
@@ -23,15 +23,16 @@ export declare const bundleQueryRuleSchema: z.ZodDiscriminatedUnion<"type", [z.Z
23
23
  categorySlug: z.ZodOptional<z.ZodString>;
24
24
  brandSlug: z.ZodOptional<z.ZodString>;
25
25
  tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
26
- listingType: z.ZodOptional<z.ZodEnum<["standard", "pre-order"]>>;
26
+ /** Eligible listing types for a dynamic bundle rule. */
27
+ listingType: z.ZodOptional<z.ZodEnum<["standard", "pre-order", "prize-draw"]>>;
27
28
  }, "strip", z.ZodTypeAny, {
28
29
  tags?: string[] | undefined;
29
- listingType?: "standard" | "pre-order" | undefined;
30
+ listingType?: "standard" | "pre-order" | "prize-draw" | undefined;
30
31
  categorySlug?: string | undefined;
31
32
  brandSlug?: string | undefined;
32
33
  }, {
33
34
  tags?: string[] | undefined;
34
- listingType?: "standard" | "pre-order" | undefined;
35
+ listingType?: "standard" | "pre-order" | "prize-draw" | undefined;
35
36
  categorySlug?: string | undefined;
36
37
  brandSlug?: string | undefined;
37
38
  }>;
@@ -40,7 +41,7 @@ export declare const bundleQueryRuleSchema: z.ZodDiscriminatedUnion<"type", [z.Z
40
41
  }, "strip", z.ZodTypeAny, {
41
42
  filter: {
42
43
  tags?: string[] | undefined;
43
- listingType?: "standard" | "pre-order" | undefined;
44
+ listingType?: "standard" | "pre-order" | "prize-draw" | undefined;
44
45
  categorySlug?: string | undefined;
45
46
  brandSlug?: string | undefined;
46
47
  };
@@ -50,7 +51,7 @@ export declare const bundleQueryRuleSchema: z.ZodDiscriminatedUnion<"type", [z.Z
50
51
  }, {
51
52
  filter: {
52
53
  tags?: string[] | undefined;
53
- listingType?: "standard" | "pre-order" | undefined;
54
+ listingType?: "standard" | "pre-order" | "prize-draw" | undefined;
54
55
  categorySlug?: string | undefined;
55
56
  brandSlug?: string | undefined;
56
57
  };
@@ -75,6 +76,11 @@ export declare const bundleCreateSchema: z.ZodObject<{
75
76
  name: z.ZodString;
76
77
  slug: z.ZodOptional<z.ZodString>;
77
78
  description: z.ZodOptional<z.ZodString>;
79
+ /**
80
+ * "special" — curated bundle; updates partOfBundleIds on member products.
81
+ * "brand" — brand discovery collection; does not touch product reverse pointers.
82
+ */
83
+ bundleKind: z.ZodEnum<["special", "brand"]>;
78
84
  bundlePriceInPaise: z.ZodNumber;
79
85
  bundleQueryRule: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
80
86
  type: z.ZodLiteral<"static">;
@@ -91,15 +97,16 @@ export declare const bundleCreateSchema: z.ZodObject<{
91
97
  categorySlug: z.ZodOptional<z.ZodString>;
92
98
  brandSlug: z.ZodOptional<z.ZodString>;
93
99
  tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
94
- listingType: z.ZodOptional<z.ZodEnum<["standard", "pre-order"]>>;
100
+ /** Eligible listing types for a dynamic bundle rule. */
101
+ listingType: z.ZodOptional<z.ZodEnum<["standard", "pre-order", "prize-draw"]>>;
95
102
  }, "strip", z.ZodTypeAny, {
96
103
  tags?: string[] | undefined;
97
- listingType?: "standard" | "pre-order" | undefined;
104
+ listingType?: "standard" | "pre-order" | "prize-draw" | undefined;
98
105
  categorySlug?: string | undefined;
99
106
  brandSlug?: string | undefined;
100
107
  }, {
101
108
  tags?: string[] | undefined;
102
- listingType?: "standard" | "pre-order" | undefined;
109
+ listingType?: "standard" | "pre-order" | "prize-draw" | undefined;
103
110
  categorySlug?: string | undefined;
104
111
  brandSlug?: string | undefined;
105
112
  }>;
@@ -108,7 +115,7 @@ export declare const bundleCreateSchema: z.ZodObject<{
108
115
  }, "strip", z.ZodTypeAny, {
109
116
  filter: {
110
117
  tags?: string[] | undefined;
111
- listingType?: "standard" | "pre-order" | undefined;
118
+ listingType?: "standard" | "pre-order" | "prize-draw" | undefined;
112
119
  categorySlug?: string | undefined;
113
120
  brandSlug?: string | undefined;
114
121
  };
@@ -118,7 +125,7 @@ export declare const bundleCreateSchema: z.ZodObject<{
118
125
  }, {
119
126
  filter: {
120
127
  tags?: string[] | undefined;
121
- listingType?: "standard" | "pre-order" | undefined;
128
+ listingType?: "standard" | "pre-order" | "prize-draw" | undefined;
122
129
  categorySlug?: string | undefined;
123
130
  brandSlug?: string | undefined;
124
131
  };
@@ -127,6 +134,17 @@ export declare const bundleCreateSchema: z.ZodObject<{
127
134
  orderBy?: "price-asc" | "price-desc" | "createdAt-desc" | undefined;
128
135
  }>]>;
129
136
  bundleProductIds: z.ZodArray<z.ZodString, "many">;
137
+ /** Richer per-member metadata (draw counts for prize-draws). */
138
+ bundleItemDetails: z.ZodOptional<z.ZodArray<z.ZodObject<{
139
+ productId: z.ZodString;
140
+ drawCount: z.ZodOptional<z.ZodNumber>;
141
+ }, "strip", z.ZodTypeAny, {
142
+ productId: string;
143
+ drawCount?: number | undefined;
144
+ }, {
145
+ productId: string;
146
+ drawCount?: number | undefined;
147
+ }>, "many">>;
130
148
  display: z.ZodOptional<z.ZodObject<{
131
149
  coverImage: z.ZodOptional<z.ZodString>;
132
150
  icon: z.ZodOptional<z.ZodString>;
@@ -144,6 +162,7 @@ export declare const bundleCreateSchema: z.ZodObject<{
144
162
  }, "strip", z.ZodTypeAny, {
145
163
  name: string;
146
164
  bundleProductIds: string[];
165
+ bundleKind: "brand" | "special";
147
166
  bundlePriceInPaise: number;
148
167
  bundleQueryRule: {
149
168
  type: "static";
@@ -151,7 +170,7 @@ export declare const bundleCreateSchema: z.ZodObject<{
151
170
  } | {
152
171
  filter: {
153
172
  tags?: string[] | undefined;
154
- listingType?: "standard" | "pre-order" | undefined;
173
+ listingType?: "standard" | "pre-order" | "prize-draw" | undefined;
155
174
  categorySlug?: string | undefined;
156
175
  brandSlug?: string | undefined;
157
176
  };
@@ -167,9 +186,14 @@ export declare const bundleCreateSchema: z.ZodObject<{
167
186
  isActive?: boolean | undefined;
168
187
  description?: string | undefined;
169
188
  slug?: string | undefined;
189
+ bundleItemDetails?: {
190
+ productId: string;
191
+ drawCount?: number | undefined;
192
+ }[] | undefined;
170
193
  }, {
171
194
  name: string;
172
195
  bundleProductIds: string[];
196
+ bundleKind: "brand" | "special";
173
197
  bundlePriceInPaise: number;
174
198
  bundleQueryRule: {
175
199
  type: "static";
@@ -177,7 +201,7 @@ export declare const bundleCreateSchema: z.ZodObject<{
177
201
  } | {
178
202
  filter: {
179
203
  tags?: string[] | undefined;
180
- listingType?: "standard" | "pre-order" | undefined;
204
+ listingType?: "standard" | "pre-order" | "prize-draw" | undefined;
181
205
  categorySlug?: string | undefined;
182
206
  brandSlug?: string | undefined;
183
207
  };
@@ -193,10 +217,15 @@ export declare const bundleCreateSchema: z.ZodObject<{
193
217
  isActive?: boolean | undefined;
194
218
  description?: string | undefined;
195
219
  slug?: string | undefined;
220
+ bundleItemDetails?: {
221
+ productId: string;
222
+ drawCount?: number | undefined;
223
+ }[] | undefined;
196
224
  }>;
197
225
  export declare const bundleUpdateSchema: z.ZodObject<{
198
226
  name: z.ZodOptional<z.ZodString>;
199
227
  description: z.ZodOptional<z.ZodString>;
228
+ bundleKind: z.ZodOptional<z.ZodEnum<["special", "brand"]>>;
200
229
  bundlePriceInPaise: z.ZodOptional<z.ZodNumber>;
201
230
  bundleQueryRule: z.ZodOptional<z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
202
231
  type: z.ZodLiteral<"static">;
@@ -213,15 +242,16 @@ export declare const bundleUpdateSchema: z.ZodObject<{
213
242
  categorySlug: z.ZodOptional<z.ZodString>;
214
243
  brandSlug: z.ZodOptional<z.ZodString>;
215
244
  tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
216
- listingType: z.ZodOptional<z.ZodEnum<["standard", "pre-order"]>>;
245
+ /** Eligible listing types for a dynamic bundle rule. */
246
+ listingType: z.ZodOptional<z.ZodEnum<["standard", "pre-order", "prize-draw"]>>;
217
247
  }, "strip", z.ZodTypeAny, {
218
248
  tags?: string[] | undefined;
219
- listingType?: "standard" | "pre-order" | undefined;
249
+ listingType?: "standard" | "pre-order" | "prize-draw" | undefined;
220
250
  categorySlug?: string | undefined;
221
251
  brandSlug?: string | undefined;
222
252
  }, {
223
253
  tags?: string[] | undefined;
224
- listingType?: "standard" | "pre-order" | undefined;
254
+ listingType?: "standard" | "pre-order" | "prize-draw" | undefined;
225
255
  categorySlug?: string | undefined;
226
256
  brandSlug?: string | undefined;
227
257
  }>;
@@ -230,7 +260,7 @@ export declare const bundleUpdateSchema: z.ZodObject<{
230
260
  }, "strip", z.ZodTypeAny, {
231
261
  filter: {
232
262
  tags?: string[] | undefined;
233
- listingType?: "standard" | "pre-order" | undefined;
263
+ listingType?: "standard" | "pre-order" | "prize-draw" | undefined;
234
264
  categorySlug?: string | undefined;
235
265
  brandSlug?: string | undefined;
236
266
  };
@@ -240,7 +270,7 @@ export declare const bundleUpdateSchema: z.ZodObject<{
240
270
  }, {
241
271
  filter: {
242
272
  tags?: string[] | undefined;
243
- listingType?: "standard" | "pre-order" | undefined;
273
+ listingType?: "standard" | "pre-order" | "prize-draw" | undefined;
244
274
  categorySlug?: string | undefined;
245
275
  brandSlug?: string | undefined;
246
276
  };
@@ -249,6 +279,16 @@ export declare const bundleUpdateSchema: z.ZodObject<{
249
279
  orderBy?: "price-asc" | "price-desc" | "createdAt-desc" | undefined;
250
280
  }>]>>;
251
281
  bundleProductIds: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
282
+ bundleItemDetails: z.ZodOptional<z.ZodArray<z.ZodObject<{
283
+ productId: z.ZodString;
284
+ drawCount: z.ZodOptional<z.ZodNumber>;
285
+ }, "strip", z.ZodTypeAny, {
286
+ productId: string;
287
+ drawCount?: number | undefined;
288
+ }, {
289
+ productId: string;
290
+ drawCount?: number | undefined;
291
+ }>, "many">>;
252
292
  display: z.ZodOptional<z.ZodObject<{
253
293
  coverImage: z.ZodOptional<z.ZodString>;
254
294
  icon: z.ZodOptional<z.ZodString>;
@@ -273,6 +313,7 @@ export declare const bundleUpdateSchema: z.ZodObject<{
273
313
  isActive?: boolean | undefined;
274
314
  description?: string | undefined;
275
315
  bundleProductIds?: string[] | undefined;
316
+ bundleKind?: "brand" | "special" | undefined;
276
317
  bundlePriceInPaise?: number | undefined;
277
318
  bundleQueryRule?: {
278
319
  type: "static";
@@ -280,7 +321,7 @@ export declare const bundleUpdateSchema: z.ZodObject<{
280
321
  } | {
281
322
  filter: {
282
323
  tags?: string[] | undefined;
283
- listingType?: "standard" | "pre-order" | undefined;
324
+ listingType?: "standard" | "pre-order" | "prize-draw" | undefined;
284
325
  categorySlug?: string | undefined;
285
326
  brandSlug?: string | undefined;
286
327
  };
@@ -288,6 +329,10 @@ export declare const bundleUpdateSchema: z.ZodObject<{
288
329
  limit: number;
289
330
  orderBy?: "price-asc" | "price-desc" | "createdAt-desc" | undefined;
290
331
  } | undefined;
332
+ bundleItemDetails?: {
333
+ productId: string;
334
+ drawCount?: number | undefined;
335
+ }[] | undefined;
291
336
  }, {
292
337
  name?: string | undefined;
293
338
  display?: {
@@ -298,6 +343,7 @@ export declare const bundleUpdateSchema: z.ZodObject<{
298
343
  isActive?: boolean | undefined;
299
344
  description?: string | undefined;
300
345
  bundleProductIds?: string[] | undefined;
346
+ bundleKind?: "brand" | "special" | undefined;
301
347
  bundlePriceInPaise?: number | undefined;
302
348
  bundleQueryRule?: {
303
349
  type: "static";
@@ -305,7 +351,7 @@ export declare const bundleUpdateSchema: z.ZodObject<{
305
351
  } | {
306
352
  filter: {
307
353
  tags?: string[] | undefined;
308
- listingType?: "standard" | "pre-order" | undefined;
354
+ listingType?: "standard" | "pre-order" | "prize-draw" | undefined;
309
355
  categorySlug?: string | undefined;
310
356
  brandSlug?: string | undefined;
311
357
  };
@@ -313,6 +359,10 @@ export declare const bundleUpdateSchema: z.ZodObject<{
313
359
  limit: number;
314
360
  orderBy?: "price-asc" | "price-desc" | "createdAt-desc" | undefined;
315
361
  } | undefined;
362
+ bundleItemDetails?: {
363
+ productId: string;
364
+ drawCount?: number | undefined;
365
+ }[] | undefined;
316
366
  }>;
317
367
  export type BundleCreateBody = z.infer<typeof bundleCreateSchema>;
318
368
  export type BundleUpdateBody = z.infer<typeof bundleUpdateSchema>;