@mohasinac/appkit 4.9.4 → 4.9.5
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/layout/DashboardLayoutClient.js +7 -2
- package/dist/_internal/client/features/layout/SidebarCollapseToggle.d.ts +5 -1
- package/dist/_internal/client/features/layout/SidebarCollapseToggle.js +8 -2
- package/dist/_internal/client/features/layout/sidebarPositionClasses.d.ts +20 -0
- package/dist/_internal/client/features/layout/sidebarPositionClasses.js +20 -0
- package/dist/_internal/client/hand-mode/HandModeProvider.d.ts +21 -0
- package/dist/_internal/client/hand-mode/HandModeProvider.js +81 -0
- package/dist/_internal/client/hand-mode/index.d.ts +2 -0
- package/dist/_internal/client/hand-mode/index.js +1 -0
- package/dist/_internal/server/features/checkout/actions.d.ts +43 -0
- package/dist/_internal/server/features/checkout/actions.js +172 -81
- package/dist/_internal/server/features/checkout/index.d.ts +1 -1
- package/dist/_internal/server/features/checkout/index.js +1 -1
- package/dist/_internal/server/features/seo/robots.js +3 -2
- package/dist/_internal/server/features/seo/sitemap.js +70 -5
- package/dist/_internal/shared/config/schema.d.ts +1 -0
- package/dist/_internal/shared/listing-types/classified/config.js +1 -1
- package/dist/_internal/shared/listing-types/digital-code/config.js +1 -1
- package/dist/_internal/shared/listing-types/live/config.js +1 -1
- package/dist/client.d.ts +4 -0
- package/dist/client.js +9 -0
- package/dist/constants/api-endpoints.d.ts +3 -0
- package/dist/constants/api-endpoints.js +1 -0
- package/dist/features/about/components/AboutView.js +2 -2
- package/dist/features/about/components/DeveloperView.js +2 -2
- package/dist/features/about/schemas/firestore.d.ts +1 -0
- package/dist/features/account/components/UserSidebar.js +8 -2
- package/dist/features/account/hooks/useProfile.d.ts +2 -0
- package/dist/features/admin/components/AdminSidebar.js +8 -2
- package/dist/features/auth/schemas/firestore.d.ts +3 -0
- package/dist/features/cart/components/CartDrawer.js +4 -1
- package/dist/features/homepage/components/BrandsSection.js +3 -1
- package/dist/features/homepage/components/FeaturedBundlesSection.js +12 -1
- package/dist/features/homepage/components/GoogleReviewsSection.js +2 -2
- package/dist/features/homepage/components/HeroCarousel.js +3 -1
- package/dist/features/homepage/components/SectionCarousel.js +5 -2
- package/dist/features/homepage/components/SocialFeedSection.js +2 -2
- package/dist/features/homepage/components/WhatsAppCommunitySection.js +3 -1
- package/dist/features/layout/BackToTop.js +3 -1
- package/dist/features/layout/SidebarLayout.js +7 -1
- package/dist/features/seller/components/SellerSidebar.js +8 -2
- package/dist/features/shell/QuickFormDrawer.js +3 -1
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +31 -0
- package/dist/hand-mode/index.d.ts +12 -0
- package/dist/hand-mode/index.js +11 -0
- package/dist/react/contexts/SessionContext.d.ts +5 -0
- package/dist/react/contexts/SessionContext.js +2 -0
- package/dist/seed/site-settings-seed-data.js +3 -2
- package/dist/server-entry.d.ts +1 -1
- package/dist/server-entry.js +1 -1
- package/dist/styles.css +2 -1
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/Drawer.js +7 -4
- package/dist/ui/components/FilterDrawer.js +1 -1
- package/dist/ui/components/HandModeRow.d.ts +12 -0
- package/dist/ui/components/HandModeRow.js +18 -0
- package/dist/ui/components/Layout.d.ts +5 -1
- package/dist/ui/components/Layout.js +2 -1
- package/dist/ui/components/Layout.style.css +1 -0
- package/dist/ui/components/ListingFilterDrawer.js +3 -1
- package/dist/ui/components/Modal.js +3 -1
- package/dist/ui/components/SideDrawer.js +8 -5
- package/dist/ui/components/SideModal.js +7 -4
- package/dist/ui/index.d.ts +1 -0
- package/dist/ui/index.js +1 -0
- package/package.json +1 -1
|
@@ -33,6 +33,7 @@ import { DASHBOARD_DESKTOP_MEDIA_QUERY } from "../../../shared/features/layout/c
|
|
|
33
33
|
import { filterNavItems } from "./filterNavItems";
|
|
34
34
|
import { useSiteSettings } from "../../../../core/hooks/useSiteSettings";
|
|
35
35
|
import { useTheme } from "../../theme";
|
|
36
|
+
import { useHandMode } from "../../hand-mode";
|
|
36
37
|
import { BackgroundRenderer, Div, Nav, Span } from "../../../../ui";
|
|
37
38
|
import { useToast } from "../../../../ui/components/Toast";
|
|
38
39
|
import { useVisualViewportInset } from "../../../../react/hooks/useVisualViewportInset";
|
|
@@ -149,7 +150,10 @@ function DashboardBottomNav({ items, activeHref }) {
|
|
|
149
150
|
return (_jsxs(Link, { href: item.href, className: `flex flex-1 flex-col items-center justify-center gap-[var(--appkit-space-0-5)] py-[var(--appkit-space-2)] ${isActive ? "text-[var(--appkit-color-primary)]" : "text-[var(--appkit-color-text-muted)]"}`, "aria-current": isActive ? "page" : undefined, children: [item.icon && _jsx(Span, { size: "base", children: item.icon }), _jsx(Span, { size: "xs", className: "max-w-full truncate", children: item.label })] }, item.href));
|
|
150
151
|
}) }));
|
|
151
152
|
}
|
|
152
|
-
|
|
153
|
+
// Asymmetric padding clears the persistent sidebar rail — right-heavy by
|
|
154
|
+
// default (rail docks left), left-heavy in left-hand mode (rail docks right).
|
|
155
|
+
const DEFAULT_CONTENT_PADDING_RIGHT = "px-[var(--appkit-space-5)] py-[var(--appkit-space-8)] lg:pl-14 lg:pr-6 xl:pl-16 xl:pr-10";
|
|
156
|
+
const DEFAULT_CONTENT_PADDING_LEFT = "px-[var(--appkit-space-5)] py-[var(--appkit-space-8)] lg:pr-14 lg:pl-6 xl:pr-16 xl:pl-10";
|
|
153
157
|
const DEFAULT_CONTENT_MAX_WIDTH = "max-w-screen-2xl";
|
|
154
158
|
export function DashboardLayoutClient({ variant, groups, permissions, activeHref: explicitActiveHref, responsive: _responsive, crossNav, className, contentPadding, contentSurface, contentMaxWidth, lightBackground, darkBackground, children, }) {
|
|
155
159
|
const pathname = usePathname();
|
|
@@ -178,6 +182,7 @@ export function DashboardLayoutClient({ variant, groups, permissions, activeHref
|
|
|
178
182
|
const { data: settings } = useSiteSettings();
|
|
179
183
|
const navConfig = settings?.navConfig;
|
|
180
184
|
const { theme } = useTheme();
|
|
185
|
+
const { hand } = useHandMode();
|
|
181
186
|
// Explicit props win; otherwise fall back to the same siteSettings.background
|
|
182
187
|
// the public shell reads, so admin/store/user dashboards match it by default.
|
|
183
188
|
const resolvedLightBackground = lightBackground ?? settings?.background?.light;
|
|
@@ -193,7 +198,7 @@ export function DashboardLayoutClient({ variant, groups, permissions, activeHref
|
|
|
193
198
|
return (_jsxs(_Fragment, { children: [hasBackground && (_jsx(BackgroundRenderer, { mode: theme === "dark" ? "dark" : "light", lightMode: resolvedLightBackground ?? { type: "color", value: "" }, darkMode: resolvedDarkBackground ?? { type: "color", value: "" } })), variant === "admin" && (_jsx(AdminSidebar, { variant: "sidebar", desktopOpen: desktopOpen, mobileOpen: mobileOpen, activePath: activeHref, groups: adminGroups, onCloseMobile: close, onToggle: toggle, className: className, renderFooter: renderCrossNavFooter })), variant === "store" && (_jsx(StoreSidebar, { variant: "sidebar", desktopOpen: desktopOpen, mobileOpen: mobileOpen, activeHref: activeHref, items: [], groups: filteredGroups, onCloseMobile: close, onToggle: toggle, className: className, renderFooter: renderCrossNavFooter })), variant === "user" && (_jsx(UserSidebar, { variant: "sidebar", desktopOpen: desktopOpen, mobileOpen: mobileOpen, items: filteredGroups.flatMap((g) => g.items), groups: filteredGroups, onCloseMobile: close, onToggle: toggle, className: className, renderFooter: renderCrossNavFooter })), _jsx(Div, { className: [
|
|
194
199
|
"w-full flex-1 flex flex-col min-h-[calc(100dvh-var(--header-height,3.5rem))]",
|
|
195
200
|
"pb-[var(--appkit-space-16)] lg:pb-[0]", // clears the fixed mobile bottom-nav
|
|
196
|
-
contentPadding ??
|
|
201
|
+
contentPadding ?? (hand === "left" ? DEFAULT_CONTENT_PADDING_LEFT : DEFAULT_CONTENT_PADDING_RIGHT),
|
|
197
202
|
contentSurface,
|
|
198
203
|
].filter(Boolean).join(" "), children: _jsx(Div, { className: ["w-full flex-1 mx-auto", contentMaxWidth ?? DEFAULT_CONTENT_MAX_WIDTH].filter(Boolean).join(" "), children: children }) }), _jsx(DashboardBottomNav, { items: filteredGroups.flatMap((g) => g.items), activeHref: activeHref })] }));
|
|
199
204
|
}
|
|
@@ -15,5 +15,9 @@ export interface SidebarCollapseToggleProps {
|
|
|
15
15
|
/** aria-label override; defaults to "Collapse sidebar" / "Expand sidebar". */
|
|
16
16
|
expandedLabel?: string;
|
|
17
17
|
collapsedLabel?: string;
|
|
18
|
+
/** Which edge the parent sidebar panel is docked to. Defaults to "right"
|
|
19
|
+
* (the handle protrudes to the panel's outer/right edge). "left" mirrors
|
|
20
|
+
* the rounded corner and chevron direction for left-hand mode. */
|
|
21
|
+
edge?: "left" | "right";
|
|
18
22
|
}
|
|
19
|
-
export declare function SidebarCollapseToggle({ expanded, onToggle, expandedLabel, collapsedLabel, }: SidebarCollapseToggleProps): React.JSX.Element;
|
|
23
|
+
export declare function SidebarCollapseToggle({ expanded, onToggle, expandedLabel, collapsedLabel, edge, }: SidebarCollapseToggleProps): React.JSX.Element;
|
|
@@ -7,6 +7,12 @@ const HANDLE_STYLE = {
|
|
|
7
7
|
const CHEVRON_STYLE = {
|
|
8
8
|
color: "var(--appkit-color-text-on-primary)",
|
|
9
9
|
};
|
|
10
|
-
export function SidebarCollapseToggle({ expanded, onToggle, expandedLabel = "Collapse sidebar", collapsedLabel = "Expand sidebar", }) {
|
|
11
|
-
|
|
10
|
+
export function SidebarCollapseToggle({ expanded, onToggle, expandedLabel = "Collapse sidebar", collapsedLabel = "Expand sidebar", edge = "right", }) {
|
|
11
|
+
// The panel's free (open) edge is opposite its dock side: a left-docked
|
|
12
|
+
// panel (edge="left") protrudes its handle to the right, and vice versa.
|
|
13
|
+
const roundedClass = edge === "left" ? "rounded-r-[1.25rem]" : "rounded-l-[1.25rem]";
|
|
14
|
+
// Chevron rotation direction mirrors along with the dock side so it keeps
|
|
15
|
+
// pointing "into" the collapsed panel regardless of which edge it's on.
|
|
16
|
+
const chevronRotated = edge === "left" ? !expanded : expanded;
|
|
17
|
+
return (_jsx(IconButton, { type: "button", onClick: onToggle, "aria-label": expanded ? expandedLabel : collapsedLabel, "aria-expanded": expanded, variant: "ghost", style: HANDLE_STYLE, className: `w-9 shrink-0 ${roundedClass} shadow-lg transition-all duration-200 hover:shadow-xl hover:brightness-110 active:scale-[0.96]`, icon: _jsx("svg", { className: `w-4 h-4 drop-shadow-sm transition-transform duration-300 ${chevronRotated ? "rotate-180" : ""}`, style: CHEVRON_STYLE, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2.5, d: "M15 19l-7-7 7-7" }) }) }));
|
|
12
18
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { HandMode } from "../../hand-mode";
|
|
2
|
+
/** Classes for the desktop persistent sidebar rail (Admin/Seller/User
|
|
3
|
+
* `variant="sidebar"`) — flips which edge it docks to, its border side, and
|
|
4
|
+
* its collapsed-state translate direction, based on the hand-mode
|
|
5
|
+
* preference. Kept as a single shared helper so the flip logic isn't
|
|
6
|
+
* hand-forked identically across all three sidebar files. */
|
|
7
|
+
export interface SidebarRailClasses {
|
|
8
|
+
edgeClass: string;
|
|
9
|
+
borderClass: string;
|
|
10
|
+
collapsedTranslateClass: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function getSidebarRailClasses(hand: HandMode): SidebarRailClasses;
|
|
13
|
+
/** Classes for the mobile overlay drawer variant (Admin/Seller/User
|
|
14
|
+
* `variant="overlay"` → `DrawerPanel`) — same edge/border flip, no
|
|
15
|
+
* translate state since the panel is conditionally mounted, not animated. */
|
|
16
|
+
export interface SidebarOverlayClasses {
|
|
17
|
+
edgeClass: string;
|
|
18
|
+
borderClass: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function getSidebarOverlayClasses(hand: HandMode): SidebarOverlayClasses;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export function getSidebarRailClasses(hand) {
|
|
2
|
+
if (hand === "left") {
|
|
3
|
+
return {
|
|
4
|
+
edgeClass: "left-0",
|
|
5
|
+
borderClass: "border-r",
|
|
6
|
+
collapsedTranslateClass: "-translate-x-[calc(100%-1.25rem)]",
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
return {
|
|
10
|
+
edgeClass: "right-0",
|
|
11
|
+
borderClass: "border-l",
|
|
12
|
+
collapsedTranslateClass: "translate-x-[calc(100%-1.25rem)]",
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export function getSidebarOverlayClasses(hand) {
|
|
16
|
+
if (hand === "left") {
|
|
17
|
+
return { edgeClass: "left-0", borderClass: "border-r" };
|
|
18
|
+
}
|
|
19
|
+
return { edgeClass: "right-0", borderClass: "border-l" };
|
|
20
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
export type HandMode = "left" | "right";
|
|
3
|
+
export interface HandModeContextValue {
|
|
4
|
+
/** Which side CTA panels/drawers/sidebars dock to and where close buttons sit. */
|
|
5
|
+
hand: HandMode;
|
|
6
|
+
/** Persist a new hand-mode preference and re-render the flipped layout. */
|
|
7
|
+
setHand: (next: HandMode) => void;
|
|
8
|
+
/** Flip preference between right ↔ left. */
|
|
9
|
+
toggleHand: () => void;
|
|
10
|
+
}
|
|
11
|
+
export interface HandModeProviderProps {
|
|
12
|
+
/**
|
|
13
|
+
* Seed value from the signed-in user's server-persisted preference
|
|
14
|
+
* (`user.uiPreferences.handMode`), used only when no value is already
|
|
15
|
+
* cached in localStorage on this device.
|
|
16
|
+
*/
|
|
17
|
+
initialHand?: HandMode;
|
|
18
|
+
children: ReactNode;
|
|
19
|
+
}
|
|
20
|
+
export declare function HandModeProvider({ initialHand, children }: HandModeProviderProps): import("react").JSX.Element;
|
|
21
|
+
export declare function useHandMode(): HandModeContextValue;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { normalizeError } from "../../../errors/normalize";
|
|
4
|
+
import { createContext, useCallback, useContext, useEffect, useMemo, useState, } from "react";
|
|
5
|
+
// Brand-neutral localStorage namespace, matching the `appkit:theme-mode` /
|
|
6
|
+
// `appkit:theme-id` convention. audit-ssr-in-appkit blocks hardcoded brand
|
|
7
|
+
// strings inside `_internal/`.
|
|
8
|
+
const HAND_STORAGE_KEY = "appkit:hand-mode";
|
|
9
|
+
// Default (non-null) context value — unlike `ThemeContext` (which throws
|
|
10
|
+
// outside its provider because it's only ever consumed at the app-shell
|
|
11
|
+
// level), `useHandMode()` is called from base UI primitives (SideDrawer,
|
|
12
|
+
// Modal, Drawer, BackToTop, ...) that ship as part of the public design
|
|
13
|
+
// system and must degrade gracefully to the "right" default for any
|
|
14
|
+
// consumer that hasn't mounted `<HandModeProvider>`.
|
|
15
|
+
const DEFAULT_CONTEXT_VALUE = {
|
|
16
|
+
hand: "right",
|
|
17
|
+
setHand: () => { },
|
|
18
|
+
toggleHand: () => { },
|
|
19
|
+
};
|
|
20
|
+
const HandModeContext = createContext(DEFAULT_CONTEXT_VALUE);
|
|
21
|
+
function readHandPreference() {
|
|
22
|
+
if (typeof window === "undefined")
|
|
23
|
+
return "right";
|
|
24
|
+
try {
|
|
25
|
+
const stored = window.localStorage.getItem(HAND_STORAGE_KEY);
|
|
26
|
+
if (stored === "left" || stored === "right")
|
|
27
|
+
return stored;
|
|
28
|
+
}
|
|
29
|
+
catch (_err) {
|
|
30
|
+
void normalizeError(_err); /* localStorage unavailable in privacy/iframe context */
|
|
31
|
+
}
|
|
32
|
+
return "right";
|
|
33
|
+
}
|
|
34
|
+
/** Apply the hand-mode preference to `<html>` via a `data-hand` attribute.
|
|
35
|
+
* Deliberately NOT `dir="rtl"/"ltr"` — that would trigger the browser's
|
|
36
|
+
* bidi text-direction and native-control-mirroring behavior repo-wide, which
|
|
37
|
+
* is not what this "reachability" preference means. `data-hand` is a plain
|
|
38
|
+
* presentational attribute, same category as `data-theme`. */
|
|
39
|
+
function applyHand(hand) {
|
|
40
|
+
if (typeof document === "undefined")
|
|
41
|
+
return;
|
|
42
|
+
document.documentElement.setAttribute("data-hand", hand);
|
|
43
|
+
}
|
|
44
|
+
export function HandModeProvider({ initialHand, children }) {
|
|
45
|
+
const [hand, setHandState] = useState(() => {
|
|
46
|
+
if (typeof window === "undefined")
|
|
47
|
+
return initialHand ?? "right";
|
|
48
|
+
const stored = readHandPreference();
|
|
49
|
+
// readHandPreference() falls back to "right" when nothing is cached —
|
|
50
|
+
// in that case, prefer the server-seeded value over the hardcoded default.
|
|
51
|
+
try {
|
|
52
|
+
if (window.localStorage.getItem(HAND_STORAGE_KEY) === null) {
|
|
53
|
+
return initialHand ?? stored;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch (_err) {
|
|
57
|
+
void normalizeError(_err);
|
|
58
|
+
}
|
|
59
|
+
return stored;
|
|
60
|
+
});
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
applyHand(hand);
|
|
63
|
+
}, [hand]);
|
|
64
|
+
const setHand = useCallback((next) => {
|
|
65
|
+
setHandState(next);
|
|
66
|
+
try {
|
|
67
|
+
window.localStorage.setItem(HAND_STORAGE_KEY, next);
|
|
68
|
+
}
|
|
69
|
+
catch (_err) {
|
|
70
|
+
void normalizeError(_err); /* localStorage unavailable; preference applies for this session */
|
|
71
|
+
}
|
|
72
|
+
}, []);
|
|
73
|
+
const toggleHand = useCallback(() => {
|
|
74
|
+
setHand(hand === "left" ? "right" : "left");
|
|
75
|
+
}, [hand, setHand]);
|
|
76
|
+
const value = useMemo(() => ({ hand, setHand, toggleHand }), [hand, setHand, toggleHand]);
|
|
77
|
+
return _jsx(HandModeContext.Provider, { value: value, children: children });
|
|
78
|
+
}
|
|
79
|
+
export function useHandMode() {
|
|
80
|
+
return useContext(HandModeContext);
|
|
81
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { HandModeProvider, useHandMode } from "./HandModeProvider";
|
|
@@ -32,6 +32,18 @@ export interface CreateCheckoutOrderInput {
|
|
|
32
32
|
/** Buyer opted into shipment protection at checkout. Defaults false (unchecked). Only actually charged/honored when siteSettings.commissions.shipmentProtectionFeeEnabled is also true. */
|
|
33
33
|
shipmentProtectionAddon?: boolean;
|
|
34
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Exported so `/api/payment/create-order` (pre-charge amount) and the
|
|
37
|
+
* read-only `previewCheckoutPricing` (Order Summary display) can compute the
|
|
38
|
+
* exact same per-seller shipping fee that order creation charges/records —
|
|
39
|
+
* a single source of truth instead of a third re-implementation.
|
|
40
|
+
*/
|
|
41
|
+
export declare function resolveShippingCost(storeId: string | undefined): Promise<{
|
|
42
|
+
shippingFee: number;
|
|
43
|
+
storeOwnerId: string | undefined;
|
|
44
|
+
storeEmiEnabled: boolean;
|
|
45
|
+
storeState: string | undefined;
|
|
46
|
+
}>;
|
|
35
47
|
/**
|
|
36
48
|
* Place order(s) from the user's cart in a single Firestore transaction.
|
|
37
49
|
*
|
|
@@ -39,6 +51,37 @@ export interface CreateCheckoutOrderInput {
|
|
|
39
51
|
* auth + rate-limiting before calling this and supply the resolved user fields.
|
|
40
52
|
*/
|
|
41
53
|
export declare function createCheckoutOrderAction(input: CreateCheckoutOrderInput): Promise<CheckoutOrderResult>;
|
|
54
|
+
export interface CheckoutPricingPreviewInput {
|
|
55
|
+
userId: string;
|
|
56
|
+
/** Omitted for digital-only carts — GST can't be computed without a buyer state. */
|
|
57
|
+
addressId?: string;
|
|
58
|
+
paymentMethod: CheckoutPaymentMethod;
|
|
59
|
+
excludedProductIds?: string[];
|
|
60
|
+
whatsappNotifyAddon?: boolean;
|
|
61
|
+
giftWrapAddon?: boolean;
|
|
62
|
+
shipmentProtectionAddon?: boolean;
|
|
63
|
+
}
|
|
64
|
+
export interface CheckoutPricingPreview {
|
|
65
|
+
subtotal: number;
|
|
66
|
+
shippingFee: number;
|
|
67
|
+
codHandlingFee: number;
|
|
68
|
+
whatsappNotifyFee: number;
|
|
69
|
+
giftWrapFee: number;
|
|
70
|
+
shipmentProtectionFee: number;
|
|
71
|
+
gstAmount: number;
|
|
72
|
+
couponDiscount: number;
|
|
73
|
+
total: number;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Read-only "what will I actually be charged" preview for the checkout
|
|
77
|
+
* Order Summary. Reuses the exact same building blocks
|
|
78
|
+
* createCheckoutOrderAction / verifyAndPlaceRazorpayOrderAction use when an
|
|
79
|
+
* order is actually placed — resolveShippingCost, the compute*Fee helpers,
|
|
80
|
+
* computeGroupCouponDiscount, and the GST calc — so the number shown to the
|
|
81
|
+
* buyer can never diverge from what gets charged/recorded. Never decrements
|
|
82
|
+
* stock, creates an order, or writes coupon usage.
|
|
83
|
+
*/
|
|
84
|
+
export declare function previewCheckoutPricing(input: CheckoutPricingPreviewInput): Promise<CheckoutPricingPreview>;
|
|
42
85
|
/**
|
|
43
86
|
* Mark an order as paid after a payment provider verifies the transaction.
|
|
44
87
|
* Consumers call this from /api/payment/verify or a payment webhook.
|
|
@@ -11,7 +11,7 @@ import { roundRupees } from "../../../../utils/number.formatter";
|
|
|
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, claimedCouponsRepository, addressesRepository, } from "../../../../repositories";
|
|
14
|
+
import { unitOfWork, siteSettingsRepository, userRepository, storeRepository, couponsRepository, claimedCouponsRepository, addressesRepository, productRepository, } from "../../../../repositories";
|
|
15
15
|
import { calculateGst } from "../../../shared/fees/calculator";
|
|
16
16
|
import { computePreOrderDepositAmount } from "../../../shared/checkout/order-math";
|
|
17
17
|
import { failedCheckoutRepository } from "../../../../features/checkout/repository/failed-checkout.repository";
|
|
@@ -151,7 +151,13 @@ function accumulateCouponUsage(accumulator, couponCode, couponId, discountAmount
|
|
|
151
151
|
entry.totalDiscount += discountAmount;
|
|
152
152
|
accumulator.set(couponCode, entry);
|
|
153
153
|
}
|
|
154
|
-
|
|
154
|
+
/**
|
|
155
|
+
* Exported so `/api/payment/create-order` (pre-charge amount) and the
|
|
156
|
+
* read-only `previewCheckoutPricing` (Order Summary display) can compute the
|
|
157
|
+
* exact same per-seller shipping fee that order creation charges/records —
|
|
158
|
+
* a single source of truth instead of a third re-implementation.
|
|
159
|
+
*/
|
|
160
|
+
export async function resolveShippingCost(storeId) {
|
|
155
161
|
if (!storeId)
|
|
156
162
|
return { shippingFee: 0, storeOwnerId: undefined, storeEmiEnabled: false, storeState: undefined };
|
|
157
163
|
const store = await storeRepository.findById(storeId);
|
|
@@ -172,6 +178,55 @@ async function resolveStoreState(storeId) {
|
|
|
172
178
|
const pickup = addresses.find((a) => a.isDefault) ?? addresses[0];
|
|
173
179
|
return pickup?.state;
|
|
174
180
|
}
|
|
181
|
+
/**
|
|
182
|
+
* Prorates every buyer-applied coupon against one seller-group's total. Pure
|
|
183
|
+
* — no side effects (usage-accumulator bookkeeping is layered on by callers
|
|
184
|
+
* that actually place an order). Shared by createOrderForGroup,
|
|
185
|
+
* createRazorpayGroupOrder, and the read-only previewCheckoutPricing so the
|
|
186
|
+
* three can never drift on how a coupon splits across sellers (was three
|
|
187
|
+
* near-identical copies before this extraction — Duplication Decision
|
|
188
|
+
* Framework's Rule of Three).
|
|
189
|
+
*/
|
|
190
|
+
function computeGroupCouponDiscount(appliedCoupons, groupLines, groupTotal, cartSubtotal, storeId) {
|
|
191
|
+
let couponDiscount = 0;
|
|
192
|
+
const appliedDiscounts = [];
|
|
193
|
+
for (const coupon of appliedCoupons) {
|
|
194
|
+
let couponGroupDiscount = 0;
|
|
195
|
+
const isSellerScoped = coupon.scope === "seller" && coupon.storeId;
|
|
196
|
+
if (isSellerScoped) {
|
|
197
|
+
if (coupon.storeId !== storeId)
|
|
198
|
+
continue;
|
|
199
|
+
if (coupon.applicableItemIds?.length) {
|
|
200
|
+
const eligibleTotal = groupLines
|
|
201
|
+
.filter((l) => coupon.applicableItemIds.includes(l.itemId))
|
|
202
|
+
.reduce((s, l) => s + l.lineTotal, 0);
|
|
203
|
+
couponGroupDiscount =
|
|
204
|
+
eligibleTotal > 0
|
|
205
|
+
? Math.min(roundRupees((eligibleTotal / groupTotal) * coupon.discountAmount), eligibleTotal)
|
|
206
|
+
: 0;
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
couponGroupDiscount = Math.min(coupon.discountAmount, groupTotal);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
else if (cartSubtotal > 0) {
|
|
213
|
+
couponGroupDiscount = Math.min(roundRupees((groupTotal / cartSubtotal) * coupon.discountAmount), groupTotal);
|
|
214
|
+
}
|
|
215
|
+
if (couponGroupDiscount > 0) {
|
|
216
|
+
couponDiscount += couponGroupDiscount;
|
|
217
|
+
appliedDiscounts.push({
|
|
218
|
+
code: coupon.code,
|
|
219
|
+
couponId: coupon.couponId,
|
|
220
|
+
type: "coupon",
|
|
221
|
+
discountAmount: couponGroupDiscount,
|
|
222
|
+
scope: coupon.scope,
|
|
223
|
+
storeId: coupon.storeId,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
couponDiscount = Math.min(couponDiscount, groupTotal);
|
|
228
|
+
return { couponDiscount, appliedDiscounts };
|
|
229
|
+
}
|
|
175
230
|
function buildStockUpdatePayload(product, qtyDelta) {
|
|
176
231
|
const lt = (product.listingType ?? "standard");
|
|
177
232
|
const rule = getListingRule(lt);
|
|
@@ -313,50 +368,15 @@ async function createOrderForGroup(group, orderType, ctx) {
|
|
|
313
368
|
const whatsappNotifyFee = computeWhatsAppNotifyFee(whatsappNotifyAddon, commissions);
|
|
314
369
|
const giftWrapFee = computeGiftWrapFee(giftWrapAddon, commissions);
|
|
315
370
|
const shipmentProtectionFee = computeShipmentProtectionFee(groupTotal, shipmentProtectionAddon, commissions);
|
|
316
|
-
let couponDiscount = 0;
|
|
317
|
-
const appliedDiscounts = [];
|
|
318
371
|
const groupCouponCodes = new Set();
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
if (
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
if (coupon.applicableItemIds?.length) {
|
|
326
|
-
const eligibleTotal = group
|
|
327
|
-
.filter(({ item }) => coupon.applicableItemIds.includes(item.itemId))
|
|
328
|
-
.reduce((s, { item }) => s + item.price * item.quantity, 0);
|
|
329
|
-
couponGroupDiscount =
|
|
330
|
-
eligibleTotal > 0
|
|
331
|
-
? Math.min(roundRupees((eligibleTotal / groupTotal) * coupon.discountAmount), eligibleTotal)
|
|
332
|
-
: 0;
|
|
333
|
-
}
|
|
334
|
-
else {
|
|
335
|
-
couponGroupDiscount = Math.min(coupon.discountAmount, groupTotal);
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
else {
|
|
339
|
-
if (cartSubtotal > 0) {
|
|
340
|
-
couponGroupDiscount = Math.min(roundRupees((groupTotal / cartSubtotal) * coupon.discountAmount), groupTotal);
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
if (couponGroupDiscount > 0) {
|
|
344
|
-
couponDiscount += couponGroupDiscount;
|
|
345
|
-
appliedDiscounts.push({
|
|
346
|
-
code: coupon.code,
|
|
347
|
-
couponId: coupon.couponId,
|
|
348
|
-
type: "coupon",
|
|
349
|
-
discountAmount: couponGroupDiscount,
|
|
350
|
-
scope: coupon.scope,
|
|
351
|
-
storeId: coupon.storeId,
|
|
352
|
-
});
|
|
353
|
-
if (coupon.couponId) {
|
|
354
|
-
accumulateCouponUsage(couponUsageAccumulator, coupon.code, coupon.couponId, couponGroupDiscount);
|
|
355
|
-
groupCouponCodes.add(coupon.code);
|
|
356
|
-
}
|
|
372
|
+
const groupLines = group.map(({ item }) => ({ itemId: item.itemId, lineTotal: item.price * item.quantity }));
|
|
373
|
+
const { couponDiscount, appliedDiscounts } = computeGroupCouponDiscount(appliedCoupons, groupLines, groupTotal, cartSubtotal, firstItem.storeId);
|
|
374
|
+
for (const discount of appliedDiscounts) {
|
|
375
|
+
if (discount.couponId) {
|
|
376
|
+
accumulateCouponUsage(couponUsageAccumulator, discount.code, discount.couponId, discount.discountAmount);
|
|
377
|
+
groupCouponCodes.add(discount.code);
|
|
357
378
|
}
|
|
358
379
|
}
|
|
359
|
-
couponDiscount = Math.min(couponDiscount, groupTotal);
|
|
360
380
|
const orderTotal = Math.max(0, groupTotal - couponDiscount) + shippingFee + codHandlingFee + whatsappNotifyFee + giftWrapFee + shipmentProtectionFee + (emiSchedule?.surchargeAmount ?? 0) + (gstBreakdown?.gstAmount ?? 0);
|
|
361
381
|
const imageUrls = [
|
|
362
382
|
...new Set(group
|
|
@@ -733,6 +753,105 @@ export async function createCheckoutOrderAction(input) {
|
|
|
733
753
|
...(unavailable.length > 0 ? { unavailableItems: unavailable } : {}),
|
|
734
754
|
};
|
|
735
755
|
}
|
|
756
|
+
const EMPTY_PRICING_PREVIEW = {
|
|
757
|
+
subtotal: 0,
|
|
758
|
+
shippingFee: 0,
|
|
759
|
+
codHandlingFee: 0,
|
|
760
|
+
whatsappNotifyFee: 0,
|
|
761
|
+
giftWrapFee: 0,
|
|
762
|
+
shipmentProtectionFee: 0,
|
|
763
|
+
gstAmount: 0,
|
|
764
|
+
couponDiscount: 0,
|
|
765
|
+
total: 0,
|
|
766
|
+
};
|
|
767
|
+
/**
|
|
768
|
+
* Read-only "what will I actually be charged" preview for the checkout
|
|
769
|
+
* Order Summary. Reuses the exact same building blocks
|
|
770
|
+
* createCheckoutOrderAction / verifyAndPlaceRazorpayOrderAction use when an
|
|
771
|
+
* order is actually placed — resolveShippingCost, the compute*Fee helpers,
|
|
772
|
+
* computeGroupCouponDiscount, and the GST calc — so the number shown to the
|
|
773
|
+
* buyer can never diverge from what gets charged/recorded. Never decrements
|
|
774
|
+
* stock, creates an order, or writes coupon usage.
|
|
775
|
+
*/
|
|
776
|
+
export async function previewCheckoutPricing(input) {
|
|
777
|
+
const { userId: uid, addressId, paymentMethod, excludedProductIds = [], whatsappNotifyAddon = false, giftWrapAddon = false, shipmentProtectionAddon = false, } = input;
|
|
778
|
+
const siteSettings = await siteSettingsRepository.getSingleton();
|
|
779
|
+
const commissions = siteSettings?.commissions ?? CHECKOUT_DEFAULT_COMMISSIONS;
|
|
780
|
+
const cart = await unitOfWork.carts.getOrCreate(uid);
|
|
781
|
+
const excludedSet = new Set(excludedProductIds);
|
|
782
|
+
const selectedSet = cart.selectedItemIds?.length ? new Set(cart.selectedItemIds) : null;
|
|
783
|
+
const cartItems = (cart.items ?? []).filter((item) => !excludedSet.has(item.productId) && (!selectedSet || selectedSet.has(item.itemId)));
|
|
784
|
+
if (cartItems.length === 0)
|
|
785
|
+
return EMPTY_PRICING_PREVIEW;
|
|
786
|
+
// Best-effort product lookup — a preview never throws on a since-unpublished
|
|
787
|
+
// product, it just falls back to the item's cart-snapshot price for that
|
|
788
|
+
// line (same fallback cartRepository.getSubtotal() implicitly relies on).
|
|
789
|
+
const uniqueProductIds = [...new Set(cartItems.map((item) => item.productId))];
|
|
790
|
+
const productDocs = await Promise.all(uniqueProductIds.map((pid) => productRepository.findById(pid)));
|
|
791
|
+
const productById = new Map(uniqueProductIds.map((pid, i) => [pid, productDocs[i]]));
|
|
792
|
+
let resolvedAddress = null;
|
|
793
|
+
if (addressId) {
|
|
794
|
+
const addressDoc = await unitOfWork.addresses.findById(addressId);
|
|
795
|
+
resolvedAddress =
|
|
796
|
+
addressDoc && addressDoc.ownerType === "user" && addressDoc.ownerId === uid ? addressDoc : null;
|
|
797
|
+
}
|
|
798
|
+
const unitPriceFor = (item, product) => item.bundleCategorySlug && item.bundleProductIds?.length ? item.price : (product?.price ?? item.price);
|
|
799
|
+
const checks = cartItems.map((item) => ({ item, product: productById.get(item.productId) ?? null }));
|
|
800
|
+
const orderGroups = splitCartIntoOrderGroups(checks);
|
|
801
|
+
const cartSubtotal = orderGroups.reduce((s, { items: g }) => s + g.reduce((gs, { item, product }) => gs + unitPriceFor(item, product) * item.quantity, 0), 0);
|
|
802
|
+
const appliedCoupons = cart.appliedCoupons ?? [];
|
|
803
|
+
let shippingFee = 0;
|
|
804
|
+
let codHandlingFee = 0;
|
|
805
|
+
let whatsappNotifyFeeTotal = 0;
|
|
806
|
+
let giftWrapFeeTotal = 0;
|
|
807
|
+
let shipmentProtectionFeeTotal = 0;
|
|
808
|
+
let gstAmount = 0;
|
|
809
|
+
let couponDiscount = 0;
|
|
810
|
+
for (const { items: group } of orderGroups) {
|
|
811
|
+
const firstItem = group[0].item;
|
|
812
|
+
const groupTotal = group.reduce((sum, { item, product }) => sum + unitPriceFor(item, product) * item.quantity, 0);
|
|
813
|
+
const { shippingFee: groupShippingFee, storeState } = await resolveShippingCost(firstItem.storeId);
|
|
814
|
+
shippingFee += groupShippingFee;
|
|
815
|
+
// Mirrors createOrderForGroup exactly — the COD handling fee is only
|
|
816
|
+
// ever charged for literal "cod" (pay the courier), never upi_manual or
|
|
817
|
+
// cash (both pre-paid via UPI/bank transfer before dispatch).
|
|
818
|
+
if (paymentMethod === "cod")
|
|
819
|
+
codHandlingFee += computeCodHandlingFee(groupTotal, commissions);
|
|
820
|
+
whatsappNotifyFeeTotal += computeWhatsAppNotifyFee(whatsappNotifyAddon, commissions);
|
|
821
|
+
giftWrapFeeTotal += computeGiftWrapFee(giftWrapAddon, commissions);
|
|
822
|
+
shipmentProtectionFeeTotal += computeShipmentProtectionFee(groupTotal, shipmentProtectionAddon, commissions);
|
|
823
|
+
if (siteSettings?.gst?.enabled && resolvedAddress?.state) {
|
|
824
|
+
const intraState = !!storeState && storeState === resolvedAddress.state;
|
|
825
|
+
for (const { item, product } of group) {
|
|
826
|
+
const lineTotal = unitPriceFor(item, product) * item.quantity;
|
|
827
|
+
const rate = product?.gstRate ?? 0;
|
|
828
|
+
if (rate > 0)
|
|
829
|
+
gstAmount += calculateGst(rate, intraState, lineTotal).gstAmount;
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
const groupLines = group.map(({ item, product }) => ({ itemId: item.itemId, lineTotal: unitPriceFor(item, product) * item.quantity }));
|
|
833
|
+
const { couponDiscount: groupCouponDiscount } = computeGroupCouponDiscount(appliedCoupons, groupLines, groupTotal, cartSubtotal, firstItem.storeId);
|
|
834
|
+
couponDiscount += groupCouponDiscount;
|
|
835
|
+
}
|
|
836
|
+
const total = Math.max(0, cartSubtotal - couponDiscount) +
|
|
837
|
+
shippingFee +
|
|
838
|
+
codHandlingFee +
|
|
839
|
+
whatsappNotifyFeeTotal +
|
|
840
|
+
giftWrapFeeTotal +
|
|
841
|
+
shipmentProtectionFeeTotal +
|
|
842
|
+
gstAmount;
|
|
843
|
+
return {
|
|
844
|
+
subtotal: cartSubtotal,
|
|
845
|
+
shippingFee,
|
|
846
|
+
codHandlingFee,
|
|
847
|
+
whatsappNotifyFee: whatsappNotifyFeeTotal,
|
|
848
|
+
giftWrapFee: giftWrapFeeTotal,
|
|
849
|
+
shipmentProtectionFee: shipmentProtectionFeeTotal,
|
|
850
|
+
gstAmount,
|
|
851
|
+
couponDiscount,
|
|
852
|
+
total,
|
|
853
|
+
};
|
|
854
|
+
}
|
|
736
855
|
/**
|
|
737
856
|
* Mark an order as paid after a payment provider verifies the transaction.
|
|
738
857
|
* Consumers call this from /api/payment/verify or a payment webhook.
|
|
@@ -864,43 +983,8 @@ async function createRazorpayGroupOrder(group, orderType, ctx) {
|
|
|
864
983
|
// of re-implementing it inline — was two sequential findById calls per
|
|
865
984
|
// seller group here, duplicated from resolveShippingCost.
|
|
866
985
|
const { shippingFee, storeOwnerId } = await resolveShippingCost(firstItem.storeId);
|
|
867
|
-
|
|
868
|
-
const appliedDiscounts =
|
|
869
|
-
for (const coupon of appliedCoupons) {
|
|
870
|
-
let couponGroupDiscount = 0;
|
|
871
|
-
const isSellerScoped = coupon.scope === "seller" && coupon.storeId;
|
|
872
|
-
if (isSellerScoped) {
|
|
873
|
-
if (coupon.storeId !== firstItem.storeId)
|
|
874
|
-
continue;
|
|
875
|
-
if (coupon.applicableItemIds?.length) {
|
|
876
|
-
const eligibleTotal = group
|
|
877
|
-
.filter(({ item }) => coupon.applicableItemIds.includes(item.itemId))
|
|
878
|
-
.reduce((s, { item, product }) => s + unitPriceFor(item, product) * item.quantity, 0);
|
|
879
|
-
couponGroupDiscount =
|
|
880
|
-
eligibleTotal > 0
|
|
881
|
-
? Math.min(roundRupees((eligibleTotal / groupTotal) * coupon.discountAmount), eligibleTotal)
|
|
882
|
-
: 0;
|
|
883
|
-
}
|
|
884
|
-
else {
|
|
885
|
-
couponGroupDiscount = Math.min(coupon.discountAmount, groupTotal);
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
else if (cartSubtotal > 0) {
|
|
889
|
-
couponGroupDiscount = Math.min(roundRupees((groupTotal / cartSubtotal) * coupon.discountAmount), groupTotal);
|
|
890
|
-
}
|
|
891
|
-
if (couponGroupDiscount > 0) {
|
|
892
|
-
couponDiscount += couponGroupDiscount;
|
|
893
|
-
appliedDiscounts.push({
|
|
894
|
-
code: coupon.code,
|
|
895
|
-
couponId: coupon.couponId,
|
|
896
|
-
type: "coupon",
|
|
897
|
-
discountAmount: couponGroupDiscount,
|
|
898
|
-
scope: coupon.scope,
|
|
899
|
-
storeId: coupon.storeId,
|
|
900
|
-
});
|
|
901
|
-
}
|
|
902
|
-
}
|
|
903
|
-
couponDiscount = Math.min(couponDiscount, groupTotal);
|
|
986
|
+
const groupLines = group.map(({ item, product }) => ({ itemId: item.itemId, lineTotal: unitPriceFor(item, product) * item.quantity }));
|
|
987
|
+
const { couponDiscount, appliedDiscounts } = computeGroupCouponDiscount(appliedCoupons, groupLines, groupTotal, cartSubtotal, firstItem.storeId);
|
|
904
988
|
const rawPlatformFee = roundRupees(groupTotal * (platformFeePercent / 100));
|
|
905
989
|
const gstOnFee = roundRupees(rawPlatformFee * (gstPercent / 100));
|
|
906
990
|
const platformFee = rawPlatformFee + gstOnFee;
|
|
@@ -1165,7 +1249,14 @@ export async function verifyAndPlaceRazorpayOrderAction(input) {
|
|
|
1165
1249
|
}, 0);
|
|
1166
1250
|
const expectedPlatformFee = roundRupees(cartSubtotalRs * (platformFeePercent / 100));
|
|
1167
1251
|
const expectedGstOnFee = roundRupees(expectedPlatformFee * (gstPercent / 100));
|
|
1168
|
-
|
|
1252
|
+
// Same per-seller-group shipping sum /api/payment/create-order charged
|
|
1253
|
+
// upfront (and the same resolveShippingCost createRazorpayGroupOrder
|
|
1254
|
+
// below will use to build each order's recorded totalPrice) — keeps this
|
|
1255
|
+
// floor check from passing an amount that's short by the shipping fee.
|
|
1256
|
+
const expectedShippingGroups = splitCartIntoOrderGroups(bucketedPaid.available);
|
|
1257
|
+
const expectedShippingFees = await Promise.all(expectedShippingGroups.map((g) => resolveShippingCost(g.items[0].item.storeId)));
|
|
1258
|
+
const expectedShippingFee = expectedShippingFees.reduce((sum, r) => sum + r.shippingFee, 0);
|
|
1259
|
+
const expectedPaymentAmountRs = cartSubtotalRs + expectedPlatformFee + expectedGstOnFee + expectedShippingFee;
|
|
1169
1260
|
const rzpOrderRecord = await fetchRazorpayOrder(razorpay_order_id);
|
|
1170
1261
|
const paidAmountRs = paiseToRupees(rzpOrderRecord.amount);
|
|
1171
1262
|
if (paidAmountRs < expectedPaymentAmountRs - 1) {
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { createCheckoutOrderAction, attachPaymentAction, verifyAndPlaceRazorpayOrderAction, type CreateCheckoutOrderInput, type VerifyAndPlaceRazorpayOrderInput, } from "./actions";
|
|
1
|
+
export { createCheckoutOrderAction, attachPaymentAction, verifyAndPlaceRazorpayOrderAction, previewCheckoutPricing, resolveShippingCost, type CreateCheckoutOrderInput, type VerifyAndPlaceRazorpayOrderInput, type CheckoutPricingPreviewInput, type CheckoutPricingPreview, } from "./actions";
|
|
2
2
|
export { formatShippingAddress, type CheckoutOrderResult, } from "./data";
|
|
3
3
|
export { CHECKOUT_DEFAULT_COMMISSIONS, CHECKOUT_PAYMENT_METHODS, type CheckoutPaymentMethod, } from "../../../shared/features/checkout/config";
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { createCheckoutOrderAction, attachPaymentAction, verifyAndPlaceRazorpayOrderAction, } from "./actions";
|
|
1
|
+
export { createCheckoutOrderAction, attachPaymentAction, verifyAndPlaceRazorpayOrderAction, previewCheckoutPricing, resolveShippingCost, } from "./actions";
|
|
2
2
|
export { formatShippingAddress, } from "./data";
|
|
3
3
|
export { CHECKOUT_DEFAULT_COMMISSIONS, CHECKOUT_PAYMENT_METHODS, } from "../../../shared/features/checkout/config";
|
|
@@ -7,12 +7,13 @@ export function buildRobots({ siteUrl }) {
|
|
|
7
7
|
disallow: [
|
|
8
8
|
"/admin/",
|
|
9
9
|
"/api/", // audit-hardcoded-api-routes-ok: robots.txt disallow prefix, not an endpoint reference
|
|
10
|
-
"/
|
|
10
|
+
"/store/",
|
|
11
11
|
"/user/",
|
|
12
12
|
"/auth/",
|
|
13
13
|
"/checkout/",
|
|
14
14
|
"/cart/",
|
|
15
|
-
"/
|
|
15
|
+
"/wishlist",
|
|
16
|
+
"/preview/",
|
|
16
17
|
"/track/",
|
|
17
18
|
"/unauthorized/",
|
|
18
19
|
"/_next/",
|