@mohasinac/appkit 4.24.0 → 4.26.0
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/server/features/bundles/actions.d.ts +4 -1
- package/dist/_internal/server/features/bundles/actions.js +54 -5
- package/dist/_internal/server/features/checkout/actions.js +44 -73
- package/dist/_internal/server/features/checkout/bundle-expansion.d.ts +4 -6
- package/dist/_internal/server/features/checkout/bundle-expansion.js +19 -17
- package/dist/_internal/server/features/checkout/expand-order-items.d.ts +35 -0
- package/dist/_internal/server/features/checkout/expand-order-items.js +85 -0
- package/dist/_internal/server/features/checkout/stock-restore.js +6 -5
- package/dist/_internal/server/features/grouped/index.d.ts +2 -0
- package/dist/_internal/server/features/grouped/index.js +2 -0
- package/dist/_internal/server/features/grouped/metadata.d.ts +17 -0
- package/dist/_internal/server/features/grouped/metadata.js +48 -0
- package/dist/_internal/server/features/grouped/og.d.ts +16 -0
- package/dist/_internal/server/features/grouped/og.js +24 -0
- package/dist/_internal/server/features/products/group-members.d.ts +55 -0
- package/dist/_internal/server/features/products/group-members.js +41 -0
- package/dist/_internal/server/features/seo/sitemap.js +24 -1
- package/dist/_internal/shared/checkout/line-members.d.ts +60 -0
- package/dist/_internal/shared/checkout/line-members.js +68 -0
- package/dist/_internal/shared/checkout/order-math.d.ts +63 -4
- package/dist/_internal/shared/checkout/order-math.js +119 -7
- package/dist/_internal/shared/checkout/rules/_registry.js +8 -2
- package/dist/client.d.ts +9 -3
- package/dist/client.js +4 -2
- package/dist/constants/api-endpoints.d.ts +21 -0
- package/dist/constants/api-endpoints.js +7 -0
- package/dist/features/admin/schemas/firestore.d.ts +10 -26
- package/dist/features/admin/schemas/firestore.js +10 -26
- package/dist/features/analytics/types.d.ts +1 -1
- package/dist/features/analytics/types.js +3 -0
- package/dist/features/cart/actions/cart-actions.d.ts +31 -1
- package/dist/features/cart/actions/cart-actions.js +163 -0
- package/dist/features/cart/components/CartGroupLineRow.d.ts +42 -0
- package/dist/features/cart/components/CartGroupLineRow.js +33 -0
- package/dist/features/cart/components/index.d.ts +2 -0
- package/dist/features/cart/components/index.js +1 -0
- package/dist/features/cart/repository/cart.repository.d.ts +33 -1
- package/dist/features/cart/repository/cart.repository.js +152 -2
- package/dist/features/cart/schemas/firestore.d.ts +7 -2
- package/dist/features/categories/components/BundleBuyNowCta.d.ts +18 -5
- package/dist/features/categories/components/BundleBuyNowCta.js +24 -10
- package/dist/features/categories/components/BundleDetailView.d.ts +8 -1
- package/dist/features/categories/components/BundleDetailView.js +2 -2
- package/dist/features/categories/components/BundlesListView.d.ts +1 -0
- package/dist/features/categories/components/CategoryBundlesListing.d.ts +3 -0
- package/dist/features/grouped/components/GroupedListingDetailView.d.ts +21 -0
- package/dist/features/grouped/components/GroupedListingDetailView.js +17 -0
- package/dist/features/grouped/components/GroupedListingsCarousel.js +6 -1
- package/dist/features/orders/index.d.ts +1 -1
- package/dist/features/orders/index.js +1 -1
- package/dist/features/orders/schemas/firestore.d.ts +11 -0
- package/dist/features/orders/utils/bundle-grouping.d.ts +29 -19
- package/dist/features/orders/utils/bundle-grouping.js +32 -22
- package/dist/features/products/components/GroupMemberPicker.d.ts +46 -0
- package/dist/features/products/components/GroupMemberPicker.js +108 -0
- package/dist/features/products/components/ShowGroupSection.js +7 -9
- package/dist/features/tester/seed-data/categories-tester-seed-data.js +6 -0
- package/dist/features/tester/seed-data/products-tester-seed-data.js +44 -1
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +33 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +6 -2
- package/dist/next/routing/route-map.d.ts +12 -0
- package/dist/next/routing/route-map.js +6 -0
- package/dist/seed/notifications-seed-data.js +225 -0
- package/dist/seed/payouts-seed-data.js +47 -0
- package/dist/seed/scammers-seed-data.js +34 -0
- package/dist/seed/stores-seed-data.js +34 -0
- package/dist/seed/support-tickets-seed-data.js +34 -0
- package/dist/server-entry.d.ts +1 -1
- package/dist/server-entry.js +1 -1
- package/dist/server.d.ts +3 -1
- package/dist/server.js +5 -1
- package/dist/styles.css +1 -1
- package/dist/tailwind-utilities.css +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grouped-listing OG card.
|
|
3
|
+
*
|
|
4
|
+
* Deliberately delegates to `renderBundleOgImage` rather than re-implementing
|
|
5
|
+
* the layout: the card is the same shape (title, description, cover, item
|
|
6
|
+
* count) minus the price, and `BundleOgData.priceLabel` / `.stockStatus` are
|
|
7
|
+
* already nullable for exactly that case. Forking it would give two ~150-line
|
|
8
|
+
* OG layouts to keep visually in step — the Duplication Framework's
|
|
9
|
+
* "same prop surface, same output" consolidate rule.
|
|
10
|
+
*/
|
|
11
|
+
import type { ReactElement } from "react";
|
|
12
|
+
import type { GroupedListingDocument } from "../../../../features/grouped/schemas/firestore";
|
|
13
|
+
export declare function renderGroupedListingOg(doc: Pick<GroupedListingDocument, "title" | "description" | "coverImage" | "productIds"> | null | undefined, opts: {
|
|
14
|
+
siteName: string;
|
|
15
|
+
baseUrl?: string;
|
|
16
|
+
}): ReactElement;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grouped-listing OG card.
|
|
3
|
+
*
|
|
4
|
+
* Deliberately delegates to `renderBundleOgImage` rather than re-implementing
|
|
5
|
+
* the layout: the card is the same shape (title, description, cover, item
|
|
6
|
+
* count) minus the price, and `BundleOgData.priceLabel` / `.stockStatus` are
|
|
7
|
+
* already nullable for exactly that case. Forking it would give two ~150-line
|
|
8
|
+
* OG layouts to keep visually in step — the Duplication Framework's
|
|
9
|
+
* "same prop surface, same output" consolidate rule.
|
|
10
|
+
*/
|
|
11
|
+
import { renderBundleOgImage } from "../bundles/og";
|
|
12
|
+
import { resolveOgImageUrl } from "../seo/og";
|
|
13
|
+
export function renderGroupedListingOg(doc, opts) {
|
|
14
|
+
return renderBundleOgImage({
|
|
15
|
+
name: doc?.title ?? "Group",
|
|
16
|
+
description: doc?.description?.slice(0, 140) ??
|
|
17
|
+
`A curated group of related listings on ${opts.siteName}.`,
|
|
18
|
+
coverImageUrl: resolveOgImageUrl(doc?.coverImage ?? null, opts.baseUrl),
|
|
19
|
+
// A group has no price of its own — the buyer's selection is the price.
|
|
20
|
+
priceLabel: null,
|
|
21
|
+
itemCount: doc?.productIds?.length ?? null,
|
|
22
|
+
stockStatus: null,
|
|
23
|
+
}, opts.siteName);
|
|
24
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public projection for a product-group member.
|
|
3
|
+
*
|
|
4
|
+
* `GET /api/products/group/[groupId]` previously returned
|
|
5
|
+
* `sanitizeProductsForPublic(items)`, which is a DELETE-LIST of four
|
|
6
|
+
* seller-identity keys — every other field of `ProductDocument` went to the
|
|
7
|
+
* browser, including fields nobody had thought about. Per § "Public Data
|
|
8
|
+
* Projections", a public payload is built by naming what goes out, never by
|
|
9
|
+
* spreading a document and removing keys: a denial keyed on today's field list
|
|
10
|
+
* is structurally blind to the field added tomorrow.
|
|
11
|
+
*
|
|
12
|
+
* The member picker needs more than the old response carried (stock, store,
|
|
13
|
+
* sold state, gstRate), so widening the payload was required either way —
|
|
14
|
+
* which made this the moment to make it an allow-list rather than a wider
|
|
15
|
+
* deny-list.
|
|
16
|
+
*/
|
|
17
|
+
import type { ProductDocument } from "../../../../features/products/schemas/firestore";
|
|
18
|
+
import type { ListingType } from "../../../../features/products/types/index";
|
|
19
|
+
/**
|
|
20
|
+
* Every field the group picker and the group table render. If a field is not
|
|
21
|
+
* here it does not reach the browser.
|
|
22
|
+
*
|
|
23
|
+
* Deliberately absent, with reasons:
|
|
24
|
+
* - `sellerId` / `ownerId` / `sellerName` / `sellerEmail` — seller identity;
|
|
25
|
+
* public clients see store-scoped identity only.
|
|
26
|
+
* - `costPrice`, `profitMargin`, `supplier*`, `adminNotes` — seller-private
|
|
27
|
+
* commercial data.
|
|
28
|
+
* - `stockQuantity` — the internal figure; `availableQuantity` is the one the
|
|
29
|
+
* buyer may act on.
|
|
30
|
+
*/
|
|
31
|
+
export interface PublicGroupMember {
|
|
32
|
+
id: string;
|
|
33
|
+
slug?: string;
|
|
34
|
+
title: string;
|
|
35
|
+
price: number;
|
|
36
|
+
currency?: string;
|
|
37
|
+
/** First image only — the picker renders a thumbnail, never a gallery. */
|
|
38
|
+
image?: string;
|
|
39
|
+
condition?: string;
|
|
40
|
+
listingType: ListingType;
|
|
41
|
+
isGroupParent?: boolean;
|
|
42
|
+
groupTitle?: string;
|
|
43
|
+
/** Drives the stepper's `max`. */
|
|
44
|
+
availableQuantity: number;
|
|
45
|
+
storeId: string;
|
|
46
|
+
storeName?: string;
|
|
47
|
+
status: string;
|
|
48
|
+
isSold?: boolean;
|
|
49
|
+
/** Needed so the picker can show a tax-inclusive hint and the cart line can
|
|
50
|
+
* snapshot it for per-member GST proration at checkout. */
|
|
51
|
+
gstRate?: 0 | 5 | 12 | 18 | 28;
|
|
52
|
+
hsnCode?: string;
|
|
53
|
+
}
|
|
54
|
+
export declare function toPublicGroupMember(doc: ProductDocument): PublicGroupMember;
|
|
55
|
+
export declare function toPublicGroupMembers(docs: readonly ProductDocument[]): PublicGroupMember[];
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public projection for a product-group member.
|
|
3
|
+
*
|
|
4
|
+
* `GET /api/products/group/[groupId]` previously returned
|
|
5
|
+
* `sanitizeProductsForPublic(items)`, which is a DELETE-LIST of four
|
|
6
|
+
* seller-identity keys — every other field of `ProductDocument` went to the
|
|
7
|
+
* browser, including fields nobody had thought about. Per § "Public Data
|
|
8
|
+
* Projections", a public payload is built by naming what goes out, never by
|
|
9
|
+
* spreading a document and removing keys: a denial keyed on today's field list
|
|
10
|
+
* is structurally blind to the field added tomorrow.
|
|
11
|
+
*
|
|
12
|
+
* The member picker needs more than the old response carried (stock, store,
|
|
13
|
+
* sold state, gstRate), so widening the payload was required either way —
|
|
14
|
+
* which made this the moment to make it an allow-list rather than a wider
|
|
15
|
+
* deny-list.
|
|
16
|
+
*/
|
|
17
|
+
import { normalizeListingType } from "../../../../features/products/utils/listing-type";
|
|
18
|
+
export function toPublicGroupMember(doc) {
|
|
19
|
+
return {
|
|
20
|
+
id: doc.id,
|
|
21
|
+
slug: doc.slug,
|
|
22
|
+
title: doc.title,
|
|
23
|
+
price: doc.price,
|
|
24
|
+
currency: doc.currency,
|
|
25
|
+
image: doc.images?.[0] ?? doc.mainImage,
|
|
26
|
+
condition: doc.condition,
|
|
27
|
+
listingType: normalizeListingType(doc),
|
|
28
|
+
isGroupParent: doc.isGroupParent,
|
|
29
|
+
groupTitle: doc.groupTitle,
|
|
30
|
+
availableQuantity: doc.availableQuantity ?? 0,
|
|
31
|
+
storeId: doc.storeId,
|
|
32
|
+
storeName: doc.storeName,
|
|
33
|
+
status: doc.status,
|
|
34
|
+
isSold: doc.isSold,
|
|
35
|
+
gstRate: doc.gstRate,
|
|
36
|
+
hsnCode: doc.hsnCode,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export function toPublicGroupMembers(docs) {
|
|
40
|
+
return docs.map(toPublicGroupMember);
|
|
41
|
+
}
|
|
@@ -210,6 +210,27 @@ async function fetchCategoryTypeUrls(baseUrl, categoryType, buildUrl, label) {
|
|
|
210
210
|
const fetchCategoryUrls = (baseUrl) => fetchCategoryTypeUrls(baseUrl, "listing", (slug) => ROUTES.PUBLIC.CATEGORY_DETAIL(slug), "category");
|
|
211
211
|
const fetchBrandUrls = (baseUrl) => fetchCategoryTypeUrls(baseUrl, "brand", (slug) => ROUTES.PUBLIC.BRAND_DETAIL(slug), "brand");
|
|
212
212
|
const fetchBundleUrls = (baseUrl) => fetchCategoryTypeUrls(baseUrl, "bundle", (slug) => ROUTES.PUBLIC.BUNDLE_DETAIL(slug), "bundle");
|
|
213
|
+
/**
|
|
214
|
+
* `groupedListings` documents. `listSitemapGroupedListings` has existed since
|
|
215
|
+
* the feature was built, but there was no public page for it to point at until
|
|
216
|
+
* `/groups/{slug}` — so it was never wired into the sitemap.
|
|
217
|
+
*/
|
|
218
|
+
async function fetchGroupedListingUrls(baseUrl) {
|
|
219
|
+
try {
|
|
220
|
+
const { listSitemapGroupedListings } = await import("../grouped/data");
|
|
221
|
+
const groups = await listSitemapGroupedListings();
|
|
222
|
+
return groups.map((g) => ({
|
|
223
|
+
url: `${baseUrl}${ROUTES.PUBLIC.GROUP_DETAIL(g.slug)}`,
|
|
224
|
+
lastModified: g.updatedAt,
|
|
225
|
+
changeFrequency: "weekly",
|
|
226
|
+
priority: 0.5,
|
|
227
|
+
}));
|
|
228
|
+
}
|
|
229
|
+
catch (err) {
|
|
230
|
+
void normalizeError(err);
|
|
231
|
+
return [];
|
|
232
|
+
}
|
|
233
|
+
}
|
|
213
234
|
async function fetchBlogPostUrls(baseUrl) {
|
|
214
235
|
try {
|
|
215
236
|
const db = getAdminDb();
|
|
@@ -293,11 +314,12 @@ async function fetchScammerUrls(baseUrl) {
|
|
|
293
314
|
}
|
|
294
315
|
}
|
|
295
316
|
export async function buildSitemap({ baseUrl }) {
|
|
296
|
-
const [productUrls, categoryUrls, brandUrls, bundleUrls, eventUrls, blogUrls, auctionUrls, preOrderUrls, prizeDrawUrls, classifiedUrls, digitalCodeUrls, liveUrls, storeUrls, scammerUrls,] = await Promise.all([
|
|
317
|
+
const [productUrls, categoryUrls, brandUrls, bundleUrls, groupedListingUrls, eventUrls, blogUrls, auctionUrls, preOrderUrls, prizeDrawUrls, classifiedUrls, digitalCodeUrls, liveUrls, storeUrls, scammerUrls,] = await Promise.all([
|
|
297
318
|
fetchProductUrls(baseUrl),
|
|
298
319
|
fetchCategoryUrls(baseUrl),
|
|
299
320
|
fetchBrandUrls(baseUrl),
|
|
300
321
|
fetchBundleUrls(baseUrl),
|
|
322
|
+
fetchGroupedListingUrls(baseUrl),
|
|
301
323
|
fetchEventUrls(baseUrl),
|
|
302
324
|
fetchBlogPostUrls(baseUrl),
|
|
303
325
|
fetchAuctionUrls(baseUrl),
|
|
@@ -314,6 +336,7 @@ export async function buildSitemap({ baseUrl }) {
|
|
|
314
336
|
...categoryUrls,
|
|
315
337
|
...brandUrls,
|
|
316
338
|
...bundleUrls,
|
|
339
|
+
...groupedListingUrls,
|
|
317
340
|
...blogUrls,
|
|
318
341
|
...productUrls,
|
|
319
342
|
...auctionUrls,
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* "What products does this cart line actually consume?" — the one answer.
|
|
3
|
+
*
|
|
4
|
+
* Lives in `shared/` rather than `server/features/checkout/` because three
|
|
5
|
+
* different layers need it and two of them are not server code:
|
|
6
|
+
* - `order-math.ts` (pricing + tax, shared)
|
|
7
|
+
* - `rules/_registry.ts` (preflight skip, shared)
|
|
8
|
+
* - `bundle-expansion.ts` (stock fan-out, server)
|
|
9
|
+
* - `stock-restore.ts` (restore fan-out, server)
|
|
10
|
+
*
|
|
11
|
+
* Pure functions, no Firestore, no framework imports.
|
|
12
|
+
*/
|
|
13
|
+
import type { CartItemDocument } from "../../../features/cart/schemas/firestore";
|
|
14
|
+
/** One member of a cart line, with the units required per COPY of that line. */
|
|
15
|
+
export interface CartLineMemberRef {
|
|
16
|
+
productId: string;
|
|
17
|
+
quantity: number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Resolve a line's members. Three sources, in priority order — never read
|
|
21
|
+
* `bundleProductIds` directly at a call site:
|
|
22
|
+
*
|
|
23
|
+
* 1. `groupMembers` — authoritative, and the only shape that can express
|
|
24
|
+
* per-member quantities.
|
|
25
|
+
* 2. `bundleProductIds` — legacy bundle lines written before `groupMembers`
|
|
26
|
+
* existed, resolved at quantity 1 each. This fallback is what makes carts
|
|
27
|
+
* that were already in flight behave byte-identically, with no migration.
|
|
28
|
+
* 3. the line's own `productId` — an ordinary single-product line.
|
|
29
|
+
*
|
|
30
|
+
* Keeping this a derivation rather than storing a second array is deliberate: a
|
|
31
|
+
* hand-maintained mirror drifts the first time one write path forgets it
|
|
32
|
+
* (Recurrent Root Cause #42).
|
|
33
|
+
*/
|
|
34
|
+
export declare function getCartLineMembers(item: CartItemDocument): CartLineMemberRef[];
|
|
35
|
+
/**
|
|
36
|
+
* True when the line stands for several products rather than one.
|
|
37
|
+
*
|
|
38
|
+
* Replaces the scattered `item.bundleProductIds?.length` tests, which each
|
|
39
|
+
* meant "is this a bundle" and would all have silently answered "no" for a
|
|
40
|
+
* `groupMembers`-backed line.
|
|
41
|
+
*/
|
|
42
|
+
export declare function isMultiMemberLine(item: CartItemDocument): boolean;
|
|
43
|
+
/** Just the product ids — see `getCartLineMembers` for the resolution order. */
|
|
44
|
+
export declare function getCartItemMemberIds(item: CartItemDocument): string[];
|
|
45
|
+
/** The order-side shape this module needs. Structural on purpose — it matches
|
|
46
|
+
* `OrderDocumentItem` without dragging the orders schema into shared checkout. */
|
|
47
|
+
export interface OrderItemMemberSource {
|
|
48
|
+
productId: string;
|
|
49
|
+
bundleProductIds?: string[];
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The order-side twin of `getCartLineMembers`, used by stock restore.
|
|
53
|
+
*
|
|
54
|
+
* A LEGACY order row is collapsed: one row standing for a whole bundle, with
|
|
55
|
+
* its members listed in `bundleProductIds` at one unit each. Rows written after
|
|
56
|
+
* grouped lines were expanded carry no `bundleProductIds` at all — each row is
|
|
57
|
+
* already exactly one product — so they fall through to the single-member
|
|
58
|
+
* branch and need no special case.
|
|
59
|
+
*/
|
|
60
|
+
export declare function getOrderItemMemberRefs(item: OrderItemMemberSource): CartLineMemberRef[];
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* "What products does this cart line actually consume?" — the one answer.
|
|
3
|
+
*
|
|
4
|
+
* Lives in `shared/` rather than `server/features/checkout/` because three
|
|
5
|
+
* different layers need it and two of them are not server code:
|
|
6
|
+
* - `order-math.ts` (pricing + tax, shared)
|
|
7
|
+
* - `rules/_registry.ts` (preflight skip, shared)
|
|
8
|
+
* - `bundle-expansion.ts` (stock fan-out, server)
|
|
9
|
+
* - `stock-restore.ts` (restore fan-out, server)
|
|
10
|
+
*
|
|
11
|
+
* Pure functions, no Firestore, no framework imports.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Resolve a line's members. Three sources, in priority order — never read
|
|
15
|
+
* `bundleProductIds` directly at a call site:
|
|
16
|
+
*
|
|
17
|
+
* 1. `groupMembers` — authoritative, and the only shape that can express
|
|
18
|
+
* per-member quantities.
|
|
19
|
+
* 2. `bundleProductIds` — legacy bundle lines written before `groupMembers`
|
|
20
|
+
* existed, resolved at quantity 1 each. This fallback is what makes carts
|
|
21
|
+
* that were already in flight behave byte-identically, with no migration.
|
|
22
|
+
* 3. the line's own `productId` — an ordinary single-product line.
|
|
23
|
+
*
|
|
24
|
+
* Keeping this a derivation rather than storing a second array is deliberate: a
|
|
25
|
+
* hand-maintained mirror drifts the first time one write path forgets it
|
|
26
|
+
* (Recurrent Root Cause #42).
|
|
27
|
+
*/
|
|
28
|
+
export function getCartLineMembers(item) {
|
|
29
|
+
if (item.groupMembers?.length) {
|
|
30
|
+
return item.groupMembers.map((m) => ({
|
|
31
|
+
productId: m.productId,
|
|
32
|
+
quantity: m.quantity,
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
35
|
+
if (item.bundleProductIds?.length) {
|
|
36
|
+
return item.bundleProductIds.map((productId) => ({ productId, quantity: 1 }));
|
|
37
|
+
}
|
|
38
|
+
return [{ productId: item.productId, quantity: 1 }];
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* True when the line stands for several products rather than one.
|
|
42
|
+
*
|
|
43
|
+
* Replaces the scattered `item.bundleProductIds?.length` tests, which each
|
|
44
|
+
* meant "is this a bundle" and would all have silently answered "no" for a
|
|
45
|
+
* `groupMembers`-backed line.
|
|
46
|
+
*/
|
|
47
|
+
export function isMultiMemberLine(item) {
|
|
48
|
+
return Boolean(item.groupMembers?.length || item.bundleProductIds?.length);
|
|
49
|
+
}
|
|
50
|
+
/** Just the product ids — see `getCartLineMembers` for the resolution order. */
|
|
51
|
+
export function getCartItemMemberIds(item) {
|
|
52
|
+
return getCartLineMembers(item).map((m) => m.productId);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The order-side twin of `getCartLineMembers`, used by stock restore.
|
|
56
|
+
*
|
|
57
|
+
* A LEGACY order row is collapsed: one row standing for a whole bundle, with
|
|
58
|
+
* its members listed in `bundleProductIds` at one unit each. Rows written after
|
|
59
|
+
* grouped lines were expanded carry no `bundleProductIds` at all — each row is
|
|
60
|
+
* already exactly one product — so they fall through to the single-member
|
|
61
|
+
* branch and need no special case.
|
|
62
|
+
*/
|
|
63
|
+
export function getOrderItemMemberRefs(item) {
|
|
64
|
+
if (item.bundleProductIds?.length) {
|
|
65
|
+
return item.bundleProductIds.map((productId) => ({ productId, quantity: 1 }));
|
|
66
|
+
}
|
|
67
|
+
return [{ productId: item.productId, quantity: 1 }];
|
|
68
|
+
}
|
|
@@ -17,13 +17,14 @@ import type { ProductDocument } from "../../../features/products/schemas/firesto
|
|
|
17
17
|
* copies of this function looked at `lockedPrice`, so an accepted offer was
|
|
18
18
|
* billed at the seller's listed price while the cart UI displayed the agreed
|
|
19
19
|
* one (`item.lockedPrice ?? item.price`) — the buyer saw ₹X and paid ₹Y.
|
|
20
|
-
* 2. **A
|
|
20
|
+
* 2. **A bundle line** — `item.price` is the bundle's own locked price, which
|
|
21
|
+
* is deliberately LESS than the sum of its members. Checked before the
|
|
22
|
+
* members branch for exactly that reason. Covers both modern bundle lines
|
|
23
|
+
* (`lineKind: "bundle"`) and legacy ones (SB-UNI-5 2026-05-13).
|
|
24
|
+
* 3. **A grouped line** — the sum of its members' own snapshotted prices,
|
|
21
25
|
* weighted by each member's per-copy quantity. A `"group"` line pins
|
|
22
26
|
* `item.quantity` to 1, so this is both the unit price and the line total;
|
|
23
27
|
* see the invariant on `CartLineMember`.
|
|
24
|
-
* 3. **A legacy bundle line** (SB-UNI-5 2026-05-13) — written before
|
|
25
|
-
* `groupMembers` existed. `item.price` is the bundle price locked at
|
|
26
|
-
* add-time.
|
|
27
28
|
* 4. **Everything else** — the live Firestore price, so a stale cart-cached
|
|
28
29
|
* price is never charged on a COD/UPI order.
|
|
29
30
|
*
|
|
@@ -44,6 +45,64 @@ export declare function unitPriceFor(item: CartItemDocument, product: ProductDoc
|
|
|
44
45
|
* itself — that multiplication is where the six hand-rolled copies diverged.
|
|
45
46
|
*/
|
|
46
47
|
export declare function lineTotalFor(item: CartItemDocument, product: ProductDocument | null): number;
|
|
48
|
+
/** One taxable slice of a line. A single-product line has exactly one. */
|
|
49
|
+
export interface LineTaxComponent {
|
|
50
|
+
productId: string;
|
|
51
|
+
/** Rupees this slice is taxed on. Slices of a line sum to its line total. */
|
|
52
|
+
taxable: number;
|
|
53
|
+
gstRate: number;
|
|
54
|
+
hsnCode?: string;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Split a line into its taxable slices.
|
|
58
|
+
*
|
|
59
|
+
* GST cannot go through `unitPriceFor`: that returns a scalar, and a
|
|
60
|
+
* multi-member line genuinely has SEVERAL rates — a ₹200 sticker at 12% next to
|
|
61
|
+
* a ₹2,000 top at 18% inside one selection. A scalar can only ever carry one of
|
|
62
|
+
* them, which is why a bundle line has paid **zero** GST for its whole
|
|
63
|
+
* existence: `previewCheckoutPricing` looked its product up by `item.productId`,
|
|
64
|
+
* which on a bundle is a CATEGORY id, so `product` was null and
|
|
65
|
+
* `product?.gstRate ?? 0` quietly resolved to 0.
|
|
66
|
+
*
|
|
67
|
+
* Three shapes:
|
|
68
|
+
*
|
|
69
|
+
* - **Plain line** — one slice, taxed on the whole line total. Identical to
|
|
70
|
+
* what the two GST loops did before.
|
|
71
|
+
* - **Grouped line** — one slice per member, each taxed on its own
|
|
72
|
+
* `unitPrice × member.quantity × item.quantity`. Slices sum to the line
|
|
73
|
+
* total exactly, because a grouped line has no price of its own.
|
|
74
|
+
* - **Bundle line** — the taxable base is the bundle's LOCKED price, not the
|
|
75
|
+
* sum of the members' list prices, so each member's slice is a prorated
|
|
76
|
+
* share of it (see `allocateBundlePrice`). Charging tax on the undiscounted
|
|
77
|
+
* total would tax the buyer on money they never paid.
|
|
78
|
+
*
|
|
79
|
+
* `productById` supplies the live `gstRate`; each member's snapshotted rate is
|
|
80
|
+
* the fallback for a member whose listing has since been archived.
|
|
81
|
+
*/
|
|
82
|
+
export declare function lineTaxComponentsFor(item: CartItemDocument, productById: Map<string, ProductDocument>): LineTaxComponent[];
|
|
83
|
+
/**
|
|
84
|
+
* Total GST for one order group, and the base it was charged on.
|
|
85
|
+
*
|
|
86
|
+
* Exists so `createOrderForGroup` (what the buyer is CHARGED) and
|
|
87
|
+
* `previewCheckoutPricing` (what the buyer is SHOWN) cannot disagree. They were
|
|
88
|
+
* two hand-written loops, and Root Cause #59 is precisely the pattern of fixing
|
|
89
|
+
* one and not the other — here that would mean quoting one tax figure and
|
|
90
|
+
* charging another.
|
|
91
|
+
*/
|
|
92
|
+
export declare function sumGroupGst(items: readonly CartItemDocument[], productById: Map<string, ProductDocument>, intraState: boolean): {
|
|
93
|
+
taxableAmount: number;
|
|
94
|
+
gstAmount: number;
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* Split `total` across members in proportion to their list prices, with the
|
|
98
|
+
* rounding remainder pushed onto the LAST member so the parts sum to exactly
|
|
99
|
+
* `total`, to the last decimal place. Rounding each share independently leaves
|
|
100
|
+
* an order whose `taxableAmount` doesn't reconcile with the sum of its lines.
|
|
101
|
+
*
|
|
102
|
+
* Falls back to an even split when every weight is zero (a bundle of free
|
|
103
|
+
* items), which is the only case where proportion is undefined.
|
|
104
|
+
*/
|
|
105
|
+
export declare function allocateAcrossMembers(total: number, weights: number[]): number[];
|
|
47
106
|
/**
|
|
48
107
|
* P-6 — pre-order groups charge each product's own `preOrderDepositPercent`
|
|
49
108
|
* (falling back to the generic COD deposit % when a product doesn't have one
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { roundRupees } from "../../../utils/number.formatter";
|
|
2
|
+
import { calculateGst } from "../fees/calculator";
|
|
2
3
|
/**
|
|
3
4
|
* The single source of truth for "what do we charge for ONE COPY of this line".
|
|
4
5
|
*
|
|
@@ -10,13 +11,14 @@ import { roundRupees } from "../../../utils/number.formatter";
|
|
|
10
11
|
* copies of this function looked at `lockedPrice`, so an accepted offer was
|
|
11
12
|
* billed at the seller's listed price while the cart UI displayed the agreed
|
|
12
13
|
* one (`item.lockedPrice ?? item.price`) — the buyer saw ₹X and paid ₹Y.
|
|
13
|
-
* 2. **A
|
|
14
|
+
* 2. **A bundle line** — `item.price` is the bundle's own locked price, which
|
|
15
|
+
* is deliberately LESS than the sum of its members. Checked before the
|
|
16
|
+
* members branch for exactly that reason. Covers both modern bundle lines
|
|
17
|
+
* (`lineKind: "bundle"`) and legacy ones (SB-UNI-5 2026-05-13).
|
|
18
|
+
* 3. **A grouped line** — the sum of its members' own snapshotted prices,
|
|
14
19
|
* weighted by each member's per-copy quantity. A `"group"` line pins
|
|
15
20
|
* `item.quantity` to 1, so this is both the unit price and the line total;
|
|
16
21
|
* see the invariant on `CartLineMember`.
|
|
17
|
-
* 3. **A legacy bundle line** (SB-UNI-5 2026-05-13) — written before
|
|
18
|
-
* `groupMembers` existed. `item.price` is the bundle price locked at
|
|
19
|
-
* add-time.
|
|
20
22
|
* 4. **Everything else** — the live Firestore price, so a stale cart-cached
|
|
21
23
|
* price is never charged on a COD/UPI order.
|
|
22
24
|
*
|
|
@@ -34,12 +36,18 @@ export function unitPriceFor(item, product) {
|
|
|
34
36
|
if (typeof item.lockedPrice === "number" && item.lockedPrice > 0) {
|
|
35
37
|
return item.lockedPrice;
|
|
36
38
|
}
|
|
39
|
+
// A BUNDLE is priced as a whole, and that price is the entire point of it
|
|
40
|
+
// being a bundle — it is deliberately less than the sum of its members. This
|
|
41
|
+
// branch must come BEFORE the members branch: a bundle line carries
|
|
42
|
+
// `groupMembers` too (so the cart can show what's inside and checkout can
|
|
43
|
+
// prorate GST), and summing them would silently charge the undiscounted total.
|
|
44
|
+
if (item.lineKind === "bundle" || (item.bundleCategorySlug && item.bundleProductIds?.length)) {
|
|
45
|
+
return item.price;
|
|
46
|
+
}
|
|
47
|
+
// A GROUP line has no price of its own — it is exactly what the buyer picked.
|
|
37
48
|
if (item.groupMembers?.length) {
|
|
38
49
|
return roundRupees(item.groupMembers.reduce((sum, m) => sum + m.unitPrice * m.quantity, 0));
|
|
39
50
|
}
|
|
40
|
-
if (item.bundleCategorySlug && item.bundleProductIds?.length) {
|
|
41
|
-
return item.price;
|
|
42
|
-
}
|
|
43
51
|
// Null-safe fallback: one of the four call sites this replaced (the cart
|
|
44
52
|
// summary/preview path) already guarded with `product?.price ?? item.price`,
|
|
45
53
|
// and collapsing onto a non-null assertion would have regressed it into a
|
|
@@ -54,6 +62,110 @@ export function unitPriceFor(item, product) {
|
|
|
54
62
|
export function lineTotalFor(item, product) {
|
|
55
63
|
return unitPriceFor(item, product) * item.quantity;
|
|
56
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Split a line into its taxable slices.
|
|
67
|
+
*
|
|
68
|
+
* GST cannot go through `unitPriceFor`: that returns a scalar, and a
|
|
69
|
+
* multi-member line genuinely has SEVERAL rates — a ₹200 sticker at 12% next to
|
|
70
|
+
* a ₹2,000 top at 18% inside one selection. A scalar can only ever carry one of
|
|
71
|
+
* them, which is why a bundle line has paid **zero** GST for its whole
|
|
72
|
+
* existence: `previewCheckoutPricing` looked its product up by `item.productId`,
|
|
73
|
+
* which on a bundle is a CATEGORY id, so `product` was null and
|
|
74
|
+
* `product?.gstRate ?? 0` quietly resolved to 0.
|
|
75
|
+
*
|
|
76
|
+
* Three shapes:
|
|
77
|
+
*
|
|
78
|
+
* - **Plain line** — one slice, taxed on the whole line total. Identical to
|
|
79
|
+
* what the two GST loops did before.
|
|
80
|
+
* - **Grouped line** — one slice per member, each taxed on its own
|
|
81
|
+
* `unitPrice × member.quantity × item.quantity`. Slices sum to the line
|
|
82
|
+
* total exactly, because a grouped line has no price of its own.
|
|
83
|
+
* - **Bundle line** — the taxable base is the bundle's LOCKED price, not the
|
|
84
|
+
* sum of the members' list prices, so each member's slice is a prorated
|
|
85
|
+
* share of it (see `allocateBundlePrice`). Charging tax on the undiscounted
|
|
86
|
+
* total would tax the buyer on money they never paid.
|
|
87
|
+
*
|
|
88
|
+
* `productById` supplies the live `gstRate`; each member's snapshotted rate is
|
|
89
|
+
* the fallback for a member whose listing has since been archived.
|
|
90
|
+
*/
|
|
91
|
+
export function lineTaxComponentsFor(item, productById) {
|
|
92
|
+
const lineTotal = lineTotalFor(item, productById.get(item.productId) ?? null);
|
|
93
|
+
const members = item.groupMembers;
|
|
94
|
+
if (!members?.length) {
|
|
95
|
+
const product = productById.get(item.productId);
|
|
96
|
+
return [{
|
|
97
|
+
productId: item.productId,
|
|
98
|
+
taxable: lineTotal,
|
|
99
|
+
gstRate: product?.gstRate ?? 0,
|
|
100
|
+
hsnCode: product?.hsnCode,
|
|
101
|
+
}];
|
|
102
|
+
}
|
|
103
|
+
const rateFor = (m) => productById.get(m.productId)?.gstRate ?? m.gstRate ?? 0;
|
|
104
|
+
const hsnFor = (m) => productById.get(m.productId)?.hsnCode ?? m.hsnCode;
|
|
105
|
+
const isBundle = item.lineKind === "bundle" || Boolean(item.bundleCategorySlug);
|
|
106
|
+
if (!isBundle) {
|
|
107
|
+
return members.map((m) => ({
|
|
108
|
+
productId: m.productId,
|
|
109
|
+
taxable: roundRupees(m.unitPrice * m.quantity * item.quantity),
|
|
110
|
+
gstRate: rateFor(m),
|
|
111
|
+
hsnCode: hsnFor(m),
|
|
112
|
+
}));
|
|
113
|
+
}
|
|
114
|
+
const shares = allocateAcrossMembers(lineTotal, members.map((m) => m.unitPrice * m.quantity));
|
|
115
|
+
return members.map((m, i) => ({
|
|
116
|
+
productId: m.productId,
|
|
117
|
+
taxable: shares[i],
|
|
118
|
+
gstRate: rateFor(m),
|
|
119
|
+
hsnCode: hsnFor(m),
|
|
120
|
+
}));
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Total GST for one order group, and the base it was charged on.
|
|
124
|
+
*
|
|
125
|
+
* Exists so `createOrderForGroup` (what the buyer is CHARGED) and
|
|
126
|
+
* `previewCheckoutPricing` (what the buyer is SHOWN) cannot disagree. They were
|
|
127
|
+
* two hand-written loops, and Root Cause #59 is precisely the pattern of fixing
|
|
128
|
+
* one and not the other — here that would mean quoting one tax figure and
|
|
129
|
+
* charging another.
|
|
130
|
+
*/
|
|
131
|
+
export function sumGroupGst(items, productById, intraState) {
|
|
132
|
+
let taxableAmount = 0;
|
|
133
|
+
let gstAmount = 0;
|
|
134
|
+
for (const item of items) {
|
|
135
|
+
for (const slice of lineTaxComponentsFor(item, productById)) {
|
|
136
|
+
if (slice.gstRate <= 0)
|
|
137
|
+
continue;
|
|
138
|
+
taxableAmount += slice.taxable;
|
|
139
|
+
gstAmount += calculateGst(slice.gstRate, intraState, slice.taxable).gstAmount;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return { taxableAmount, gstAmount };
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Split `total` across members in proportion to their list prices, with the
|
|
146
|
+
* rounding remainder pushed onto the LAST member so the parts sum to exactly
|
|
147
|
+
* `total`, to the last decimal place. Rounding each share independently leaves
|
|
148
|
+
* an order whose `taxableAmount` doesn't reconcile with the sum of its lines.
|
|
149
|
+
*
|
|
150
|
+
* Falls back to an even split when every weight is zero (a bundle of free
|
|
151
|
+
* items), which is the only case where proportion is undefined.
|
|
152
|
+
*/
|
|
153
|
+
export function allocateAcrossMembers(total, weights) {
|
|
154
|
+
if (weights.length === 0)
|
|
155
|
+
return [];
|
|
156
|
+
const sum = weights.reduce((a, b) => a + b, 0);
|
|
157
|
+
const out = [];
|
|
158
|
+
let running = 0;
|
|
159
|
+
for (let i = 0; i < weights.length - 1; i++) {
|
|
160
|
+
const share = sum > 0
|
|
161
|
+
? roundRupees((total * weights[i]) / sum) // audit-money-units-ok: proportional split of decimal rupees
|
|
162
|
+
: roundRupees(total / weights.length);
|
|
163
|
+
out.push(share);
|
|
164
|
+
running += share;
|
|
165
|
+
}
|
|
166
|
+
out.push(roundRupees(total - running));
|
|
167
|
+
return out;
|
|
168
|
+
}
|
|
57
169
|
/**
|
|
58
170
|
* P-6 — pre-order groups charge each product's own `preOrderDepositPercent`
|
|
59
171
|
* (falling back to the generic COD deposit % when a product doesn't have one
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* No grep of the consumer codebase needed.
|
|
10
10
|
*/
|
|
11
|
+
import { isMultiMemberLine } from "../line-members";
|
|
11
12
|
import { standardRule } from "./standard.rule";
|
|
12
13
|
import { auctionRule } from "./auction.rule";
|
|
13
14
|
import { preOrderRule } from "./preorder.rule";
|
|
@@ -77,8 +78,13 @@ export function getSplitKey(item) {
|
|
|
77
78
|
export function runSyncPreflight(pairs, paymentMethod) {
|
|
78
79
|
const byType = new Map();
|
|
79
80
|
for (const pair of pairs) {
|
|
80
|
-
|
|
81
|
-
|
|
81
|
+
// Multi-member lines (bundle or grouped selection) skip the per-listing-type
|
|
82
|
+
// preflight: their `listingType` describes the wrapper, not the members, so
|
|
83
|
+
// a per-type rule would be applied to the wrong thing. Tested via the shared
|
|
84
|
+
// predicate rather than `bundleProductIds?.length`, which answers "is this a
|
|
85
|
+
// legacy bundle" and would say no for a `groupMembers`-backed line.
|
|
86
|
+
if (isMultiMemberLine(pair.item))
|
|
87
|
+
continue;
|
|
82
88
|
const lt = (pair.item.listingType ?? "standard");
|
|
83
89
|
const bucket = byType.get(lt) ?? [];
|
|
84
90
|
bucket.push(pair);
|
package/dist/client.d.ts
CHANGED
|
@@ -225,12 +225,14 @@ export { ProtectedRoute, AuthStatusPanel, ForgotPasswordView, LoginForm, Registe
|
|
|
225
225
|
export type { AuthGuardUser, ForgotPasswordViewProps, LoginFormProps, LoginFormValues, RegisterFormProps, RegisterFormValues, ResetPasswordViewProps, VerifyEmailViewProps, } from "./features/auth/index";
|
|
226
226
|
export { useLogout, useLogin, useGoogleLogin, useLinkGoogleAccount, useRegister, useForgotPassword, useResetPassword, useVerifyEmail, useChangeEmail } from "./features/auth/index";
|
|
227
227
|
export type { LoginCredentials, RegisterData, ForgotPasswordData, ResetPasswordData, VerifyEmailData, ChangeEmailData } from "./features/auth/index";
|
|
228
|
-
export { CartView, CartItemRow, CartSummary, CartPriceBreakdown, CartDrawer, CheckoutView, CheckoutSuccessView, CheckoutAddressStep, useGuestCart, useCartCount, useAddToCart, useCart, useGuestCartMerge, useCartQuery } from "./features/cart/index";
|
|
228
|
+
export { CartView, CartItemRow, CartGroupLineRow, CartSummary, CartPriceBreakdown, CartDrawer, CheckoutView, CheckoutSuccessView, CheckoutAddressStep, useGuestCart, useCartCount, useAddToCart, useCart, useGuestCartMerge, useCartQuery } from "./features/cart/index";
|
|
229
229
|
export { StoreAddonsPicker } from "./features/cart/index";
|
|
230
230
|
export type { CartPriceBreakdownData, CartPriceBreakdownStore, StoreAddonsValue, StoreAddonsRates } from "./features/cart/index";
|
|
231
231
|
export { getCartOps, CART_OPS_CHANGE_EVENT } from "./features/cart/utils/pending-ops";
|
|
232
232
|
export type { CartOp } from "./features/cart/utils/pending-ops";
|
|
233
233
|
export type { CartItem, CartItemMeta, CartData, GuestCartItem } from "./features/cart/index";
|
|
234
|
+
export type { CartLineKind, CartGroupSource, CartLineMember, } from "./features/cart/schemas/firestore";
|
|
235
|
+
export type { CartGroupLineRowProps } from "./features/cart/components/CartGroupLineRow";
|
|
234
236
|
export { useAddresses, useCreateAddress, useUpdateAddress, useDeleteAddress, useSetDefaultAddress, useAddress } from "./features/account/index";
|
|
235
237
|
export type { Address, AddressFormData } from "./features/account/index";
|
|
236
238
|
export { AddressBook, AddressCard, AddressForm } from "./features/account/index";
|
|
@@ -262,6 +264,10 @@ export type { WishlistCapEventDetail } from "./features/wishlist/hooks/useWishli
|
|
|
262
264
|
export { getGuestWishlistItems, addToGuestWishlist, removeFromGuestWishlist, isInGuestWishlist, clearGuestWishlist, getGuestWishlistCount, getGuestWishlistByType, } from "./features/wishlist/utils/guest-wishlist";
|
|
263
265
|
export { InteractiveProductCard } from "./features/products/index";
|
|
264
266
|
export type { InteractiveProductCardProps } from "./features/products/index";
|
|
267
|
+
export { GroupedListingDetailView } from "./features/grouped/components/GroupedListingDetailView";
|
|
268
|
+
export type { GroupedListingDetailViewProps } from "./features/grouped/components/GroupedListingDetailView";
|
|
269
|
+
export { GroupMemberPicker } from "./features/products/components/GroupMemberPicker";
|
|
270
|
+
export type { GroupMemberPickerProps, GroupPickerMember, } from "./features/products/components/GroupMemberPicker";
|
|
265
271
|
export { MarketplaceBundleCard } from "./features/products/components/MarketplaceBundleCard";
|
|
266
272
|
export type { MarketplaceBundleCardProps, MarketplaceBundleCardData, MarketplaceBundleCardLabels, } from "./features/products/components/MarketplaceBundleCard";
|
|
267
273
|
export { CompareOverlay } from "./features/products/components/CompareOverlay";
|
|
@@ -418,8 +424,8 @@ export type { BecomeSellerResult, SellerCreateProductViewProps, SellerEditProduc
|
|
|
418
424
|
export { BecomeSellerView } from "./features/account/index";
|
|
419
425
|
export type { BidDocument } from "./features/auctions/index";
|
|
420
426
|
export type { ClaimedCouponDocument } from "./features/promotions/schemas";
|
|
421
|
-
export {
|
|
422
|
-
export type {
|
|
427
|
+
export { groupOrderItemsByLine } from "./features/orders/index";
|
|
428
|
+
export type { LineOrderGroup, OutOfStockPolicy } from "./features/orders/index";
|
|
423
429
|
export { MediaUploadField } from "./features/media/index";
|
|
424
430
|
export { PrizeRevealModal } from "./features/products/components/PrizeRevealModal";
|
|
425
431
|
export { useProduct } from "./features/products/index";
|
package/dist/client.js
CHANGED
|
@@ -260,7 +260,7 @@ export { PAGE_VIEW_ENTITY_TYPES } from "./features/analytics/types";
|
|
|
260
260
|
export { AuctionBidsTable } from "./features/auctions/components/AuctionBidsTable";
|
|
261
261
|
export { ProtectedRoute, AuthStatusPanel, ForgotPasswordView, LoginForm, RegisterForm, ResetPasswordView, VerifyEmailView } from "./features/auth/index";
|
|
262
262
|
export { useLogout, useLogin, useGoogleLogin, useLinkGoogleAccount, useRegister, useForgotPassword, useResetPassword, useVerifyEmail, useChangeEmail } from "./features/auth/index";
|
|
263
|
-
export { CartView, CartItemRow, CartSummary, CartPriceBreakdown, CartDrawer, CheckoutView, CheckoutSuccessView, CheckoutAddressStep, useGuestCart, useCartCount, useAddToCart, useCart, useGuestCartMerge, useCartQuery } from "./features/cart/index";
|
|
263
|
+
export { CartView, CartItemRow, CartGroupLineRow, CartSummary, CartPriceBreakdown, CartDrawer, CheckoutView, CheckoutSuccessView, CheckoutAddressStep, useGuestCart, useCartCount, useAddToCart, useCart, useGuestCartMerge, useCartQuery } from "./features/cart/index";
|
|
264
264
|
export { StoreAddonsPicker } from "./features/cart/index";
|
|
265
265
|
export { getCartOps, CART_OPS_CHANGE_EVENT } from "./features/cart/utils/pending-ops";
|
|
266
266
|
export { useAddresses, useCreateAddress, useUpdateAddress, useDeleteAddress, useSetDefaultAddress, useAddress } from "./features/account/index";
|
|
@@ -282,6 +282,8 @@ export { SyncManagerMount } from "./core/components/SyncManagerMount";
|
|
|
282
282
|
export { useWishlistCount, useWishlistCountWithLimit, WISHLIST_CAP_EVENT } from "./features/wishlist/hooks/useWishlistCount";
|
|
283
283
|
export { getGuestWishlistItems, addToGuestWishlist, removeFromGuestWishlist, isInGuestWishlist, clearGuestWishlist, getGuestWishlistCount, getGuestWishlistByType, } from "./features/wishlist/utils/guest-wishlist";
|
|
284
284
|
export { InteractiveProductCard } from "./features/products/index";
|
|
285
|
+
export { GroupedListingDetailView } from "./features/grouped/components/GroupedListingDetailView";
|
|
286
|
+
export { GroupMemberPicker } from "./features/products/components/GroupMemberPicker";
|
|
285
287
|
export { MarketplaceBundleCard } from "./features/products/components/MarketplaceBundleCard";
|
|
286
288
|
export { CompareOverlay } from "./features/products/components/CompareOverlay";
|
|
287
289
|
export { COMPARE_MAX_ITEMS } from "./features/products/constants/action-defs";
|
|
@@ -404,7 +406,7 @@ export { SpinWheelView, EventRaffleEntryForm } from "./features/events/index";
|
|
|
404
406
|
export { Card, CardBody } from "./ui/index";
|
|
405
407
|
export { SellerCouponsView, SellerCreateProductView, SellerEditProductView, useBecomeSeller, } from "./features/seller/index";
|
|
406
408
|
export { BecomeSellerView } from "./features/account/index";
|
|
407
|
-
export {
|
|
409
|
+
export { groupOrderItemsByLine } from "./features/orders/index";
|
|
408
410
|
export { MediaUploadField } from "./features/media/index";
|
|
409
411
|
export { PrizeRevealModal } from "./features/products/components/PrizeRevealModal";
|
|
410
412
|
export { useProduct } from "./features/products/index";
|