@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.
Files changed (113) hide show
  1. package/.github/workflows/release.yml +34 -0
  2. package/.releaserc.json +25 -0
  3. package/commerce/components/Image.tsx +209 -0
  4. package/commerce/components/JsonLd.tsx +285 -0
  5. package/commerce/sdk/analytics.ts +24 -0
  6. package/commerce/sdk/formatPrice.ts +23 -0
  7. package/commerce/sdk/url.ts +9 -0
  8. package/commerce/sdk/useOffer.ts +75 -0
  9. package/commerce/sdk/useVariantPossibilities.ts +43 -0
  10. package/commerce/types/commerce.ts +1105 -0
  11. package/commerce/utils/canonical.ts +11 -0
  12. package/commerce/utils/constants.ts +9 -0
  13. package/commerce/utils/filters.ts +10 -0
  14. package/commerce/utils/productToAnalyticsItem.ts +67 -0
  15. package/commerce/utils/stateByZip.ts +50 -0
  16. package/knip.json +19 -0
  17. package/package.json +77 -0
  18. package/shopify/actions/cart/addItems.ts +37 -0
  19. package/shopify/actions/cart/updateCoupons.ts +32 -0
  20. package/shopify/actions/cart/updateItems.ts +32 -0
  21. package/shopify/actions/user/signIn.ts +45 -0
  22. package/shopify/actions/user/signUp.ts +36 -0
  23. package/shopify/client.ts +58 -0
  24. package/shopify/index.ts +32 -0
  25. package/shopify/init.ts +40 -0
  26. package/shopify/loaders/ProductDetailsPage.ts +35 -0
  27. package/shopify/loaders/ProductList.ts +101 -0
  28. package/shopify/loaders/ProductListingPage.ts +180 -0
  29. package/shopify/loaders/RelatedProducts.ts +45 -0
  30. package/shopify/loaders/cart.ts +73 -0
  31. package/shopify/loaders/shop.ts +40 -0
  32. package/shopify/loaders/user.ts +44 -0
  33. package/shopify/utils/admin/admin.ts +57 -0
  34. package/shopify/utils/admin/queries.ts +29 -0
  35. package/shopify/utils/cart.ts +28 -0
  36. package/shopify/utils/cookies.ts +85 -0
  37. package/shopify/utils/enums.ts +438 -0
  38. package/shopify/utils/graphql.ts +69 -0
  39. package/shopify/utils/storefront/queries.ts +530 -0
  40. package/shopify/utils/storefront/storefront.graphql.gen.ts +113 -0
  41. package/shopify/utils/transform.ts +436 -0
  42. package/shopify/utils/types.ts +191 -0
  43. package/shopify/utils/user.ts +23 -0
  44. package/shopify/utils/utils.ts +164 -0
  45. package/tsconfig.json +11 -0
  46. package/vtex/README.md +6 -0
  47. package/vtex/actions/address.ts +211 -0
  48. package/vtex/actions/auth.ts +337 -0
  49. package/vtex/actions/checkout.ts +497 -0
  50. package/vtex/actions/index.ts +11 -0
  51. package/vtex/actions/masterData.ts +170 -0
  52. package/vtex/actions/misc.ts +196 -0
  53. package/vtex/actions/newsletter.ts +108 -0
  54. package/vtex/actions/orders.ts +37 -0
  55. package/vtex/actions/profile.ts +119 -0
  56. package/vtex/actions/session.ts +87 -0
  57. package/vtex/actions/trigger.ts +43 -0
  58. package/vtex/actions/wishlist.ts +116 -0
  59. package/vtex/client.ts +423 -0
  60. package/vtex/hooks/index.ts +4 -0
  61. package/vtex/hooks/useAutocomplete.ts +89 -0
  62. package/vtex/hooks/useCart.ts +219 -0
  63. package/vtex/hooks/useUser.ts +78 -0
  64. package/vtex/hooks/useWishlist.ts +119 -0
  65. package/vtex/index.ts +14 -0
  66. package/vtex/inline-loaders/productDetailsPage.ts +75 -0
  67. package/vtex/inline-loaders/productList.ts +163 -0
  68. package/vtex/inline-loaders/productListingPage.ts +447 -0
  69. package/vtex/inline-loaders/relatedProducts.ts +83 -0
  70. package/vtex/inline-loaders/suggestions.ts +49 -0
  71. package/vtex/inline-loaders/workflowProducts.ts +68 -0
  72. package/vtex/invoke.ts +202 -0
  73. package/vtex/loaders/address.ts +120 -0
  74. package/vtex/loaders/brands.ts +51 -0
  75. package/vtex/loaders/cart.ts +49 -0
  76. package/vtex/loaders/catalog.ts +165 -0
  77. package/vtex/loaders/collections.ts +57 -0
  78. package/vtex/loaders/index.ts +19 -0
  79. package/vtex/loaders/legacy.ts +671 -0
  80. package/vtex/loaders/logistics.ts +115 -0
  81. package/vtex/loaders/navbar.ts +29 -0
  82. package/vtex/loaders/orders.ts +103 -0
  83. package/vtex/loaders/pageType.ts +62 -0
  84. package/vtex/loaders/payment.ts +107 -0
  85. package/vtex/loaders/profile.ts +138 -0
  86. package/vtex/loaders/promotion.ts +33 -0
  87. package/vtex/loaders/search.ts +127 -0
  88. package/vtex/loaders/session.ts +91 -0
  89. package/vtex/loaders/user.ts +89 -0
  90. package/vtex/loaders/wishlist.ts +89 -0
  91. package/vtex/loaders/wishlistProducts.ts +81 -0
  92. package/vtex/loaders/workflow.ts +323 -0
  93. package/vtex/logo.png +0 -0
  94. package/vtex/middleware.ts +229 -0
  95. package/vtex/types.ts +248 -0
  96. package/vtex/utils/batch.ts +21 -0
  97. package/vtex/utils/cookies.ts +76 -0
  98. package/vtex/utils/enrichment.ts +540 -0
  99. package/vtex/utils/fetchCache.ts +150 -0
  100. package/vtex/utils/index.ts +17 -0
  101. package/vtex/utils/intelligentSearch.ts +84 -0
  102. package/vtex/utils/legacy.ts +155 -0
  103. package/vtex/utils/pickAndOmit.ts +30 -0
  104. package/vtex/utils/proxy.ts +196 -0
  105. package/vtex/utils/resourceRange.ts +10 -0
  106. package/vtex/utils/segment.ts +163 -0
  107. package/vtex/utils/similars.ts +38 -0
  108. package/vtex/utils/sitemap.ts +133 -0
  109. package/vtex/utils/slugCache.ts +32 -0
  110. package/vtex/utils/slugify.ts +13 -0
  111. package/vtex/utils/transform.ts +1331 -0
  112. package/vtex/utils/types.ts +1884 -0
  113. package/vtex/utils/vtexId.ts +103 -0
@@ -0,0 +1,127 @@
1
+ /**
2
+ * VTEX search-related loaders (Intelligent Search + Catalog).
3
+ * Pure async functions — require configureVtex() to have been called.
4
+ *
5
+ * Ported from deco-cx/apps:
6
+ * vtex/loaders/intelligentSearch/topsearches.ts
7
+ * vtex/loaders/intelligentSearch/productSearchValidator.ts
8
+ * vtex/loaders/options/productIdByTerm.ts
9
+ *
10
+ * @see https://developers.vtex.com/docs/api-reference/intelligent-search-api
11
+ */
12
+ import { intelligentSearch, getVtexConfig } from "../client";
13
+ import type { Suggestion } from "../utils/types";
14
+
15
+ // ---------------------------------------------------------------------------
16
+ // getTopSearches
17
+ // ---------------------------------------------------------------------------
18
+
19
+ /**
20
+ * Fetch the top searches from Intelligent Search.
21
+ *
22
+ * @param locale - BCP-47 locale (defaults to the configured locale or "pt-BR")
23
+ */
24
+ export async function getTopSearches(locale?: string): Promise<Suggestion> {
25
+ const cfg = getVtexConfig();
26
+ const effectiveLocale = locale ?? cfg.locale ?? "pt-BR";
27
+
28
+ return intelligentSearch<Suggestion>("/top_searches", {
29
+ locale: effectiveLocale,
30
+ });
31
+ }
32
+
33
+ // ---------------------------------------------------------------------------
34
+ // validateProductSearch
35
+ // ---------------------------------------------------------------------------
36
+
37
+ export interface FacetsSearchProps {
38
+ query?: string;
39
+ facets?: string;
40
+ sort?: string;
41
+ count?: number;
42
+ page?: number;
43
+ locale?: string;
44
+ }
45
+
46
+ /**
47
+ * Validate whether a product search returns results.
48
+ *
49
+ * Runs the given search parameters against Intelligent Search.
50
+ * If no results are found and the props include facets, retries
51
+ * the search without facets.
52
+ *
53
+ * Returns the raw IS response or `null` when nothing is found.
54
+ */
55
+ export async function validateProductSearch<T = unknown>(
56
+ props: FacetsSearchProps,
57
+ fetcher: (props: FacetsSearchProps) => Promise<T[] | null>,
58
+ ): Promise<T[] | null> {
59
+ const results = await fetcher(props);
60
+ if (results !== null && results.length > 0) return results;
61
+
62
+ if (props.facets) {
63
+ return fetcher({ ...props, facets: "" });
64
+ }
65
+
66
+ return null;
67
+ }
68
+
69
+ // ---------------------------------------------------------------------------
70
+ // getProductIdByTerm
71
+ // ---------------------------------------------------------------------------
72
+
73
+ interface ProductIdOption {
74
+ value: string;
75
+ label: string;
76
+ image?: string;
77
+ }
78
+
79
+ interface ISSuggestionProduct {
80
+ productId: string;
81
+ productName: string;
82
+ brand: string;
83
+ linkText: string;
84
+ items: Array<{
85
+ itemId: string;
86
+ name: string;
87
+ images: Array<{ imageUrl: string; imageText: string }>;
88
+ sellers: Array<{
89
+ commertialOffer: { Price: number; ListPrice: number };
90
+ }>;
91
+ }>;
92
+ }
93
+
94
+ interface ISSuggestionResponse {
95
+ searches: Array<{ term: string; count: number }>;
96
+ products: ISSuggestionProduct[];
97
+ }
98
+
99
+ /**
100
+ * Search for products by free-text term and return a list of
101
+ * `{ value (SKU ID), label, image }` options.
102
+ *
103
+ * Hits the IS autocomplete_suggestions endpoint.
104
+ */
105
+ export async function getProductIdByTerm(
106
+ term?: string,
107
+ ): Promise<ProductIdOption[]> {
108
+ const query = (term ?? "").trim();
109
+ if (!query) return [];
110
+
111
+ const data = await intelligentSearch<ISSuggestionResponse>(
112
+ "/autocomplete_suggestions/",
113
+ { query },
114
+ );
115
+
116
+ if (!data.products?.length) {
117
+ return [{ value: "No products found", label: "No products found" }];
118
+ }
119
+
120
+ return data.products.flatMap((product) =>
121
+ (product.items ?? []).map((item) => ({
122
+ value: item.itemId,
123
+ label: `${item.itemId} - ${product.productName} ${item.name} - ${product.productId}`,
124
+ image: item.images?.[0]?.imageUrl,
125
+ })),
126
+ );
127
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * VTEX Session API loaders.
3
+ * Pure async functions — require configureVtex() to have been called.
4
+ *
5
+ * Ported from deco-cx/apps:
6
+ * vtex/loaders/session/getSession.ts
7
+ * vtex/loaders/session/getUserSessions.ts
8
+ *
9
+ * @see https://developers.vtex.com/docs/api-reference/session-manager-api
10
+ */
11
+ import { vtexFetch, vtexIOGraphQL } from "../client";
12
+
13
+ // ---------------------------------------------------------------------------
14
+ // getSession (REST)
15
+ // ---------------------------------------------------------------------------
16
+
17
+ /**
18
+ * Fetch the current session data.
19
+ *
20
+ * @param items - Keys to retrieve, e.g. `["public.variable1", "profile.email"]`.
21
+ * Pass `["*"]` (or omit) to retrieve all keys.
22
+ * @param authCookie - The raw `cookie` header value forwarded from the user request.
23
+ *
24
+ * @see https://developers.vtex.com/docs/api-reference/session-manager-api#get-/api/sessions
25
+ */
26
+ export async function getSession<T = any>(
27
+ items: string[] = ["*"],
28
+ authCookie: string,
29
+ ): Promise<T> {
30
+ const qs = new URLSearchParams({ items: items.join(",") });
31
+ return vtexFetch<T>(`/api/sessions?${qs}`, {
32
+ headers: { cookie: authCookie },
33
+ });
34
+ }
35
+
36
+ // ---------------------------------------------------------------------------
37
+ // getUserSessions (authenticated — VTEX IO GraphQL)
38
+ // ---------------------------------------------------------------------------
39
+
40
+ export interface LoginSession {
41
+ id: string;
42
+ cacheId: string;
43
+ deviceType: string;
44
+ city: string;
45
+ lastAccess: string;
46
+ browser: string;
47
+ os: string;
48
+ ip: string;
49
+ fullAddress: string;
50
+ firstAccess: string;
51
+ }
52
+
53
+ export interface LoginSessionsInfo {
54
+ currentLoginSessionId: string;
55
+ loginSessions: LoginSession[];
56
+ }
57
+
58
+ const USER_SESSIONS_QUERY = `query getUserSessions {
59
+ loginSessionsInfo {
60
+ currentLoginSessionId
61
+ loginSessions {
62
+ id
63
+ cacheId
64
+ deviceType
65
+ city
66
+ lastAccess
67
+ browser
68
+ os
69
+ ip
70
+ fullAddress
71
+ firstAccess
72
+ }
73
+ }
74
+ }`;
75
+
76
+ /**
77
+ * Fetch all active login sessions for the currently authenticated user.
78
+ * Requires a valid VTEX auth cookie.
79
+ */
80
+ export async function getUserSessions(
81
+ authCookie: string,
82
+ ): Promise<LoginSessionsInfo> {
83
+ const { loginSessionsInfo } = await vtexIOGraphQL<{
84
+ loginSessionsInfo: LoginSessionsInfo;
85
+ }>(
86
+ { query: USER_SESSIONS_QUERY },
87
+ { cookie: authCookie },
88
+ );
89
+
90
+ return loginSessionsInfo;
91
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * VTEX User API loader.
3
+ * Pure async function — requires configureVtex() to have been called.
4
+ *
5
+ * Ported from deco-cx/apps:
6
+ * vtex/loaders/user.ts
7
+ *
8
+ * @see https://developers.vtex.com/docs/api-reference/checkout-api
9
+ */
10
+ import { vtexIOGraphQL } from "../client";
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // Types
14
+ // ---------------------------------------------------------------------------
15
+
16
+ export interface Person {
17
+ "@id": string;
18
+ email: string;
19
+ givenName?: string;
20
+ familyName?: string;
21
+ taxID?: string;
22
+ gender?: string;
23
+ telephone?: string;
24
+ }
25
+
26
+ interface VtexUser {
27
+ id: string;
28
+ userId: string;
29
+ email: string;
30
+ firstName?: string;
31
+ lastName?: string;
32
+ profilePicture?: string;
33
+ gender?: string;
34
+ document?: string;
35
+ homePhone?: string;
36
+ businessPhone?: string;
37
+ }
38
+
39
+ // ---------------------------------------------------------------------------
40
+ // getUser (authenticated — VTEX IO GraphQL)
41
+ // ---------------------------------------------------------------------------
42
+
43
+ const USER_QUERY = `query getUserProfile {
44
+ profile {
45
+ id
46
+ userId
47
+ email
48
+ firstName
49
+ lastName
50
+ profilePicture
51
+ gender
52
+ document
53
+ homePhone
54
+ businessPhone
55
+ }
56
+ }`;
57
+
58
+ /**
59
+ * Fetch the authenticated user as a Schema.org-style `Person`.
60
+ * Returns `null` when no valid session exists or the query fails.
61
+ *
62
+ * @param authCookie - Raw `cookie` header value from the user request.
63
+ */
64
+ export async function getUser(
65
+ authCookie: string,
66
+ ): Promise<Person | null> {
67
+ try {
68
+ const { profile: user } = await vtexIOGraphQL<{ profile: VtexUser }>(
69
+ { query: USER_QUERY },
70
+ { cookie: authCookie },
71
+ );
72
+
73
+ return {
74
+ "@id": user.userId ?? user.id,
75
+ email: user.email,
76
+ givenName: user.firstName,
77
+ familyName: user.lastName,
78
+ taxID: user.document?.replace(/[^\d]/g, ""),
79
+ gender: user.gender
80
+ ? user.gender === "f"
81
+ ? "https://schema.org/Female"
82
+ : "https://schema.org/Male"
83
+ : undefined,
84
+ telephone: user.homePhone ?? user.businessPhone,
85
+ };
86
+ } catch {
87
+ return null;
88
+ }
89
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * VTEX Wishlist API loader.
3
+ * Pure async function — requires configureVtex() to have been called.
4
+ *
5
+ * Ported from deco-cx/apps:
6
+ * vtex/loaders/wishlist.ts
7
+ *
8
+ * @see https://developers.vtex.com/docs/guides/vtex-wish-list
9
+ */
10
+ import { vtexIOGraphQL } from "../client";
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // Types
14
+ // ---------------------------------------------------------------------------
15
+
16
+ export interface WishlistItem {
17
+ id: string;
18
+ productId: string;
19
+ sku: string;
20
+ title: string;
21
+ }
22
+
23
+ export interface GetWishlistOpts {
24
+ shopperId: string;
25
+ count?: number;
26
+ page?: number;
27
+ allRecords?: boolean;
28
+ }
29
+
30
+ // ---------------------------------------------------------------------------
31
+ // getWishlist (authenticated — VTEX IO GraphQL)
32
+ // ---------------------------------------------------------------------------
33
+
34
+ const WISHLIST_QUERY = `query GetWishlist($shopperId: String!, $name: String!, $from: Int, $to: Int) {
35
+ viewList(shopperId: $shopperId, name: $name, from: $from, to: $to)
36
+ @context(provider: "vtex.wish-list@1.x") {
37
+ name
38
+ data {
39
+ id
40
+ productId
41
+ sku
42
+ title
43
+ }
44
+ }
45
+ }`;
46
+
47
+ /**
48
+ * Fetch the wishlist for a given shopper.
49
+ * Requires a valid VTEX auth cookie.
50
+ *
51
+ * @param authCookie - Raw `cookie` header value from the user request.
52
+ * @param opts.shopperId - The shopper identifier (usually the e-mail from the JWT `sub` claim).
53
+ * @param opts.count - Items per page (default: all).
54
+ * @param opts.page - Zero-based page index (default: 0).
55
+ * @param opts.allRecords - When true, ignores pagination and returns every item.
56
+ */
57
+ export async function getWishlist(
58
+ authCookie: string,
59
+ opts: GetWishlistOpts,
60
+ ): Promise<WishlistItem[]> {
61
+ try {
62
+ const { viewList } = await vtexIOGraphQL<{
63
+ viewList: { name?: string; data: WishlistItem[] };
64
+ }>(
65
+ {
66
+ operationName: "GetWishlist",
67
+ query: WISHLIST_QUERY,
68
+ variables: {
69
+ name: "Wishlist",
70
+ shopperId: opts.shopperId,
71
+ },
72
+ },
73
+ { cookie: authCookie },
74
+ );
75
+
76
+ const data = viewList.data ?? [];
77
+
78
+ if (opts.allRecords) return data;
79
+
80
+ const count = opts.count ?? Infinity;
81
+ const page = opts.page ?? 0;
82
+ return data.slice(count * page, count * (page + 1));
83
+ } catch (error) {
84
+ if (error instanceof DOMException && error.name === "AbortError") {
85
+ throw error;
86
+ }
87
+ return [];
88
+ }
89
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Wishlist Products loader.
3
+ * Returns a ProductListingPage built from the user's wishlist items.
4
+ *
5
+ * Ported from deco-cx/apps vtex/loaders/product/wishlist.ts
6
+ */
7
+ import type {
8
+ Product,
9
+ ProductListingPage,
10
+ } from "../../commerce/types/commerce";
11
+ import { getWishlist } from "./wishlist";
12
+
13
+ export interface WishlistProductsProps {
14
+ /** Items per page @default 12 */
15
+ count?: number;
16
+ /** 1 to start from index 1 @default 0 */
17
+ offset?: 0 | 1;
18
+ /** The user's auth cookie string */
19
+ authCookie: string;
20
+ /** The user's shopper ID (email) */
21
+ shopperId: string;
22
+ /** Current page URL (for pagination links) */
23
+ url: string;
24
+ }
25
+
26
+ function withPage(baseUrl: string, page: number): string {
27
+ const url = new URL(baseUrl);
28
+ url.searchParams.set("page", `${page}`);
29
+ return `?${url.searchParams}`;
30
+ }
31
+
32
+ export async function wishlistProducts(
33
+ props: WishlistProductsProps,
34
+ ): Promise<ProductListingPage | null> {
35
+ const {
36
+ count: recordPerPage = 12,
37
+ offset = 0,
38
+ authCookie,
39
+ shopperId,
40
+ url: rawUrl,
41
+ } = props;
42
+
43
+ const url = new URL(rawUrl);
44
+ const page = Math.max(
45
+ 0,
46
+ Number(url.searchParams.get("page") ?? offset) - offset,
47
+ );
48
+ const items = await getWishlist(authCookie, { shopperId, allRecords: true });
49
+ const records = items.length;
50
+ const start = page * recordPerPage;
51
+ const end = (page + 1) * recordPerPage;
52
+
53
+ const products: Product[] = items
54
+ .map(({ sku, productId }) => ({
55
+ "@type": "Product" as const,
56
+ inProductGroupWithID: productId,
57
+ productID: sku,
58
+ sku,
59
+ }))
60
+ .slice(start, end);
61
+
62
+ return {
63
+ "@type": "ProductListingPage",
64
+ breadcrumb: {
65
+ "@type": "BreadcrumbList",
66
+ itemListElement: [],
67
+ numberOfItems: 0,
68
+ },
69
+ filters: [],
70
+ products,
71
+ pageInfo: {
72
+ currentPage: page + offset,
73
+ nextPage: records > end ? withPage(rawUrl, page + 1 + offset) : undefined,
74
+ previousPage: page > 0 ? withPage(rawUrl, page - 1 + offset) : undefined,
75
+ recordPerPage,
76
+ records,
77
+ },
78
+ sortOptions: [],
79
+ seo: null,
80
+ };
81
+ }