@decocms/apps 0.20.1
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/.github/workflows/release.yml +34 -0
- package/.releaserc.json +25 -0
- package/commerce/components/Image.tsx +209 -0
- package/commerce/components/JsonLd.tsx +285 -0
- package/commerce/sdk/analytics.ts +24 -0
- package/commerce/sdk/formatPrice.ts +23 -0
- package/commerce/sdk/url.ts +9 -0
- package/commerce/sdk/useOffer.ts +75 -0
- package/commerce/sdk/useVariantPossibilities.ts +43 -0
- package/commerce/types/commerce.ts +1105 -0
- package/commerce/utils/canonical.ts +11 -0
- package/commerce/utils/constants.ts +9 -0
- package/commerce/utils/filters.ts +10 -0
- package/commerce/utils/productToAnalyticsItem.ts +67 -0
- package/commerce/utils/stateByZip.ts +50 -0
- package/knip.json +19 -0
- package/package.json +77 -0
- package/shopify/actions/cart/addItems.ts +37 -0
- package/shopify/actions/cart/updateCoupons.ts +32 -0
- package/shopify/actions/cart/updateItems.ts +32 -0
- package/shopify/actions/user/signIn.ts +45 -0
- package/shopify/actions/user/signUp.ts +36 -0
- package/shopify/client.ts +58 -0
- package/shopify/index.ts +32 -0
- package/shopify/init.ts +40 -0
- package/shopify/loaders/ProductDetailsPage.ts +35 -0
- package/shopify/loaders/ProductList.ts +101 -0
- package/shopify/loaders/ProductListingPage.ts +180 -0
- package/shopify/loaders/RelatedProducts.ts +45 -0
- package/shopify/loaders/cart.ts +73 -0
- package/shopify/loaders/shop.ts +40 -0
- package/shopify/loaders/user.ts +44 -0
- package/shopify/utils/admin/admin.ts +57 -0
- package/shopify/utils/admin/queries.ts +29 -0
- package/shopify/utils/cart.ts +28 -0
- package/shopify/utils/cookies.ts +85 -0
- package/shopify/utils/enums.ts +438 -0
- package/shopify/utils/graphql.ts +69 -0
- package/shopify/utils/storefront/queries.ts +530 -0
- package/shopify/utils/storefront/storefront.graphql.gen.ts +113 -0
- package/shopify/utils/transform.ts +436 -0
- package/shopify/utils/types.ts +191 -0
- package/shopify/utils/user.ts +23 -0
- package/shopify/utils/utils.ts +164 -0
- package/tsconfig.json +11 -0
- package/vtex/README.md +6 -0
- package/vtex/actions/address.ts +211 -0
- package/vtex/actions/auth.ts +337 -0
- package/vtex/actions/checkout.ts +497 -0
- package/vtex/actions/index.ts +11 -0
- package/vtex/actions/masterData.ts +170 -0
- package/vtex/actions/misc.ts +196 -0
- package/vtex/actions/newsletter.ts +108 -0
- package/vtex/actions/orders.ts +37 -0
- package/vtex/actions/profile.ts +119 -0
- package/vtex/actions/session.ts +87 -0
- package/vtex/actions/trigger.ts +43 -0
- package/vtex/actions/wishlist.ts +116 -0
- package/vtex/client.ts +423 -0
- package/vtex/hooks/index.ts +4 -0
- package/vtex/hooks/useAutocomplete.ts +89 -0
- package/vtex/hooks/useCart.ts +219 -0
- package/vtex/hooks/useUser.ts +78 -0
- package/vtex/hooks/useWishlist.ts +119 -0
- package/vtex/index.ts +14 -0
- package/vtex/inline-loaders/productDetailsPage.ts +75 -0
- package/vtex/inline-loaders/productList.ts +163 -0
- package/vtex/inline-loaders/productListingPage.ts +447 -0
- package/vtex/inline-loaders/relatedProducts.ts +83 -0
- package/vtex/inline-loaders/suggestions.ts +49 -0
- package/vtex/inline-loaders/workflowProducts.ts +68 -0
- package/vtex/invoke.ts +202 -0
- package/vtex/loaders/address.ts +120 -0
- package/vtex/loaders/brands.ts +51 -0
- package/vtex/loaders/cart.ts +49 -0
- package/vtex/loaders/catalog.ts +165 -0
- package/vtex/loaders/collections.ts +57 -0
- package/vtex/loaders/index.ts +19 -0
- package/vtex/loaders/legacy.ts +671 -0
- package/vtex/loaders/logistics.ts +115 -0
- package/vtex/loaders/navbar.ts +29 -0
- package/vtex/loaders/orders.ts +103 -0
- package/vtex/loaders/pageType.ts +62 -0
- package/vtex/loaders/payment.ts +107 -0
- package/vtex/loaders/profile.ts +138 -0
- package/vtex/loaders/promotion.ts +33 -0
- package/vtex/loaders/search.ts +127 -0
- package/vtex/loaders/session.ts +91 -0
- package/vtex/loaders/user.ts +89 -0
- package/vtex/loaders/wishlist.ts +89 -0
- package/vtex/loaders/wishlistProducts.ts +81 -0
- package/vtex/loaders/workflow.ts +323 -0
- package/vtex/logo.png +0 -0
- package/vtex/middleware.ts +229 -0
- package/vtex/types.ts +248 -0
- package/vtex/utils/batch.ts +21 -0
- package/vtex/utils/cookies.ts +76 -0
- package/vtex/utils/enrichment.ts +540 -0
- package/vtex/utils/fetchCache.ts +150 -0
- package/vtex/utils/index.ts +17 -0
- package/vtex/utils/intelligentSearch.ts +84 -0
- package/vtex/utils/legacy.ts +155 -0
- package/vtex/utils/pickAndOmit.ts +30 -0
- package/vtex/utils/proxy.ts +196 -0
- package/vtex/utils/resourceRange.ts +10 -0
- package/vtex/utils/segment.ts +163 -0
- package/vtex/utils/similars.ts +38 -0
- package/vtex/utils/sitemap.ts +133 -0
- package/vtex/utils/slugCache.ts +32 -0
- package/vtex/utils/slugify.ts +13 -0
- package/vtex/utils/transform.ts +1331 -0
- package/vtex/utils/types.ts +1884 -0
- package/vtex/utils/vtexId.ts +103 -0
package/vtex/invoke.ts
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed invoke object for VTEX actions — same DX as deco-cx/deco.
|
|
3
|
+
*
|
|
4
|
+
* Client-side components call `invoke.vtex.actions.*({ data: ... })`.
|
|
5
|
+
* Under the hood, each call is a `createServerFn` that executes
|
|
6
|
+
* on the server with full VTEX credentials (appKey/appToken).
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```tsx
|
|
10
|
+
* import { invoke } from "@decocms/apps/vtex/invoke";
|
|
11
|
+
*
|
|
12
|
+
* // Add to cart
|
|
13
|
+
* const cart = await invoke.vtex.actions.addItemsToCart({
|
|
14
|
+
* data: { orderFormId: "abc", orderItems: [{ id: "1", seller: "1", quantity: 1 }] }
|
|
15
|
+
* });
|
|
16
|
+
*
|
|
17
|
+
* // Subscribe to newsletter
|
|
18
|
+
* await invoke.vtex.actions.subscribe({ data: { email: "user@example.com" } });
|
|
19
|
+
*
|
|
20
|
+
* // Notify me when back in stock
|
|
21
|
+
* await invoke.vtex.actions.notifyMe({ data: { email: "u@e.com", skuId: "123" } });
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
import { createInvokeFn } from "@decocms/start/sdk/createInvoke";
|
|
25
|
+
import {
|
|
26
|
+
getOrCreateCart,
|
|
27
|
+
addItemsToCart,
|
|
28
|
+
updateCartItems,
|
|
29
|
+
addCouponToCart,
|
|
30
|
+
simulateCart,
|
|
31
|
+
getSellersByRegion,
|
|
32
|
+
setShippingPostalCode,
|
|
33
|
+
type SimulationItem,
|
|
34
|
+
} from "./actions/checkout";
|
|
35
|
+
import {
|
|
36
|
+
createDocument,
|
|
37
|
+
getDocument,
|
|
38
|
+
patchDocument,
|
|
39
|
+
searchDocuments,
|
|
40
|
+
uploadAttachment,
|
|
41
|
+
type UploadAttachmentOpts,
|
|
42
|
+
type CreateDocumentResult,
|
|
43
|
+
} from "./actions/masterData";
|
|
44
|
+
import { createSession } from "./actions/session";
|
|
45
|
+
import { subscribe, type SubscribeProps } from "./actions/newsletter";
|
|
46
|
+
import { notifyMe, type NotifyMeProps } from "./actions/misc";
|
|
47
|
+
import type { OrderForm } from "./types";
|
|
48
|
+
import type { RegionResult } from "./actions/checkout";
|
|
49
|
+
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
// invoke.vtex.actions — typed server functions callable from client
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
|
|
54
|
+
export const invoke = {
|
|
55
|
+
vtex: {
|
|
56
|
+
actions: {
|
|
57
|
+
// -- Cart (unwrap VtexFetchResult -> OrderForm) -----------------------
|
|
58
|
+
|
|
59
|
+
getOrCreateCart: createInvokeFn(
|
|
60
|
+
(input: { orderFormId?: string }) =>
|
|
61
|
+
getOrCreateCart(input.orderFormId),
|
|
62
|
+
{ unwrap: true },
|
|
63
|
+
) as unknown as (ctx: { data: { orderFormId?: string } }) => Promise<OrderForm>,
|
|
64
|
+
|
|
65
|
+
addItemsToCart: createInvokeFn(
|
|
66
|
+
(input: {
|
|
67
|
+
orderFormId: string;
|
|
68
|
+
orderItems: Array<{
|
|
69
|
+
id: string;
|
|
70
|
+
seller: string;
|
|
71
|
+
quantity: number;
|
|
72
|
+
}>;
|
|
73
|
+
}) => addItemsToCart(input.orderFormId, input.orderItems),
|
|
74
|
+
{ unwrap: true },
|
|
75
|
+
) as unknown as (ctx: {
|
|
76
|
+
data: {
|
|
77
|
+
orderFormId: string;
|
|
78
|
+
orderItems: Array<{
|
|
79
|
+
id: string;
|
|
80
|
+
seller: string;
|
|
81
|
+
quantity: number;
|
|
82
|
+
}>;
|
|
83
|
+
};
|
|
84
|
+
}) => Promise<OrderForm>,
|
|
85
|
+
|
|
86
|
+
updateCartItems: createInvokeFn(
|
|
87
|
+
(input: {
|
|
88
|
+
orderFormId: string;
|
|
89
|
+
orderItems: Array<{ index: number; quantity: number }>;
|
|
90
|
+
}) => updateCartItems(input.orderFormId, input.orderItems),
|
|
91
|
+
{ unwrap: true },
|
|
92
|
+
) as unknown as (ctx: {
|
|
93
|
+
data: {
|
|
94
|
+
orderFormId: string;
|
|
95
|
+
orderItems: Array<{ index: number; quantity: number }>;
|
|
96
|
+
};
|
|
97
|
+
}) => Promise<OrderForm>,
|
|
98
|
+
|
|
99
|
+
addCouponToCart: createInvokeFn(
|
|
100
|
+
(input: { orderFormId: string; text: string }) =>
|
|
101
|
+
addCouponToCart(input.orderFormId, input.text),
|
|
102
|
+
{ unwrap: true },
|
|
103
|
+
) as unknown as (ctx: {
|
|
104
|
+
data: { orderFormId: string; text: string };
|
|
105
|
+
}) => Promise<OrderForm>,
|
|
106
|
+
|
|
107
|
+
simulateCart: createInvokeFn(
|
|
108
|
+
(input: {
|
|
109
|
+
items: SimulationItem[];
|
|
110
|
+
postalCode: string;
|
|
111
|
+
country?: string;
|
|
112
|
+
}) => simulateCart(input.items, input.postalCode, input.country),
|
|
113
|
+
),
|
|
114
|
+
|
|
115
|
+
// -- Shipping / Region ------------------------------------------------
|
|
116
|
+
|
|
117
|
+
getSellersByRegion: createInvokeFn(
|
|
118
|
+
(input: { postalCode: string; salesChannel?: string }) =>
|
|
119
|
+
getSellersByRegion(input.postalCode, input.salesChannel),
|
|
120
|
+
) as (ctx: {
|
|
121
|
+
data: { postalCode: string; salesChannel?: string };
|
|
122
|
+
}) => Promise<RegionResult | null>,
|
|
123
|
+
|
|
124
|
+
setShippingPostalCode: createInvokeFn(
|
|
125
|
+
(input: {
|
|
126
|
+
orderFormId: string;
|
|
127
|
+
postalCode: string;
|
|
128
|
+
country?: string;
|
|
129
|
+
}) =>
|
|
130
|
+
setShippingPostalCode(
|
|
131
|
+
input.orderFormId,
|
|
132
|
+
input.postalCode,
|
|
133
|
+
input.country,
|
|
134
|
+
),
|
|
135
|
+
) as (ctx: {
|
|
136
|
+
data: {
|
|
137
|
+
orderFormId: string;
|
|
138
|
+
postalCode: string;
|
|
139
|
+
country?: string;
|
|
140
|
+
};
|
|
141
|
+
}) => Promise<boolean>,
|
|
142
|
+
|
|
143
|
+
// -- Session ----------------------------------------------------------
|
|
144
|
+
|
|
145
|
+
createSession: createInvokeFn(
|
|
146
|
+
(input: Record<string, any>) => createSession(input),
|
|
147
|
+
{ unwrap: true },
|
|
148
|
+
),
|
|
149
|
+
|
|
150
|
+
// -- MasterData -------------------------------------------------------
|
|
151
|
+
|
|
152
|
+
createDocument: createInvokeFn(
|
|
153
|
+
(input: { entity: string; data: Record<string, any> }) =>
|
|
154
|
+
createDocument(input.entity, input.data),
|
|
155
|
+
) as (ctx: {
|
|
156
|
+
data: { entity: string; data: Record<string, any> };
|
|
157
|
+
}) => Promise<CreateDocumentResult>,
|
|
158
|
+
|
|
159
|
+
getDocument: createInvokeFn(
|
|
160
|
+
(input: { entity: string; documentId: string }) =>
|
|
161
|
+
getDocument(input.entity, input.documentId),
|
|
162
|
+
),
|
|
163
|
+
|
|
164
|
+
patchDocument: createInvokeFn(
|
|
165
|
+
(input: {
|
|
166
|
+
entity: string;
|
|
167
|
+
documentId: string;
|
|
168
|
+
data: Record<string, any>;
|
|
169
|
+
}) => patchDocument(input.entity, input.documentId, input.data),
|
|
170
|
+
) as (ctx: {
|
|
171
|
+
data: {
|
|
172
|
+
entity: string;
|
|
173
|
+
documentId: string;
|
|
174
|
+
data: Record<string, any>;
|
|
175
|
+
};
|
|
176
|
+
}) => Promise<void>,
|
|
177
|
+
|
|
178
|
+
searchDocuments: createInvokeFn(
|
|
179
|
+
(input: { entity: string; filter: string }) =>
|
|
180
|
+
searchDocuments(input.entity, input.filter),
|
|
181
|
+
),
|
|
182
|
+
|
|
183
|
+
uploadAttachment: createInvokeFn(
|
|
184
|
+
(input: UploadAttachmentOpts) => uploadAttachment(input),
|
|
185
|
+
) as (ctx: {
|
|
186
|
+
data: UploadAttachmentOpts;
|
|
187
|
+
}) => Promise<{ ok: true }>,
|
|
188
|
+
|
|
189
|
+
// -- Newsletter -------------------------------------------------------
|
|
190
|
+
|
|
191
|
+
subscribe: createInvokeFn(
|
|
192
|
+
(input: SubscribeProps) => subscribe(input),
|
|
193
|
+
) as (ctx: { data: SubscribeProps }) => Promise<void>,
|
|
194
|
+
|
|
195
|
+
// -- Misc -------------------------------------------------------------
|
|
196
|
+
|
|
197
|
+
notifyMe: createInvokeFn(
|
|
198
|
+
(input: NotifyMeProps) => notifyMe(input),
|
|
199
|
+
) as (ctx: { data: NotifyMeProps }) => Promise<void>,
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
} as const;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VTEX Address API loaders.
|
|
3
|
+
* Pure async functions — require configureVtex() to have been called.
|
|
4
|
+
*
|
|
5
|
+
* Ported from deco-cx/apps:
|
|
6
|
+
* vtex/loaders/address/getAddressByPostalCode.ts
|
|
7
|
+
* vtex/loaders/address/getUserAddresses.ts
|
|
8
|
+
*
|
|
9
|
+
* @see https://developers.vtex.com/docs/api-reference/checkout-api
|
|
10
|
+
*/
|
|
11
|
+
import { vtexFetch, vtexIOGraphQL } from "../client";
|
|
12
|
+
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Types
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
export interface PostalAddress {
|
|
18
|
+
"@type": "PostalAddress";
|
|
19
|
+
postalCode?: string;
|
|
20
|
+
addressLocality?: string;
|
|
21
|
+
addressRegion?: string;
|
|
22
|
+
addressCountry?: string;
|
|
23
|
+
streetAddress?: string;
|
|
24
|
+
identifier?: string;
|
|
25
|
+
areaServed?: string;
|
|
26
|
+
description?: string;
|
|
27
|
+
disambiguatingDescription?: string;
|
|
28
|
+
latitude?: number;
|
|
29
|
+
longitude?: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface VtexAddress {
|
|
33
|
+
addressId: string;
|
|
34
|
+
addressType: string;
|
|
35
|
+
addressName: string;
|
|
36
|
+
city: string;
|
|
37
|
+
complement: string;
|
|
38
|
+
country: string;
|
|
39
|
+
neighborhood: string;
|
|
40
|
+
number: string;
|
|
41
|
+
postalCode: string;
|
|
42
|
+
geoCoordinates: number[];
|
|
43
|
+
receiverName: string;
|
|
44
|
+
state: string;
|
|
45
|
+
street: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// getAddressByPostalCode
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Look up a postal address by country + postal code (public API).
|
|
54
|
+
* @see https://developers.vtex.com/docs/api-reference/checkout-api#get-/api/checkout/pub/postal-code/-countryCode-/-postalCode-
|
|
55
|
+
*/
|
|
56
|
+
export async function getAddressByPostalCode(
|
|
57
|
+
countryCode: string,
|
|
58
|
+
postalCode: string,
|
|
59
|
+
): Promise<PostalAddress> {
|
|
60
|
+
const data = await vtexFetch<Record<string, any>>(
|
|
61
|
+
`/api/checkout/pub/postal-code/${countryCode}/${postalCode}`,
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
"@type": "PostalAddress",
|
|
66
|
+
postalCode: data.postalCode,
|
|
67
|
+
addressLocality: data.city,
|
|
68
|
+
addressRegion: data.state,
|
|
69
|
+
addressCountry: data.country,
|
|
70
|
+
streetAddress: data.street || undefined,
|
|
71
|
+
identifier: data.number || undefined,
|
|
72
|
+
areaServed: data.neighborhood || undefined,
|
|
73
|
+
description: data.complement || undefined,
|
|
74
|
+
disambiguatingDescription: data.reference || undefined,
|
|
75
|
+
latitude: data.geoCoordinates?.[0] || undefined,
|
|
76
|
+
longitude: data.geoCoordinates?.[1] || undefined,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
// getUserAddresses (authenticated — VTEX IO GraphQL)
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
|
|
84
|
+
const USER_ADDRESSES_QUERY = `query Addresses @context(scope: "private") {
|
|
85
|
+
profile {
|
|
86
|
+
cacheId
|
|
87
|
+
addresses {
|
|
88
|
+
addressId: id
|
|
89
|
+
addressType
|
|
90
|
+
addressName
|
|
91
|
+
city
|
|
92
|
+
complement
|
|
93
|
+
country
|
|
94
|
+
neighborhood
|
|
95
|
+
number
|
|
96
|
+
postalCode
|
|
97
|
+
geoCoordinates
|
|
98
|
+
receiverName
|
|
99
|
+
state
|
|
100
|
+
street
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}`;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Fetch addresses for the currently authenticated user.
|
|
107
|
+
* Requires a valid VTEX auth cookie.
|
|
108
|
+
*/
|
|
109
|
+
export async function getUserAddresses(
|
|
110
|
+
authCookie: string,
|
|
111
|
+
): Promise<VtexAddress[]> {
|
|
112
|
+
const { profile } = await vtexIOGraphQL<{
|
|
113
|
+
profile: { addresses: VtexAddress[] };
|
|
114
|
+
}>(
|
|
115
|
+
{ query: USER_ADDRESSES_QUERY },
|
|
116
|
+
{ cookie: authCookie },
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
return profile?.addresses ?? [];
|
|
120
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VTEX brand loaders.
|
|
3
|
+
* Returns schema.org Brand[] matching the original deco-cx/apps format.
|
|
4
|
+
*
|
|
5
|
+
* @see https://developers.vtex.com/docs/api-reference/catalog-api#get-/api/catalog_system/pub/brand/list
|
|
6
|
+
*/
|
|
7
|
+
import { vtexFetch, getVtexConfig } from "../client";
|
|
8
|
+
import { toBrand } from "../utils/transform";
|
|
9
|
+
import type { Brand } from "../../commerce/types/commerce";
|
|
10
|
+
import type { Brand as BrandVTEX } from "../utils/types";
|
|
11
|
+
|
|
12
|
+
export interface ListBrandsOpts {
|
|
13
|
+
/** When true, only returns active brands. @default false */
|
|
14
|
+
filterInactive?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* List brands from the VTEX catalog, transformed to schema.org Brand format.
|
|
19
|
+
*/
|
|
20
|
+
export async function listBrands(
|
|
21
|
+
opts?: ListBrandsOpts,
|
|
22
|
+
): Promise<Brand[]> {
|
|
23
|
+
const config = getVtexConfig();
|
|
24
|
+
const baseUrl = `https://${config.account}.vteximg.com.br/arquivos/ids`;
|
|
25
|
+
|
|
26
|
+
const brands = await vtexFetch<BrandVTEX[]>(
|
|
27
|
+
"/api/catalog_system/pub/brand/list",
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const filtered = opts?.filterInactive
|
|
31
|
+
? brands.filter((b) => b.isActive)
|
|
32
|
+
: brands;
|
|
33
|
+
|
|
34
|
+
return filtered.map((b) => toBrand(b, baseUrl));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Get a single brand by ID, as schema.org Brand.
|
|
39
|
+
*/
|
|
40
|
+
export async function getBrandById(brandId: number): Promise<Brand | null> {
|
|
41
|
+
try {
|
|
42
|
+
const config = getVtexConfig();
|
|
43
|
+
const baseUrl = `https://${config.account}.vteximg.com.br/arquivos/ids`;
|
|
44
|
+
const brand = await vtexFetch<BrandVTEX>(
|
|
45
|
+
`/api/catalog_system/pub/brand/${brandId}`,
|
|
46
|
+
);
|
|
47
|
+
return toBrand(brand, baseUrl);
|
|
48
|
+
} catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VTEX Cart (OrderForm) loader.
|
|
3
|
+
* Pure async function — requires configureVtex() to have been called.
|
|
4
|
+
*
|
|
5
|
+
* Ported from deco-cx/apps:
|
|
6
|
+
* vtex/loaders/cart.ts
|
|
7
|
+
*
|
|
8
|
+
* @see https://developers.vtex.com/docs/api-reference/checkout-api#get-/api/checkout/pub/orderForm
|
|
9
|
+
*/
|
|
10
|
+
import { vtexFetch, getVtexConfig } from "../client";
|
|
11
|
+
import type { OrderForm } from "../utils/types";
|
|
12
|
+
import { forceHttpsOnAssets } from "../utils/transform";
|
|
13
|
+
import { DEFAULT_EXPECTED_SECTIONS } from "../actions/checkout";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Fetch the current cart (OrderForm).
|
|
17
|
+
*
|
|
18
|
+
* When `orderFormId` is provided the existing cart is retrieved;
|
|
19
|
+
* otherwise a fresh OrderForm is created via POST.
|
|
20
|
+
*
|
|
21
|
+
* @param orderFormId - Optional existing orderForm ID (from checkout cookie)
|
|
22
|
+
* @param salesChannel - Optional sales channel override
|
|
23
|
+
* @param authCookie - Optional cookie string for authenticated requests
|
|
24
|
+
*/
|
|
25
|
+
export async function getCart(
|
|
26
|
+
orderFormId?: string,
|
|
27
|
+
opts?: { salesChannel?: string; authCookie?: string },
|
|
28
|
+
): Promise<OrderForm> {
|
|
29
|
+
const { salesChannel } = getVtexConfig();
|
|
30
|
+
const sc = opts?.salesChannel ?? salesChannel;
|
|
31
|
+
const headers: Record<string, string> = {};
|
|
32
|
+
if (opts?.authCookie) headers.cookie = opts.authCookie;
|
|
33
|
+
|
|
34
|
+
const scParam = sc ? `?sc=${sc}` : "";
|
|
35
|
+
|
|
36
|
+
const body = JSON.stringify({ expectedOrderFormSections: DEFAULT_EXPECTED_SECTIONS });
|
|
37
|
+
|
|
38
|
+
const cart = orderFormId
|
|
39
|
+
? await vtexFetch<OrderForm>(
|
|
40
|
+
`/api/checkout/pub/orderForm/${orderFormId}${scParam}`,
|
|
41
|
+
{ method: "POST", headers, body },
|
|
42
|
+
)
|
|
43
|
+
: await vtexFetch<OrderForm>(
|
|
44
|
+
`/api/checkout/pub/orderForm${scParam}`,
|
|
45
|
+
{ method: "POST", headers, body },
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
return forceHttpsOnAssets(cart);
|
|
49
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VTEX Catalog System API loaders.
|
|
3
|
+
* Pure async functions using vtexFetch. Require configureVtex() to have been called.
|
|
4
|
+
*
|
|
5
|
+
* Ported from deco-cx/apps vtex/loaders/legacy/*.ts and vtex/utils/client.ts
|
|
6
|
+
* @see https://developers.vtex.com/docs/api-reference/catalog-api
|
|
7
|
+
*/
|
|
8
|
+
import { vtexFetch, getVtexConfig } from "../client";
|
|
9
|
+
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Product search (public)
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
export type LegacySort =
|
|
15
|
+
| ""
|
|
16
|
+
| "OrderByTopSaleDESC"
|
|
17
|
+
| "OrderByReleaseDateDESC"
|
|
18
|
+
| "OrderByBestDiscountDESC"
|
|
19
|
+
| "OrderByPriceDESC"
|
|
20
|
+
| "OrderByPriceASC"
|
|
21
|
+
| "OrderByNameASC"
|
|
22
|
+
| "OrderByNameDESC"
|
|
23
|
+
| "OrderByScoreDESC";
|
|
24
|
+
|
|
25
|
+
export interface SearchProductsOpts {
|
|
26
|
+
fq?: string | string[];
|
|
27
|
+
ft?: string;
|
|
28
|
+
sort?: LegacySort;
|
|
29
|
+
from?: number;
|
|
30
|
+
to?: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Search products using the VTEX Catalog API.
|
|
35
|
+
* @see https://developers.vtex.com/docs/api-reference/search-api#get-/api/catalog_system/pub/products/search
|
|
36
|
+
*/
|
|
37
|
+
export async function searchProducts<T = any>(opts: SearchProductsOpts = {}): Promise<T[]> {
|
|
38
|
+
const params = new URLSearchParams();
|
|
39
|
+
const fqs = Array.isArray(opts.fq) ? opts.fq : opts.fq ? [opts.fq] : [];
|
|
40
|
+
for (const f of fqs) params.append("fq", f);
|
|
41
|
+
if (opts.ft) params.set("ft", opts.ft);
|
|
42
|
+
if (opts.sort) params.set("O", opts.sort);
|
|
43
|
+
if (opts.from != null) params.set("_from", String(opts.from));
|
|
44
|
+
if (opts.to != null) params.set("_to", String(opts.to));
|
|
45
|
+
|
|
46
|
+
const { salesChannel } = getVtexConfig();
|
|
47
|
+
if (salesChannel) params.set("sc", salesChannel);
|
|
48
|
+
|
|
49
|
+
return vtexFetch<T[]>(`/api/catalog_system/pub/products/search/?${params}`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Get a product by productId or skuId.
|
|
54
|
+
* @see https://developers.vtex.com/docs/api-reference/search-api#get-/api/catalog_system/pub/products/search
|
|
55
|
+
*/
|
|
56
|
+
export async function getProductByIdOrSku<T = any>(opts: {
|
|
57
|
+
productId?: string;
|
|
58
|
+
skuId?: string;
|
|
59
|
+
}): Promise<T | null> {
|
|
60
|
+
let fq: string;
|
|
61
|
+
if (opts.productId) fq = `productId:${opts.productId}`;
|
|
62
|
+
else if (opts.skuId) fq = `skuId:${opts.skuId}`;
|
|
63
|
+
else throw new Error("Neither productId nor skuId provided.");
|
|
64
|
+
|
|
65
|
+
const results = await searchProducts<T>({ fq });
|
|
66
|
+
return results[0] ?? null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
// Product variations (public)
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Get product SKU variations.
|
|
75
|
+
* @see https://developers.vtex.com/docs/api-reference/catalog-api#get-/api/catalog_system/pub/products/variations/-productId-
|
|
76
|
+
*/
|
|
77
|
+
export async function getProductVariations<T = any>(productId: string): Promise<T> {
|
|
78
|
+
return vtexFetch<T>(`/api/catalog_system/pub/products/variations/${productId}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
// Product specification (private — requires appKey/appToken)
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Get product specifications (private API).
|
|
87
|
+
* @see https://developers.vtex.com/docs/api-reference/catalog-api#get-/api/catalog_system/pvt/products/-productId-/specification
|
|
88
|
+
*/
|
|
89
|
+
export async function getProductSpecification<T = any>(productId: string): Promise<T> {
|
|
90
|
+
return vtexFetch<T>(`/api/catalog_system/pvt/products/${productId}/Specification`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
// Cross-selling
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
|
|
97
|
+
export type CrossSellingType =
|
|
98
|
+
| "similars"
|
|
99
|
+
| "suggestions"
|
|
100
|
+
| "accessories"
|
|
101
|
+
| "whosawalsosaw"
|
|
102
|
+
| "whosawalsobought"
|
|
103
|
+
| "whoboughtalsobought"
|
|
104
|
+
| "showtogether";
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Get cross-selling products.
|
|
108
|
+
* @see https://developers.vtex.com/docs/api-reference/catalog-api#get-/api/catalog_system/pub/products/crossselling/-type-/-productId-
|
|
109
|
+
*/
|
|
110
|
+
export async function getCrossSelling<T = any>(
|
|
111
|
+
type: CrossSellingType,
|
|
112
|
+
productId: string,
|
|
113
|
+
): Promise<T[]> {
|
|
114
|
+
return vtexFetch<T[]>(
|
|
115
|
+
`/api/catalog_system/pub/products/crossselling/${type}/${productId}`,
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
// Categories
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Get the category tree up to a given depth level.
|
|
125
|
+
* @see https://developers.vtex.com/docs/api-reference/catalog-api#get-/api/catalog_system/pub/category/tree/-categoryLevels-
|
|
126
|
+
*/
|
|
127
|
+
export async function getCategoryTree<T = any>(levels = 3): Promise<T[]> {
|
|
128
|
+
return vtexFetch<T[]>(`/api/catalog_system/pub/category/tree/${levels}`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
// Brands
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Get all brands.
|
|
137
|
+
* @see https://developers.vtex.com/docs/api-reference/catalog-api#get-/api/catalog_system/pub/brand/list
|
|
138
|
+
*/
|
|
139
|
+
export async function getBrands<T = any>(): Promise<T[]> {
|
|
140
|
+
return vtexFetch<T[]>("/api/catalog_system/pub/brand/list");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ---------------------------------------------------------------------------
|
|
144
|
+
// Page type
|
|
145
|
+
// ---------------------------------------------------------------------------
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Get the page type for a given term/path.
|
|
149
|
+
* @see https://developers.vtex.com/docs/api-reference/catalog-api#get-/api/catalog_system/pub/portal/pagetype/-term-
|
|
150
|
+
*/
|
|
151
|
+
export async function getPageType<T = any>(term: string): Promise<T> {
|
|
152
|
+
return vtexFetch<T>(`/api/catalog_system/pub/portal/pagetype/${term}`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ---------------------------------------------------------------------------
|
|
156
|
+
// Facets (legacy)
|
|
157
|
+
// ---------------------------------------------------------------------------
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Get facets/filters for a search term.
|
|
161
|
+
* @see https://developers.vtex.com/docs/api-reference/search-api#get-/api/catalog_system/pub/facets/search/-term-
|
|
162
|
+
*/
|
|
163
|
+
export async function getFacets<T = any>(term: string): Promise<T> {
|
|
164
|
+
return vtexFetch<T>(`/api/catalog_system/pub/facets/search/${term}`);
|
|
165
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VTEX Collections loader.
|
|
3
|
+
* Pure async function — requires configureVtex() to have been called.
|
|
4
|
+
*
|
|
5
|
+
* Ported from deco-cx/apps:
|
|
6
|
+
* vtex/loaders/collections/list.ts
|
|
7
|
+
*
|
|
8
|
+
* @see https://developers.vtex.com/docs/api-reference/catalog-api#get-/api/catalog_system/pvt/collection/search
|
|
9
|
+
*/
|
|
10
|
+
import { vtexFetch } from "../client";
|
|
11
|
+
import type { CollectionList } from "../utils/types";
|
|
12
|
+
|
|
13
|
+
export interface CollectionOption {
|
|
14
|
+
value: string;
|
|
15
|
+
label: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Fetch collections, optionally filtering by search term.
|
|
20
|
+
*
|
|
21
|
+
* Without a term returns up to 3 000 collections; with a term
|
|
22
|
+
* returns the first 15 matches. Results are mapped to a simple
|
|
23
|
+
* `{ value, label }` list suitable for dropdowns / selectors.
|
|
24
|
+
*
|
|
25
|
+
* Note: uses the **pvt** (private) endpoint — requires appKey/appToken.
|
|
26
|
+
*/
|
|
27
|
+
export async function getCollections(
|
|
28
|
+
term?: string,
|
|
29
|
+
): Promise<CollectionOption[]> {
|
|
30
|
+
const params = new URLSearchParams();
|
|
31
|
+
|
|
32
|
+
if (term) {
|
|
33
|
+
params.set("page", "1");
|
|
34
|
+
params.set("pageSize", "15");
|
|
35
|
+
const list = await vtexFetch<CollectionList>(
|
|
36
|
+
`/api/catalog_system/pvt/collection/search/${encodeURIComponent(term)}?${params}`,
|
|
37
|
+
);
|
|
38
|
+
return mapToOptions(list);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
params.set("page", "1");
|
|
42
|
+
params.set("pageSize", "3000");
|
|
43
|
+
params.set("orderByAsc", "false");
|
|
44
|
+
const list = await vtexFetch<CollectionList>(
|
|
45
|
+
`/api/catalog_system/pvt/collection/search?${params}`,
|
|
46
|
+
);
|
|
47
|
+
return mapToOptions(list);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function mapToOptions(list: CollectionList): CollectionOption[] {
|
|
51
|
+
return (
|
|
52
|
+
list.items?.map((c) => ({
|
|
53
|
+
value: `${c.id}`,
|
|
54
|
+
label: `${c.id} - ${c.name}`,
|
|
55
|
+
})) ?? []
|
|
56
|
+
);
|
|
57
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export * from "./address";
|
|
2
|
+
export * from "./brands";
|
|
3
|
+
export * from "./cart";
|
|
4
|
+
export * from "./catalog";
|
|
5
|
+
export * from "./collections";
|
|
6
|
+
export * from "./legacy";
|
|
7
|
+
export * from "./logistics";
|
|
8
|
+
export * from "./navbar";
|
|
9
|
+
export * from "./orders";
|
|
10
|
+
export * from "./pageType";
|
|
11
|
+
export * from "./payment";
|
|
12
|
+
export * from "./profile";
|
|
13
|
+
export * from "./promotion";
|
|
14
|
+
export * from "./search";
|
|
15
|
+
export * from "./session";
|
|
16
|
+
export * from "./user";
|
|
17
|
+
export * from "./wishlist";
|
|
18
|
+
export * from "./wishlistProducts";
|
|
19
|
+
export * from "./workflow";
|