@mohasinac/appkit 3.5.6 → 3.5.8

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 (59) hide show
  1. package/dist/_internal/client/features/tour/TourProvider.d.ts +11 -3
  2. package/dist/_internal/client/features/tour/TourProvider.js +93 -4
  3. package/dist/_internal/server/features/checkout/actions.js +65 -11
  4. package/dist/_internal/server/features/orders/adapters.js +3 -0
  5. package/dist/_internal/shared/actions/action-registry.js +20 -0
  6. package/dist/_internal/shared/checkout/order-math.d.ts +21 -0
  7. package/dist/_internal/shared/checkout/order-math.js +24 -0
  8. package/dist/_internal/shared/features/orders/config.d.ts +1 -1
  9. package/dist/_internal/shared/features/orders/config.js +1 -1
  10. package/dist/_internal/shared/fees/calculator.d.ts +13 -0
  11. package/dist/_internal/shared/fees/calculator.js +8 -0
  12. package/dist/client.d.ts +8 -0
  13. package/dist/client.js +8 -0
  14. package/dist/constants/api-endpoints.d.ts +9 -0
  15. package/dist/constants/api-endpoints.js +3 -0
  16. package/dist/features/admin/components/AdminBundleEditorView.d.ts +8 -1
  17. package/dist/features/admin/components/AdminBundleEditorView.js +15 -11
  18. package/dist/features/admin/components/AdminPayoutsView.js +41 -4
  19. package/dist/features/admin/components/AdminSiteSettingsView.js +49 -1
  20. package/dist/features/admin/schemas/firestore.d.ts +8 -0
  21. package/dist/features/admin/schemas/firestore.js +6 -0
  22. package/dist/features/layout/TitleBarLayout.js +4 -4
  23. package/dist/features/messages/hooks/useConversations.d.ts +7 -1
  24. package/dist/features/messages/hooks/useConversations.js +7 -6
  25. package/dist/features/orders/actions/order-actions.d.ts +10 -0
  26. package/dist/features/orders/actions/order-actions.js +57 -4
  27. package/dist/features/orders/schemas/firestore.d.ts +16 -0
  28. package/dist/features/orders/types/index.d.ts +2 -0
  29. package/dist/features/products/schemas/firestore.d.ts +4 -0
  30. package/dist/features/scams/actions/scam-actions.d.ts +12 -0
  31. package/dist/features/scams/actions/scam-actions.js +31 -0
  32. package/dist/features/scams/components/SellerTrustBadge.d.ts +16 -0
  33. package/dist/features/scams/components/SellerTrustBadge.js +21 -0
  34. package/dist/features/scams/components/index.d.ts +2 -0
  35. package/dist/features/scams/components/index.js +1 -0
  36. package/dist/features/seller/components/SellerBundlesView.d.ts +5 -5
  37. package/dist/features/seller/components/SellerBundlesView.js +81 -84
  38. package/dist/features/seller/components/SellerProductShell.d.ts +2 -0
  39. package/dist/features/seller/components/SellerProductShell.js +7 -1
  40. package/dist/features/stores/components/StoreDetailLayoutView.d.ts +8 -1
  41. package/dist/features/stores/components/StoreDetailLayoutView.js +9 -3
  42. package/dist/features/stores/components/StoreHeader.d.ts +4 -1
  43. package/dist/features/stores/components/StoreHeader.js +3 -2
  44. package/dist/index.d.ts +7 -3
  45. package/dist/index.js +6 -2
  46. package/dist/jobs.d.ts +2 -0
  47. package/dist/jobs.js +6 -0
  48. package/dist/next/routing/route-map.d.ts +3 -0
  49. package/dist/next/routing/route-map.js +1 -0
  50. package/dist/security/rate-limit.d.ts +5 -0
  51. package/dist/security/rate-limit.js +2 -0
  52. package/dist/seed/categories-seed-data.js +180 -1
  53. package/dist/seed/grouped-listings-seed-data.d.ts +2 -0
  54. package/dist/seed/products-preorders-seed-data.d.ts +2 -0
  55. package/dist/server.d.ts +4 -0
  56. package/dist/server.js +5 -0
  57. package/dist/styles.css +11 -0
  58. package/dist/ui/components/Tabs.style.css +11 -0
  59. package/package.json +2 -1
@@ -1,11 +1,19 @@
1
1
  import React from "react";
2
+ import type { DriveStep } from "driver.js";
3
+ export type TourRole = "buyer" | "seller" | "admin";
2
4
  export interface TourContextValue {
3
- startTour: () => void;
5
+ /** Starts the tour for the given role (defaults to "buyer"). */
6
+ startTour: (role?: TourRole) => void;
4
7
  }
5
8
  export declare function useTour(): TourContextValue;
9
+ export declare const CUSTOMER_TOUR_STEPS: DriveStep[];
10
+ export declare const SELLER_TOUR_STEPS: DriveStep[];
11
+ export declare const ADMIN_TOUR_STEPS: DriveStep[];
6
12
  /**
7
- * TourProvider — skeleton shell for Patch 1.
8
- * driver.js steps are wired in Patch 2+; this provider is an identity wrapper.
13
+ * TourProvider — lazily imports driver.js on first use so it never lands in
14
+ * the initial bundle. Respects prefers-reduced-motion by disabling driver.js's
15
+ * highlight animation (the tour itself — step-to-step navigation — still
16
+ * works; only the animated transition is skipped).
9
17
  */
10
18
  export declare function TourProvider({ children }: {
11
19
  children: React.ReactNode;
@@ -7,13 +7,102 @@ const TourContext = React.createContext({
7
7
  export function useTour() {
8
8
  return React.useContext(TourContext);
9
9
  }
10
+ const step = (element, title, description) => ({
11
+ element,
12
+ popover: { title, description },
13
+ skipMissingElement: true,
14
+ });
15
+ // P-16 — role-specific step sets. Anchors are `data-tour="…"` attributes on
16
+ // TitleBarLayout's icon buttons (present on every page) plus a couple of
17
+ // dashboard-specific anchors. `skipMissingElement: true` on every step means
18
+ // a step whose target isn't on the current page is silently skipped rather
19
+ // than breaking the tour — the same tour can be started from anywhere.
20
+ export const CUSTOMER_TOUR_STEPS = [
21
+ step('[data-tour="nav-search"]', "Search", "Search by name, brand, or price to find what you're after."),
22
+ step('[data-tour="nav-wishlist"]', "Wishlist", "Save items here to come back to them later."),
23
+ step('[data-tour="nav-cart"]', "Cart", "Review your items and check out when you're ready."),
24
+ step('[data-tour="nav-profile"]', "Your account", "Manage your profile, orders, and messages here."),
25
+ {
26
+ popover: {
27
+ title: "Checkout",
28
+ description: "At checkout, pay via UPI, cash, EMI, or COD — whatever suits you.",
29
+ },
30
+ skipMissingElement: true,
31
+ },
32
+ {
33
+ popover: {
34
+ title: "Track your orders",
35
+ description: "Every order's status, tracking, and invoice lives in My Orders.",
36
+ },
37
+ skipMissingElement: true,
38
+ },
39
+ ];
40
+ export const SELLER_TOUR_STEPS = [
41
+ step('[data-tour="store-products"]', "Your listings", "Create and manage your product listings here."),
42
+ {
43
+ popover: {
44
+ title: "Product form",
45
+ description: "Fill in details, images, and pricing — you can save as a draft anytime.",
46
+ },
47
+ skipMissingElement: true,
48
+ },
49
+ step('[data-tour="store-orders"]', "Incoming orders", "See and manage orders as buyers place them."),
50
+ {
51
+ popover: {
52
+ title: "Mark as shipped",
53
+ description: "Open an order to enter the carrier name and tracking number once it ships.",
54
+ },
55
+ skipMissingElement: true,
56
+ },
57
+ step('[data-tour="nav-profile"]', "Store settings", "Set up your store profile, shipping, and pickup address."),
58
+ step('[data-tour="store-analytics"]', "Analytics", "Track your sales, revenue, and top products."),
59
+ step('[data-tour="store-payouts"]', "Payouts", "See what you've earned and your payout history."),
60
+ ];
61
+ export const ADMIN_TOUR_STEPS = [
62
+ step('[data-tour="admin-orders"]', "Marketplace orders", "All orders across every store, in one place."),
63
+ {
64
+ popover: {
65
+ title: "Verify payments",
66
+ description: "Open an order to confirm manual payments and update its status.",
67
+ },
68
+ skipMissingElement: true,
69
+ },
70
+ step('[data-tour="admin-products"]', "Moderation", "Approve or reject listings before they go live."),
71
+ step('[data-tour="admin-users"]', "Users", "Manage buyer and seller accounts."),
72
+ step('[data-tour="admin-stores"]', "Stores", "Approve new store applications."),
73
+ step('[data-tour="admin-analytics"]', "Analytics", "Platform-wide GMV, orders, and top products."),
74
+ step('[data-tour="admin-settings"]', "Site settings", "Configure how the platform behaves."),
75
+ step('[data-tour="admin-feature-flags"]', "Feature flags", "Turn platform features on or off."),
76
+ ];
77
+ const STEP_SETS = {
78
+ buyer: CUSTOMER_TOUR_STEPS,
79
+ seller: SELLER_TOUR_STEPS,
80
+ admin: ADMIN_TOUR_STEPS,
81
+ };
10
82
  /**
11
- * TourProvider — skeleton shell for Patch 1.
12
- * driver.js steps are wired in Patch 2+; this provider is an identity wrapper.
83
+ * TourProvider — lazily imports driver.js on first use so it never lands in
84
+ * the initial bundle. Respects prefers-reduced-motion by disabling driver.js's
85
+ * highlight animation (the tour itself — step-to-step navigation — still
86
+ * works; only the animated transition is skipped).
13
87
  */
14
88
  export function TourProvider({ children }) {
15
- const startTour = React.useCallback(() => {
16
- // driver.js will be imported and configured here in Patch 2+.
89
+ const startTour = React.useCallback((role = "buyer") => {
90
+ if (typeof window === "undefined")
91
+ return;
92
+ void (async () => {
93
+ const [{ driver }] = await Promise.all([
94
+ import("driver.js"),
95
+ import("driver.js/dist/driver.css"),
96
+ ]);
97
+ const reducedMotion = window.matchMedia?.("(prefers-reduced-motion: reduce)")?.matches ?? false;
98
+ const config = {
99
+ showProgress: true,
100
+ animate: !reducedMotion,
101
+ allowClose: true,
102
+ steps: STEP_SETS[role],
103
+ };
104
+ driver(config).drive();
105
+ })();
17
106
  }, []);
18
107
  return (_jsx(TourContext.Provider, { value: { startTour }, children: children }));
19
108
  }
@@ -11,7 +11,9 @@ import { safeFireAndForget } from "../../../../utils/safe-fire-forget";
11
11
  import { ApiError, ValidationError, NotFoundError, ERROR_MESSAGES } from "../../../../errors";
12
12
  import { ORDER_FIELDS } from "../../../../constants/field-names";
13
13
  import { serverLogger } from "../../../../monitoring";
14
- import { unitOfWork, siteSettingsRepository, userRepository, storeRepository, couponsRepository, notificationRepository, claimedCouponsRepository, } from "../../../../repositories";
14
+ import { unitOfWork, siteSettingsRepository, userRepository, storeRepository, couponsRepository, notificationRepository, claimedCouponsRepository, addressesRepository, } from "../../../../repositories";
15
+ import { calculateGst } from "../../../shared/fees/calculator";
16
+ import { computePreOrderDepositAmount } from "../../../shared/checkout/order-math";
15
17
  import { failedCheckoutRepository } from "../../../../features/checkout/repository/failed-checkout.repository";
16
18
  import { sendOrderConfirmationEmail } from "../../../../features/contact/server";
17
19
  import { splitCartIntoOrderGroups } from "../../../../features/orders/index";
@@ -153,17 +155,24 @@ function accumulateCouponUsage(accumulator, couponCode, couponId, discountAmount
153
155
  }
154
156
  async function resolveShippingCost(storeId) {
155
157
  if (!storeId)
156
- return { shippingFee: 0, storeOwnerId: undefined, storeEmiEnabled: false };
158
+ return { shippingFee: 0, storeOwnerId: undefined, storeEmiEnabled: false, storeState: undefined };
157
159
  const store = await storeRepository.findById(storeId);
158
160
  const storeOwnerId = store?.ownerId;
159
161
  const storeEmiEnabled = store?.emiEnabled === true;
162
+ const storeState = await resolveStoreState(storeId);
160
163
  if (!storeOwnerId)
161
- return { shippingFee: 0, storeOwnerId: undefined, storeEmiEnabled };
164
+ return { shippingFee: 0, storeOwnerId: undefined, storeEmiEnabled, storeState };
162
165
  const sellerUser = await userRepository.findById(storeOwnerId);
163
166
  const shippingConfig = sellerUser?.shippingConfig;
164
167
  if (!shippingConfig?.isConfigured)
165
- return { shippingFee: 0, storeOwnerId, storeEmiEnabled };
166
- return { shippingFee: shippingConfig.customShippingPrice ?? 0, storeOwnerId, storeEmiEnabled };
168
+ return { shippingFee: 0, storeOwnerId, storeEmiEnabled, storeState };
169
+ return { shippingFee: shippingConfig.customShippingPrice ?? 0, storeOwnerId, storeEmiEnabled, storeState };
170
+ }
171
+ /** P-8 GST — the seller's registered/pickup state, used to determine intra- vs inter-state tax. */
172
+ async function resolveStoreState(storeId) {
173
+ const addresses = await addressesRepository.listByOwner("store", storeId);
174
+ const pickup = addresses.find((a) => a.isDefault) ?? addresses[0];
175
+ return pickup?.state;
167
176
  }
168
177
  function buildStockUpdatePayload(product, qtyDelta) {
169
178
  const lt = (product.listingType ?? "standard");
@@ -238,7 +247,7 @@ function unitPriceFor(item, product) {
238
247
  * so the caller can accumulate the checkout-wide `total`.
239
248
  */
240
249
  async function createOrderForGroup(group, orderType, ctx) {
241
- const { paymentMethod, emiTenureMonths, emiSettings, commissions, appliedCoupons, cartSubtotal, couponUsageAccumulator, uid, userName, userEmail, shippingAddress, notes, adminBypass, adminBypassBy, adminBatchId, orderIds, emailsToSend, outOfStockPolicy, droppedItems, } = ctx;
250
+ const { paymentMethod, emiTenureMonths, emiSettings, commissions, appliedCoupons, cartSubtotal, couponUsageAccumulator, uid, userName, userEmail, shippingAddress, notes, adminBypass, adminBypassBy, adminBatchId, orderIds, emailsToSend, outOfStockPolicy, droppedItems, buyerState, gstSettings, } = ctx;
242
251
  const firstItem = group[0].item;
243
252
  const firstProduct = group[0].product;
244
253
  const groupTotal = group.reduce((sum, { item, product }) => sum + unitPriceFor(item, product) * item.quantity, 0);
@@ -255,6 +264,9 @@ async function createOrderForGroup(group, orderType, ctx) {
255
264
  }
256
265
  : {};
257
266
  const unitPrice = unitPriceFor(item, product);
267
+ const gstFields = product.gstRate != null
268
+ ? { gstRate: product.gstRate, ...(product.hsnCode ? { hsnCode: product.hsnCode } : {}) }
269
+ : {};
258
270
  const baseLine = {
259
271
  productId: item.productId,
260
272
  productTitle: item.productTitle,
@@ -262,15 +274,41 @@ async function createOrderForGroup(group, orderType, ctx) {
262
274
  unitPrice,
263
275
  totalPrice: unitPrice * item.quantity,
264
276
  ...bundleFields,
277
+ ...gstFields,
265
278
  };
266
279
  return itemRule.decorateOrderItem(baseLine, product);
267
280
  });
268
281
  const totalQuantity = group.reduce((sum, { item }) => sum + item.quantity, 0);
269
- const { shippingFee, storeOwnerId, storeEmiEnabled } = await resolveShippingCost(firstItem.storeId);
282
+ const { shippingFee, storeOwnerId, storeEmiEnabled, storeState } = await resolveShippingCost(firstItem.storeId);
283
+ // P-8 GST — sum per-line-item tax (each product may carry its own gstRate),
284
+ // then split the group total into a single cgst/sgst/igst breakdown for the
285
+ // order document. Skipped entirely when GST is off or the buyer's state is
286
+ // unknown (digital-only carts have no shipping address).
287
+ let gstBreakdown;
288
+ if (gstSettings?.enabled && buyerState) {
289
+ const intraState = !!storeState && storeState === buyerState;
290
+ let taxableAmount = 0;
291
+ let gstAmount = 0;
292
+ for (const { item, product } of group) {
293
+ const lineTotal = unitPriceFor(item, product) * item.quantity;
294
+ const rate = product.gstRate ?? 0;
295
+ if (rate > 0) {
296
+ taxableAmount += lineTotal;
297
+ gstAmount += calculateGst(rate, intraState, lineTotal).gstAmount;
298
+ }
299
+ }
300
+ if (taxableAmount > 0) {
301
+ gstBreakdown = intraState
302
+ ? { taxableAmount, cgst: Math.round(gstAmount / 2), sgst: gstAmount - Math.round(gstAmount / 2), igst: 0, gstAmount }
303
+ : { taxableAmount, cgst: 0, sgst: 0, igst: gstAmount, gstAmount };
304
+ }
305
+ }
270
306
  const isCodLike = paymentMethod === "cod" || paymentMethod === "upi_manual" || paymentMethod === "cash";
271
- const depositAmount = isCodLike
272
- ? Math.round(groupTotal * (commissions.codDepositPercent / 100) * 100) / 100
273
- : undefined;
307
+ const depositAmount = !isCodLike
308
+ ? undefined
309
+ : orderType === "preorder"
310
+ ? computePreOrderDepositAmount(group, commissions.codDepositPercent)
311
+ : Math.round(groupTotal * (commissions.codDepositPercent / 100) * 100) / 100;
274
312
  const codRemainingAmount = isCodLike
275
313
  ? Math.round((groupTotal - (depositAmount ?? 0)) * 100) / 100
276
314
  : undefined;
@@ -329,7 +367,7 @@ async function createOrderForGroup(group, orderType, ctx) {
329
367
  }
330
368
  }
331
369
  couponDiscount = Math.min(couponDiscount, groupTotal);
332
- const orderTotal = Math.max(0, groupTotal - couponDiscount) + shippingFee + codHandlingFee + (emiSchedule?.surchargeAmount ?? 0);
370
+ const orderTotal = Math.max(0, groupTotal - couponDiscount) + shippingFee + codHandlingFee + (emiSchedule?.surchargeAmount ?? 0) + (gstBreakdown?.gstAmount ?? 0);
333
371
  const imageUrls = [
334
372
  ...new Set(group
335
373
  .map(({ product }) => product.mainImage)
@@ -370,6 +408,11 @@ async function createOrderForGroup(group, orderType, ctx) {
370
408
  depositAmount: adminBypass ? undefined : depositAmount,
371
409
  codRemainingAmount: adminBypass ? undefined : codRemainingAmount,
372
410
  codHandlingFee: !adminBypass && codHandlingFee > 0 ? codHandlingFee : undefined,
411
+ taxableAmount: gstBreakdown?.taxableAmount,
412
+ gstAmount: gstBreakdown?.gstAmount,
413
+ cgst: gstBreakdown?.cgst || undefined,
414
+ sgst: gstBreakdown?.sgst || undefined,
415
+ igst: gstBreakdown?.igst || undefined,
373
416
  emiEnabled: !adminBypass && !!emiSchedule ? true : undefined,
374
417
  emiTenureMonths: !adminBypass && emiSchedule ? emiTenureMonths : undefined,
375
418
  emiTokenAmount: !adminBypass ? emiSchedule?.tokenAmount : undefined,
@@ -666,6 +709,8 @@ export async function createCheckoutOrderAction(input) {
666
709
  emailsToSend,
667
710
  outOfStockPolicy,
668
711
  droppedItems: unavailable,
712
+ buyerState: resolvedAddress?.state,
713
+ gstSettings: siteSettings?.gst,
669
714
  };
670
715
  for (const { items: group, orderType } of orderGroups) {
671
716
  total += await createOrderForGroup(group, orderType, groupCtx);
@@ -1018,6 +1063,15 @@ export async function verifyAndPlaceRazorpayOrderAction(input) {
1018
1063
  const platformFee = rawPlatformFee + gstOnFee;
1019
1064
  const orderTotal = Math.max(0, groupTotal - couponDiscount) + shippingFee;
1020
1065
  total += orderTotal;
1066
+ // P-8 GST — deliberately NOT wired into this Razorpay-verify path. The
1067
+ // amount-mismatch check above (expectedPaymentAmountRs) compares against
1068
+ // what the buyer already paid via the Razorpay order created earlier in
1069
+ // the flow; adding product GST here without also adding it to that
1070
+ // upstream pre-payment amount calculation would either fail the mismatch
1071
+ // check or silently under/over-charge. Wiring GST through the full
1072
+ // Razorpay create→verify round-trip is separate follow-up work, tracked
1073
+ // alongside P-13 (Razorpay is disabled by default today, so this order
1074
+ // type doesn't currently carry a GST breakdown).
1021
1075
  // S-SBUNI-RULES 2026-05-13 — order-item decoration via rule registry.
1022
1076
  const orderItems = group.map(({ item, product }) => {
1023
1077
  const lt = (product?.listingType ?? "standard");
@@ -19,6 +19,9 @@ export function orderDocumentToOrder(doc) {
19
19
  ...(item.revealedItemNumber != null
20
20
  ? { revealedItemNumber: item.revealedItemNumber }
21
21
  : {}),
22
+ ...(item.cancelledQuantity != null
23
+ ? { cancelledQuantity: item.cancelledQuantity }
24
+ : {}),
22
25
  }))
23
26
  : [
24
27
  {
@@ -654,6 +654,18 @@ export const ACTIONS = {
654
654
  confirmKind: "danger",
655
655
  },
656
656
  },
657
+ "cancel-order-items": {
658
+ id: "user.cancel-order-items",
659
+ label: "Cancel Selected Items",
660
+ description: "Cancel a subset of items on a pending or confirmed order and continue with the rest.",
661
+ kind: "danger",
662
+ confirmation: {
663
+ title: "Cancel selected items?",
664
+ body: "The selected items will be cancelled and refunded within 5–7 business days. The rest of your order will continue as normal.",
665
+ confirmLabel: "Cancel selected items",
666
+ confirmKind: "danger",
667
+ },
668
+ },
657
669
  "request-return": {
658
670
  id: "user.request-return",
659
671
  label: "Request return",
@@ -1090,6 +1102,14 @@ export const ACTIONS = {
1090
1102
  },
1091
1103
  },
1092
1104
  // ── Payout management ──────────────────────────────────────────────────
1105
+ "calculate-payouts": {
1106
+ id: "admin.calculate-payouts",
1107
+ label: "Calculate Payouts",
1108
+ ariaLabel: "Run the weekly payout eligibility calculation",
1109
+ description: "Runs the same weekly sweep as the scheduled job on demand, generating pending payout records for eligible sellers.",
1110
+ kind: "primary",
1111
+ permissions: ["admin"],
1112
+ },
1093
1113
  "grant-payout": {
1094
1114
  id: "admin.grant-payout",
1095
1115
  label: "Approve payout",
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Pure checkout line-item math shared between the checkout server actions
3
+ * and its test suite. No framework or Firestore imports — safe to unit test
4
+ * directly, unlike `checkout/actions.ts` whose module graph pulls in
5
+ * `server-only` guards that fail outside a real server-action test harness.
6
+ */
7
+ import type { CartItemDocument } from "../../../features/cart/schemas/firestore";
8
+ import type { ProductDocument } from "../../../features/products/schemas/firestore";
9
+ export declare function unitPriceFor(item: CartItemDocument, product: ProductDocument | null): number;
10
+ /**
11
+ * P-6 — pre-order groups charge each product's own `preOrderDepositPercent`
12
+ * (falling back to the generic COD deposit % when a product doesn't have one
13
+ * configured), summed per line. A single seller group can mix multiple
14
+ * pre-order products with different configured deposit percentages, so this
15
+ * cannot be a single group-level percentage the way the generic COD deposit
16
+ * calculation is.
17
+ */
18
+ export declare function computePreOrderDepositAmount(group: Array<{
19
+ item: CartItemDocument;
20
+ product: ProductDocument;
21
+ }>, defaultDepositPercent: number): number;
@@ -0,0 +1,24 @@
1
+ // SB-UNI-5 2026-05-13 — bundle cart-lines use item.price (locked bundle price
2
+ // at add-time); regular lines use product.price (current Firestore). Prevents
3
+ // stale cart-cached prices from being charged on COD/UPI orders.
4
+ export function unitPriceFor(item, product) {
5
+ return item.bundleCategorySlug && item.bundleProductIds?.length
6
+ ? item.price
7
+ : product.price;
8
+ }
9
+ /**
10
+ * P-6 — pre-order groups charge each product's own `preOrderDepositPercent`
11
+ * (falling back to the generic COD deposit % when a product doesn't have one
12
+ * configured), summed per line. A single seller group can mix multiple
13
+ * pre-order products with different configured deposit percentages, so this
14
+ * cannot be a single group-level percentage the way the generic COD deposit
15
+ * calculation is.
16
+ */
17
+ export function computePreOrderDepositAmount(group, defaultDepositPercent) {
18
+ const raw = group.reduce((sum, { item, product }) => {
19
+ const lineTotal = unitPriceFor(item, product) * item.quantity;
20
+ const pct = product.preOrderDepositPercent ?? defaultDepositPercent;
21
+ return sum + lineTotal * (pct / 100);
22
+ }, 0);
23
+ return Math.round(raw * 100) / 100;
24
+ }
@@ -1,5 +1,5 @@
1
1
  export declare const ORDERS_PAGE_SIZE = 20;
2
- export declare const ORDER_CANCELLABLE_STATUSES: readonly ["pending", "processing"];
2
+ export declare const ORDER_CANCELLABLE_STATUSES: readonly ["pending", "confirmed"];
3
3
  export declare const ORDER_RETURN_WINDOW_DAYS = 7;
4
4
  export declare const ORDER_AUTO_CONFIRM_DAYS = 14;
5
5
  export declare const ORDER_CANCEL_REASON_MAX_LENGTH = 500;
@@ -1,5 +1,5 @@
1
1
  export const ORDERS_PAGE_SIZE = 20;
2
- export const ORDER_CANCELLABLE_STATUSES = ["pending", "processing"];
2
+ export const ORDER_CANCELLABLE_STATUSES = ["pending", "confirmed"];
3
3
  export const ORDER_RETURN_WINDOW_DAYS = 7;
4
4
  export const ORDER_AUTO_CONFIRM_DAYS = 14;
5
5
  export const ORDER_CANCEL_REASON_MAX_LENGTH = 500;
@@ -51,3 +51,16 @@ export interface CodHandlingFeeRates {
51
51
  }
52
52
  /** COD handling fee charged to the buyer: max(fixed floor, subtotal × percent). */
53
53
  export declare function computeCodHandlingFee(subtotal: number, rates: CodHandlingFeeRates): number;
54
+ /**
55
+ * P-8 GST — buyer-facing product tax, distinct from the platform-commission
56
+ * GST above. Intra-state orders split the rate evenly between CGST + SGST;
57
+ * inter-state orders charge the full rate as IGST. All amounts in paise.
58
+ */
59
+ export interface GstBreakdown {
60
+ taxableAmount: number;
61
+ cgst: number;
62
+ sgst: number;
63
+ igst: number;
64
+ gstAmount: number;
65
+ }
66
+ export declare function calculateGst(rate: number, intraState: boolean, taxableAmountInPaise: number): GstBreakdown;
@@ -38,3 +38,11 @@ export function computeCodHandlingFee(subtotal, rates) {
38
38
  const percentFee = Math.round(subtotal * (percent / 100));
39
39
  return Math.max(minInPaise, percentFee);
40
40
  }
41
+ export function calculateGst(rate, intraState, taxableAmountInPaise) {
42
+ const gstAmount = Math.round(taxableAmountInPaise * (rate / 100));
43
+ if (intraState) {
44
+ const half = Math.round(gstAmount / 2);
45
+ return { taxableAmount: taxableAmountInPaise, cgst: half, sgst: half, igst: 0, gstAmount: half * 2 };
46
+ }
47
+ return { taxableAmount: taxableAmountInPaise, cgst: 0, sgst: 0, igst: gstAmount, gstAmount };
48
+ }
package/dist/client.d.ts CHANGED
@@ -70,6 +70,9 @@ export { FirebaseClientAuthProvider } from "./providers/firebase-client/index";
70
70
  export { FirebaseClientRealtimeProvider } from "./providers/firebase-client/index";
71
71
  export { EventFormDrawer, EventParticipateView, EventPollWidget } from "./features/events/index";
72
72
  export type { EventParticipateViewProps } from "./features/events/index";
73
+ export { useBulkEvent } from "./features/events/index";
74
+ export type { UseBulkEventOptions, UseBulkEventReturn, BulkEventStatus } from "./features/events/index";
75
+ export { RTDB_PATHS } from "./providers/db-firebase/index";
73
76
  export { LotterySlotGrid } from "./_internal/client/features/lottery/LotterySlotGrid";
74
77
  export { LotteryPullForm } from "./_internal/client/features/lottery/LotteryPullForm";
75
78
  export { LotteryListView } from "./_internal/client/features/lottery/LotteryListView";
@@ -157,6 +160,9 @@ export { AdminDashboardView, AdminAnalyticsView, AdminPrizeDrawsView, AdminCarou
157
160
  export type { AdminDashboardViewProps, AdminAnalyticsViewProps, AdminAnalyticsViewLabels, AdminPrizeDrawsViewProps, AdminCarouselViewProps, AdminFulfillmentViewProps, AdminListingScaffoldRow, ListingViewConfig } from "./features/admin/index";
158
161
  export type { BulkActionItem } from "./ui/components/BulkActionBar";
159
162
  export { ADMIN_ENDPOINTS } from "./constants/index";
163
+ export { SELLER_ENDPOINTS } from "./constants/index";
164
+ export { TourProvider, useTour } from "./_internal/client/features/tour/TourProvider";
165
+ export type { TourContextValue, TourRole } from "./_internal/client/features/tour/TourProvider";
160
166
  export { apiClient, ApiClientError } from "./http/index";
161
167
  export { UserSidebar } from "./features/account/components/UserSidebar";
162
168
  export type { UserSidebarProps, UserNavItem, UserNavGroup } from "./features/account/components/UserSidebar";
@@ -296,6 +302,8 @@ export { MEGABYTE, MAX_IMAGE_BYTES, MAX_PDF_BYTES, MAX_VIDEO_BYTES, MAX_LABEL, M
296
302
  export type { MediaKind, AllowedImageMime, AllowedVideoMime, AllowedDocMime, AllowedMime, } from "./_internal/shared/media/limits";
297
303
  export { ScamAwarenessModal } from "./features/scams/components/ScamAwarenessModal";
298
304
  export type { ScamAwarenessModalProps } from "./features/scams/components/ScamAwarenessModal";
305
+ export { SellerTrustBadge } from "./features/scams/components/SellerTrustBadge";
306
+ export type { SellerTrustBadgeProps } from "./features/scams/components/SellerTrustBadge";
299
307
  export { ActionPermissionsManager } from "./features/site-settings/components/ActionPermissionsManager";
300
308
  export type { ActionPermissionsManagerProps } from "./features/site-settings/components/ActionPermissionsManager";
301
309
  export { NavPermissionsManager } from "./features/site-settings/components/NavPermissionsManager";
package/dist/client.js CHANGED
@@ -120,6 +120,10 @@ export { FirebaseClientRealtimeProvider } from "./providers/firebase-client/inde
120
120
  // [CLIENT-ONLY]-Cannot run in SSR mode â€" uses browser-only APIs (window, navigator, localStorage, matchMedia, DOM events) that do not exist in Node.js.
121
121
  // EventFormDrawer - Component for event form drawer.
122
122
  export { EventFormDrawer, EventParticipateView, EventPollWidget } from "./features/events/index";
123
+ // useBulkEvent - subscribes to an enqueued async job's RTDB progress channel (Async Job Primitive).
124
+ export { useBulkEvent } from "./features/events/index";
125
+ // RTDB_PATHS - realtime database path constants (e.g. RTDB_PATHS.BULK_EVENTS for useBulkEvent).
126
+ export { RTDB_PATHS } from "./providers/db-firebase/index";
123
127
  // Lottery feature — client components (safe for client bundles — no server imports)
124
128
  export { LotterySlotGrid } from "./_internal/client/features/lottery/LotterySlotGrid";
125
129
  export { LotteryPullForm } from "./_internal/client/features/lottery/LotteryPullForm";
@@ -198,6 +202,9 @@ export { ZodSetup } from "./validation/ZodSetup";
198
202
  export { AdminSidebar } from "./features/admin/components/AdminSidebar";
199
203
  export { AdminDashboardView, AdminAnalyticsView, AdminPrizeDrawsView, AdminCarouselView, AdminSublistingCategoriesView, AdminFulfillmentView, DataTable, DataListingView, useAdminListingData, toRecordArray, toStringValue, toRelativeDate, toRupees } from "./features/admin/index";
200
204
  export { ADMIN_ENDPOINTS } from "./constants/index";
205
+ export { SELLER_ENDPOINTS } from "./constants/index";
206
+ // P-16 — Tour system (driver.js-backed onboarding walkthrough).
207
+ export { TourProvider, useTour } from "./_internal/client/features/tour/TourProvider";
201
208
  export { apiClient, ApiClientError } from "./http/index";
202
209
  export { UserSidebar } from "./features/account/components/UserSidebar";
203
210
  export { CouponsIndexListing } from "./features/promotions/components/CouponsIndexListing";
@@ -301,6 +308,7 @@ export { LISTING_TYPE_REGISTRY, pluginFor, detectListingTypeFromSlug } from "./_
301
308
  // Media upload limits — shared by client uploaders + server sign/finalize routes.
302
309
  export { MEGABYTE, MAX_IMAGE_BYTES, MAX_PDF_BYTES, MAX_VIDEO_BYTES, MAX_LABEL, MAX_BYTES, ALLOWED_IMAGE_MIMES, ALLOWED_VIDEO_MIMES, ALLOWED_DOC_MIMES, ALLOWED_MIMES, ALLOWED_TYPES_LABEL, MIME_TO_EXT, PDF_MAGIC, VIDEO_CONVERSION_HINTS, classifyMime, isAllowedMime, maxBytesFor, getConversionHint, } from "./_internal/shared/media/limits";
303
310
  export { ScamAwarenessModal } from "./features/scams/components/ScamAwarenessModal";
311
+ export { SellerTrustBadge } from "./features/scams/components/SellerTrustBadge";
304
312
  // [CLIENT-ONLY] — Admin panel components for action/nav permission management.
305
313
  export { ActionPermissionsManager } from "./features/site-settings/components/ActionPermissionsManager";
306
314
  export { NavPermissionsManager } from "./features/site-settings/components/NavPermissionsManager";
@@ -376,6 +376,9 @@ export declare const SELLER_ENDPOINTS: {
376
376
  readonly GOOGLE_REVIEWS_SYNC: "/api/store/google-reviews/sync";
377
377
  readonly PRODUCTS_SCAN: (barcode: string) => string;
378
378
  readonly ORDERS_FULFILLMENT: "/api/store/fulfillment";
379
+ readonly BUNDLES: "/api/store/bundles";
380
+ readonly BUNDLE_BY_ID: (id: string) => string;
381
+ readonly CONVERSATIONS: "/api/store/conversations";
379
382
  readonly ORDERS_ASSIGN: (orderId: string) => string;
380
383
  readonly PRODUCT_BY_ID: (id: string) => string;
381
384
  readonly PRODUCT_DUPLICATE: (id: string) => string;
@@ -807,6 +810,9 @@ export declare const API_ENDPOINTS: {
807
810
  readonly GOOGLE_REVIEWS_SYNC: "/api/store/google-reviews/sync";
808
811
  readonly PRODUCTS_SCAN: (barcode: string) => string;
809
812
  readonly ORDERS_FULFILLMENT: "/api/store/fulfillment";
813
+ readonly BUNDLES: "/api/store/bundles";
814
+ readonly BUNDLE_BY_ID: (id: string) => string;
815
+ readonly CONVERSATIONS: "/api/store/conversations";
810
816
  readonly ORDERS_ASSIGN: (orderId: string) => string;
811
817
  readonly PRODUCT_BY_ID: (id: string) => string;
812
818
  readonly PRODUCT_DUPLICATE: (id: string) => string;
@@ -1240,6 +1246,9 @@ export declare const API_ROUTES: {
1240
1246
  readonly GOOGLE_REVIEWS_SYNC: "/api/store/google-reviews/sync";
1241
1247
  readonly PRODUCTS_SCAN: (barcode: string) => string;
1242
1248
  readonly ORDERS_FULFILLMENT: "/api/store/fulfillment";
1249
+ readonly BUNDLES: "/api/store/bundles";
1250
+ readonly BUNDLE_BY_ID: (id: string) => string;
1251
+ readonly CONVERSATIONS: "/api/store/conversations";
1243
1252
  readonly ORDERS_ASSIGN: (orderId: string) => string;
1244
1253
  readonly PRODUCT_BY_ID: (id: string) => string;
1245
1254
  readonly PRODUCT_DUPLICATE: (id: string) => string;
@@ -491,6 +491,9 @@ export const SELLER_ENDPOINTS = {
491
491
  GOOGLE_REVIEWS_SYNC: "/api/store/google-reviews/sync",
492
492
  PRODUCTS_SCAN: (barcode) => `/api/store/products/scan?barcode=${encodeURIComponent(barcode)}`,
493
493
  ORDERS_FULFILLMENT: "/api/store/fulfillment",
494
+ BUNDLES: "/api/store/bundles",
495
+ BUNDLE_BY_ID: (id) => `/api/store/bundles/${id}`,
496
+ CONVERSATIONS: "/api/store/conversations",
494
497
  ORDERS_ASSIGN: (orderId) => `/api/store/orders/${orderId}/assign`,
495
498
  PRODUCT_BY_ID: (id) => `/api/store/products/${id}`,
496
499
  PRODUCT_DUPLICATE: (id) => `/api/store/products/${id}/duplicate`,
@@ -18,5 +18,12 @@ export interface AdminBundleEditorViewProps {
18
18
  onSaved?: (id: string) => void;
19
19
  /** Called after a successful delete. */
20
20
  onDeleted?: () => void;
21
+ /**
22
+ * "admin" (default) hits /api/admin/bundles and lets the product picker
23
+ * search all products. "store" hits /api/store/bundles (server-scoped to
24
+ * the caller's own store) and restricts the picker to the seller's own
25
+ * products.
26
+ */
27
+ scope?: "admin" | "store";
21
28
  }
22
- export declare function AdminBundleEditorView({ bundleId, onSaved, onDeleted, }: AdminBundleEditorViewProps): React.JSX.Element;
29
+ export declare function AdminBundleEditorView({ bundleId, onSaved, onDeleted, scope, }: AdminBundleEditorViewProps): React.JSX.Element;