@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,164 @@
1
+ import {
2
+ InputMaybe,
3
+ ProductCollectionSortKeys,
4
+ ProductFilter,
5
+ SearchSortKeys as SearchSortKeysShopify,
6
+ } from "../utils/storefront/storefront.graphql.gen";
7
+
8
+ export const sortOptions = [
9
+ { value: "", label: "relevance:desc" },
10
+ { value: "price-ascending", label: "price:asc" },
11
+ { value: "price-descending", label: "price:desc" },
12
+ { value: "best-selling", label: "orders:desc" },
13
+ { value: "title-ascending", label: "name:asc" },
14
+ { value: "title-descending", label: "name:desc" },
15
+ { value: "created-descending", label: "release:desc" },
16
+ ];
17
+
18
+ // only these sorts work for search at shopify
19
+ export const searchSortOptions = [
20
+ { value: "", label: "relevance:desc" },
21
+ { value: "price-ascending", label: "price:asc" },
22
+ { value: "price-descending", label: "price:desc" },
23
+ ];
24
+
25
+ export type CollectionSortKeys =
26
+ | ""
27
+ | "price-descending"
28
+ | "price-ascending"
29
+ | "best-selling"
30
+ | "title-descending"
31
+ | "title-ascending"
32
+ | "created-descending";
33
+
34
+ export type SearchSortKeys =
35
+ | ""
36
+ | "price-descending"
37
+ | "price-ascending";
38
+
39
+ export const searchSortShopify: Record<
40
+ string,
41
+ { sortKey: SearchSortKeysShopify; reverse: boolean }
42
+ > = {
43
+ "": {
44
+ sortKey: "RELEVANCE",
45
+ reverse: false,
46
+ },
47
+ "price-descending": {
48
+ sortKey: "PRICE",
49
+ reverse: true,
50
+ },
51
+ "price-ascending": {
52
+ sortKey: "PRICE",
53
+ reverse: false,
54
+ },
55
+ };
56
+
57
+ export const sortShopify: Record<
58
+ string,
59
+ {
60
+ sortKey: ProductCollectionSortKeys | SearchSortKeysShopify;
61
+ reverse: boolean;
62
+ }
63
+ > = {
64
+ "": {
65
+ sortKey: "RELEVANCE",
66
+ reverse: false,
67
+ },
68
+ "price-descending": {
69
+ sortKey: "PRICE",
70
+ reverse: true,
71
+ },
72
+ "price-ascending": {
73
+ sortKey: "PRICE",
74
+ reverse: false,
75
+ },
76
+ "best-selling": {
77
+ sortKey: "BEST_SELLING",
78
+ reverse: false,
79
+ },
80
+ "title-descending": {
81
+ sortKey: "TITLE",
82
+ reverse: true,
83
+ },
84
+ "title-ascending": {
85
+ sortKey: "TITLE",
86
+ reverse: false,
87
+ },
88
+ "created-descending": {
89
+ sortKey: "CREATED",
90
+ reverse: true,
91
+ },
92
+ };
93
+
94
+ export const filterToObject = (
95
+ type: string,
96
+ filter: InputMaybe<ProductFilter>,
97
+ ) => {
98
+ if (type == "tag") {
99
+ return { tag: filter?.tag };
100
+ }
101
+ if (type == "productType") {
102
+ return { productType: filter?.productType };
103
+ }
104
+ if (type == "productVendor") {
105
+ return { productVendor: filter?.productVendor };
106
+ }
107
+ if (type == "priceMin") {
108
+ return { price: { min: filter?.price?.min } };
109
+ }
110
+ if (type == "priceMax") {
111
+ return { price: { max: filter?.price?.max } };
112
+ }
113
+ if (type == "variantOption") {
114
+ return { variantOption: filter?.variantOption };
115
+ }
116
+ };
117
+
118
+ export const getFiltersByUrl = (url: URL) => {
119
+ const filters: InputMaybe<ProductFilter[]> | undefined = [];
120
+ url.searchParams.forEach((value, key) => {
121
+ if (key.startsWith("filter.v.option")) {
122
+ filters.push({
123
+ variantOption: { name: key.split(".")[3], value: value },
124
+ });
125
+ } else if (key.startsWith("filter.p.tag")) {
126
+ filters.push({ tag: value });
127
+ } else if (key.startsWith("filter.p.type")) {
128
+ filters.push({ productType: value });
129
+ } else if (key.startsWith("filter.p.product_type")) {
130
+ filters.push({ productType: value });
131
+ } else if (key.startsWith("filter.p.vendor")) {
132
+ filters.push({ productVendor: value });
133
+ } else if (key.startsWith("filter.v.availability")) {
134
+ filters.push({ available: value.toLowerCase() === "in stock" });
135
+ } else if (key.startsWith("filter.v.price.gte")) {
136
+ filters.push({ price: { min: Number(value) } });
137
+ } else if (key.startsWith("filter.v.price.lte")) {
138
+ filters.push({ price: { max: Number(value) } });
139
+ } else if (key.startsWith("filter.p.m.custom")) {
140
+ filters.push({
141
+ productMetafield: {
142
+ namespace: "custom",
143
+ key: key.replace("filter.p.m.custom.", ""),
144
+ value: value,
145
+ },
146
+ });
147
+ } else if (key.startsWith("filter.v.t.shopify")) {
148
+ filters.push({
149
+ taxonomyMetafield: {
150
+ namespace: "shopify",
151
+ key: key.replace("filter.v.t.shopify.", ""),
152
+ value: value,
153
+ },
154
+ });
155
+ } else if (key.startsWith("filter.p.t.category")) {
156
+ filters.push({
157
+ category: {
158
+ id: value,
159
+ },
160
+ });
161
+ }
162
+ });
163
+ return filters;
164
+ };
package/tsconfig.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "compilerOptions": {
3
+ "jsx": "react-jsx",
4
+ "moduleResolution": "bundler",
5
+ "module": "ESNext",
6
+ "target": "ES2022",
7
+ "skipLibCheck": true,
8
+ "strictNullChecks": true
9
+ },
10
+ "include": ["**/*.ts", "**/*.tsx"]
11
+ }
package/vtex/README.md ADDED
@@ -0,0 +1,6 @@
1
+ VTEX is a cloud-based e-commerce platform and digital commerce company that provides a comprehensive set of tools and services for businesses looking to establish and manage their online retail operations.
2
+
3
+ This app wrapps VTEX API into a comprehensive set of loaders/actions/workflows
4
+ empowering non technical users to interact and act upon their headless commerce.
5
+
6
+ If you want to use a custom search engine (Algolia, Typesense etc), you will need to fill the App Key & App Token properties. For these, follow this guide
@@ -0,0 +1,211 @@
1
+ /**
2
+ * VTEX Address management actions (store-graphql).
3
+ * Ported from deco-cx/apps:
4
+ * - vtex/actions/address/create.ts
5
+ * - vtex/actions/address/delete.ts
6
+ * - vtex/actions/address/update.ts
7
+ * @see https://developers.vtex.com/docs/guides/profile-system
8
+ */
9
+ import { vtexFetch, getVtexConfig } from "../client";
10
+ import { buildAuthCookieHeader } from "../utils/vtexId";
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // Types
14
+ // ---------------------------------------------------------------------------
15
+
16
+ export interface AddressInput {
17
+ addressName: string;
18
+ addressType?: string;
19
+ city?: string;
20
+ complement?: string;
21
+ country?: string;
22
+ geoCoordinates?: number[];
23
+ neighborhood?: string;
24
+ number?: string;
25
+ postalCode?: string;
26
+ receiverName?: string;
27
+ reference?: string;
28
+ state?: string;
29
+ street?: string;
30
+ }
31
+
32
+ export interface SavedAddress {
33
+ id: string;
34
+ cacheId: string;
35
+ addressId: string;
36
+ userId?: string;
37
+ addressName: string;
38
+ addressType: string | null;
39
+ city: string | null;
40
+ complement: string | null;
41
+ country: string | null;
42
+ geoCoordinates: number[] | null;
43
+ neighborhood: string | null;
44
+ number: string | null;
45
+ postalCode: string | null;
46
+ receiverName: string | null;
47
+ reference: string | null;
48
+ state: string | null;
49
+ street: string | null;
50
+ name?: string;
51
+ }
52
+
53
+ export interface DeleteAddressResult {
54
+ cacheId: string;
55
+ addresses: SavedAddress[];
56
+ }
57
+
58
+ export interface UpdateAddressResult {
59
+ cacheId: string;
60
+ addresses: SavedAddress;
61
+ }
62
+
63
+ // ---------------------------------------------------------------------------
64
+ // GraphQL helper (myvtex.com private graphql)
65
+ // ---------------------------------------------------------------------------
66
+
67
+ interface GqlResponse<T> {
68
+ data: T;
69
+ errors?: Array<{ message: string }>;
70
+ }
71
+
72
+ async function gql<T>(
73
+ query: string,
74
+ variables: Record<string, unknown>,
75
+ authCookie: string,
76
+ ): Promise<T> {
77
+ const { account } = getVtexConfig();
78
+ const result = await vtexFetch<GqlResponse<T>>(
79
+ `https://${account}.myvtex.com/_v/private/graphql/v1`,
80
+ {
81
+ method: "POST",
82
+ body: JSON.stringify({ query, variables }),
83
+ headers: { Cookie: buildAuthCookieHeader(authCookie, account) },
84
+ },
85
+ );
86
+ if (result.errors?.length) {
87
+ throw new Error(`GraphQL error: ${result.errors[0].message}`);
88
+ }
89
+ return result.data;
90
+ }
91
+
92
+ // ---------------------------------------------------------------------------
93
+ // Mutations
94
+ // ---------------------------------------------------------------------------
95
+
96
+ const SAVE_ADDRESS = `mutation SaveAddress($address: AddressInput!) {
97
+ saveAddress(address: $address) @context(provider: "vtex.store-graphql") {
98
+ addressId
99
+ cacheId
100
+ id
101
+ userId
102
+ receiverName
103
+ complement
104
+ neighborhood
105
+ country
106
+ state
107
+ number
108
+ street
109
+ geoCoordinates
110
+ postalCode
111
+ city
112
+ name
113
+ addressName
114
+ addressType
115
+ }
116
+ }`;
117
+
118
+ const DELETE_ADDRESS = `mutation DeleteAddress($addressId: String) {
119
+ deleteAddress(id: $addressId) {
120
+ cacheId
121
+ addresses {
122
+ addressId: id
123
+ addressType
124
+ addressName
125
+ city
126
+ complement
127
+ country
128
+ neighborhood
129
+ number
130
+ postalCode
131
+ geoCoordinates
132
+ receiverName
133
+ reference
134
+ state
135
+ street
136
+ }
137
+ }
138
+ }`;
139
+
140
+ const UPDATE_ADDRESS = `mutation UpdateAddress($addressId: String!, $addressFields: AddressInput) {
141
+ updateAddress(id: $addressId, fields: $addressFields) @context(provider: "vtex.store-graphql") {
142
+ cacheId
143
+ addresses: address {
144
+ addressId: id
145
+ addressType
146
+ addressName
147
+ city
148
+ complement
149
+ country
150
+ neighborhood
151
+ number
152
+ postalCode
153
+ geoCoordinates
154
+ receiverName
155
+ reference
156
+ state
157
+ street
158
+ }
159
+ }
160
+ }`;
161
+
162
+ // ---------------------------------------------------------------------------
163
+ // Actions
164
+ // ---------------------------------------------------------------------------
165
+
166
+ /** Create a new user address. Requires the user's VtexIdclientAutCookie token. */
167
+ export async function createAddress(
168
+ input: AddressInput,
169
+ authCookie: string,
170
+ ): Promise<SavedAddress> {
171
+ const { saveAddress } = await gql<{ saveAddress: SavedAddress }>(
172
+ SAVE_ADDRESS,
173
+ { address: input },
174
+ authCookie,
175
+ );
176
+ return saveAddress;
177
+ }
178
+
179
+ /** Delete an address by its ID. Returns remaining addresses. */
180
+ export async function deleteAddress(
181
+ addressId: string,
182
+ authCookie: string,
183
+ ): Promise<DeleteAddressResult> {
184
+ const { deleteAddress: result } = await gql<{ deleteAddress: DeleteAddressResult }>(
185
+ DELETE_ADDRESS,
186
+ { addressId },
187
+ authCookie,
188
+ );
189
+ return result;
190
+ }
191
+
192
+ /** Update an existing address. Returns the updated address. */
193
+ export async function updateAddress(
194
+ addressId: string,
195
+ fields: Partial<AddressInput>,
196
+ authCookie: string,
197
+ ): Promise<UpdateAddressResult> {
198
+ const { updateAddress: result } = await gql<{ updateAddress: UpdateAddressResult }>(
199
+ UPDATE_ADDRESS,
200
+ {
201
+ addressId,
202
+ addressFields: {
203
+ ...fields,
204
+ receiverName: fields.receiverName ?? null,
205
+ complement: fields.complement ?? null,
206
+ },
207
+ },
208
+ authCookie,
209
+ );
210
+ return result;
211
+ }