@decocms/apps-vtex 7.2.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/package.json +67 -0
- package/src/README.md +6 -0
- package/src/__tests__/client-segment-cookie.test.ts +255 -0
- package/src/__tests__/client-set-cookie-forward.test.ts +257 -0
- package/src/actions/address.ts +250 -0
- package/src/actions/analytics/sendEvent.ts +86 -0
- package/src/actions/auth.ts +333 -0
- package/src/actions/checkout.ts +522 -0
- package/src/actions/index.ts +11 -0
- package/src/actions/masterData.ts +168 -0
- package/src/actions/misc.ts +188 -0
- package/src/actions/newsletter.ts +105 -0
- package/src/actions/orders.ts +34 -0
- package/src/actions/profile.ts +201 -0
- package/src/actions/session.ts +85 -0
- package/src/actions/trigger.ts +43 -0
- package/src/actions/wishlist.ts +114 -0
- package/src/client.ts +667 -0
- package/src/commerceLoaders.ts +261 -0
- package/src/hooks/__tests__/createUseCart.test.ts +184 -0
- package/src/hooks/__tests__/createUseUser.test.ts +48 -0
- package/src/hooks/__tests__/createUseWishlist.test.ts +87 -0
- package/src/hooks/createUseCart.ts +360 -0
- package/src/hooks/createUseUser.ts +153 -0
- package/src/hooks/createUseWishlist.ts +242 -0
- package/src/hooks/index.ts +19 -0
- package/src/hooks/useAutocomplete.ts +83 -0
- package/src/hooks/useCart.ts +243 -0
- package/src/hooks/useUser.ts +78 -0
- package/src/hooks/useWishlist.ts +119 -0
- package/src/index.ts +17 -0
- package/src/invoke.ts +181 -0
- package/src/loaders/ProductDetailsPage.ts +1 -0
- package/src/loaders/ProductList.ts +1 -0
- package/src/loaders/ProductListingPage.ts +1 -0
- package/src/loaders/address.ts +115 -0
- package/src/loaders/autocomplete.ts +58 -0
- package/src/loaders/brands.ts +44 -0
- package/src/loaders/cart.ts +52 -0
- package/src/loaders/catalog.ts +163 -0
- package/src/loaders/collections.ts +55 -0
- package/src/loaders/index.ts +19 -0
- package/src/loaders/intelligentSearch/__tests__/productListingPage.test.ts +20 -0
- package/src/loaders/intelligentSearch/productDetailsPage.ts +95 -0
- package/src/loaders/intelligentSearch/productList.ts +154 -0
- package/src/loaders/intelligentSearch/productListingPage.ts +506 -0
- package/src/loaders/intelligentSearch/suggestions.ts +46 -0
- package/src/loaders/legacy/productDetailsPage.ts +1 -0
- package/src/loaders/legacy/productList.ts +1 -0
- package/src/loaders/legacy/relatedProductsLoader.ts +81 -0
- package/src/loaders/legacy.ts +635 -0
- package/src/loaders/logistics.ts +111 -0
- package/src/loaders/minicart.ts +78 -0
- package/src/loaders/navbar.ts +26 -0
- package/src/loaders/orders.ts +92 -0
- package/src/loaders/pageType.ts +68 -0
- package/src/loaders/payment.ts +97 -0
- package/src/loaders/productListFull.ts +159 -0
- package/src/loaders/profile.ts +138 -0
- package/src/loaders/promotion.ts +30 -0
- package/src/loaders/search.ts +124 -0
- package/src/loaders/session.ts +83 -0
- package/src/loaders/user.ts +87 -0
- package/src/loaders/wishlist.ts +89 -0
- package/src/loaders/wishlistProducts.ts +69 -0
- package/src/loaders/workflow/products.ts +64 -0
- package/src/loaders/workflow.ts +316 -0
- package/src/logo.png +0 -0
- package/src/middleware.ts +240 -0
- package/src/mod.ts +152 -0
- package/src/registry.ts +9 -0
- package/src/types.ts +248 -0
- package/src/utils/__tests__/cookieSanitizer.test.ts +162 -0
- package/src/utils/__tests__/fetch.test.ts +80 -0
- package/src/utils/__tests__/fetchCache.test.ts +112 -0
- package/src/utils/__tests__/instrumentedFetch.test.ts +158 -0
- package/src/utils/__tests__/intelligentSearch.test.ts +88 -0
- package/src/utils/__tests__/minicart.test.ts +184 -0
- package/src/utils/__tests__/operationRouter.test.ts +227 -0
- package/src/utils/__tests__/resourceRange.test.ts +32 -0
- package/src/utils/__tests__/sitemap.test.ts +185 -0
- package/src/utils/__tests__/slugify.test.ts +31 -0
- package/src/utils/__tests__/transform.test.ts +698 -0
- package/src/utils/accountLoaders.ts +203 -0
- package/src/utils/authHelpers.ts +85 -0
- package/src/utils/batch.ts +18 -0
- package/src/utils/cookieSanitizer.ts +173 -0
- package/src/utils/cookies.ts +167 -0
- package/src/utils/enrichment.ts +560 -0
- package/src/utils/fetch.ts +107 -0
- package/src/utils/fetchCache.ts +222 -0
- package/src/utils/index.ts +19 -0
- package/src/utils/instrumentedFetch.ts +78 -0
- package/src/utils/intelligentSearch.ts +96 -0
- package/src/utils/legacy.ts +139 -0
- package/src/utils/minicart.ts +139 -0
- package/src/utils/operationRouter.ts +139 -0
- package/src/utils/pickAndOmit.ts +25 -0
- package/src/utils/proxy.ts +435 -0
- package/src/utils/resourceRange.ts +10 -0
- package/src/utils/segment.ts +152 -0
- package/src/utils/similars.ts +37 -0
- package/src/utils/sitemap.ts +282 -0
- package/src/utils/slugCache.ts +28 -0
- package/src/utils/slugify.ts +13 -0
- package/src/utils/transform.ts +1509 -0
- package/src/utils/types.ts +1884 -0
- package/src/utils/vtexId.ts +138 -0
- package/tsconfig.json +7 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VTEX Logistics & Sales-Channel API loaders.
|
|
3
|
+
* Pure async functions — require configureVtex() to have been called.
|
|
4
|
+
*
|
|
5
|
+
* Ported from deco-cx/apps:
|
|
6
|
+
* vtex/loaders/logistics/getSalesChannelById.ts
|
|
7
|
+
* vtex/loaders/logistics/listPickupPoints.ts
|
|
8
|
+
* vtex/loaders/logistics/listPickupPointsByLocation.ts
|
|
9
|
+
* vtex/loaders/logistics/listSalesChannelById.ts (actually lists all)
|
|
10
|
+
* vtex/loaders/logistics/listStockByStore.ts
|
|
11
|
+
*
|
|
12
|
+
* @see https://developers.vtex.com/docs/api-reference/logistics-api
|
|
13
|
+
*/
|
|
14
|
+
import { vtexFetch } from "../client";
|
|
15
|
+
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// Types
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
export interface ProductBalance {
|
|
21
|
+
warehouseId: string;
|
|
22
|
+
warehouseName: string;
|
|
23
|
+
totalQuantity: number;
|
|
24
|
+
reservedQuantity: number;
|
|
25
|
+
hasUnlimitedQuantity: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface PickupPointsByLocationOpts {
|
|
29
|
+
geoCoordinates?: number[];
|
|
30
|
+
postalCode?: string;
|
|
31
|
+
countryCode?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface PickupPointsResponse<T = any> {
|
|
35
|
+
paging: { page: number; pageSize: number; total: number; pages: number };
|
|
36
|
+
items: Array<{ distance: number; pickupPoint: T }>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
// Sales Channels
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Get a single sales channel by ID (public API).
|
|
45
|
+
* @see https://developers.vtex.com/docs/api-reference/catalog-api#get-/api/catalog_system/pub/saleschannel/-salesChannelId-
|
|
46
|
+
*/
|
|
47
|
+
export async function getSalesChannelById<T = any>(id: string): Promise<T> {
|
|
48
|
+
return vtexFetch<T>(`/api/catalog_system/pub/saleschannel/${id}`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* List all sales channels (private API — requires appKey/appToken).
|
|
53
|
+
* @see https://developers.vtex.com/docs/api-reference/catalog-api#get-/api/catalog_system/pvt/saleschannel/list
|
|
54
|
+
*/
|
|
55
|
+
export async function listSalesChannels<T = any>(): Promise<T[]> {
|
|
56
|
+
return vtexFetch<T[]>("/api/catalog_system/pvt/saleschannel/list");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
// Pickup Points
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* List all configured pickup points (private API — requires appKey/appToken).
|
|
65
|
+
* @see https://developers.vtex.com/docs/api-reference/logistics-api#get-/api/logistics/pvt/configuration/pickuppoints
|
|
66
|
+
*/
|
|
67
|
+
export async function listPickupPoints<T = any>(): Promise<T[]> {
|
|
68
|
+
return vtexFetch<T[]>("/api/logistics/pvt/configuration/pickuppoints");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Search pickup points near a geographic location (public API).
|
|
73
|
+
* Pass either `geoCoordinates` (lon/lat array) **or** `postalCode` + `countryCode`.
|
|
74
|
+
*
|
|
75
|
+
* @see https://developers.vtex.com/docs/api-reference/checkout-api#get-/api/checkout/pub/pickup-points
|
|
76
|
+
*/
|
|
77
|
+
export async function listPickupPointsByLocation<T = any>(
|
|
78
|
+
opts: PickupPointsByLocationOpts,
|
|
79
|
+
): Promise<PickupPointsResponse<T>> {
|
|
80
|
+
const params = new URLSearchParams();
|
|
81
|
+
if (opts.geoCoordinates) {
|
|
82
|
+
params.set("geoCoordinates", opts.geoCoordinates.join(","));
|
|
83
|
+
} else {
|
|
84
|
+
if (opts.postalCode) params.set("postalCode", opts.postalCode);
|
|
85
|
+
if (opts.countryCode) params.set("countryCode", opts.countryCode);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return vtexFetch<PickupPointsResponse<T>>(`/api/checkout/pub/pickup-points?${params}`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
// Stock / Inventory
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* List inventory balances for a SKU across all warehouses.
|
|
97
|
+
* @see https://developers.vtex.com/docs/api-reference/logistics-api#get-/api/logistics/pvt/inventory/skus/-skuId-
|
|
98
|
+
*/
|
|
99
|
+
export async function listStockByStore(skuId: number): Promise<ProductBalance[]> {
|
|
100
|
+
try {
|
|
101
|
+
const result = await vtexFetch<{
|
|
102
|
+
skuId?: string;
|
|
103
|
+
balance?: ProductBalance[];
|
|
104
|
+
}>(`/api/logistics/pvt/inventory/skus/${skuId}`);
|
|
105
|
+
|
|
106
|
+
return result.balance ?? [];
|
|
107
|
+
} catch (error) {
|
|
108
|
+
console.error("[listStockByStore]", error);
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSR loader returning the canonical `Minicart` for the current request.
|
|
3
|
+
*
|
|
4
|
+
* Reads `orderFormId` from the `checkout.vtex.com__orderFormId` cookie and
|
|
5
|
+
* fetches the corresponding OrderForm via `getOrCreateCart`. When no
|
|
6
|
+
* orderFormId cookie exists (first-time visitor, no items added yet), returns
|
|
7
|
+
* an empty `Minicart` shell — we deliberately avoid creating a new OrderForm
|
|
8
|
+
* server-side to prevent zero-item carts on every page view.
|
|
9
|
+
*
|
|
10
|
+
* Configurable via Props (free-shipping target, locale, checkout URL).
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* // setup/commerce-loaders.ts
|
|
15
|
+
* import minicart from "@decocms/apps/vtex/loaders/minicart";
|
|
16
|
+
* registerInlineLoader("vtex/loaders/minicart", minicart);
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { RequestContext } from "@decocms/blocks/sdk/requestContext";
|
|
21
|
+
import type { Minicart } from "@decocms/apps-commerce/types";
|
|
22
|
+
import { getOrCreateCart } from "../actions/checkout";
|
|
23
|
+
import type { OrderForm } from "../types";
|
|
24
|
+
import { vtexOrderFormToMinicart } from "../utils/minicart";
|
|
25
|
+
|
|
26
|
+
const ORDER_FORM_COOKIE = "checkout.vtex.com__orderFormId";
|
|
27
|
+
|
|
28
|
+
export interface MinicartProps {
|
|
29
|
+
/** Free-shipping threshold in major units. `0` disables the progress bar. */
|
|
30
|
+
freeShippingTarget?: number;
|
|
31
|
+
/** Override the OrderForm's locale (BCP-47, e.g. `"pt-BR"`). */
|
|
32
|
+
locale?: string;
|
|
33
|
+
/** Where the checkout button sends the user. Default: `/checkout`. */
|
|
34
|
+
checkoutHref?: string;
|
|
35
|
+
/** Whether the UI should expose the coupon input. Default: `true`. */
|
|
36
|
+
enableCoupon?: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function readOrderFormIdFromRequest(): string | undefined {
|
|
40
|
+
const ctx = RequestContext.current;
|
|
41
|
+
const cookieHeader = ctx?.request.headers.get("cookie");
|
|
42
|
+
if (!cookieHeader) return undefined;
|
|
43
|
+
const match = cookieHeader.match(new RegExp(`(?:^|;\\s*)${ORDER_FORM_COOKIE}=([^;]+)`));
|
|
44
|
+
return match?.[1] ? decodeURIComponent(match[1]) : undefined;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Empty cart shell returned when no orderFormId is yet associated with the visitor. */
|
|
48
|
+
function emptyMinicart(opts: MinicartProps): Minicart<OrderForm | null> {
|
|
49
|
+
return {
|
|
50
|
+
original: null,
|
|
51
|
+
storefront: {
|
|
52
|
+
items: [],
|
|
53
|
+
subtotal: 0,
|
|
54
|
+
discounts: 0,
|
|
55
|
+
total: 0,
|
|
56
|
+
locale: opts.locale ?? "pt-BR",
|
|
57
|
+
currency: "BRL",
|
|
58
|
+
enableCoupon: opts.enableCoupon ?? true,
|
|
59
|
+
freeShippingTarget: opts.freeShippingTarget ?? 0,
|
|
60
|
+
checkoutHref: opts.checkoutHref ?? "/checkout",
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export default async function vtexMinicart(
|
|
66
|
+
props: MinicartProps = {},
|
|
67
|
+
): Promise<Minicart<OrderForm | null>> {
|
|
68
|
+
const orderFormId = readOrderFormIdFromRequest();
|
|
69
|
+
if (!orderFormId) return emptyMinicart(props);
|
|
70
|
+
|
|
71
|
+
const orderForm = await getOrCreateCart({ orderFormId });
|
|
72
|
+
return vtexOrderFormToMinicart(orderForm, {
|
|
73
|
+
freeShippingTarget: props.freeShippingTarget,
|
|
74
|
+
locale: props.locale,
|
|
75
|
+
checkoutHref: props.checkoutHref,
|
|
76
|
+
enableCoupon: props.enableCoupon,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VTEX Navbar (category tree) loader.
|
|
3
|
+
* Pure async function — requires configureVtex() to have been called.
|
|
4
|
+
*
|
|
5
|
+
* Ported from deco-cx/apps:
|
|
6
|
+
* vtex/loaders/navbar.ts
|
|
7
|
+
*
|
|
8
|
+
* @see https://developers.vtex.com/docs/api-reference/catalog-api#get-/api/catalog_system/pub/category/tree/-categoryLevels-
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { SiteNavigationElement } from "@decocms/apps-commerce/types";
|
|
12
|
+
import { vtexFetch } from "../client";
|
|
13
|
+
import { categoryTreeToNavbar } from "../utils/transform";
|
|
14
|
+
import type { Category } from "../utils/types";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Fetch the category tree and transform it into an array of
|
|
18
|
+
* `SiteNavigationElement` nodes suitable for navigation menus.
|
|
19
|
+
*
|
|
20
|
+
* @param levels - Depth of the category tree (default: 2)
|
|
21
|
+
*/
|
|
22
|
+
export async function getNavbar(levels: number = 2): Promise<SiteNavigationElement[]> {
|
|
23
|
+
const tree = await vtexFetch<Category[]>(`/api/catalog_system/pub/category/tree/${levels}`);
|
|
24
|
+
|
|
25
|
+
return categoryTreeToNavbar(tree);
|
|
26
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VTEX Orders API loaders.
|
|
3
|
+
* Pure async functions — require configureVtex() to have been called.
|
|
4
|
+
*
|
|
5
|
+
* Ported from deco-cx/apps:
|
|
6
|
+
* vtex/loaders/orders/getById.ts
|
|
7
|
+
* vtex/loaders/orders/list.ts
|
|
8
|
+
*
|
|
9
|
+
* @see https://developers.vtex.com/docs/api-reference/orders-api
|
|
10
|
+
*/
|
|
11
|
+
import { vtexFetch } from "../client";
|
|
12
|
+
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// getOrderById (authenticated — REST)
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Fetch a single user order by its ID.
|
|
19
|
+
* The user must be authenticated or the caller must have OMS permissions.
|
|
20
|
+
*
|
|
21
|
+
* @see https://developers.vtex.com/docs/api-reference/orders-api#get-/api/oms/user/orders/-orderId-
|
|
22
|
+
*/
|
|
23
|
+
export async function getOrderById<T = any>(orderId: string, authCookie: string): Promise<T> {
|
|
24
|
+
return vtexFetch<T>(`/api/oms/user/orders/${orderId}`, {
|
|
25
|
+
headers: { cookie: authCookie },
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// listOrders (authenticated — REST)
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
export interface ListOrdersOpts {
|
|
34
|
+
clientEmail: string;
|
|
35
|
+
page?: string;
|
|
36
|
+
perPage?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* List orders for a given client e-mail.
|
|
41
|
+
* The user must be authenticated or the caller must have OMS permissions.
|
|
42
|
+
*
|
|
43
|
+
* @see https://developers.vtex.com/docs/api-reference/orders-api#get-/api/oms/user/orders
|
|
44
|
+
*/
|
|
45
|
+
export async function listOrders<T = any>(opts: ListOrdersOpts, authCookie: string): Promise<T> {
|
|
46
|
+
const { clientEmail, page = "0", perPage = "15" } = opts;
|
|
47
|
+
const params = new URLSearchParams({
|
|
48
|
+
clientEmail,
|
|
49
|
+
page,
|
|
50
|
+
per_page: perPage,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
return vtexFetch<T>(`/api/oms/user/orders?${params}`, {
|
|
54
|
+
headers: { cookie: authCookie },
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
// getOrderPlaced (order confirmation — Checkout API)
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Fetch order details for the order-placed / confirmation page.
|
|
64
|
+
*
|
|
65
|
+
* Accepts either:
|
|
66
|
+
* - An **order group ID** (no hyphen) → returns all orders in the group
|
|
67
|
+
* - A single **order ID** (contains hyphen) → returns that order wrapped in an array
|
|
68
|
+
*
|
|
69
|
+
* The caller must supply an `authCookie` containing at least the
|
|
70
|
+
* VtexIdclientAutCookie, CheckoutDataAccess, and Vtex_CHKO_Auth cookies
|
|
71
|
+
* that VTEX sets after checkout.
|
|
72
|
+
*
|
|
73
|
+
* Ported from deco-cx/apps:
|
|
74
|
+
* vtex/loaders/orderplaced.ts
|
|
75
|
+
*
|
|
76
|
+
* @see https://developers.vtex.com/docs/api-reference/checkout-api#get-/api/checkout/pub/orders/order-group/-orderGroupId-
|
|
77
|
+
*/
|
|
78
|
+
export async function getOrderPlaced<T = any>(orderId: string, authCookie: string): Promise<T[]> {
|
|
79
|
+
const isOrderGroup = !orderId.includes("-");
|
|
80
|
+
|
|
81
|
+
if (isOrderGroup) {
|
|
82
|
+
return vtexFetch<T[]>(`/api/checkout/pub/orders/order-group/${orderId}`, {
|
|
83
|
+
headers: { cookie: authCookie },
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const order = await vtexFetch<T>(`/api/checkout/pub/orders/${orderId}`, {
|
|
88
|
+
headers: { cookie: authCookie },
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
return [order];
|
|
92
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VTEX page type resolver.
|
|
3
|
+
*
|
|
4
|
+
* Given a URL path, determines if it corresponds to a product, category,
|
|
5
|
+
* brand, department, collection, search, or 404 in VTEX's catalog.
|
|
6
|
+
*
|
|
7
|
+
* @see https://developers.vtex.com/docs/api-reference/catalog-api#get-/api/catalog_system/pub/portal/pagetype/-path-
|
|
8
|
+
*/
|
|
9
|
+
import { type PageType, vtexFetch } from "../client";
|
|
10
|
+
|
|
11
|
+
export type { PageType };
|
|
12
|
+
|
|
13
|
+
export type VtexPageKind =
|
|
14
|
+
| "product"
|
|
15
|
+
| "category"
|
|
16
|
+
| "department"
|
|
17
|
+
| "subcategory"
|
|
18
|
+
| "brand"
|
|
19
|
+
| "collection"
|
|
20
|
+
| "search"
|
|
21
|
+
| "fulltext"
|
|
22
|
+
| "notfound";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Resolve a URL path to a VTEX page type.
|
|
26
|
+
*
|
|
27
|
+
* @param urlPath - The path to resolve (e.g., "/shoes/running")
|
|
28
|
+
* @returns The page type with kind normalization, or null on error
|
|
29
|
+
*/
|
|
30
|
+
export async function resolvePageType(
|
|
31
|
+
urlPath: string,
|
|
32
|
+
): Promise<{ pageType: PageType; kind: VtexPageKind } | null> {
|
|
33
|
+
const cleanPath = urlPath.replace(/^\//, "").replace(/\/$/, "");
|
|
34
|
+
if (!cleanPath) return null;
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const pt = await vtexFetch<PageType>(`/api/catalog_system/pub/portal/pagetype/${cleanPath}`);
|
|
38
|
+
|
|
39
|
+
const kind = normalizeKind(pt.pageType);
|
|
40
|
+
return { pageType: pt, kind };
|
|
41
|
+
} catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function normalizeKind(pageType: PageType["pageType"]): VtexPageKind {
|
|
47
|
+
switch (pageType) {
|
|
48
|
+
case "Product":
|
|
49
|
+
return "product";
|
|
50
|
+
case "Category":
|
|
51
|
+
return "category";
|
|
52
|
+
case "Department":
|
|
53
|
+
return "department";
|
|
54
|
+
case "SubCategory":
|
|
55
|
+
return "subcategory";
|
|
56
|
+
case "Brand":
|
|
57
|
+
return "brand";
|
|
58
|
+
case "Collection":
|
|
59
|
+
case "Cluster":
|
|
60
|
+
return "collection";
|
|
61
|
+
case "Search":
|
|
62
|
+
return "search";
|
|
63
|
+
case "FullText":
|
|
64
|
+
return "fulltext";
|
|
65
|
+
default:
|
|
66
|
+
return "notfound";
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VTEX Payment API loaders.
|
|
3
|
+
* Pure async functions — require configureVtex() to have been called.
|
|
4
|
+
*
|
|
5
|
+
* Ported from deco-cx/apps:
|
|
6
|
+
* vtex/loaders/payment/paymentSystems.ts
|
|
7
|
+
* vtex/loaders/payment/userPayments.ts
|
|
8
|
+
*/
|
|
9
|
+
import { vtexIOGraphQL } from "../client";
|
|
10
|
+
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Types
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
export interface PaymentSystem {
|
|
16
|
+
name: string;
|
|
17
|
+
groupName: string;
|
|
18
|
+
requiresDocument: boolean;
|
|
19
|
+
displayDocument: boolean;
|
|
20
|
+
validator: {
|
|
21
|
+
regex: string | null;
|
|
22
|
+
mask: string | null;
|
|
23
|
+
cardCodeMask: string | null;
|
|
24
|
+
cardCodeRegex: string | null;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface Payment {
|
|
29
|
+
accountStatus: string | null;
|
|
30
|
+
cardNumber: string;
|
|
31
|
+
expirationDate: string;
|
|
32
|
+
id: string;
|
|
33
|
+
isExpired: boolean;
|
|
34
|
+
paymentSystem: string;
|
|
35
|
+
paymentSystemName: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// getPaymentSystems (authenticated — VTEX IO GraphQL)
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
const PAYMENT_SYSTEMS_QUERY = `query getPaymentSystems {
|
|
43
|
+
paymentSystems {
|
|
44
|
+
name
|
|
45
|
+
groupName
|
|
46
|
+
requiresDocument
|
|
47
|
+
displayDocument
|
|
48
|
+
validator {
|
|
49
|
+
regex
|
|
50
|
+
mask
|
|
51
|
+
cardCodeMask
|
|
52
|
+
cardCodeRegex
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}`;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* List available payment systems for the authenticated user.
|
|
59
|
+
* Requires a valid VTEX auth cookie.
|
|
60
|
+
*/
|
|
61
|
+
export async function getPaymentSystems(authCookie: string): Promise<PaymentSystem[]> {
|
|
62
|
+
const { paymentSystems } = await vtexIOGraphQL<{
|
|
63
|
+
paymentSystems: PaymentSystem[];
|
|
64
|
+
}>({ query: PAYMENT_SYSTEMS_QUERY }, { cookie: authCookie });
|
|
65
|
+
|
|
66
|
+
return paymentSystems;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
// getUserPayments (authenticated — VTEX IO GraphQL)
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
const USER_PAYMENTS_QUERY = `query getUserPayments {
|
|
74
|
+
profile {
|
|
75
|
+
payments {
|
|
76
|
+
accountStatus
|
|
77
|
+
cardNumber
|
|
78
|
+
expirationDate
|
|
79
|
+
id
|
|
80
|
+
isExpired
|
|
81
|
+
paymentSystem
|
|
82
|
+
paymentSystemName
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}`;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* List saved payment methods for the authenticated user.
|
|
89
|
+
* Requires a valid VTEX auth cookie.
|
|
90
|
+
*/
|
|
91
|
+
export async function getUserPayments(authCookie: string): Promise<Payment[]> {
|
|
92
|
+
const data = await vtexIOGraphQL<{
|
|
93
|
+
profile: { payments: Payment[] } | null;
|
|
94
|
+
}>({ query: USER_PAYMENTS_QUERY }, { cookie: authCookie });
|
|
95
|
+
|
|
96
|
+
return data.profile?.payments ?? [];
|
|
97
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Product list loader using VTEX Intelligent Search + shared transform pipeline.
|
|
3
|
+
* Maps IS response to schema.org Product[] following deco-cx/apps pattern.
|
|
4
|
+
*
|
|
5
|
+
* Supports: query search, collection IDs, SKU IDs, facets, sort, and
|
|
6
|
+
* hideUnavailableItems — matching the original productList.ts behavior.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Product } from "@decocms/apps-commerce/types";
|
|
10
|
+
import { getVtexConfig, intelligentSearch, toFacetPath } from "../client";
|
|
11
|
+
import { pickSku, sortProducts, toProduct } from "../utils/transform";
|
|
12
|
+
import type { Product as ProductVTEX } from "../utils/types";
|
|
13
|
+
|
|
14
|
+
export interface ProductListProps {
|
|
15
|
+
props?: CollectionProps | QueryProps | ProductIDProps | FacetsProps;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** @title Collection ID */
|
|
19
|
+
export interface CollectionProps {
|
|
20
|
+
/** VTEX product cluster id (e.g. `"150"`). */
|
|
21
|
+
collection: string;
|
|
22
|
+
count?: number;
|
|
23
|
+
sort?: string;
|
|
24
|
+
hideUnavailableItems?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** @title Keyword Search */
|
|
28
|
+
export interface QueryProps {
|
|
29
|
+
query: string;
|
|
30
|
+
count?: number;
|
|
31
|
+
sort?: string;
|
|
32
|
+
/** Pass either a raw IS fuzzy value (`"0" | "1" | "auto"`) or call `mapLabelledFuzzyToFuzzy()`. */
|
|
33
|
+
fuzzy?: string;
|
|
34
|
+
hideUnavailableItems?: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** @title Product IDs */
|
|
38
|
+
export interface ProductIDProps {
|
|
39
|
+
ids: string[];
|
|
40
|
+
hideUnavailableItems?: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** @title Advanced Facets */
|
|
44
|
+
export interface FacetsProps {
|
|
45
|
+
query?: string;
|
|
46
|
+
/** Facets path (e.g. `category-1/moda-feminina/category-2/calcados`). */
|
|
47
|
+
facets: string;
|
|
48
|
+
count?: number;
|
|
49
|
+
sort?: string;
|
|
50
|
+
hideUnavailableItems?: boolean;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function isCollectionProps(p: any): p is CollectionProps {
|
|
54
|
+
return typeof p?.collection === "string";
|
|
55
|
+
}
|
|
56
|
+
function isProductIDProps(p: any): p is ProductIDProps {
|
|
57
|
+
return Array.isArray(p?.ids) && p.ids.length > 0;
|
|
58
|
+
}
|
|
59
|
+
function isFacetsProps(p: any): p is FacetsProps {
|
|
60
|
+
return typeof p?.facets === "string";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function resolveParams(props: ProductListProps): {
|
|
64
|
+
query: string;
|
|
65
|
+
count: number;
|
|
66
|
+
sort: string;
|
|
67
|
+
facetPath: string;
|
|
68
|
+
fuzzy?: string;
|
|
69
|
+
hideUnavailableItems: boolean;
|
|
70
|
+
ids?: string[];
|
|
71
|
+
} {
|
|
72
|
+
const inner = props.props ?? props;
|
|
73
|
+
|
|
74
|
+
if (isProductIDProps(inner)) {
|
|
75
|
+
return {
|
|
76
|
+
query: `sku:${inner.ids.join(";")}`,
|
|
77
|
+
count: inner.ids.length,
|
|
78
|
+
sort: "",
|
|
79
|
+
facetPath: "",
|
|
80
|
+
hideUnavailableItems: inner.hideUnavailableItems ?? false,
|
|
81
|
+
ids: inner.ids,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (isFacetsProps(inner)) {
|
|
86
|
+
return {
|
|
87
|
+
query: inner.query ?? "",
|
|
88
|
+
count: inner.count ?? 12,
|
|
89
|
+
sort: inner.sort ?? "",
|
|
90
|
+
facetPath: inner.facets,
|
|
91
|
+
hideUnavailableItems: inner.hideUnavailableItems ?? false,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (isCollectionProps(inner)) {
|
|
96
|
+
return {
|
|
97
|
+
query: "",
|
|
98
|
+
count: inner.count ?? 12,
|
|
99
|
+
sort: inner.sort ?? "",
|
|
100
|
+
facetPath: toFacetPath([{ key: "productClusterIds", value: inner.collection }]),
|
|
101
|
+
hideUnavailableItems: inner.hideUnavailableItems ?? false,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
query: (inner as any).query ?? "",
|
|
107
|
+
count: (inner as any).count ?? 12,
|
|
108
|
+
sort: (inner as any).sort ?? "",
|
|
109
|
+
facetPath: "",
|
|
110
|
+
fuzzy: (inner as any).fuzzy,
|
|
111
|
+
hideUnavailableItems: (inner as any).hideUnavailableItems ?? false,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export default async function vtexProductList(props: ProductListProps): Promise<Product[] | null> {
|
|
116
|
+
try {
|
|
117
|
+
const { query, count, sort, facetPath, fuzzy, hideUnavailableItems, ids } =
|
|
118
|
+
resolveParams(props);
|
|
119
|
+
|
|
120
|
+
const config = getVtexConfig();
|
|
121
|
+
const locale = config.locale ?? "pt-BR";
|
|
122
|
+
|
|
123
|
+
const params: Record<string, string> = {
|
|
124
|
+
page: "1",
|
|
125
|
+
count: String(count),
|
|
126
|
+
locale,
|
|
127
|
+
hideUnavailableItems: String(hideUnavailableItems),
|
|
128
|
+
};
|
|
129
|
+
if (query) params.query = query;
|
|
130
|
+
if (sort) params.sort = sort;
|
|
131
|
+
if (fuzzy) params.fuzzy = fuzzy;
|
|
132
|
+
|
|
133
|
+
const endpoint = facetPath ? `/product_search/${facetPath}` : "/product_search/";
|
|
134
|
+
|
|
135
|
+
const data = await intelligentSearch<{ products: ProductVTEX[] }>(endpoint, params);
|
|
136
|
+
|
|
137
|
+
const vtexProducts = data.products ?? [];
|
|
138
|
+
const baseUrl = config.publicUrl
|
|
139
|
+
? `https://${config.publicUrl}`
|
|
140
|
+
: `https://${config.account}.vtexcommercestable.${config.domain ?? "com.br"}`;
|
|
141
|
+
|
|
142
|
+
let products = vtexProducts.map((p) => {
|
|
143
|
+
const fetchedSkus = ids ? new Set(ids) : null;
|
|
144
|
+
const preferredSku = fetchedSkus
|
|
145
|
+
? (p.items.find((item) => fetchedSkus.has(item.itemId)) ?? pickSku(p))
|
|
146
|
+
: pickSku(p);
|
|
147
|
+
return toProduct(p, preferredSku, 0, { baseUrl, priceCurrency: "BRL" });
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
if (ids) {
|
|
151
|
+
products = sortProducts(products, ids, "sku");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return products;
|
|
155
|
+
} catch (error) {
|
|
156
|
+
console.error("[VTEX] ProductList error:", error);
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
}
|