@mohasinac/appkit 4.25.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 +38 -68
- 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/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/seo/sitemap.js +24 -1
- package/dist/_internal/shared/checkout/order-math.d.ts +63 -4
- package/dist/_internal/shared/checkout/order-math.js +119 -7
- package/dist/client.d.ts +7 -3
- package/dist/client.js +3 -2
- package/dist/constants/api-endpoints.d.ts +0 -6
- package/dist/constants/api-endpoints.js +0 -2
- package/dist/features/analytics/types.d.ts +1 -1
- package/dist/features/analytics/types.js +3 -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.js +6 -0
- 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/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 +1 -1
- package/dist/index.js +1 -1
- package/dist/next/routing/route-map.d.ts +12 -0
- package/dist/next/routing/route-map.js +6 -0
- package/dist/server-entry.d.ts +1 -1
- package/dist/server-entry.js +1 -1
- package/dist/server.d.ts +2 -0
- package/dist/server.js +4 -0
- package/dist/styles.css +1 -1
- package/dist/tailwind-utilities.css +1 -1
- package/package.json +1 -1
|
@@ -17,7 +17,7 @@ const STOCK_BADGE_TEXT = {
|
|
|
17
17
|
out_of_stock: BUNDLE_COPY.stockBadge.out_of_stock,
|
|
18
18
|
};
|
|
19
19
|
const PLACEHOLDER_EMOJI = "๐ฆ";
|
|
20
|
-
export function BundleDetailView({ bundle, members = [], onBuyNow, relatedBundles = [], }) {
|
|
20
|
+
export function BundleDetailView({ bundle, members = [], onBuyNow, onAddToCart, relatedBundles = [], }) {
|
|
21
21
|
const memberCount = members.length || bundle.bundleProductIds?.length || 0;
|
|
22
22
|
const stock = bundle.bundleStockStatus ?? "in_stock";
|
|
23
23
|
const cover = bundle.display?.coverImage;
|
|
@@ -25,5 +25,5 @@ export function BundleDetailView({ bundle, members = [], onBuyNow, relatedBundle
|
|
|
25
25
|
? formatCurrency(bundle.bundlePrice, "INR")
|
|
26
26
|
: BUNDLE_COPY.detail.priceFallback;
|
|
27
27
|
const discount = computeBundleDiscount(bundle.bundlePrice, bundle.bundleOriginalTotal);
|
|
28
|
-
return (_jsxs(Main, { children: [_jsx(Section, { padding: "y-2xl", children: _jsx(Container, { size: "xl", children: _jsxs(Stack, { gap: "lg", children: [_jsxs(Stack, { gap: "lg", direction: "md-row", align: "start", children: [_jsx(Div, { className: "w-full md:w-2/5", children: members.length > 0 ? (_jsx(BundleCollage, { members: members })) : (_jsx(Div, { className: `aspect-video w-full ${__O.hidden}`, rounded: "2xl", surface: "subtle", children: _jsx(Div, { className: "h-full w-full", children: _jsx(MediaImage, { src: cover, alt: bundle.name, size: "hero", fallback: PLACEHOLDER_EMOJI }) }) })) }), _jsxs(Stack, { gap: "md", className: "flex-1", children: [_jsx(Heading, { level: 1, size: "3xl", weight: "semibold", color: "primary", children: bundle.name }), _jsxs(Row, { gap: "sm", align: "center", wrap: true, children: [_jsx(Text, { size: "lg", weight: "bold", children: priceLabel }), discount && (_jsxs(_Fragment, { children: [_jsx(Text, { size: "sm", color: "muted", className: "line-through", children: formatCurrency(discount.originalTotal, "INR") }), _jsx(Badge, { variant: "success", children: BUNDLE_COPY.detail.discountBadge(discount.percent) })] })), _jsxs(Text, { size: "sm", color: "muted", children: ["\u00B7 ", BUNDLE_COPY.detail.itemCount(memberCount)] }), _jsx(Badge, { variant: BUNDLE_STOCK_VARIANT[stock], children: STOCK_BADGE_TEXT[stock] })] }), discount && (_jsx(Text, { size: "sm", color: "success", weight: "medium", children: BUNDLE_COPY.detail.savingsLabel(formatCurrency(discount.savings, "INR")) })), bundle.description && (_jsxs(Stack, { gap: "xs", children: [_jsx(Heading, { level: 2, className: "text-[var(--appkit-color-text-muted)]", size: "sm", weight: "semibold", children: BUNDLE_COPY.detailDescriptionHeading }), _jsx(Text, { className: "whitespace-pre-line", children: bundle.description })] })), onBuyNow ? (_jsx(BundleBuyNowCta, { bundleSlug: bundle.slug, outOfStock: stock === "out_of_stock", onBuyNow: onBuyNow })) : (_jsxs(Div, { textSize: "sm", className: "border-dashed", border: "strong", surface: "muted", rounded: "xl", padding: "md", "aria-live": "polite", children: [_jsx(Text, { weight: "semibold", className: "mb-1 block", children: BUNDLE_COPY.detail.ctaDisabled }), _jsx(Text, { size: "sm", color: "muted", children: BUNDLE_COPY.detail.ctaHint })] }))] })] }), _jsxs(Stack, { gap: "md", children: [_jsx(Heading, { level: 2, size: "xl", weight: "semibold", color: "primary", children: BUNDLE_COPY.detailItemsHeading }), members.length === 0 ? (_jsx(Text, { color: "muted", children: BUNDLE_COPY.detail.emptyMembers })) : (_jsx(Ul, { gap: "2", className: "grid grid-cols-1 sm:grid-cols-2", children: members.map((p, i) => (_jsxs(Li, { layout: "flex-center", gap: "2", rounded: "lg", border: "subtle", padding: "inlineSm", textSize: "sm", children: [_jsxs(Span, { size: "xs", weight: "semibold", className: "w-5", color: "faint", children: ["#", i + 1] }), _jsx(Link, { href: String(ROUTES.PUBLIC.PRODUCT_DETAIL?.(p.slug ?? p.id) ?? "#"), className: "flex-1 truncate font-medium hover:underline hover:text-primary", children: p.title }), _jsx(Span, { size: "xs", color: "muted", children: formatCurrency(p.price ?? 0, p.currency ?? "INR") })] }, p.id))) }))] })] }) }) }), relatedBundles.length > 0 && (_jsx(Section, { border: "subtle", surface: "default", className: "border-t", padding: "y-lg", children: _jsxs(Container, { size: "xl", children: [_jsx(Heading, { level: 2, className: "mb-4", size: "xl", weight: "semibold", children: "Related Bundles" }), _jsx(CategoryGrid, { categories: relatedBundles, getHref: (c) => String(ROUTES.PUBLIC.BUNDLE_DETAIL(c.slug)) })] }) }))] }));
|
|
28
|
+
return (_jsxs(Main, { children: [_jsx(Section, { padding: "y-2xl", children: _jsx(Container, { size: "xl", children: _jsxs(Stack, { gap: "lg", children: [_jsxs(Stack, { gap: "lg", direction: "md-row", align: "start", children: [_jsx(Div, { className: "w-full md:w-2/5", children: members.length > 0 ? (_jsx(BundleCollage, { members: members })) : (_jsx(Div, { className: `aspect-video w-full ${__O.hidden}`, rounded: "2xl", surface: "subtle", children: _jsx(Div, { className: "h-full w-full", children: _jsx(MediaImage, { src: cover, alt: bundle.name, size: "hero", fallback: PLACEHOLDER_EMOJI }) }) })) }), _jsxs(Stack, { gap: "md", className: "flex-1", children: [_jsx(Heading, { level: 1, size: "3xl", weight: "semibold", color: "primary", children: bundle.name }), _jsxs(Row, { gap: "sm", align: "center", wrap: true, children: [_jsx(Text, { size: "lg", weight: "bold", children: priceLabel }), discount && (_jsxs(_Fragment, { children: [_jsx(Text, { size: "sm", color: "muted", className: "line-through", children: formatCurrency(discount.originalTotal, "INR") }), _jsx(Badge, { variant: "success", children: BUNDLE_COPY.detail.discountBadge(discount.percent) })] })), _jsxs(Text, { size: "sm", color: "muted", children: ["\u00B7 ", BUNDLE_COPY.detail.itemCount(memberCount)] }), _jsx(Badge, { variant: BUNDLE_STOCK_VARIANT[stock], children: STOCK_BADGE_TEXT[stock] })] }), discount && (_jsx(Text, { size: "sm", color: "success", weight: "medium", children: BUNDLE_COPY.detail.savingsLabel(formatCurrency(discount.savings, "INR")) })), bundle.description && (_jsxs(Stack, { gap: "xs", children: [_jsx(Heading, { level: 2, className: "text-[var(--appkit-color-text-muted)]", size: "sm", weight: "semibold", children: BUNDLE_COPY.detailDescriptionHeading }), _jsx(Text, { className: "whitespace-pre-line", children: bundle.description })] })), onBuyNow ? (_jsx(BundleBuyNowCta, { bundleSlug: bundle.slug, outOfStock: stock === "out_of_stock", onBuyNow: onBuyNow, onAddToCart: onAddToCart })) : (_jsxs(Div, { textSize: "sm", className: "border-dashed", border: "strong", surface: "muted", rounded: "xl", padding: "md", "aria-live": "polite", children: [_jsx(Text, { weight: "semibold", className: "mb-1 block", children: BUNDLE_COPY.detail.ctaDisabled }), _jsx(Text, { size: "sm", color: "muted", children: BUNDLE_COPY.detail.ctaHint })] }))] })] }), _jsxs(Stack, { gap: "md", children: [_jsx(Heading, { level: 2, size: "xl", weight: "semibold", color: "primary", children: BUNDLE_COPY.detailItemsHeading }), members.length === 0 ? (_jsx(Text, { color: "muted", children: BUNDLE_COPY.detail.emptyMembers })) : (_jsx(Ul, { gap: "2", className: "grid grid-cols-1 sm:grid-cols-2", children: members.map((p, i) => (_jsxs(Li, { layout: "flex-center", gap: "2", rounded: "lg", border: "subtle", padding: "inlineSm", textSize: "sm", children: [_jsxs(Span, { size: "xs", weight: "semibold", className: "w-5", color: "faint", children: ["#", i + 1] }), _jsx(Link, { href: String(ROUTES.PUBLIC.PRODUCT_DETAIL?.(p.slug ?? p.id) ?? "#"), className: "flex-1 truncate font-medium hover:underline hover:text-primary", children: p.title }), _jsx(Span, { size: "xs", color: "muted", children: formatCurrency(p.price ?? 0, p.currency ?? "INR") })] }, p.id))) }))] })] }) }) }), relatedBundles.length > 0 && (_jsx(Section, { border: "subtle", surface: "default", className: "border-t", padding: "y-lg", children: _jsxs(Container, { size: "xl", children: [_jsx(Heading, { level: 2, className: "mb-4", size: "xl", weight: "semibold", children: "Related Bundles" }), _jsx(CategoryGrid, { categories: relatedBundles, getHref: (c) => String(ROUTES.PUBLIC.BUNDLE_DETAIL(c.slug)) })] }) }))] }));
|
|
29
29
|
}
|
|
@@ -3,8 +3,11 @@ import type { CategoryDocument } from "../schemas";
|
|
|
3
3
|
export interface CategoryBundlesListingProps {
|
|
4
4
|
initialBundles: CategoryDocument[];
|
|
5
5
|
brandName?: string;
|
|
6
|
+
/** Cards render the CTA in `compact` mode, which has no stepper โ quantity
|
|
7
|
+
* is always 1 from here. The full stepper lives on the bundle detail page. */
|
|
6
8
|
onBuyNow?: (input: {
|
|
7
9
|
bundleSlug: string;
|
|
10
|
+
quantity: number;
|
|
8
11
|
}) => Promise<unknown>;
|
|
9
12
|
}
|
|
10
13
|
export declare function CategoryBundlesListing({ initialBundles, brandName, onBuyNow, }: CategoryBundlesListingProps): React.JSX.Element;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GroupedListingDetailView โ the public page for a `groupedListings` document.
|
|
3
|
+
*
|
|
4
|
+
* Before this, a grouped listing had no page at all: it existed only as a
|
|
5
|
+
* horizontal carousel on other pages, so there was nowhere to see the whole
|
|
6
|
+
* group and nothing to link to from a cart line built out of it.
|
|
7
|
+
*
|
|
8
|
+
* A grouped listing has NO price of its own โ SB-UNI-V removed its pricing
|
|
9
|
+
* fields on purpose. That is not a gap to fill: under "pick as you wish" the
|
|
10
|
+
* price simply IS the sum of what the buyer selected, which is exactly what
|
|
11
|
+
* `GroupMemberPicker` computes and what `unitPriceFor` recomputes server-side.
|
|
12
|
+
*/
|
|
13
|
+
import React from "react";
|
|
14
|
+
import { type GroupPickerMember } from "../../products/components/GroupMemberPicker";
|
|
15
|
+
import type { GroupedListingDocument } from "../schemas/firestore";
|
|
16
|
+
export interface GroupedListingDetailViewProps {
|
|
17
|
+
group: GroupedListingDocument;
|
|
18
|
+
/** Members, already projected for public consumption. */
|
|
19
|
+
members: GroupPickerMember[];
|
|
20
|
+
}
|
|
21
|
+
export declare function GroupedListingDetailView({ group, members }: GroupedListingDetailViewProps): React.JSX.Element;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Container, Div, Heading, Section, Span, Stack, Text, } from "../../../ui";
|
|
4
|
+
import { MediaImage } from "../../media/MediaImage";
|
|
5
|
+
import { GroupMemberPicker } from "../../products/components/GroupMemberPicker";
|
|
6
|
+
const __O = { hidden: "overflow-hidden" };
|
|
7
|
+
/** Human label per theme โ mirrors GROUP_THEME_TITLE on the carousel. */
|
|
8
|
+
const THEME_LABEL = {
|
|
9
|
+
related: "Related items",
|
|
10
|
+
character: "Same character",
|
|
11
|
+
lineage: "Same lineage",
|
|
12
|
+
set: "Complete the set",
|
|
13
|
+
generic: "Grouped listing",
|
|
14
|
+
};
|
|
15
|
+
export function GroupedListingDetailView({ group, members }) {
|
|
16
|
+
return (_jsx(Section, { padding: "section", children: _jsx(Container, { children: _jsxs(Stack, { gap: "comfortable", children: [group.coverImage && (_jsx(Div, { className: `relative h-48 w-full md:h-64 ${__O.hidden}`, rounded: "xl", surface: "muted", children: _jsx(MediaImage, { src: group.coverImage, alt: group.title, size: "banner" }) })), _jsxs(Stack, { gap: "xs", children: [_jsx(Span, { size: "xs", color: "muted", transform: "uppercase", weight: "semibold", children: THEME_LABEL[group.groupTheme] }), _jsx(Heading, { level: 1, children: group.title }), group.description && (_jsx(Text, { color: "muted", children: group.description })), _jsxs(Text, { size: "sm", color: "muted", children: [members.length, " item", members.length === 1 ? "" : "s", " in this group"] })] }), members.length > 0 ? (_jsxs(Stack, { gap: "sm", children: [_jsx(Heading, { level: 2, size: "lg", children: "Pick what you want" }), _jsx(Text, { size: "sm", color: "muted", children: "Choose a quantity for each item \u2014 they'll go into your cart as a single line you can edit later." }), _jsx(GroupMemberPicker, { groupId: group.id, groupSource: "grouped-listing", members: members })] })) : (_jsx(Text, { color: "muted", children: "Nothing in this group is available right now." }))] }) }) }));
|
|
17
|
+
}
|
|
@@ -9,6 +9,7 @@ import { ProductCard } from "../../products/components/ProductGrid";
|
|
|
9
9
|
import { toProductItem } from "../../../_internal/shared/features/products/to-product-item";
|
|
10
10
|
import { CAROUSEL_PER_VIEW } from "../../homepage/constants/carousel-per-view";
|
|
11
11
|
import { Stack } from "../../../ui";
|
|
12
|
+
import { ROUTES } from "../../../next/routing/route-map";
|
|
12
13
|
const GROUP_THEME_TITLE = {
|
|
13
14
|
related: "Other listings you might like",
|
|
14
15
|
character: "Same character",
|
|
@@ -29,6 +30,10 @@ export function GroupedListingsCarousel({ groups }) {
|
|
|
29
30
|
return null;
|
|
30
31
|
return (_jsx(Stack, { gap: "xl", children: visibleGroups.map((group) => {
|
|
31
32
|
const items = group.items.map((p) => toProductItem(p));
|
|
32
|
-
return (_jsx(SectionCarousel, { title: group.title || GROUP_THEME_TITLE[group.groupTheme], pillLabel: "Grouped Listings", headingVariant: "editorial",
|
|
33
|
+
return (_jsx(SectionCarousel, { title: group.title || GROUP_THEME_TITLE[group.groupTheme], pillLabel: "Grouped Listings", headingVariant: "editorial",
|
|
34
|
+
// The group now has a page of its own, where the buyer can pick
|
|
35
|
+
// several members and add them as one cart line. Until it existed,
|
|
36
|
+
// this carousel was the only way a grouped listing was ever seen.
|
|
37
|
+
viewMoreHref: group.slug ? String(ROUTES.PUBLIC.GROUP_DETAIL(group.slug)) : undefined, viewMoreLabel: "Pick items from this group \u2192", items: items, isLoading: false, perView: CAROUSEL_PER_VIEW.standard, keyExtractor: (item) => item.id, renderItem: (item) => (_jsx(ProductCard, { product: item, href: item.slug || item.id ? pluginFor(item.listingType ?? "standard").detailRoute(item.slug || item.id) : undefined })) }, group.id));
|
|
33
38
|
}) }));
|
|
34
39
|
}
|
|
@@ -4,5 +4,5 @@ export * from "./components";
|
|
|
4
4
|
export * from "./schemas";
|
|
5
5
|
export * from "./columns";
|
|
6
6
|
export * from "./utils/order-splitter";
|
|
7
|
-
export {
|
|
7
|
+
export { groupOrderItemsByLine, type LineOrderGroup, type OrderItemForLineGrouping, } from "./utils/bundle-grouping";
|
|
8
8
|
export { manifest } from "./manifest";
|
|
@@ -5,5 +5,5 @@ export * from "./schemas";
|
|
|
5
5
|
export * from "./columns";
|
|
6
6
|
export * from "./utils/order-splitter";
|
|
7
7
|
// S-SBUNI-5 2026-05-13 โ bundle-aware order item grouping.
|
|
8
|
-
export {
|
|
8
|
+
export { groupOrderItemsByLine, } from "./utils/bundle-grouping";
|
|
9
9
|
export { manifest } from "./manifest";
|
|
@@ -188,7 +188,18 @@ export interface OrderDocumentItem {
|
|
|
188
188
|
cancelledReason?: string;
|
|
189
189
|
/** SB-UNI-5 โ forwarded from the cart line when this order item is an expanded bundle, so the order-detail UI can collapse it back under a "Bundle: <name>" header and stock-restore logic can fan back out to member products. */
|
|
190
190
|
bundleCategorySlug?: string;
|
|
191
|
+
/** @deprecated Legacy collapsed rows only โ expanded rows carry one product each. */
|
|
191
192
|
bundleProductIds?: string[];
|
|
193
|
+
/**
|
|
194
|
+
* Collapse key for a multi-member cart line, shared by every row the line
|
|
195
|
+
* expanded into. `groupOrderItemsByLine` groups on it so the receipt still
|
|
196
|
+
* reads as one "Bundle: X" / "Group: X" block.
|
|
197
|
+
*/
|
|
198
|
+
groupSlug?: string;
|
|
199
|
+
/** Display name of the bundle / group these rows came from. */
|
|
200
|
+
groupTitle?: string;
|
|
201
|
+
/** Which kind of multi-member line produced these rows. */
|
|
202
|
+
groupSource?: "bundle" | "product-group" | "grouped-listing";
|
|
192
203
|
/** P-8 GST โ snapshotted from the product at order time so the invoice stays accurate even if the product's HSN/rate later changes. */
|
|
193
204
|
hsnCode?: string;
|
|
194
205
|
gstRate?: 0 | 5 | 12 | 18 | 28;
|
|
@@ -7,42 +7,52 @@
|
|
|
7
7
|
* checkout receipt mirrors the buyer's intent. This helper is the pure
|
|
8
8
|
* grouping logic โ the UI primitive lives next to the consumer view.
|
|
9
9
|
*
|
|
10
|
-
* Two flavours of
|
|
11
|
-
* 1. ONE
|
|
12
|
-
* describe the bundle
|
|
13
|
-
* bundle
|
|
14
|
-
*
|
|
15
|
-
* 2.
|
|
16
|
-
*
|
|
17
|
-
*
|
|
10
|
+
* Two flavours of multi-member row can appear on an order:
|
|
11
|
+
* 1. LEGACY โ ONE row whose `bundleCategorySlug` + `bundleProductIds[]`
|
|
12
|
+
* describe the whole bundle, with the locked bundlePrice as its line
|
|
13
|
+
* total. Every bundle order placed before row expansion looks like this,
|
|
14
|
+
* and they keep rendering unchanged.
|
|
15
|
+
* 2. CURRENT โ expanded N rows, one per member, all sharing a `groupSlug`
|
|
16
|
+
* (and, for bundles, the same `bundleCategorySlug`). This is what
|
|
17
|
+
* checkout writes now, so each row can carry its own HSN/GST and be
|
|
18
|
+
* cancelled independently.
|
|
18
19
|
*
|
|
19
|
-
* Either way
|
|
20
|
-
*
|
|
20
|
+
* Either way this returns a stable, ordered list of groups so the renderer
|
|
21
|
+
* iterates once and the buyer's receipt reads the same.
|
|
21
22
|
*/
|
|
22
23
|
/** Minimal shape consumed from an order item (Firestore wire format). */
|
|
23
|
-
export interface
|
|
24
|
-
/**
|
|
24
|
+
export interface OrderItemForLineGrouping {
|
|
25
|
+
/** Collapse key for expanded rows (bundle OR buyer-assembled group). */
|
|
26
|
+
groupSlug?: string;
|
|
27
|
+
/** Display name of the bundle / group. */
|
|
28
|
+
groupTitle?: string;
|
|
29
|
+
/** Bundle discriminator โ also set on expanded bundle rows. */
|
|
25
30
|
bundleCategorySlug?: string;
|
|
26
|
-
/**
|
|
31
|
+
/** @deprecated Legacy collapsed rows only. */
|
|
27
32
|
bundleProductIds?: string[];
|
|
28
33
|
}
|
|
29
|
-
export type
|
|
34
|
+
export type LineOrderGroup<T extends OrderItemForLineGrouping> = {
|
|
30
35
|
kind: "single";
|
|
31
36
|
item: T;
|
|
32
37
|
index: number;
|
|
33
38
|
} | {
|
|
34
39
|
kind: "bundle";
|
|
40
|
+
/** The key these rows were collapsed on. */
|
|
35
41
|
bundleCategorySlug: string;
|
|
42
|
+
groupTitle?: string;
|
|
36
43
|
items: Array<{
|
|
37
44
|
item: T;
|
|
38
45
|
index: number;
|
|
39
46
|
}>;
|
|
40
|
-
/**
|
|
47
|
+
/** Members represented: the row count when expanded, the snapshot length when legacy. */
|
|
41
48
|
memberCount: number;
|
|
42
49
|
};
|
|
43
50
|
/**
|
|
44
|
-
* Walk the items array preserving original order; collapse
|
|
45
|
-
*
|
|
46
|
-
*
|
|
51
|
+
* Walk the items array preserving original order; collapse rows sharing a
|
|
52
|
+
* collapse key into one group. Single rows pass through unchanged.
|
|
53
|
+
*
|
|
54
|
+
* The key is `groupSlug ?? bundleCategorySlug` โ the fallback is what keeps
|
|
55
|
+
* legacy collapsed bundle rows (written before `groupSlug` existed) grouping
|
|
56
|
+
* exactly as they always did, with no order migration.
|
|
47
57
|
*/
|
|
48
|
-
export declare function
|
|
58
|
+
export declare function groupOrderItemsByLine<T extends OrderItemForLineGrouping>(items: T[]): Array<LineOrderGroup<T>>;
|
|
@@ -7,47 +7,57 @@
|
|
|
7
7
|
* checkout receipt mirrors the buyer's intent. This helper is the pure
|
|
8
8
|
* grouping logic โ the UI primitive lives next to the consumer view.
|
|
9
9
|
*
|
|
10
|
-
* Two flavours of
|
|
11
|
-
* 1. ONE
|
|
12
|
-
* describe the bundle
|
|
13
|
-
* bundle
|
|
14
|
-
*
|
|
15
|
-
* 2.
|
|
16
|
-
*
|
|
17
|
-
*
|
|
10
|
+
* Two flavours of multi-member row can appear on an order:
|
|
11
|
+
* 1. LEGACY โ ONE row whose `bundleCategorySlug` + `bundleProductIds[]`
|
|
12
|
+
* describe the whole bundle, with the locked bundlePrice as its line
|
|
13
|
+
* total. Every bundle order placed before row expansion looks like this,
|
|
14
|
+
* and they keep rendering unchanged.
|
|
15
|
+
* 2. CURRENT โ expanded N rows, one per member, all sharing a `groupSlug`
|
|
16
|
+
* (and, for bundles, the same `bundleCategorySlug`). This is what
|
|
17
|
+
* checkout writes now, so each row can carry its own HSN/GST and be
|
|
18
|
+
* cancelled independently.
|
|
18
19
|
*
|
|
19
|
-
* Either way
|
|
20
|
-
*
|
|
20
|
+
* Either way this returns a stable, ordered list of groups so the renderer
|
|
21
|
+
* iterates once and the buyer's receipt reads the same.
|
|
21
22
|
*/
|
|
22
23
|
/**
|
|
23
|
-
* Walk the items array preserving original order; collapse
|
|
24
|
-
*
|
|
25
|
-
*
|
|
24
|
+
* Walk the items array preserving original order; collapse rows sharing a
|
|
25
|
+
* collapse key into one group. Single rows pass through unchanged.
|
|
26
|
+
*
|
|
27
|
+
* The key is `groupSlug ?? bundleCategorySlug` โ the fallback is what keeps
|
|
28
|
+
* legacy collapsed bundle rows (written before `groupSlug` existed) grouping
|
|
29
|
+
* exactly as they always did, with no order migration.
|
|
26
30
|
*/
|
|
27
|
-
export function
|
|
31
|
+
export function groupOrderItemsByLine(items) {
|
|
28
32
|
const groups = [];
|
|
29
|
-
const
|
|
33
|
+
const indexByKey = new Map();
|
|
30
34
|
items.forEach((item, index) => {
|
|
31
|
-
const
|
|
32
|
-
if (!
|
|
35
|
+
const key = item.groupSlug ?? item.bundleCategorySlug;
|
|
36
|
+
if (!key) {
|
|
33
37
|
groups.push({ kind: "single", item, index });
|
|
34
38
|
return;
|
|
35
39
|
}
|
|
36
|
-
const existingIdx =
|
|
40
|
+
const existingIdx = indexByKey.get(key);
|
|
37
41
|
if (existingIdx === undefined) {
|
|
38
|
-
const memberCount = item.bundleProductIds?.length ?? 1;
|
|
39
42
|
groups.push({
|
|
40
43
|
kind: "bundle",
|
|
41
|
-
bundleCategorySlug:
|
|
44
|
+
bundleCategorySlug: key,
|
|
45
|
+
groupTitle: item.groupTitle,
|
|
46
|
+
// Legacy rows carry the member list; expanded rows ARE the members, so
|
|
47
|
+
// the count is grown as siblings arrive below.
|
|
48
|
+
memberCount: item.bundleProductIds?.length ?? 1,
|
|
42
49
|
items: [{ item, index }],
|
|
43
|
-
memberCount,
|
|
44
50
|
});
|
|
45
|
-
|
|
51
|
+
indexByKey.set(key, groups.length - 1);
|
|
46
52
|
return;
|
|
47
53
|
}
|
|
48
54
|
const existing = groups[existingIdx];
|
|
49
55
|
if (existing.kind === "bundle") {
|
|
50
56
|
existing.items.push({ item, index });
|
|
57
|
+
// Expanded rows: one row per member, so the row count IS the member count.
|
|
58
|
+
if (!item.bundleProductIds?.length) {
|
|
59
|
+
existing.memberCount = existing.items.length;
|
|
60
|
+
}
|
|
51
61
|
}
|
|
52
62
|
});
|
|
53
63
|
return groups;
|
|
@@ -188,6 +188,12 @@ export const categoriesTesterSeedData = [
|
|
|
188
188
|
bundleProductIds: ["product-tester-standard-1", "product-tester-standard-2"],
|
|
189
189
|
bundleOriginalTotal: 348, // 199 + 149
|
|
190
190
|
bundleStockStatus: "in_stock",
|
|
191
|
+
// Without these, `addBundleToCartAction` wrote `storeId: ""` on the cart
|
|
192
|
+
// line, which produced an order group keyed `standard:` with an undefined
|
|
193
|
+
// storeId โ no seller notification, no shipping resolution, no payout. The
|
|
194
|
+
// bug was seeded in, so it reproduced on every tester run.
|
|
195
|
+
createdByStoreId: "store-tester-qa-seller",
|
|
196
|
+
createdByStoreName: "Tester Sandbox Store",
|
|
191
197
|
rootId: "bundle-tester-sandbox",
|
|
192
198
|
parentIds: [],
|
|
193
199
|
childrenIds: [],
|
|
@@ -232,6 +232,9 @@ export const productsTesterSeedData = [
|
|
|
232
232
|
customSections: [],
|
|
233
233
|
isPromoted: false,
|
|
234
234
|
isOnSale: false,
|
|
235
|
+
// 18% โ contrasts with member #3's 12% so one grouped line spans two rates.
|
|
236
|
+
gstRate: 18,
|
|
237
|
+
hsnCode: "95030090",
|
|
235
238
|
// Child of the "group-tester-sandbox-bundle" product-group ("Set") parent
|
|
236
239
|
// below โ findByGroupId() queries products by this field directly, so
|
|
237
240
|
// without it the parent's groupChildSlugs[] mirror is never actually
|
|
@@ -265,6 +268,40 @@ export const productsTesterSeedData = [
|
|
|
265
268
|
customSections: [],
|
|
266
269
|
isPromoted: false,
|
|
267
270
|
isOnSale: false,
|
|
271
|
+
gstRate: 18,
|
|
272
|
+
hsnCode: "95030090",
|
|
273
|
+
groupId: "group-tester-sandbox-bundle",
|
|
274
|
+
groupParentSlug: "group-tester-sandbox-bundle",
|
|
275
|
+
groupTitle: TESTER_GROUP_TITLE,
|
|
276
|
+
createdAt: new Date(),
|
|
277
|
+
updatedAt: new Date(),
|
|
278
|
+
}),
|
|
279
|
+
withTokens({
|
|
280
|
+
id: "product-tester-standard-3",
|
|
281
|
+
slug: "product-tester-standard-3",
|
|
282
|
+
title: "Test Sticker Pack โ Standard Listing #3",
|
|
283
|
+
description: "Third member of the test product group. Carries a DIFFERENT gstRate from its siblings on purpose: it is the only fixture that exercises per-member GST proration on a grouped/bundled cart line, where taxing the whole line at one product's rate would be wrong in both directions. Auto-expires in 7 days.",
|
|
284
|
+
categorySlugs: COLLECTIBLES_CATEGORY_SLUGS,
|
|
285
|
+
categoryNames: COLLECTIBLES_CATEGORY_NAMES,
|
|
286
|
+
brandSlug: "brand-tester-sandbox",
|
|
287
|
+
brand: "TestBrand",
|
|
288
|
+
price: 99,
|
|
289
|
+
currency: "INR",
|
|
290
|
+
stockQuantity: 10,
|
|
291
|
+
availableQuantity: 10,
|
|
292
|
+
isSold: false,
|
|
293
|
+
mainImage: seedExtMedia("https://picsum.photos/seed/product-image-tester-standard-3-20260101/900/900"),
|
|
294
|
+
images: [seedExtMedia("https://picsum.photos/seed/product-image-tester-standard-3-20260101/900/900")],
|
|
295
|
+
status: PRODUCT_FIELDS.STATUS_VALUES.PUBLISHED,
|
|
296
|
+
condition: PRODUCT_FIELDS.CONDITION_VALUES.NEW,
|
|
297
|
+
listingType: "standard",
|
|
298
|
+
customFields: [],
|
|
299
|
+
customSections: [],
|
|
300
|
+
isPromoted: false,
|
|
301
|
+
isOnSale: false,
|
|
302
|
+
// 12% against the siblings' 18% โ the whole point of this fixture.
|
|
303
|
+
gstRate: 12,
|
|
304
|
+
hsnCode: "49081000",
|
|
268
305
|
groupId: "group-tester-sandbox-bundle",
|
|
269
306
|
groupParentSlug: "group-tester-sandbox-bundle",
|
|
270
307
|
groupTitle: TESTER_GROUP_TITLE,
|
|
@@ -644,7 +681,13 @@ export const productsTesterSeedData = [
|
|
|
644
681
|
isGroupParent: true,
|
|
645
682
|
groupId: "group-tester-sandbox-bundle",
|
|
646
683
|
groupTitle: TESTER_GROUP_TITLE,
|
|
647
|
-
|
|
684
|
+
// Parentโchild mirror: every id here must also carry `groupId` on its own
|
|
685
|
+
// document. The pair drifting is Root Cause #42's second instance.
|
|
686
|
+
groupChildSlugs: [
|
|
687
|
+
"product-tester-standard-1",
|
|
688
|
+
"product-tester-standard-2",
|
|
689
|
+
"product-tester-standard-3",
|
|
690
|
+
],
|
|
648
691
|
createdAt: new Date(),
|
|
649
692
|
updatedAt: new Date(),
|
|
650
693
|
}),
|
|
@@ -729,6 +729,39 @@ const rawTesterChecklistItems = [
|
|
|
729
729
|
href: "/cart",
|
|
730
730
|
cases: [
|
|
731
731
|
{ key: "update-qty", label: "Updating item quantity in cart recalculates the total" },
|
|
732
|
+
{
|
|
733
|
+
key: "group-picker-add-selected",
|
|
734
|
+
label: "On a product that is part of a group, \"Pick items โ\" opens a picker with +/- per member, and \"Add selected to cart\" creates ONE cart line (not one per item)",
|
|
735
|
+
description: "Use the Test Gadget page. Set two different members to different quantities, check the running total in the picker footer, then add. The cart should show a single line, not three.",
|
|
736
|
+
href: "/products/product-tester-standard-1",
|
|
737
|
+
},
|
|
738
|
+
{
|
|
739
|
+
key: "group-line-editable-in-cart",
|
|
740
|
+
label: "A grouped cart line expands to show its members, each with its own +/- and remove; removing the last member removes the whole line",
|
|
741
|
+
description: "There should be NO line-level quantity stepper on a grouped line โ the per-member steppers carry the whole selection. Changing one member should update both the line total and the seller subtotal.",
|
|
742
|
+
href: "/cart",
|
|
743
|
+
},
|
|
744
|
+
{
|
|
745
|
+
key: "group-picker-cross-store",
|
|
746
|
+
label: "A group whose members belong to different sellers shows the read-only table with no quantity column and an explanation, rather than an \"Add selected\" button that always fails",
|
|
747
|
+
href: "/products/product-tester-standard-1",
|
|
748
|
+
},
|
|
749
|
+
{
|
|
750
|
+
key: "bundle-copies-stepper",
|
|
751
|
+
label: "A bundle page has a \"Copies\" +/- stepper and BOTH \"Buy now\" and \"Add to cart\" โ Add to cart stays on the page, Buy now goes to checkout",
|
|
752
|
+
description: "A bundle is all-or-nothing: there must be no per-member quantity controls on the bundle page, only the copies stepper.",
|
|
753
|
+
href: "/bundles/bundle-tester-sandbox",
|
|
754
|
+
},
|
|
755
|
+
{
|
|
756
|
+
key: "bundle-line-editable-in-cart",
|
|
757
|
+
label: "A bundle in the cart shows its member list read-only and a copies stepper; changing copies recalculates the line at the bundle's discounted price, not the sum of member prices",
|
|
758
|
+
href: "/cart",
|
|
759
|
+
},
|
|
760
|
+
{
|
|
761
|
+
key: "grouped-listing-page-picker",
|
|
762
|
+
label: "A grouped listing has its own page with the same picker, reachable from the \"Pick items from this group โ\" button on a group carousel",
|
|
763
|
+
href: "/groups/group-beyblade-original-lineage",
|
|
764
|
+
},
|
|
732
765
|
{ key: "apply-coupon", label: "Applying a coupon code at checkout works" },
|
|
733
766
|
{ key: "remove-coupon", label: "Removing an already-applied coupon from the cart recalculates the total back down" },
|
|
734
767
|
{ key: "remove-item", label: "Removing an item from the cart works" },
|
package/dist/index.d.ts
CHANGED
|
@@ -3125,7 +3125,7 @@ export type { SessionUser } from "./react/index";
|
|
|
3125
3125
|
export type { UseUrlTableOptions } from "./react/index";
|
|
3126
3126
|
export { SUCCESS_MESSAGES } from "./values/index";
|
|
3127
3127
|
export { splitCartIntoOrderGroups } from "./features/orders/index";
|
|
3128
|
-
export {
|
|
3128
|
+
export { groupOrderItemsByLine, type LineOrderGroup, type OrderItemForLineGrouping, } from "./features/orders/index";
|
|
3129
3129
|
export { buildSieveFilters } from "./utils/filter.helper";
|
|
3130
3130
|
export { formatCurrency, formatFileSize } from "./utils/number.formatter";
|
|
3131
3131
|
export { formatMonthYear } from "./utils/date.formatter";
|
package/dist/index.js
CHANGED
|
@@ -5530,7 +5530,7 @@ export { SUCCESS_MESSAGES } from "./values/index";
|
|
|
5530
5530
|
// -- Missing order utils
|
|
5531
5531
|
export { splitCartIntoOrderGroups } from "./features/orders/index";
|
|
5532
5532
|
// S-SBUNI-5 2026-05-13 รขโฌ" bundle-aware order item grouping for receipts.
|
|
5533
|
-
export {
|
|
5533
|
+
export { groupOrderItemsByLine, } from "./features/orders/index";
|
|
5534
5534
|
// -- Missing utils
|
|
5535
5535
|
export { buildSieveFilters } from "./utils/filter.helper";
|
|
5536
5536
|
export { formatCurrency, formatFileSize } from "./utils/number.formatter";
|
|
@@ -25,6 +25,12 @@ export declare const DEFAULT_ROUTE_MAP: {
|
|
|
25
25
|
readonly PRE_ORDER_DETAIL: (id: string) => string;
|
|
26
26
|
readonly BUNDLES: "/bundles";
|
|
27
27
|
readonly BUNDLE_DETAIL: (slug: string) => string;
|
|
28
|
+
/**
|
|
29
|
+
* A `groupedListings` document's public page. Distinct from a product
|
|
30
|
+
* GROUP (`product.groupId`), whose canonical page is its parent product's
|
|
31
|
+
* detail page โ see ยง "Bundles vs Product Groups vs Grouped Listings".
|
|
32
|
+
*/
|
|
33
|
+
readonly GROUP_DETAIL: (slug: string) => string;
|
|
28
34
|
readonly PRIZE_DRAWS: "/prize-draws";
|
|
29
35
|
readonly PRIZE_DRAW_DETAIL: (slug: string) => string;
|
|
30
36
|
readonly SELLER_GUIDE_BUNDLES: "/seller-guide/bundles";
|
|
@@ -407,6 +413,12 @@ export declare const ROUTES: {
|
|
|
407
413
|
readonly PRE_ORDER_DETAIL: (id: string) => string;
|
|
408
414
|
readonly BUNDLES: "/bundles";
|
|
409
415
|
readonly BUNDLE_DETAIL: (slug: string) => string;
|
|
416
|
+
/**
|
|
417
|
+
* A `groupedListings` document's public page. Distinct from a product
|
|
418
|
+
* GROUP (`product.groupId`), whose canonical page is its parent product's
|
|
419
|
+
* detail page โ see ยง "Bundles vs Product Groups vs Grouped Listings".
|
|
420
|
+
*/
|
|
421
|
+
readonly GROUP_DETAIL: (slug: string) => string;
|
|
410
422
|
readonly PRIZE_DRAWS: "/prize-draws";
|
|
411
423
|
readonly PRIZE_DRAW_DETAIL: (slug: string) => string;
|
|
412
424
|
readonly SELLER_GUIDE_BUNDLES: "/seller-guide/bundles";
|
|
@@ -14,6 +14,12 @@ export const DEFAULT_ROUTE_MAP = {
|
|
|
14
14
|
// SB1-J โ bundles + prize draws (additive, pages not yet built)
|
|
15
15
|
BUNDLES: "/bundles",
|
|
16
16
|
BUNDLE_DETAIL: (slug) => `/bundles/${slug}`,
|
|
17
|
+
/**
|
|
18
|
+
* A `groupedListings` document's public page. Distinct from a product
|
|
19
|
+
* GROUP (`product.groupId`), whose canonical page is its parent product's
|
|
20
|
+
* detail page โ see ยง "Bundles vs Product Groups vs Grouped Listings".
|
|
21
|
+
*/
|
|
22
|
+
GROUP_DETAIL: (slug) => `/groups/${slug}`,
|
|
17
23
|
PRIZE_DRAWS: "/prize-draws",
|
|
18
24
|
PRIZE_DRAW_DETAIL: (slug) => `/prize-draws/${slug}`,
|
|
19
25
|
SELLER_GUIDE_BUNDLES: "/seller-guide/bundles",
|
package/dist/server-entry.d.ts
CHANGED
|
@@ -65,7 +65,7 @@ export { renderFaqOg, renderFaqOgImage, type FaqOgData, } from "./_internal/serv
|
|
|
65
65
|
export { renderScamOg, renderScamOgImage, type ScamOgData, getScammerForDetail, getScammerProfilePageData as getScammerProfilePageDataCached, } from "./_internal/server/features/scams/index";
|
|
66
66
|
export { getSearchResults, searchAction, type SearchQuery, } from "./_internal/server/features/search/index";
|
|
67
67
|
export { getCategoryForDetail, listRootCategories, listFeaturedCategories, listMenuCategories, getCategoryTree, listSitemapCategories, CATEGORIES_PAGE_SIZE, CATEGORIES_ROOT_TIER, CATEGORIES_MAX_DEPTH, CATEGORIES_SITEMAP_LIMIT, CATEGORIES_FEATURED_LIMIT, CATEGORIES_MENU_LIMIT, } from "./_internal/server/features/categories/index";
|
|
68
|
-
export { getGroupedListingForDetail, getGroupedListingWithItems, listGroupedListings, listFeaturedGroupedListings, listSitemapGroupedListings, getGroupsForProduct, getGroupsWithItemsForProduct, GROUPED_LISTINGS_PAGE_SIZE, GROUPED_LISTINGS_FEATURED_LIMIT, GROUPED_LISTINGS_SITEMAP_LIMIT, type GroupedListingWithItems, type ListGroupedListingsParams, type SitemapGroupedListing, } from "./_internal/server/features/grouped/index";
|
|
68
|
+
export { getGroupedListingForDetail, getGroupedListingWithItems, listGroupedListings, listFeaturedGroupedListings, listSitemapGroupedListings, getGroupsForProduct, getGroupsWithItemsForProduct, GROUPED_LISTINGS_PAGE_SIZE, GROUPED_LISTINGS_FEATURED_LIMIT, GROUPED_LISTINGS_SITEMAP_LIMIT, buildGroupedListingMetadata, renderGroupedListingOg, type GroupedListingWithItems, type ListGroupedListingsParams, type SitemapGroupedListing, type GroupedListingMetadataOptions, } from "./_internal/server/features/grouped/index";
|
|
69
69
|
export { createCheckoutOrderAction, attachPaymentAction, verifyAndPlaceRazorpayOrderAction, previewCheckoutPricing, resolveShippingCost, formatShippingAddress, CHECKOUT_DEFAULT_COMMISSIONS, CHECKOUT_PAYMENT_METHODS, type CreateCheckoutOrderInput, type VerifyAndPlaceRazorpayOrderInput, type CheckoutOrderResult, type CheckoutPaymentMethod, type CheckoutPricingPreviewInput, type CheckoutPricingPreview, } from "./_internal/server/features/checkout/index";
|
|
70
70
|
export { unitPriceFor, lineTotalFor, computePreOrderDepositAmount, } from "./_internal/shared/checkout/order-math";
|
|
71
71
|
export { processRefundAction, type ProcessRefundInput, } from "./_internal/server/features/refunds/actions";
|
package/dist/server-entry.js
CHANGED
|
@@ -91,7 +91,7 @@ export { getSearchResults, searchAction, } from "./_internal/server/features/sea
|
|
|
91
91
|
// S3: categories data layer
|
|
92
92
|
export { getCategoryForDetail, listRootCategories, listFeaturedCategories, listMenuCategories, getCategoryTree, listSitemapCategories, CATEGORIES_PAGE_SIZE, CATEGORIES_ROOT_TIER, CATEGORIES_MAX_DEPTH, CATEGORIES_SITEMAP_LIMIT, CATEGORIES_FEATURED_LIMIT, CATEGORIES_MENU_LIMIT, } from "./_internal/server/features/categories/index";
|
|
93
93
|
// S3: grouped listings data layer
|
|
94
|
-
export { getGroupedListingForDetail, getGroupedListingWithItems, listGroupedListings, listFeaturedGroupedListings, listSitemapGroupedListings, getGroupsForProduct, getGroupsWithItemsForProduct, GROUPED_LISTINGS_PAGE_SIZE, GROUPED_LISTINGS_FEATURED_LIMIT, GROUPED_LISTINGS_SITEMAP_LIMIT, } from "./_internal/server/features/grouped/index";
|
|
94
|
+
export { getGroupedListingForDetail, getGroupedListingWithItems, listGroupedListings, listFeaturedGroupedListings, listSitemapGroupedListings, getGroupsForProduct, getGroupsWithItemsForProduct, GROUPED_LISTINGS_PAGE_SIZE, GROUPED_LISTINGS_FEATURED_LIMIT, GROUPED_LISTINGS_SITEMAP_LIMIT, buildGroupedListingMetadata, renderGroupedListingOg, } from "./_internal/server/features/grouped/index";
|
|
95
95
|
// SB-UNI-V โ bundles data layer deleted; consumers should query
|
|
96
96
|
// `categoriesRepository.findBySlugAndType(slug, "bundle")` /
|
|
97
97
|
// `.listByType("bundle", opts)` / `.findById(id)` directly.
|
package/dist/server.d.ts
CHANGED
|
@@ -503,6 +503,8 @@ export { getProductForDetail, listSitemapProducts, computeRelatedItems, toProduc
|
|
|
503
503
|
export { listPublicProducts, parsePublicProductParams, defaultAvailabilityForListingTypes, PUBLIC_PRODUCT_MAX_PAGE_SIZE, ANY_STATUS, type PublicProductListInput, type PublicProductListResult, type PublicProductListOptions, type PublicProductExecutor, type PublicProductQuery, } from "./_internal/server/features/products/index";
|
|
504
504
|
export { getBundleForDetail, listBundleMembers, listFeaturedBundles, getRelatedBundles, resolveBundleMemberIds, resolveBundleOriginalTotal, resolveBundleCategorySlugs, buildBundleMetadata, renderBundleOg, renderBundleOgImage, addBundleToCartAction, type BundleDataOptions, type BundleMetadataOptions, type BundleOgData, } from "./_internal/server/features/bundles/index";
|
|
505
505
|
export { getGroupsForProduct, getGroupsWithItemsForProduct, getGroupsForCategory, getGroupsForBrand } from "./_internal/server/features/grouped/index";
|
|
506
|
+
export { getGroupedListingForDetail, getGroupedListingWithItems, buildGroupedListingMetadata, renderGroupedListingOg, type GroupedListingWithItems, type GroupedListingMetadataOptions, } from "./_internal/server/features/grouped/index";
|
|
507
|
+
export { toPublicGroupMember, toPublicGroupMembers, type PublicGroupMember, } from "./_internal/server/features/products/group-members";
|
|
506
508
|
export { getAuctionForDetail, getProductFeaturesForAuction } from "./_internal/server/features/auctions/index";
|
|
507
509
|
export { getPreOrderForDetail, getProductFeaturesForPreOrder } from "./_internal/server/features/pre-orders/index";
|
|
508
510
|
export { getStoreForDetail, listStoreProductsInitial, listStoreAuctionsInitial, listStorePreOrdersInitial, listSitemapStores } from "./_internal/server/features/stores/index";
|
package/dist/server.js
CHANGED
|
@@ -1358,6 +1358,10 @@ export { listPublicProducts, parsePublicProductParams, defaultAvailabilityForLis
|
|
|
1358
1358
|
// S-SBUNI-3/4 2026-05-13 โ bundle data/metadata/og layer.
|
|
1359
1359
|
export { getBundleForDetail, listBundleMembers, listFeaturedBundles, getRelatedBundles, resolveBundleMemberIds, resolveBundleOriginalTotal, resolveBundleCategorySlugs, buildBundleMetadata, renderBundleOg, renderBundleOgImage, addBundleToCartAction, } from "./_internal/server/features/bundles/index";
|
|
1360
1360
|
export { getGroupsForProduct, getGroupsWithItemsForProduct, getGroupsForCategory, getGroupsForBrand } from "./_internal/server/features/grouped/index";
|
|
1361
|
+
// Public /groups/{slug} page โ data, metadata and OG.
|
|
1362
|
+
export { getGroupedListingForDetail, getGroupedListingWithItems, buildGroupedListingMetadata, renderGroupedListingOg, } from "./_internal/server/features/grouped/index";
|
|
1363
|
+
// Allow-list projection for group members โ see ยง "Public Data Projections".
|
|
1364
|
+
export { toPublicGroupMember, toPublicGroupMembers, } from "./_internal/server/features/products/group-members";
|
|
1361
1365
|
export { getAuctionForDetail, getProductFeaturesForAuction } from "./_internal/server/features/auctions/index";
|
|
1362
1366
|
export { getPreOrderForDetail, getProductFeaturesForPreOrder } from "./_internal/server/features/pre-orders/index";
|
|
1363
1367
|
export { getStoreForDetail, listStoreProductsInitial, listStoreAuctionsInitial, listStorePreOrdersInitial, listSitemapStores } from "./_internal/server/features/stores/index";
|