@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.
Files changed (109) hide show
  1. package/package.json +67 -0
  2. package/src/README.md +6 -0
  3. package/src/__tests__/client-segment-cookie.test.ts +255 -0
  4. package/src/__tests__/client-set-cookie-forward.test.ts +257 -0
  5. package/src/actions/address.ts +250 -0
  6. package/src/actions/analytics/sendEvent.ts +86 -0
  7. package/src/actions/auth.ts +333 -0
  8. package/src/actions/checkout.ts +522 -0
  9. package/src/actions/index.ts +11 -0
  10. package/src/actions/masterData.ts +168 -0
  11. package/src/actions/misc.ts +188 -0
  12. package/src/actions/newsletter.ts +105 -0
  13. package/src/actions/orders.ts +34 -0
  14. package/src/actions/profile.ts +201 -0
  15. package/src/actions/session.ts +85 -0
  16. package/src/actions/trigger.ts +43 -0
  17. package/src/actions/wishlist.ts +114 -0
  18. package/src/client.ts +667 -0
  19. package/src/commerceLoaders.ts +261 -0
  20. package/src/hooks/__tests__/createUseCart.test.ts +184 -0
  21. package/src/hooks/__tests__/createUseUser.test.ts +48 -0
  22. package/src/hooks/__tests__/createUseWishlist.test.ts +87 -0
  23. package/src/hooks/createUseCart.ts +360 -0
  24. package/src/hooks/createUseUser.ts +153 -0
  25. package/src/hooks/createUseWishlist.ts +242 -0
  26. package/src/hooks/index.ts +19 -0
  27. package/src/hooks/useAutocomplete.ts +83 -0
  28. package/src/hooks/useCart.ts +243 -0
  29. package/src/hooks/useUser.ts +78 -0
  30. package/src/hooks/useWishlist.ts +119 -0
  31. package/src/index.ts +17 -0
  32. package/src/invoke.ts +181 -0
  33. package/src/loaders/ProductDetailsPage.ts +1 -0
  34. package/src/loaders/ProductList.ts +1 -0
  35. package/src/loaders/ProductListingPage.ts +1 -0
  36. package/src/loaders/address.ts +115 -0
  37. package/src/loaders/autocomplete.ts +58 -0
  38. package/src/loaders/brands.ts +44 -0
  39. package/src/loaders/cart.ts +52 -0
  40. package/src/loaders/catalog.ts +163 -0
  41. package/src/loaders/collections.ts +55 -0
  42. package/src/loaders/index.ts +19 -0
  43. package/src/loaders/intelligentSearch/__tests__/productListingPage.test.ts +20 -0
  44. package/src/loaders/intelligentSearch/productDetailsPage.ts +95 -0
  45. package/src/loaders/intelligentSearch/productList.ts +154 -0
  46. package/src/loaders/intelligentSearch/productListingPage.ts +506 -0
  47. package/src/loaders/intelligentSearch/suggestions.ts +46 -0
  48. package/src/loaders/legacy/productDetailsPage.ts +1 -0
  49. package/src/loaders/legacy/productList.ts +1 -0
  50. package/src/loaders/legacy/relatedProductsLoader.ts +81 -0
  51. package/src/loaders/legacy.ts +635 -0
  52. package/src/loaders/logistics.ts +111 -0
  53. package/src/loaders/minicart.ts +78 -0
  54. package/src/loaders/navbar.ts +26 -0
  55. package/src/loaders/orders.ts +92 -0
  56. package/src/loaders/pageType.ts +68 -0
  57. package/src/loaders/payment.ts +97 -0
  58. package/src/loaders/productListFull.ts +159 -0
  59. package/src/loaders/profile.ts +138 -0
  60. package/src/loaders/promotion.ts +30 -0
  61. package/src/loaders/search.ts +124 -0
  62. package/src/loaders/session.ts +83 -0
  63. package/src/loaders/user.ts +87 -0
  64. package/src/loaders/wishlist.ts +89 -0
  65. package/src/loaders/wishlistProducts.ts +69 -0
  66. package/src/loaders/workflow/products.ts +64 -0
  67. package/src/loaders/workflow.ts +316 -0
  68. package/src/logo.png +0 -0
  69. package/src/middleware.ts +240 -0
  70. package/src/mod.ts +152 -0
  71. package/src/registry.ts +9 -0
  72. package/src/types.ts +248 -0
  73. package/src/utils/__tests__/cookieSanitizer.test.ts +162 -0
  74. package/src/utils/__tests__/fetch.test.ts +80 -0
  75. package/src/utils/__tests__/fetchCache.test.ts +112 -0
  76. package/src/utils/__tests__/instrumentedFetch.test.ts +158 -0
  77. package/src/utils/__tests__/intelligentSearch.test.ts +88 -0
  78. package/src/utils/__tests__/minicart.test.ts +184 -0
  79. package/src/utils/__tests__/operationRouter.test.ts +227 -0
  80. package/src/utils/__tests__/resourceRange.test.ts +32 -0
  81. package/src/utils/__tests__/sitemap.test.ts +185 -0
  82. package/src/utils/__tests__/slugify.test.ts +31 -0
  83. package/src/utils/__tests__/transform.test.ts +698 -0
  84. package/src/utils/accountLoaders.ts +203 -0
  85. package/src/utils/authHelpers.ts +85 -0
  86. package/src/utils/batch.ts +18 -0
  87. package/src/utils/cookieSanitizer.ts +173 -0
  88. package/src/utils/cookies.ts +167 -0
  89. package/src/utils/enrichment.ts +560 -0
  90. package/src/utils/fetch.ts +107 -0
  91. package/src/utils/fetchCache.ts +222 -0
  92. package/src/utils/index.ts +19 -0
  93. package/src/utils/instrumentedFetch.ts +78 -0
  94. package/src/utils/intelligentSearch.ts +96 -0
  95. package/src/utils/legacy.ts +139 -0
  96. package/src/utils/minicart.ts +139 -0
  97. package/src/utils/operationRouter.ts +139 -0
  98. package/src/utils/pickAndOmit.ts +25 -0
  99. package/src/utils/proxy.ts +435 -0
  100. package/src/utils/resourceRange.ts +10 -0
  101. package/src/utils/segment.ts +152 -0
  102. package/src/utils/similars.ts +37 -0
  103. package/src/utils/sitemap.ts +282 -0
  104. package/src/utils/slugCache.ts +28 -0
  105. package/src/utils/slugify.ts +13 -0
  106. package/src/utils/transform.ts +1509 -0
  107. package/src/utils/types.ts +1884 -0
  108. package/src/utils/vtexId.ts +138 -0
  109. 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
+ }