@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,436 @@
1
+ import type {
2
+ BreadcrumbList,
3
+ Filter,
4
+ ListItem,
5
+ Product,
6
+ ProductDetailsPage,
7
+ PropertyValue,
8
+ UnitPriceSpecification,
9
+ } from "../../commerce/types/commerce";
10
+ import { DEFAULT_IMAGE } from "../../commerce/utils/constants";
11
+
12
+ type MoneyV2 = { amount: string; currencyCode: string };
13
+ type ImageShopify = { url: string; altText?: string | null };
14
+ type VideoSource = { url: string };
15
+
16
+ interface MediaNode {
17
+ alt?: string | null;
18
+ previewImage?: ImageShopify | null;
19
+ mediaContentType: string;
20
+ sources?: VideoSource[];
21
+ }
22
+
23
+ export type SkuShopify = {
24
+ id: string;
25
+ title: string;
26
+ availableForSale: boolean;
27
+ quantityAvailable?: number;
28
+ barcode?: string | null;
29
+ sku?: string | null;
30
+ image?: ImageShopify | null;
31
+ price: MoneyV2;
32
+ compareAtPrice?: MoneyV2 | null;
33
+ selectedOptions: Array<{ name: string; value: string }>;
34
+ };
35
+
36
+ export type CollectionNode = {
37
+ handle: string;
38
+ title: string;
39
+ id?: string;
40
+ description?: string;
41
+ descriptionHtml?: string;
42
+ image?: ImageShopify | null;
43
+ };
44
+
45
+ export type ProductShopify = {
46
+ id: string;
47
+ handle: string;
48
+ title: string;
49
+ description: string;
50
+ descriptionHtml?: string;
51
+ createdAt?: string;
52
+ tags: string[];
53
+ vendor: string;
54
+ productType: string;
55
+ seo?: { title?: string | null; description?: string | null };
56
+ images: { nodes: ImageShopify[] };
57
+ media: { nodes: MediaNode[] };
58
+ variants: { nodes: SkuShopify[] };
59
+ collections?: { nodes: CollectionNode[] };
60
+ metafields?: Array<{
61
+ key: string;
62
+ value: string;
63
+ namespace: string;
64
+ type: string;
65
+ description?: string | null;
66
+ reference?: { image?: { url: string } } | null;
67
+ references?: {
68
+ edges: Array<{ node: { image?: { url: string } } }>;
69
+ } | null;
70
+ } | null>;
71
+ };
72
+
73
+ type FilterValue = { id: string; label: string; count: number; input: string };
74
+ type FilterShopify = { id: string; label: string; type: string; values: FilterValue[] };
75
+
76
+ const getPath = ({ handle }: ProductShopify, sku?: SkuShopify) =>
77
+ sku
78
+ ? `/products/${handle}-${getIdFromVariantId(sku.id)}`
79
+ : `/products/${handle}`;
80
+
81
+ const getIdFromVariantId = (x: string) => {
82
+ const splitted = x.split("/");
83
+ return Number(splitted[splitted.length - 1]);
84
+ };
85
+
86
+ const getVariantIdFromId = (id: number) => `gid://shopify/ProductVariant/${id}`;
87
+
88
+ const nonEmptyArray = <T>(array: T[] | null | undefined) =>
89
+ Array.isArray(array) && array.length > 0 ? array : null;
90
+
91
+ export const toProductPage = (
92
+ product: ProductShopify,
93
+ url: URL,
94
+ maybeSkuId?: number,
95
+ ): ProductDetailsPage => {
96
+ const skuId = maybeSkuId
97
+ ? getVariantIdFromId(maybeSkuId)
98
+ : product.variants.nodes[0]?.id;
99
+ let sku = product.variants.nodes.find((node) => node.id === skuId);
100
+
101
+ if (!sku) {
102
+ sku = product.variants.nodes[0];
103
+ }
104
+
105
+ return {
106
+ "@type": "ProductDetailsPage",
107
+ breadcrumbList: toBreadcrumbList(product, sku),
108
+ product: toProduct(product, sku, url),
109
+ seo: {
110
+ title: product.seo?.title ?? product.title,
111
+ description: product.seo?.description ?? product.description,
112
+ canonical: `${url.origin}${getPath(product, sku)}`,
113
+ },
114
+ };
115
+ };
116
+
117
+ export const toBreadcrumbItem = ({
118
+ name,
119
+ position,
120
+ item,
121
+ }: {
122
+ name: string;
123
+ position: number;
124
+ item: string;
125
+ }): ListItem => ({
126
+ "@type": "ListItem",
127
+ name: decodeURI(name),
128
+ position,
129
+ item,
130
+ });
131
+
132
+ export const toBreadcrumbList = (
133
+ product: ProductShopify,
134
+ sku: SkuShopify,
135
+ ): BreadcrumbList => {
136
+ let list: ListItem[] = [];
137
+ const collection = product.collections?.nodes[0];
138
+
139
+ if (collection) {
140
+ list = [
141
+ toBreadcrumbItem({
142
+ name: collection.title,
143
+ position: 1,
144
+ item: `/${collection.handle}`,
145
+ }),
146
+ toBreadcrumbItem({
147
+ name: product.title,
148
+ position: 2,
149
+ item: getPath(product, sku),
150
+ }),
151
+ ];
152
+ } else {
153
+ list = [
154
+ toBreadcrumbItem({
155
+ name: product.title,
156
+ position: 2,
157
+ item: getPath(product, sku),
158
+ }),
159
+ ];
160
+ }
161
+
162
+ return {
163
+ "@type": "BreadcrumbList",
164
+ numberOfItems: list.length,
165
+ itemListElement: list,
166
+ };
167
+ };
168
+
169
+ export const toProduct = (
170
+ product: ProductShopify,
171
+ sku: SkuShopify,
172
+ url: URL,
173
+ level = 0,
174
+ ): Product => {
175
+ const {
176
+ createdAt,
177
+ description,
178
+ images,
179
+ media,
180
+ id: productGroupID,
181
+ variants,
182
+ vendor,
183
+ productType,
184
+ } = product;
185
+ const {
186
+ id: productID,
187
+ barcode,
188
+ selectedOptions,
189
+ image,
190
+ price,
191
+ availableForSale,
192
+ quantityAvailable,
193
+ compareAtPrice,
194
+ } = sku;
195
+
196
+ const descriptionHtml: PropertyValue = {
197
+ "@type": "PropertyValue",
198
+ name: "descriptionHtml",
199
+ value: product.descriptionHtml,
200
+ };
201
+
202
+ const productTypeValue: PropertyValue = {
203
+ "@type": "PropertyValue",
204
+ name: "productType",
205
+ value: productType,
206
+ };
207
+
208
+ const metafields = (product.metafields ?? [])
209
+ .filter((metafield): metafield is NonNullable<typeof metafield> =>
210
+ metafield != null && metafield.key != null && metafield.value != null
211
+ )
212
+ .map((metafield): PropertyValue => {
213
+ const { key, value, reference, references } = metafield;
214
+ const hasReferenceImage = reference && "image" in reference;
215
+ const referenceImageUrl = hasReferenceImage ? reference.image?.url : null;
216
+
217
+ const hasEdges = references?.edges && references.edges.length > 0;
218
+ const edgeImages = hasEdges
219
+ ? references!.edges.map((edge) =>
220
+ edge.node && "image" in edge.node ? edge.node.image?.url : null
221
+ )
222
+ : null;
223
+
224
+ const rawValue = referenceImageUrl || edgeImages || value;
225
+ const valueToReturn = Array.isArray(rawValue)
226
+ ? JSON.stringify(rawValue)
227
+ : rawValue ?? undefined;
228
+
229
+ return {
230
+ "@type": "PropertyValue",
231
+ name: key,
232
+ value: valueToReturn,
233
+ };
234
+ });
235
+
236
+ const additionalProperty: PropertyValue[] = selectedOptions
237
+ .map(toPropertyValue)
238
+ .concat(descriptionHtml)
239
+ .concat(productTypeValue)
240
+ .concat(metafields);
241
+
242
+ const skuImages = nonEmptyArray([image]);
243
+ const hasVariant = level < 1 &&
244
+ variants.nodes.map((variant) => toProduct(product, variant, url, 1));
245
+ const priceSpec: UnitPriceSpecification[] = [{
246
+ "@type": "UnitPriceSpecification",
247
+ priceType: "https://schema.org/SalePrice",
248
+ price: Number(price.amount),
249
+ }];
250
+
251
+ if (compareAtPrice) {
252
+ priceSpec.push({
253
+ "@type": "UnitPriceSpecification",
254
+ priceType: "https://schema.org/ListPrice",
255
+ price: Number(compareAtPrice.amount),
256
+ });
257
+ }
258
+
259
+ const collectionNodes = product.collections?.nodes ?? [];
260
+
261
+ return {
262
+ "@type": "Product",
263
+ productID,
264
+ url: `${url.origin}${getPath(product, sku)}`,
265
+ name: sku.title,
266
+ description,
267
+ sku: productID,
268
+ gtin: barcode ?? undefined,
269
+ brand: { "@type": "Brand", name: vendor },
270
+ releaseDate: createdAt,
271
+ additionalProperty,
272
+ isVariantOf: {
273
+ "@type": "ProductGroup",
274
+ productGroupID,
275
+ hasVariant: hasVariant || [],
276
+ url: `${url.origin}${getPath(product)}`,
277
+ name: product.title,
278
+ additionalProperty: [
279
+ ...(product.tags ?? []).map((value) =>
280
+ toPropertyValue({ name: "TAG", value })
281
+ ),
282
+ ...collectionNodes.map((col) =>
283
+ toPropertyValue({
284
+ "@id": col.id,
285
+ name: "COLLECTION",
286
+ value: col.title,
287
+ valueReference: col.handle,
288
+ description: col.description,
289
+ disambiguatingDescription: col.descriptionHtml,
290
+ ...(col.image && {
291
+ image: [{
292
+ "@type": "ImageObject" as const,
293
+ encodingFormat: "image",
294
+ alternateName: col.image.altText ?? "",
295
+ url: col.image.url,
296
+ }],
297
+ }),
298
+ })
299
+ ),
300
+ ],
301
+ image: nonEmptyArray(images.nodes)?.map((img) => ({
302
+ "@type": "ImageObject" as const,
303
+ encodingFormat: "image",
304
+ alternateName: img.altText ?? "",
305
+ url: img.url,
306
+ })),
307
+ },
308
+ image: skuImages?.map((img) => ({
309
+ "@type": "ImageObject" as const,
310
+ encodingFormat: "image",
311
+ alternateName: img?.altText ?? "",
312
+ url: img?.url ?? "",
313
+ })) ?? [DEFAULT_IMAGE],
314
+ video: media.nodes
315
+ .filter((m) => m.mediaContentType === "VIDEO")
316
+ .map((video) => ({
317
+ "@type": "VideoObject" as const,
318
+ contentUrl: video.sources?.[0]?.url,
319
+ description: video.alt ?? undefined,
320
+ thumbnailUrl: video.previewImage?.url,
321
+ })),
322
+ offers: {
323
+ "@type": "AggregateOffer",
324
+ priceCurrency: price.currencyCode,
325
+ highPrice: compareAtPrice
326
+ ? Number(compareAtPrice.amount)
327
+ : Number(price.amount),
328
+ lowPrice: Number(price.amount),
329
+ offerCount: 1,
330
+ offers: [{
331
+ "@type": "Offer",
332
+ price: Number(price.amount),
333
+ availability: availableForSale
334
+ ? "https://schema.org/InStock"
335
+ : "https://schema.org/OutOfStock",
336
+ inventoryLevel: { value: quantityAvailable ?? 0 },
337
+ priceSpecification: priceSpec,
338
+ }],
339
+ },
340
+ };
341
+ };
342
+
343
+ const toPropertyValue = (
344
+ option: Omit<PropertyValue, "@type">,
345
+ ): PropertyValue => ({
346
+ "@type": "PropertyValue",
347
+ ...option,
348
+ });
349
+
350
+ const isSelectedFilter = (filterValue: FilterValue, url: URL) => {
351
+ let isSelected = false;
352
+ const label = getFilterValue(filterValue);
353
+
354
+ url.searchParams.forEach((value, key) => {
355
+ if (!key?.startsWith("filter")) return;
356
+ if (value === label) isSelected = true;
357
+ });
358
+ return isSelected;
359
+ };
360
+
361
+ export const toFilter = (filter: FilterShopify, url: URL): Filter => {
362
+ if (!filter.type.includes("RANGE")) {
363
+ return {
364
+ "@type": "FilterToggle",
365
+ label: filter.label,
366
+ key: filter.id,
367
+ values: filter.values.map((value) => ({
368
+ quantity: value.count,
369
+ label: value.label,
370
+ value: value.label,
371
+ selected: isSelectedFilter(value, url),
372
+ url: filtersURL(filter, value, url),
373
+ })),
374
+ quantity: filter.values.length,
375
+ };
376
+ } else {
377
+ const min = JSON.parse(filter.values[0].input).min;
378
+ const max = JSON.parse(filter.values[0].input).max;
379
+ return {
380
+ "@type": "FilterRange",
381
+ label: filter.label,
382
+ key: filter.id,
383
+ values: { min, max },
384
+ };
385
+ }
386
+ };
387
+
388
+ const filtersURL = (filter: FilterShopify, value: FilterValue, _url: URL) => {
389
+ const url = new URL(_url.href);
390
+ const params = new URLSearchParams(url.search);
391
+ params.delete("page");
392
+ params.delete("startCursor");
393
+ params.delete("endCursor");
394
+
395
+ const label = getFilterValue(value);
396
+
397
+ if (params.has(filter.id, label)) {
398
+ params.delete(filter.id, label);
399
+ } else {
400
+ params.append(filter.id, label);
401
+ }
402
+
403
+ url.search = params.toString();
404
+ return url.toString();
405
+ };
406
+
407
+ const getFilterValue = (value: FilterValue) => {
408
+ try {
409
+ const parsed = JSON.parse(value.input);
410
+
411
+ const fieldsToCheck = [
412
+ ["productMetafield", "value"],
413
+ ["taxonomyMetafield", "value"],
414
+ ["productVendor"],
415
+ ["productType"],
416
+ ["category", "id"],
417
+ ];
418
+
419
+ for (const path of fieldsToCheck) {
420
+ let current: any = parsed;
421
+ for (const key of path) {
422
+ if (current && typeof current === "object" && key in current) {
423
+ current = current[key];
424
+ } else {
425
+ current = null;
426
+ break;
427
+ }
428
+ }
429
+ if (current != null) return current;
430
+ }
431
+ } catch (error) {
432
+ console.error("Error parsing input JSON:", error);
433
+ }
434
+
435
+ return value.label;
436
+ };
@@ -0,0 +1,191 @@
1
+ import {
2
+ CountryCode,
3
+ CurrencyCode,
4
+ OrderCancelReason,
5
+ OrderFinancialStatus,
6
+ OrderFulfillmentStatus,
7
+ } from "./enums";
8
+
9
+ type Attribute = {
10
+ key: string;
11
+ value?: string;
12
+ };
13
+
14
+ type MailingAddress = {
15
+ address1?: string;
16
+ address2?: string;
17
+ city?: string;
18
+ company?: string;
19
+ country?: string;
20
+ countryCodeV2?: CountryCode;
21
+ firstName?: string;
22
+ formattedArea?: string;
23
+ id: string;
24
+ lastName?: string;
25
+ latitude?: number;
26
+ longitude?: number;
27
+ name?: string;
28
+ phone?: string;
29
+ province?: string;
30
+ provinceCode?: string;
31
+ zip?: string;
32
+ };
33
+
34
+ type MoneyV2 = {
35
+ amount: number;
36
+ currencyCode: CurrencyCode;
37
+ };
38
+
39
+ type AppliedGiftCard = {
40
+ amountUsed: MoneyV2;
41
+ balance: MoneyV2;
42
+ id: string;
43
+ lastCharacters: string;
44
+ presentmentAmountUsed: MoneyV2;
45
+ };
46
+
47
+ type ShippingRate = {
48
+ handle: string;
49
+ price: MoneyV2;
50
+ title: string;
51
+ };
52
+
53
+ type AvailableShippingRates = {
54
+ ready: boolean;
55
+ shippingRates?: ShippingRate[];
56
+ };
57
+
58
+ type CheckoutBuyerIdentity = {
59
+ countryCode: CountryCode;
60
+ };
61
+
62
+ type Order = {
63
+ billingAddress?: MailingAddress;
64
+ cancelReason?: OrderCancelReason;
65
+ canceledAt?: Date;
66
+ currencyCode: CurrencyCode;
67
+ currentSubtotalPrice: MoneyV2;
68
+ currentTotalDuties?: MoneyV2;
69
+ currentTotalPrice: MoneyV2;
70
+ currentTotalTax: MoneyV2;
71
+ customAttributes: Attribute[];
72
+ customerLocale?: string;
73
+ customerUrl?: string;
74
+ edited: boolean;
75
+ email?: string;
76
+ financialStatus?: OrderFinancialStatus;
77
+ fulfillmentStatus: OrderFulfillmentStatus;
78
+ id: string;
79
+ name: string;
80
+ orderNumber: number;
81
+ originalTotalDuties?: MoneyV2;
82
+ originalTotalPrice: MoneyV2;
83
+ phone?: string;
84
+ processedAt: Date;
85
+ shippingAddress?: MailingAddress;
86
+ };
87
+
88
+ type Checkout = {
89
+ appliedGiftCards: AppliedGiftCard[];
90
+ availableShippingRates?: AvailableShippingRates;
91
+ buyerIdentity: CheckoutBuyerIdentity;
92
+ completedAt?: Date;
93
+ createdAt: Date;
94
+ currencyCode: CurrencyCode;
95
+ customAttributes: Attribute[];
96
+ email?: string;
97
+ id: string;
98
+ lineItemsSubtotalPrice: MoneyV2;
99
+ note: string;
100
+ order: Order;
101
+ orderStatusUrl: string;
102
+ paymentDue: MoneyV2;
103
+ ready: boolean;
104
+ requireShipping: boolean;
105
+ shippingAddress: MailingAddress;
106
+ };
107
+
108
+ type Customer = {
109
+ acceptsMarketing: boolean;
110
+ createdAt: Date;
111
+ defaultAddress: MailingAddress;
112
+ displayName: string;
113
+ email: string;
114
+ firstName: string;
115
+ id: string;
116
+ checkout: Checkout;
117
+ };
118
+
119
+ type CartBuyerIdentity = {
120
+ countryCode: string;
121
+ customer: Customer;
122
+ };
123
+
124
+ export type OldCart = {
125
+ attribute?: Attribute;
126
+ attributes?: Attribute[];
127
+ buyerIdentity?: CartBuyerIdentity;
128
+ id: string;
129
+ };
130
+
131
+ export interface Money {
132
+ amount: number;
133
+ currencyCode: string;
134
+ }
135
+
136
+ export interface Image {
137
+ url: string;
138
+ width: number;
139
+ height: number;
140
+ altText: string;
141
+ }
142
+
143
+ export interface Media {
144
+ nodes: Media[];
145
+ }
146
+
147
+ export interface Media {
148
+ alt: string;
149
+ previewImage: Image;
150
+ mediaContentType: string;
151
+ }
152
+
153
+ export interface Option {
154
+ name: string;
155
+ values: string[];
156
+ }
157
+
158
+ export interface PriceRange {
159
+ minVariantPrice: Price;
160
+ maxVariantPrice: Price;
161
+ }
162
+
163
+ export interface Price {
164
+ amount: string;
165
+ currencyCode: string;
166
+ }
167
+
168
+ export interface SEO {
169
+ title: string;
170
+ description: string;
171
+ }
172
+
173
+ export interface SelectedOption {
174
+ name: string;
175
+ value: string;
176
+ }
177
+
178
+ export interface UnitPriceMeasurement {
179
+ measuredType: null;
180
+ quantityValue: number;
181
+ referenceUnit: null;
182
+ quantityUnit: null;
183
+ }
184
+
185
+ /**
186
+ * @title {{{key}}}
187
+ */
188
+ export interface Metafield {
189
+ namespace: string;
190
+ key: string;
191
+ }
@@ -0,0 +1,23 @@
1
+ import { getCookies, setCookie } from "./cookies";
2
+
3
+ const CUSTOMER_COOKIE = "secure_customer_sig";
4
+
5
+ const ONE_WEEK_MS = 7 * 24 * 3600 * 1_000;
6
+
7
+ export const getUserCookie = (headers: Headers): string | undefined => {
8
+ const cookies = getCookies(headers);
9
+
10
+ return cookies[CUSTOMER_COOKIE];
11
+ };
12
+
13
+ export const setUserCookie = (headers: Headers, accessToken: string) => {
14
+ setCookie(headers, {
15
+ name: CUSTOMER_COOKIE,
16
+ value: accessToken,
17
+ path: "/",
18
+ expires: new Date(Date.now() + ONE_WEEK_MS),
19
+ httpOnly: true,
20
+ secure: true,
21
+ sameSite: "Lax",
22
+ });
23
+ };