@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.
- package/dist/_internal/client/features/tour/TourProvider.d.ts +11 -3
- package/dist/_internal/client/features/tour/TourProvider.js +93 -4
- package/dist/_internal/server/features/checkout/actions.js +65 -11
- package/dist/_internal/server/features/orders/adapters.js +3 -0
- package/dist/_internal/shared/actions/action-registry.js +20 -0
- package/dist/_internal/shared/checkout/order-math.d.ts +21 -0
- package/dist/_internal/shared/checkout/order-math.js +24 -0
- package/dist/_internal/shared/features/orders/config.d.ts +1 -1
- package/dist/_internal/shared/features/orders/config.js +1 -1
- package/dist/_internal/shared/fees/calculator.d.ts +13 -0
- package/dist/_internal/shared/fees/calculator.js +8 -0
- package/dist/client.d.ts +8 -0
- package/dist/client.js +8 -0
- package/dist/constants/api-endpoints.d.ts +9 -0
- package/dist/constants/api-endpoints.js +3 -0
- package/dist/features/admin/components/AdminBundleEditorView.d.ts +8 -1
- package/dist/features/admin/components/AdminBundleEditorView.js +15 -11
- package/dist/features/admin/components/AdminPayoutsView.js +41 -4
- package/dist/features/admin/components/AdminSiteSettingsView.js +49 -1
- package/dist/features/admin/schemas/firestore.d.ts +8 -0
- package/dist/features/admin/schemas/firestore.js +6 -0
- package/dist/features/layout/TitleBarLayout.js +4 -4
- package/dist/features/messages/hooks/useConversations.d.ts +7 -1
- package/dist/features/messages/hooks/useConversations.js +7 -6
- package/dist/features/orders/actions/order-actions.d.ts +10 -0
- package/dist/features/orders/actions/order-actions.js +57 -4
- package/dist/features/orders/schemas/firestore.d.ts +16 -0
- package/dist/features/orders/types/index.d.ts +2 -0
- package/dist/features/products/schemas/firestore.d.ts +4 -0
- package/dist/features/scams/actions/scam-actions.d.ts +12 -0
- package/dist/features/scams/actions/scam-actions.js +31 -0
- package/dist/features/scams/components/SellerTrustBadge.d.ts +16 -0
- package/dist/features/scams/components/SellerTrustBadge.js +21 -0
- package/dist/features/scams/components/index.d.ts +2 -0
- package/dist/features/scams/components/index.js +1 -0
- package/dist/features/seller/components/SellerBundlesView.d.ts +5 -5
- package/dist/features/seller/components/SellerBundlesView.js +81 -84
- package/dist/features/seller/components/SellerProductShell.d.ts +2 -0
- package/dist/features/seller/components/SellerProductShell.js +7 -1
- package/dist/features/stores/components/StoreDetailLayoutView.d.ts +8 -1
- package/dist/features/stores/components/StoreDetailLayoutView.js +9 -3
- package/dist/features/stores/components/StoreHeader.d.ts +4 -1
- package/dist/features/stores/components/StoreHeader.js +3 -2
- package/dist/index.d.ts +7 -3
- package/dist/index.js +6 -2
- package/dist/jobs.d.ts +2 -0
- package/dist/jobs.js +6 -0
- package/dist/next/routing/route-map.d.ts +3 -0
- package/dist/next/routing/route-map.js +1 -0
- package/dist/security/rate-limit.d.ts +5 -0
- package/dist/security/rate-limit.js +2 -0
- package/dist/seed/categories-seed-data.js +180 -1
- package/dist/seed/grouped-listings-seed-data.d.ts +2 -0
- package/dist/seed/products-preorders-seed-data.d.ts +2 -0
- package/dist/server.d.ts +4 -0
- package/dist/server.js +5 -0
- package/dist/styles.css +11 -0
- package/dist/ui/components/Tabs.style.css +11 -0
- 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
|
-
|
|
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 —
|
|
8
|
-
*
|
|
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 —
|
|
12
|
-
*
|
|
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
|
-
|
|
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
|
-
?
|
|
273
|
-
:
|
|
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", "
|
|
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", "
|
|
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;
|