@faststore/core 2.0.129-alpha.0 → 2.0.132-alpha.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.
- package/.turbo/turbo-build.log +10 -12
- package/CHANGELOG.md +12 -0
- package/cms/faststore/content-types.json +5 -0
- package/cms/faststore/sections.json +45 -45
- package/lighthouserc.js +1 -0
- package/next.config.js +2 -0
- package/package.json +4 -4
- package/src/components/common/Footer/section.module.scss +1 -0
- package/src/components/common/Toast/Toast.tsx +5 -3
- package/src/components/common/Toast/section.module.scss +7 -0
- package/src/components/product/ProductCard/ProductCard.tsx +13 -7
- package/src/components/product/ProductGrid/ProductGrid.tsx +5 -0
- package/src/components/region/RegionButton/RegionButton.tsx +2 -2
- package/src/components/sections/BannerNewsletter/BannerNewsletter.tsx +1 -0
- package/src/components/sections/BannerNewsletter/section.module.scss +1 -0
- package/src/components/sections/EmptyState/EmptyState.tsx +28 -0
- package/src/components/sections/EmptyState/index.ts +2 -0
- package/src/components/sections/EmptyState/section.module.scss +8 -0
- package/src/components/sections/Hero/Hero.tsx +0 -1
- package/src/components/sections/ProductDetails/section.module.scss +1 -0
- package/src/components/sections/ProductGallery/section.module.scss +1 -0
- package/src/components/sections/ProductShelf/ProductShelf.tsx +12 -4
- package/src/components/sections/ProductTiles/ProductTiles.tsx +21 -0
- package/src/components/ui/Button/ButtonSignIn/ButtonSignIn.tsx +1 -1
- package/src/components/ui/Image/Image.tsx +7 -18
- package/src/components/ui/Image/loader.ts +16 -0
- package/src/components/ui/ImageGallery/ImageGallery.tsx +7 -14
- package/src/components/ui/SkuSelector/Selectors.tsx +4 -17
- package/src/pages/404.tsx +5 -4
- package/src/pages/500.tsx +19 -5
- package/src/pages/[...slug].tsx +0 -1
- package/src/pages/login.tsx +5 -3
- package/src/utils/utilities.ts +13 -0
- package/src/components/ui/Image/thumborUrlBuilder.ts +0 -103
- package/src/components/ui/Image/useImage.ts +0 -46
package/.turbo/turbo-build.log
CHANGED
|
@@ -17,33 +17,31 @@ info - Generating static pages (0/7)
|
|
|
17
17
|
info - Generating static pages (1/7)
|
|
18
18
|
info - Generating static pages (3/7)
|
|
19
19
|
warn - CallToAction not found. Add a new component for this section or remove it from the CMS
|
|
20
|
-
warn - CallToAction not found. Add a new component for this section or remove it from the CMS
|
|
21
|
-
warn - CallToAction not found. Add a new component for this section or remove it from the CMS
|
|
22
20
|
info - Generating static pages (5/7)
|
|
23
21
|
info - Generating static pages (7/7)
|
|
24
22
|
info - Finalizing page optimization...
|
|
25
23
|
|
|
26
24
|
Route (pages) Size First Load JS
|
|
27
|
-
┌ ● / 2.
|
|
28
|
-
├ └ css/
|
|
25
|
+
┌ ● / 2.88 kB 126 kB
|
|
26
|
+
├ └ css/d730f6a28e01cf84.css 7.22 kB
|
|
29
27
|
├ /_app 0 B 77.9 kB
|
|
30
|
-
├ ● /[...slug] 7.
|
|
31
|
-
├ └ css/
|
|
28
|
+
├ ● /[...slug] 7.49 kB 133 kB
|
|
29
|
+
├ └ css/408ab64ab5c04787.css 7.95 kB
|
|
32
30
|
├ ● /[slug]/p 11.2 kB 135 kB
|
|
33
|
-
├ └ css/
|
|
34
|
-
├ ○ /404
|
|
35
|
-
├ ● /500
|
|
31
|
+
├ └ css/bc39c02d387d74b8.css 9.34 kB
|
|
32
|
+
├ ○ /404 1.11 kB 110 kB
|
|
33
|
+
├ ● /500 1.12 kB 110 kB
|
|
36
34
|
├ ● /account 669 B 110 kB
|
|
37
35
|
├ λ /api/graphql 0 B 77.9 kB
|
|
38
36
|
├ λ /api/preview 0 B 77.9 kB
|
|
39
37
|
├ ● /checkout 657 B 110 kB
|
|
40
|
-
├ ● /login
|
|
38
|
+
├ ● /login 1.02 kB 110 kB
|
|
41
39
|
└ ● /s 961 B 121 kB
|
|
42
40
|
+ First Load JS shared by all 80.7 kB
|
|
43
41
|
├ chunks/framework-dfd14d7ce6600b03.js 45.3 kB
|
|
44
|
-
├ chunks/main-
|
|
42
|
+
├ chunks/main-fd466221927468fd.js 23.9 kB
|
|
45
43
|
├ chunks/pages/_app-b601536188b9a919.js 6.43 kB
|
|
46
|
-
├ chunks/webpack-
|
|
44
|
+
├ chunks/webpack-9f89bcf71cec5fbe.js 2.27 kB
|
|
47
45
|
└ css/587c27bbda64e700.css 2.8 kB
|
|
48
46
|
|
|
49
47
|
λ (Server) server-side renders at runtime (uses getInitialProps or getServerSideProps)
|
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,18 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [2.0.132-alpha.0](https://github.com/vtex/faststore/compare/v2.0.131-alpha.0...v2.0.132-alpha.0) (2023-05-09)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
- Lighthouse Checks 🟢 ([#1737](https://github.com/vtex/faststore/issues/1737)) ([b862703](https://github.com/vtex/faststore/commit/b862703ce991065f92765f4cecc88c9946b3d702)), closes [/github.com/GoogleChrome/lighthouse-ci/blob/main/packages/utils/src/presets/all.js#L25](https://github.com/vtex//github.com/GoogleChrome/lighthouse-ci/blob/main/packages/utils/src/presets/all.js/issues/L25)
|
|
11
|
+
|
|
12
|
+
## [2.0.130-alpha.0](https://github.com/vtex/faststore/compare/v2.0.129-alpha.0...v2.0.130-alpha.0) (2023-05-04)
|
|
13
|
+
|
|
14
|
+
### Features
|
|
15
|
+
|
|
16
|
+
- Add PLP Content-type e refacts sections to use CamelCase ([#1745](https://github.com/vtex/faststore/issues/1745)) ([00e17d5](https://github.com/vtex/faststore/commit/00e17d500c070137e7704a35adf4949dcc06f7c6))
|
|
17
|
+
|
|
6
18
|
## [2.0.129-alpha.0](https://github.com/vtex/faststore/compare/v2.0.128-alpha.0...v2.0.129-alpha.0) (2023-05-04)
|
|
7
19
|
|
|
8
20
|
### Features
|
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
"type": "object",
|
|
7
7
|
"description": "Search Bar Configuration",
|
|
8
8
|
"required": [
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
9
|
+
"searchInput",
|
|
10
|
+
"searchHistory",
|
|
11
|
+
"searchTop",
|
|
12
|
+
"searchAutocomplete"
|
|
13
13
|
],
|
|
14
14
|
"properties": {
|
|
15
|
-
"
|
|
15
|
+
"searchInput": {
|
|
16
16
|
"title": "Input Field",
|
|
17
17
|
"type": "object",
|
|
18
18
|
"properties": {
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
},
|
|
44
|
-
"
|
|
44
|
+
"searchHistory": {
|
|
45
45
|
"title": "Search History",
|
|
46
46
|
"type": "object",
|
|
47
47
|
"properties": {
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"type": "string",
|
|
51
51
|
"default": "History"
|
|
52
52
|
},
|
|
53
|
-
"
|
|
53
|
+
"clearButtonLabel": {
|
|
54
54
|
"type": "string",
|
|
55
55
|
"title": "Clear Button Label",
|
|
56
56
|
"default": "Clear History"
|
|
@@ -73,14 +73,14 @@
|
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
},
|
|
76
|
-
"
|
|
76
|
+
"maxItems": {
|
|
77
77
|
"title": "Maximum Number of History Items",
|
|
78
78
|
"type": "integer",
|
|
79
79
|
"default": 5
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
},
|
|
83
|
-
"
|
|
83
|
+
"searchTop": {
|
|
84
84
|
"title": "Top Search",
|
|
85
85
|
"type": "object",
|
|
86
86
|
"properties": {
|
|
@@ -89,14 +89,14 @@
|
|
|
89
89
|
"type": "string",
|
|
90
90
|
"default": "Top Search"
|
|
91
91
|
},
|
|
92
|
-
"
|
|
92
|
+
"maxItems": {
|
|
93
93
|
"title": "Maximum Number of Top Search Items",
|
|
94
94
|
"type": "integer",
|
|
95
95
|
"default": 5
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
},
|
|
99
|
-
"
|
|
99
|
+
"searchAutocomplete": {
|
|
100
100
|
"title": "Autocomplete",
|
|
101
101
|
"type": "object",
|
|
102
102
|
"properties": {
|
|
@@ -117,14 +117,14 @@
|
|
|
117
117
|
}
|
|
118
118
|
}
|
|
119
119
|
},
|
|
120
|
-
"
|
|
120
|
+
"maxItems": {
|
|
121
121
|
"title": "Maximum Number of Autocomplete Items",
|
|
122
122
|
"type": "integer",
|
|
123
123
|
"default": 5
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
126
|
},
|
|
127
|
-
"
|
|
127
|
+
"searchProducts": {
|
|
128
128
|
"title": "Suggested Products",
|
|
129
129
|
"type": "object",
|
|
130
130
|
"properties": {
|
|
@@ -133,7 +133,7 @@
|
|
|
133
133
|
"type": "string",
|
|
134
134
|
"default": "Suggested Products"
|
|
135
135
|
},
|
|
136
|
-
"
|
|
136
|
+
"maxItems": {
|
|
137
137
|
"title": "Maximum Number of Suggested Products",
|
|
138
138
|
"type": "integer",
|
|
139
139
|
"default": 5
|
|
@@ -168,7 +168,7 @@
|
|
|
168
168
|
}
|
|
169
169
|
}
|
|
170
170
|
},
|
|
171
|
-
"
|
|
171
|
+
"signInButton": {
|
|
172
172
|
"title": "Sign In Button",
|
|
173
173
|
"type": "object",
|
|
174
174
|
"properties": {
|
|
@@ -197,7 +197,7 @@
|
|
|
197
197
|
}
|
|
198
198
|
}
|
|
199
199
|
},
|
|
200
|
-
"
|
|
200
|
+
"cartIcon": {
|
|
201
201
|
"title": "Cart Icon",
|
|
202
202
|
"type": "object",
|
|
203
203
|
"properties": {
|
|
@@ -253,7 +253,7 @@
|
|
|
253
253
|
}
|
|
254
254
|
}
|
|
255
255
|
},
|
|
256
|
-
"
|
|
256
|
+
"pageLinks": {
|
|
257
257
|
"title": "Links",
|
|
258
258
|
"type": "array",
|
|
259
259
|
"maxItems": 8,
|
|
@@ -379,7 +379,7 @@
|
|
|
379
379
|
}
|
|
380
380
|
}
|
|
381
381
|
},
|
|
382
|
-
"
|
|
382
|
+
"footerLinks": {
|
|
383
383
|
"title": "Footer Links Sections",
|
|
384
384
|
"type": "array",
|
|
385
385
|
"maxItems": 4,
|
|
@@ -387,7 +387,7 @@
|
|
|
387
387
|
"title": "Footer Links Section",
|
|
388
388
|
"type": "object",
|
|
389
389
|
"properties": {
|
|
390
|
-
"
|
|
390
|
+
"sectionTitle": {
|
|
391
391
|
"title": "Section Title",
|
|
392
392
|
"type": "string"
|
|
393
393
|
},
|
|
@@ -416,7 +416,7 @@
|
|
|
416
416
|
}
|
|
417
417
|
}
|
|
418
418
|
},
|
|
419
|
-
"
|
|
419
|
+
"footerSocial": {
|
|
420
420
|
"title": "Social Media Links",
|
|
421
421
|
"type": "object",
|
|
422
422
|
"properties": {
|
|
@@ -425,7 +425,7 @@
|
|
|
425
425
|
"type": "string",
|
|
426
426
|
"default": "Follow Us"
|
|
427
427
|
},
|
|
428
|
-
"
|
|
428
|
+
"socialLinks": {
|
|
429
429
|
"title": "Social Media",
|
|
430
430
|
"type": "array",
|
|
431
431
|
"minItems": 0,
|
|
@@ -487,16 +487,16 @@
|
|
|
487
487
|
}
|
|
488
488
|
}
|
|
489
489
|
},
|
|
490
|
-
"
|
|
490
|
+
"copyrightInfo": {
|
|
491
491
|
"title": "Copyright Message",
|
|
492
492
|
"type": "string"
|
|
493
493
|
},
|
|
494
|
-
"
|
|
494
|
+
"acceptedPaymentMethods": {
|
|
495
495
|
"title": "Payment Methods Sections",
|
|
496
496
|
"type": "object",
|
|
497
|
-
"required": ["
|
|
497
|
+
"required": ["showPaymentMethods"],
|
|
498
498
|
"properties": {
|
|
499
|
-
"
|
|
499
|
+
"showPaymentMethods": {
|
|
500
500
|
"title": "Display Payment Methods",
|
|
501
501
|
"type": "boolean",
|
|
502
502
|
"default": true
|
|
@@ -506,7 +506,7 @@
|
|
|
506
506
|
"type": "string",
|
|
507
507
|
"default": "Payment Methods"
|
|
508
508
|
},
|
|
509
|
-
"
|
|
509
|
+
"paymentMethods": {
|
|
510
510
|
"title": "Payment Methods",
|
|
511
511
|
"type": "array",
|
|
512
512
|
"items": {
|
|
@@ -797,21 +797,21 @@
|
|
|
797
797
|
}
|
|
798
798
|
}
|
|
799
799
|
},
|
|
800
|
-
"
|
|
800
|
+
"productCardConfiguration": {
|
|
801
801
|
"title": "Product Card Configuration",
|
|
802
802
|
"type": "object",
|
|
803
803
|
"properties": {
|
|
804
|
-
"
|
|
804
|
+
"showDiscountBadge": {
|
|
805
805
|
"title": "Show discount badge?",
|
|
806
806
|
"type": "boolean",
|
|
807
807
|
"default": true
|
|
808
808
|
},
|
|
809
|
-
"
|
|
809
|
+
"showBuyButton": {
|
|
810
810
|
"title": "Show buy button?",
|
|
811
811
|
"type": "boolean",
|
|
812
812
|
"default": true
|
|
813
813
|
},
|
|
814
|
-
"
|
|
814
|
+
"buyButtonTitle": {
|
|
815
815
|
"title": "Buy Button Text",
|
|
816
816
|
"type": "string",
|
|
817
817
|
"default": "Buy"
|
|
@@ -963,26 +963,26 @@
|
|
|
963
963
|
"type": "string",
|
|
964
964
|
"default": "Receive our news and promotions in advance"
|
|
965
965
|
},
|
|
966
|
-
"
|
|
966
|
+
"privacyPolicy": {
|
|
967
967
|
"title": "Privacy Policy Disclaimer",
|
|
968
968
|
"type": "string"
|
|
969
969
|
},
|
|
970
|
-
"
|
|
970
|
+
"emailInputLabel": {
|
|
971
971
|
"title": "Email input label",
|
|
972
972
|
"type": "string",
|
|
973
973
|
"default": "Your Email"
|
|
974
974
|
},
|
|
975
|
-
"
|
|
975
|
+
"displayNameInput": {
|
|
976
976
|
"title": "Request name?",
|
|
977
977
|
"type": "boolean",
|
|
978
978
|
"default": true
|
|
979
979
|
},
|
|
980
|
-
"
|
|
980
|
+
"nameInputLabel": {
|
|
981
981
|
"title": "Name input label",
|
|
982
982
|
"type": "string",
|
|
983
983
|
"default": "Your Name"
|
|
984
984
|
},
|
|
985
|
-
"
|
|
985
|
+
"subscribeButtonLabel": {
|
|
986
986
|
"title": "Subscribe button label",
|
|
987
987
|
"type": "string",
|
|
988
988
|
"default": "Subscribe"
|
|
@@ -1096,23 +1096,23 @@
|
|
|
1096
1096
|
"type": "object",
|
|
1097
1097
|
"description": "Display Product Details Section",
|
|
1098
1098
|
"properties": {
|
|
1099
|
-
"
|
|
1099
|
+
"productTitle": {
|
|
1100
1100
|
"title": "Product Title",
|
|
1101
1101
|
"type": "object",
|
|
1102
1102
|
"properties": {
|
|
1103
|
-
"
|
|
1103
|
+
"discountBadge": {
|
|
1104
1104
|
"title": "Show Discount Badge?",
|
|
1105
1105
|
"type": "boolean",
|
|
1106
1106
|
"default": false
|
|
1107
1107
|
},
|
|
1108
|
-
"
|
|
1108
|
+
"refNumber": {
|
|
1109
1109
|
"title": "Show Reference Number?",
|
|
1110
1110
|
"type": "boolean",
|
|
1111
1111
|
"default": false
|
|
1112
1112
|
}
|
|
1113
1113
|
}
|
|
1114
1114
|
},
|
|
1115
|
-
"
|
|
1115
|
+
"buyButton": {
|
|
1116
1116
|
"title": "Buy Button",
|
|
1117
1117
|
"type": "object",
|
|
1118
1118
|
"properties": {
|
|
@@ -1140,7 +1140,7 @@
|
|
|
1140
1140
|
}
|
|
1141
1141
|
}
|
|
1142
1142
|
},
|
|
1143
|
-
"
|
|
1143
|
+
"shippingSimulator": {
|
|
1144
1144
|
"title": "Shipping Simulation",
|
|
1145
1145
|
"type": "object",
|
|
1146
1146
|
"properties": {
|
|
@@ -1149,7 +1149,7 @@
|
|
|
1149
1149
|
"type": "string",
|
|
1150
1150
|
"default": "Shipping"
|
|
1151
1151
|
},
|
|
1152
|
-
"
|
|
1152
|
+
"inputLabel": {
|
|
1153
1153
|
"title": "Input Label",
|
|
1154
1154
|
"type": "string",
|
|
1155
1155
|
"default": "Postal Code"
|
|
@@ -1169,17 +1169,17 @@
|
|
|
1169
1169
|
}
|
|
1170
1170
|
}
|
|
1171
1171
|
},
|
|
1172
|
-
"
|
|
1172
|
+
"shippingOptionsTableTitle": {
|
|
1173
1173
|
"title": "Shipping Options Table Header",
|
|
1174
1174
|
"type": "string"
|
|
1175
1175
|
}
|
|
1176
1176
|
}
|
|
1177
1177
|
},
|
|
1178
|
-
"
|
|
1178
|
+
"productDetailsContent": {
|
|
1179
1179
|
"title": "Product Details Content",
|
|
1180
1180
|
"type": "object",
|
|
1181
1181
|
"properties": {
|
|
1182
|
-
"
|
|
1182
|
+
"initiallyExpanded": {
|
|
1183
1183
|
"type": "string",
|
|
1184
1184
|
"title": "Initially Expanded?",
|
|
1185
1185
|
"enumNames": ["First", "All", "None"],
|
|
@@ -1188,7 +1188,7 @@
|
|
|
1188
1188
|
"details": {
|
|
1189
1189
|
"type": "object",
|
|
1190
1190
|
"properties": {
|
|
1191
|
-
"
|
|
1191
|
+
"displayDescription": {
|
|
1192
1192
|
"title": "Should display description?",
|
|
1193
1193
|
"type": "boolean",
|
|
1194
1194
|
"default": true
|
package/lighthouserc.js
CHANGED
package/next.config.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@faststore/core",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.132-alpha.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"browserslist": "supports es6-module and not dead",
|
|
6
6
|
"scripts": {
|
|
@@ -30,10 +30,10 @@
|
|
|
30
30
|
"@envelop/parser-cache": "^2.2.0",
|
|
31
31
|
"@envelop/validation-cache": "^2.2.0",
|
|
32
32
|
"@faststore/api": "^2.0.118-alpha.0",
|
|
33
|
-
"@faststore/components": "^2.0.
|
|
33
|
+
"@faststore/components": "^2.0.132-alpha.0",
|
|
34
34
|
"@faststore/graphql-utils": "^2.0.3-alpha.0",
|
|
35
35
|
"@faststore/sdk": "^2.0.118-alpha.0",
|
|
36
|
-
"@faststore/ui": "^2.0.
|
|
36
|
+
"@faststore/ui": "^2.0.132-alpha.0",
|
|
37
37
|
"@types/react": "^18.0.14",
|
|
38
38
|
"@vtex/client-cms": "^0.2.12",
|
|
39
39
|
"autoprefixer": "^10.4.0",
|
|
@@ -108,5 +108,5 @@
|
|
|
108
108
|
"msw": {
|
|
109
109
|
"workerDirectory": "public"
|
|
110
110
|
},
|
|
111
|
-
"gitHead": "
|
|
111
|
+
"gitHead": "e6d7561f0783b20ba4966d9e7c971146300ff129"
|
|
112
112
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
@import "@faststore/ui/src/styles/base/utilities.scss";
|
|
3
3
|
|
|
4
4
|
.section {
|
|
5
|
+
@import "@faststore/ui/src/components/atoms/Button/styles.scss";
|
|
5
6
|
@import "@faststore/ui/src/components/atoms/Link/styles.scss";
|
|
6
7
|
@import "@faststore/ui/src/components/atoms/List/styles.scss";
|
|
7
8
|
@import "@faststore/ui/src/components/atoms/Logo/styles.scss";
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useEffect } from 'react'
|
|
2
2
|
|
|
3
3
|
import { Toast as UIToast, useUI } from '@faststore/ui'
|
|
4
|
+
import Section from 'src/components/sections/Section/Section'
|
|
4
5
|
import { useCart } from 'src/sdk/cart'
|
|
6
|
+
import styles from './section.module.scss'
|
|
5
7
|
|
|
6
8
|
function Toast() {
|
|
7
9
|
const { toasts, pushToast } = useUI()
|
|
@@ -27,9 +29,9 @@ function Toast() {
|
|
|
27
29
|
return (
|
|
28
30
|
<>
|
|
29
31
|
{toasts.length > 0 && (
|
|
30
|
-
<
|
|
32
|
+
<Section className={`${styles.section} section-toast`}>
|
|
31
33
|
<UIToast />
|
|
32
|
-
</
|
|
34
|
+
</Section>
|
|
33
35
|
)}
|
|
34
36
|
</>
|
|
35
37
|
)
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/* stylelint-disable no-invalid-position-at-import-rule */
|
|
2
|
+
@import "@faststore/ui/src/styles/base/utilities.scss";
|
|
3
|
+
|
|
4
|
+
.section {
|
|
5
|
+
@import "@faststore/ui/src/components/atoms/Icon/styles.scss";
|
|
6
|
+
@import "@faststore/ui/src/components/molecules/Toast/styles.scss";
|
|
7
|
+
}
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
+
import { gql } from '@faststore/graphql-utils'
|
|
1
2
|
import {
|
|
2
3
|
ProductCard as UIProductCard,
|
|
3
4
|
ProductCardContent as UIProductCardContent,
|
|
4
5
|
ProductCardImage as UIProductCardImage,
|
|
5
6
|
} from '@faststore/ui'
|
|
6
|
-
import { gql } from '@faststore/graphql-utils'
|
|
7
7
|
import { memo } from 'react'
|
|
8
8
|
|
|
9
|
+
import type { ProductSummary_ProductFragment } from '@generated/graphql'
|
|
10
|
+
import { ImageProps } from 'next/future/image'
|
|
11
|
+
import NextLink from 'next/link'
|
|
9
12
|
import { Image } from 'src/components/ui/Image'
|
|
10
13
|
import { useFormattedPrice } from 'src/sdk/product/useFormattedPrice'
|
|
11
14
|
import { useProductLink } from 'src/sdk/product/useProductLink'
|
|
12
|
-
import type { ProductSummary_ProductFragment } from '@generated/graphql'
|
|
13
|
-
import NextLink from 'next/link'
|
|
14
15
|
|
|
15
16
|
type Variant = 'wide' | 'default'
|
|
16
17
|
|
|
@@ -29,6 +30,10 @@ export interface ProductCardProps {
|
|
|
29
30
|
* Specifies the ProductCard image's aspect ratio.
|
|
30
31
|
*/
|
|
31
32
|
aspectRatio?: number
|
|
33
|
+
/**
|
|
34
|
+
* Specifies the ProductCard image's props.
|
|
35
|
+
*/
|
|
36
|
+
imgProps?: Partial<ImageProps>
|
|
32
37
|
/**
|
|
33
38
|
* Specifies Rating Value of the product.
|
|
34
39
|
*/
|
|
@@ -53,6 +58,7 @@ function ProductCard({
|
|
|
53
58
|
bordered = false,
|
|
54
59
|
variant = 'default',
|
|
55
60
|
aspectRatio = 1,
|
|
61
|
+
imgProps,
|
|
56
62
|
ratingValue,
|
|
57
63
|
buttonLabel = 'Add',
|
|
58
64
|
onButtonClick,
|
|
@@ -90,10 +96,10 @@ function ProductCard({
|
|
|
90
96
|
<Image
|
|
91
97
|
src={img.url}
|
|
92
98
|
alt={img.alternateName}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
loading=
|
|
99
|
+
sizes={`${imgProps?.sizes ?? '(max-width: 768px) 40vw, 30vw'}`}
|
|
100
|
+
width={imgProps?.width ?? 360}
|
|
101
|
+
height={Math.round((Number(imgProps?.height) || 360) / aspectRatio)}
|
|
102
|
+
loading={imgProps?.loading}
|
|
97
103
|
/>
|
|
98
104
|
</UIProductCardImage>
|
|
99
105
|
<UIProductCardContent
|
|
@@ -11,11 +11,11 @@ function RegionButton() {
|
|
|
11
11
|
<UIButton
|
|
12
12
|
variant="tertiary"
|
|
13
13
|
size="small"
|
|
14
|
-
icon={<Icon name="MapPin" width={
|
|
14
|
+
icon={<Icon name="MapPin" width={18} height={18} weight="bold" />}
|
|
15
15
|
iconPosition="left"
|
|
16
16
|
onClick={openModal}
|
|
17
17
|
>
|
|
18
|
-
|
|
18
|
+
{postalCode ?? 'Set your location'}
|
|
19
19
|
</UIButton>
|
|
20
20
|
)
|
|
21
21
|
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
.section {
|
|
5
5
|
@import "@faststore/ui/src/components/atoms/Button/styles.scss";
|
|
6
|
+
@import "@faststore/ui/src/components/molecules/LinkButton/styles.scss";
|
|
6
7
|
@import "@faststore/ui/src/components/molecules/Banner/styles.scss";
|
|
7
8
|
@import "@faststore/ui/src/components/organisms/BannerNewsletter/styles.scss";
|
|
8
9
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { ReactNode } from 'react'
|
|
2
|
+
import type { PropsWithChildren } from 'react'
|
|
3
|
+
|
|
4
|
+
import Section from '../Section'
|
|
5
|
+
import styles from './section.module.scss'
|
|
6
|
+
|
|
7
|
+
import { EmptyState as UIEmptyState } from '@faststore/ui'
|
|
8
|
+
|
|
9
|
+
export interface EmptyStateProps {
|
|
10
|
+
title: string
|
|
11
|
+
titleIcon?: ReactNode
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function EmptyState({
|
|
15
|
+
title,
|
|
16
|
+
titleIcon,
|
|
17
|
+
children,
|
|
18
|
+
}: PropsWithChildren<EmptyStateProps>) {
|
|
19
|
+
return (
|
|
20
|
+
<Section className={`${styles.section} section-empty-state`}>
|
|
21
|
+
<UIEmptyState title={title} titleIcon={titleIcon} bkgColor="light">
|
|
22
|
+
{children}
|
|
23
|
+
</UIEmptyState>
|
|
24
|
+
</Section>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default EmptyState
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/* stylelint-disable no-invalid-position-at-import-rule */
|
|
2
|
+
@import "@faststore/ui/src/styles/base/utilities.scss";
|
|
3
|
+
|
|
4
|
+
.section {
|
|
5
|
+
@import "@faststore/ui/src/components/atoms/Icon/styles.scss";
|
|
6
|
+
@import "@faststore/ui/src/components/atoms/Loader/styles.scss";
|
|
7
|
+
@import "@faststore/ui/src/components/organisms/EmptyState/styles.scss";
|
|
8
|
+
}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
@import "@faststore/ui/src/components/atoms/Badge/styles.scss";
|
|
11
11
|
@import "@faststore/ui/src/components/atoms/Button/styles.scss";
|
|
12
12
|
@import "@faststore/ui/src/components/atoms/Input/styles.scss";
|
|
13
|
+
@import "@faststore/ui/src/components/atoms/Price/styles.scss";
|
|
13
14
|
@import "@faststore/ui/src/components/molecules/Accordion/styles.scss";
|
|
14
15
|
@import "@faststore/ui/src/components/molecules/Breadcrumb/styles.scss";
|
|
15
16
|
@import "@faststore/ui/src/components/molecules/BuyButton/styles.scss";
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
@import "@faststore/ui/src/components/molecules/Accordion/styles.scss";
|
|
17
17
|
@import "@faststore/ui/src/components/molecules/DiscountBadge/styles.scss";
|
|
18
18
|
@import "@faststore/ui/src/components/molecules/InputField/styles.scss";
|
|
19
|
+
@import "@faststore/ui/src/components/molecules/LinkButton/styles.scss";
|
|
19
20
|
@import "@faststore/ui/src/components/molecules/ProductCard/styles.scss";
|
|
20
21
|
@import "@faststore/ui/src/components/molecules/ProductCardSkeleton/styles";
|
|
21
22
|
@import "@faststore/ui/src/components/molecules/SelectField/styles.scss";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect, useRef } from 'react'
|
|
1
|
+
import { useEffect, useId, useRef } from 'react'
|
|
2
2
|
import { useInView } from 'react-intersection-observer'
|
|
3
3
|
|
|
4
4
|
import { ProductShelf as UIProductShelf } from '@faststore/ui'
|
|
@@ -7,11 +7,12 @@ import type { ProductsQueryQueryVariables } from '@generated/graphql'
|
|
|
7
7
|
import ProductShelfSkeleton from 'src/components/skeletons/ProductShelfSkeleton'
|
|
8
8
|
import { useViewItemListEvent } from 'src/sdk/analytics/hooks/useViewItemListEvent'
|
|
9
9
|
import { useProductsQuery } from 'src/sdk/product/useProductsQuery'
|
|
10
|
+
import { textToKebabCase } from 'src/utils/utilities'
|
|
10
11
|
|
|
12
|
+
import Carousel from '../../ui/Carousel'
|
|
13
|
+
import Section from '../Section'
|
|
11
14
|
import { Components } from './Overrides'
|
|
12
15
|
const { ProductCard } = Components
|
|
13
|
-
import Section from '../Section'
|
|
14
|
-
import Carousel from '../../ui/Carousel'
|
|
15
16
|
|
|
16
17
|
import styles from './section.module.scss'
|
|
17
18
|
|
|
@@ -25,6 +26,8 @@ function ProductShelf({
|
|
|
25
26
|
withDivisor = false,
|
|
26
27
|
...variables
|
|
27
28
|
}: ProductShelfProps) {
|
|
29
|
+
const titleId = textToKebabCase(title)
|
|
30
|
+
const id = useId()
|
|
28
31
|
const viewedOnce = useRef(false)
|
|
29
32
|
const { ref, inView } = useInView()
|
|
30
33
|
const products = useProductsQuery(variables)
|
|
@@ -63,7 +66,7 @@ function ProductShelf({
|
|
|
63
66
|
loading={products === undefined}
|
|
64
67
|
>
|
|
65
68
|
<UIProductShelf>
|
|
66
|
-
<Carousel>
|
|
69
|
+
<Carousel id={titleId || id}>
|
|
67
70
|
{productEdges.map((product, idx) => (
|
|
68
71
|
<ProductCard
|
|
69
72
|
bordered
|
|
@@ -71,6 +74,11 @@ function ProductShelf({
|
|
|
71
74
|
product={product.node}
|
|
72
75
|
index={idx + 1}
|
|
73
76
|
aspectRatio={aspectRatio}
|
|
77
|
+
imgProps={{
|
|
78
|
+
width: 216,
|
|
79
|
+
height: 216,
|
|
80
|
+
sizes: '(max-width: 768px) 42vw, 30vw',
|
|
81
|
+
}}
|
|
74
82
|
/>
|
|
75
83
|
))}
|
|
76
84
|
</Carousel>
|
|
@@ -33,6 +33,26 @@ const getRatio = (products: number, idx: number) => {
|
|
|
33
33
|
return 3 / 4
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
const getSizes = (products: number, idx: number) => {
|
|
37
|
+
const expandsFirstTile =
|
|
38
|
+
products === NUMBER_ITEMS_TO_EXPAND_FIRST && idx === 0
|
|
39
|
+
|
|
40
|
+
const expandsFirstTwoTile =
|
|
41
|
+
products === NUMBER_ITEMS_TO_EXPAND_FIRST_TWO && (idx === 0 || idx === 1)
|
|
42
|
+
|
|
43
|
+
if (expandsFirstTile || expandsFirstTwoTile) {
|
|
44
|
+
return {
|
|
45
|
+
width: 594,
|
|
46
|
+
height: 364,
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
width: 284,
|
|
52
|
+
height: 364,
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
36
56
|
const ProductTiles = ({ title, ...variables }: ProductTilesProps) => {
|
|
37
57
|
const viewedOnce = useRef(false)
|
|
38
58
|
const { ref, inView } = useInView()
|
|
@@ -75,6 +95,7 @@ const ProductTiles = ({ title, ...variables }: ProductTilesProps) => {
|
|
|
75
95
|
index={idx + 1}
|
|
76
96
|
variant="wide"
|
|
77
97
|
aspectRatio={getRatio(productEdges.length, idx)}
|
|
98
|
+
imgProps={getSizes(productEdges.length, idx)}
|
|
78
99
|
/>
|
|
79
100
|
</Tile>
|
|
80
101
|
))}
|
|
@@ -1,29 +1,18 @@
|
|
|
1
1
|
import { memo } from 'react'
|
|
2
2
|
|
|
3
3
|
import NextImage, { ImageProps } from 'next/future/image'
|
|
4
|
-
import
|
|
5
|
-
import { useImage } from './useImage'
|
|
4
|
+
import loader from './loader'
|
|
6
5
|
|
|
7
|
-
// Next loader function does not handle all props as height and options
|
|
8
|
-
// so we use
|
|
6
|
+
// Next loader function does not handle all props as height and options
|
|
7
|
+
// so we use a custom loader to handle images using thumbor server with VTEX CDN
|
|
9
8
|
// https://nextjs.org/docs/api-reference/next/image#loader
|
|
10
|
-
function Image({
|
|
11
|
-
const { src: thumborSrc, alt } = useImage({
|
|
12
|
-
src: String(src),
|
|
13
|
-
width: Number(width),
|
|
14
|
-
height: Number(height),
|
|
15
|
-
options: quality ? ({ filters: { quality } } as ThumborOptions) : undefined,
|
|
16
|
-
...otherProps,
|
|
17
|
-
})
|
|
18
|
-
|
|
9
|
+
function Image({ loading = 'lazy', ...otherProps }: ImageProps) {
|
|
19
10
|
return (
|
|
20
11
|
<NextImage
|
|
21
12
|
data-fs-image
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
height={height}
|
|
26
|
-
alt={alt}
|
|
13
|
+
loader={loader}
|
|
14
|
+
loading={loading}
|
|
15
|
+
priority={loading === 'eager'}
|
|
27
16
|
{...otherProps}
|
|
28
17
|
/>
|
|
29
18
|
)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import storeConfig from 'faststore.config'
|
|
2
|
+
const THUMBOR_SERVER = `https://${storeConfig.api.storeId}.vtexassets.com`
|
|
3
|
+
|
|
4
|
+
export default function customImageLoader({ src, width, quality }) {
|
|
5
|
+
const preSizeComponents = [THUMBOR_SERVER, 'unsafe']
|
|
6
|
+
|
|
7
|
+
// proportional to the width, enter a height of 0,
|
|
8
|
+
const height = 0
|
|
9
|
+
const finalSize = `${width}x${height}`
|
|
10
|
+
|
|
11
|
+
const postSizeComponents: string[] = ['center', 'middle']
|
|
12
|
+
quality && postSizeComponents.push(`filters:quality(${quality || 80})`)
|
|
13
|
+
postSizeComponents.push(encodeURIComponent(src))
|
|
14
|
+
|
|
15
|
+
return [...preSizeComponents, finalSize, ...postSizeComponents].join('/')
|
|
16
|
+
}
|
|
@@ -1,21 +1,15 @@
|
|
|
1
|
-
import { useEffect, useState } from 'react'
|
|
2
1
|
import {
|
|
3
|
-
ImageGallery as UIImageGallery,
|
|
4
2
|
ImageElementData,
|
|
5
3
|
ImageZoom,
|
|
4
|
+
ImageGallery as UIImageGallery,
|
|
6
5
|
} from '@faststore/ui'
|
|
6
|
+
import { useEffect, useState } from 'react'
|
|
7
7
|
|
|
8
|
-
import { Image } from 'src/components/ui/Image'
|
|
9
8
|
import { useRouter } from 'next/router'
|
|
9
|
+
import { Image } from 'src/components/ui/Image'
|
|
10
10
|
|
|
11
11
|
const ImageComponent = ({ url, alternateName }) => (
|
|
12
|
-
<Image
|
|
13
|
-
src={url}
|
|
14
|
-
alt={alternateName}
|
|
15
|
-
sizes="(max-width: 72px) 25vw, 30vw"
|
|
16
|
-
width={72}
|
|
17
|
-
height={72}
|
|
18
|
-
/>
|
|
12
|
+
<Image src={url} alt={alternateName} width={68} height={68} />
|
|
19
13
|
)
|
|
20
14
|
|
|
21
15
|
export interface ImageGalleryProps {
|
|
@@ -41,11 +35,10 @@ const ImageGallery = ({ images, ...otherProps }: ImageGalleryProps) => {
|
|
|
41
35
|
<Image
|
|
42
36
|
src={currentImage.url}
|
|
43
37
|
alt={currentImage.alternateName}
|
|
44
|
-
sizes="(max-width:
|
|
45
|
-
width={
|
|
46
|
-
height={
|
|
38
|
+
sizes="(max-width: 768px) 25vw, 30vw"
|
|
39
|
+
width={691}
|
|
40
|
+
height={691 * (3 / 4)}
|
|
47
41
|
loading="eager"
|
|
48
|
-
priority
|
|
49
42
|
/>
|
|
50
43
|
</ImageZoom>
|
|
51
44
|
</UIImageGallery>
|
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
import { HTMLAttributes
|
|
1
|
+
import { HTMLAttributes } from 'react'
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
SkuSelector as UISkuSelector,
|
|
6
|
-
SkuSelectorProps,
|
|
7
|
-
SkuOption,
|
|
8
|
-
} from '@faststore/ui'
|
|
3
|
+
import { SkuSelectorProps, SkuSelector as UISkuSelector } from '@faststore/ui'
|
|
9
4
|
import NextLink from 'next/link'
|
|
5
|
+
import { Image } from '../Image'
|
|
10
6
|
|
|
11
7
|
export type SkuVariantsByName = Record<
|
|
12
8
|
string,
|
|
@@ -32,16 +28,7 @@ const ImageComponent: SkuSelectorProps['ImageComponent'] = ({
|
|
|
32
28
|
src,
|
|
33
29
|
alt,
|
|
34
30
|
...otherProps
|
|
35
|
-
}) =>
|
|
36
|
-
<Image
|
|
37
|
-
src={src}
|
|
38
|
-
alt={alt}
|
|
39
|
-
width={20}
|
|
40
|
-
height={20}
|
|
41
|
-
loading="lazy"
|
|
42
|
-
{...otherProps}
|
|
43
|
-
/>
|
|
44
|
-
)
|
|
31
|
+
}) => <Image src={src} alt={alt} width={34} height={34} {...otherProps} />
|
|
45
32
|
|
|
46
33
|
function Selectors({
|
|
47
34
|
slugsMap,
|
package/src/pages/404.tsx
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { NextSeo } from 'next-seo'
|
|
2
2
|
import { useRouter } from 'next/router'
|
|
3
|
-
import { EmptyState as UIEmptyState, Icon as UIIcon } from '@faststore/ui'
|
|
4
3
|
import GlobalSections, {
|
|
5
4
|
GlobalSectionsData,
|
|
6
5
|
getGlobalSectionsData,
|
|
@@ -8,6 +7,9 @@ import GlobalSections, {
|
|
|
8
7
|
import { GetStaticProps } from 'next'
|
|
9
8
|
import { Locator } from '@vtex/client-cms'
|
|
10
9
|
|
|
10
|
+
import { Icon as UIIcon } from '@faststore/ui'
|
|
11
|
+
import EmptyState from 'src/components/sections/EmptyState'
|
|
12
|
+
|
|
11
13
|
const useErrorState = () => {
|
|
12
14
|
const router = useRouter()
|
|
13
15
|
const { from } = router.query
|
|
@@ -29,7 +31,7 @@ function Page({ globalSections }: Props) {
|
|
|
29
31
|
<GlobalSections {...globalSections}>
|
|
30
32
|
<NextSeo noindex nofollow />
|
|
31
33
|
|
|
32
|
-
<
|
|
34
|
+
<EmptyState
|
|
33
35
|
title="Not Found: 404"
|
|
34
36
|
titleIcon={
|
|
35
37
|
<UIIcon
|
|
@@ -39,10 +41,9 @@ function Page({ globalSections }: Props) {
|
|
|
39
41
|
weight="thin"
|
|
40
42
|
/>
|
|
41
43
|
}
|
|
42
|
-
bkgColor="light"
|
|
43
44
|
>
|
|
44
45
|
<p>This app could not find url {fromUrl}</p>
|
|
45
|
-
</
|
|
46
|
+
</EmptyState>
|
|
46
47
|
</GlobalSections>
|
|
47
48
|
)
|
|
48
49
|
}
|
package/src/pages/500.tsx
CHANGED
|
@@ -7,6 +7,9 @@ import GlobalSections, {
|
|
|
7
7
|
getGlobalSectionsData,
|
|
8
8
|
} from 'src/components/cms/GlobalSections'
|
|
9
9
|
|
|
10
|
+
import { Icon as UIIcon } from '@faststore/ui'
|
|
11
|
+
import EmptyState from 'src/components/sections/EmptyState'
|
|
12
|
+
|
|
10
13
|
type Props = {
|
|
11
14
|
globalSections: GlobalSectionsData
|
|
12
15
|
}
|
|
@@ -28,12 +31,23 @@ function Page({ globalSections }: Props) {
|
|
|
28
31
|
<GlobalSections {...globalSections}>
|
|
29
32
|
<NextSeo noindex nofollow />
|
|
30
33
|
|
|
31
|
-
<
|
|
32
|
-
|
|
34
|
+
<EmptyState
|
|
35
|
+
title="500"
|
|
36
|
+
titleIcon={
|
|
37
|
+
<UIIcon
|
|
38
|
+
name="CircleWavyWarning"
|
|
39
|
+
width={56}
|
|
40
|
+
height={56}
|
|
41
|
+
weight="thin"
|
|
42
|
+
/>
|
|
43
|
+
}
|
|
44
|
+
>
|
|
45
|
+
<h2>Internal Server Error</h2>
|
|
33
46
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
47
|
+
<div>
|
|
48
|
+
The server errored with id {errorId} when visiting page {fromUrl}
|
|
49
|
+
</div>
|
|
50
|
+
</EmptyState>
|
|
37
51
|
</GlobalSections>
|
|
38
52
|
)
|
|
39
53
|
}
|
package/src/pages/[...slug].tsx
CHANGED
|
@@ -11,7 +11,6 @@ import { BreadcrumbJsonLd, NextSeo } from 'next-seo'
|
|
|
11
11
|
import { useRouter } from 'next/router'
|
|
12
12
|
import { useMemo } from 'react'
|
|
13
13
|
|
|
14
|
-
import { Icon } from '@faststore/ui'
|
|
15
14
|
import type {
|
|
16
15
|
ServerCollectionPageQueryQuery,
|
|
17
16
|
ServerCollectionPageQueryQueryVariables,
|
package/src/pages/login.tsx
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { useEffect } from 'react'
|
|
2
2
|
import { NextSeo } from 'next-seo'
|
|
3
|
-
import { EmptyState as UIEmptyState, Loader as UILoader } from '@faststore/ui'
|
|
4
3
|
|
|
5
4
|
import storeConfig from '../../faststore.config'
|
|
6
5
|
import GlobalSections, {
|
|
@@ -10,6 +9,9 @@ import GlobalSections, {
|
|
|
10
9
|
import { GetStaticProps } from 'next'
|
|
11
10
|
import { Locator } from '@vtex/client-cms'
|
|
12
11
|
|
|
12
|
+
import { Loader as UILoader } from '@faststore/ui'
|
|
13
|
+
import EmptyState from 'src/components/sections/EmptyState'
|
|
14
|
+
|
|
13
15
|
type Props = {
|
|
14
16
|
globalSections: GlobalSectionsData
|
|
15
17
|
}
|
|
@@ -23,9 +25,9 @@ function Page({ globalSections }: Props) {
|
|
|
23
25
|
<GlobalSections {...globalSections}>
|
|
24
26
|
<NextSeo noindex nofollow />
|
|
25
27
|
|
|
26
|
-
<
|
|
28
|
+
<EmptyState title="Loading">
|
|
27
29
|
<UILoader />
|
|
28
|
-
</
|
|
30
|
+
</EmptyState>
|
|
29
31
|
</GlobalSections>
|
|
30
32
|
)
|
|
31
33
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//Input "Example Text!". Output: example-text
|
|
2
|
+
export function textToKebabCase(text: string): string {
|
|
3
|
+
// Replace spaces and special characters with hyphens
|
|
4
|
+
let kebabCase = text.replace(/[^\w\s]/gi, '-')
|
|
5
|
+
|
|
6
|
+
// Remove whitespace
|
|
7
|
+
kebabCase = kebabCase.replace(/\s+/g, '-')
|
|
8
|
+
|
|
9
|
+
// Convert to lowercase
|
|
10
|
+
kebabCase = kebabCase.toLowerCase()
|
|
11
|
+
|
|
12
|
+
return kebabCase ?? ''
|
|
13
|
+
}
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import storeConfig from 'faststore.config'
|
|
2
|
-
|
|
3
|
-
export type FilterValue = boolean | string | string[] | number[]
|
|
4
|
-
|
|
5
|
-
export interface Box {
|
|
6
|
-
top: number
|
|
7
|
-
bottom: number
|
|
8
|
-
left: number
|
|
9
|
-
right: number
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface ThumborOptions {
|
|
13
|
-
flipHorizontal?: boolean
|
|
14
|
-
flipVertical?: boolean
|
|
15
|
-
trim?: boolean
|
|
16
|
-
fitIn?: boolean
|
|
17
|
-
horizontalAlign?: 'left' | 'center' | 'right'
|
|
18
|
-
verticalAlign?: 'top' | 'middle' | 'bottom'
|
|
19
|
-
smart?: boolean
|
|
20
|
-
filters?: Record<string, FilterValue>
|
|
21
|
-
manualCrop?: Box | false
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const THUMBOR_SERVER = `https://${storeConfig.api.storeId}.vtexassets.com`
|
|
25
|
-
|
|
26
|
-
const cropSection = ({ left, top, right, bottom }: Box) =>
|
|
27
|
-
`${left}x${top}:${right}x${bottom}`
|
|
28
|
-
|
|
29
|
-
function filtersURIComponent(filters: Record<string, FilterValue>) {
|
|
30
|
-
const elements = ['filters']
|
|
31
|
-
|
|
32
|
-
Object.keys(filters).forEach((name) => {
|
|
33
|
-
const parameters = filters[name]
|
|
34
|
-
let stringParameters
|
|
35
|
-
|
|
36
|
-
// If we have several parameters, they were passed as an array
|
|
37
|
-
// and now they need to be comma separated, otherwise there is just one to convert to a string
|
|
38
|
-
if (Array.isArray(parameters)) {
|
|
39
|
-
stringParameters = parameters.join(',')
|
|
40
|
-
}
|
|
41
|
-
// If true, we don't even need to do anything, we just have an empty string and insert ()
|
|
42
|
-
// Ex: {grayscale: true} => grayscale()
|
|
43
|
-
else if (parameters === true) {
|
|
44
|
-
stringParameters = ''
|
|
45
|
-
} else {
|
|
46
|
-
stringParameters = String(parameters)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
elements.push(`${name}(${stringParameters})`)
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
return elements.join(':')
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export const urlBuilder = (baseUrl: string, options: ThumborOptions) => {
|
|
56
|
-
const preSizeComponents = [THUMBOR_SERVER, 'unsafe']
|
|
57
|
-
const postSizeComponents: string[] = []
|
|
58
|
-
|
|
59
|
-
// Add the trim parameter after unsafe if appliable
|
|
60
|
-
options.trim && preSizeComponents.push('trim')
|
|
61
|
-
|
|
62
|
-
// Add the crop parameter if any
|
|
63
|
-
options.manualCrop && preSizeComponents.push(cropSection(options.manualCrop))
|
|
64
|
-
|
|
65
|
-
// Add the fit-in parameter after crop if appliable
|
|
66
|
-
options.fitIn && preSizeComponents.push('fit-in')
|
|
67
|
-
|
|
68
|
-
// Adds the horizontal alignement after the size
|
|
69
|
-
postSizeComponents.push(options.horizontalAlign ?? 'center')
|
|
70
|
-
|
|
71
|
-
// Adds the vertical alignement after the size
|
|
72
|
-
postSizeComponents.push(options.verticalAlign ?? 'middle')
|
|
73
|
-
|
|
74
|
-
// Adds the smart parameter if appliable
|
|
75
|
-
options.smart && postSizeComponents.push('smart')
|
|
76
|
-
|
|
77
|
-
// Compile the filters and add them right before the URI
|
|
78
|
-
const { filters } = options
|
|
79
|
-
|
|
80
|
-
filters && postSizeComponents.push(filtersURIComponent(filters))
|
|
81
|
-
|
|
82
|
-
// Finally, adds the real image uri
|
|
83
|
-
postSizeComponents.push(encodeURIComponent(baseUrl))
|
|
84
|
-
|
|
85
|
-
return (width: number, height: number) => {
|
|
86
|
-
// Adds the final size parameter
|
|
87
|
-
let finalSize = ''
|
|
88
|
-
|
|
89
|
-
if (options.flipHorizontal) {
|
|
90
|
-
finalSize += '-'
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
finalSize += `${width}x`
|
|
94
|
-
|
|
95
|
-
if (options.flipVertical) {
|
|
96
|
-
finalSize += '-'
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
finalSize += `${height}`
|
|
100
|
-
|
|
101
|
-
return [...preSizeComponents, finalSize, ...postSizeComponents].join('/')
|
|
102
|
-
}
|
|
103
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { useMemo } from 'react'
|
|
2
|
-
import type { ImgHTMLAttributes } from 'react'
|
|
3
|
-
|
|
4
|
-
import { urlBuilder } from './thumborUrlBuilder'
|
|
5
|
-
import type { ThumborOptions } from './thumborUrlBuilder'
|
|
6
|
-
|
|
7
|
-
export interface ImageOptions extends ImgHTMLAttributes<HTMLImageElement> {
|
|
8
|
-
src: string
|
|
9
|
-
width: number
|
|
10
|
-
height: number
|
|
11
|
-
options?: ThumborOptions
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const FACTORS = [1, 2, 3]
|
|
15
|
-
const LARGE_FACTOR = FACTORS[FACTORS.length - 1]
|
|
16
|
-
|
|
17
|
-
export const useImage = ({
|
|
18
|
-
src: baseUrl,
|
|
19
|
-
width,
|
|
20
|
-
height,
|
|
21
|
-
options = {},
|
|
22
|
-
...rest
|
|
23
|
-
}: ImageOptions): ImgHTMLAttributes<HTMLImageElement> => {
|
|
24
|
-
const { srcSet, src } = useMemo(() => {
|
|
25
|
-
const builder = urlBuilder(baseUrl, options)
|
|
26
|
-
|
|
27
|
-
const srcs = FACTORS.map((factor) => {
|
|
28
|
-
const rescaledWidth = width * factor
|
|
29
|
-
|
|
30
|
-
return `${builder(rescaledWidth, height * factor)} ${rescaledWidth}w`
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
return {
|
|
34
|
-
src: builder(width * LARGE_FACTOR, height * LARGE_FACTOR),
|
|
35
|
-
srcSet: srcs.join(', '),
|
|
36
|
-
}
|
|
37
|
-
}, [height, options, baseUrl, width])
|
|
38
|
-
|
|
39
|
-
return {
|
|
40
|
-
src,
|
|
41
|
-
srcSet,
|
|
42
|
-
width: `${width}px`,
|
|
43
|
-
height: `${height}px`,
|
|
44
|
-
...rest,
|
|
45
|
-
}
|
|
46
|
-
}
|