├── .env.all ├── .env.example ├── .firebaserc ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── builder ├── footer │ ├── footer.json │ └── schema.model.json ├── header │ ├── header.json │ └── schema.model.json ├── page │ ├── home.json │ └── schema.model.json ├── product-page │ ├── product-page-template.json │ └── schema.model.json └── settings.json ├── firebase.json ├── gatsby-config.js ├── gatsby-node.js ├── package-lock.json ├── package.json ├── src ├── components │ ├── atoms │ │ ├── link.tsx │ │ ├── no-ssr.tsx │ │ ├── option-picker.tsx │ │ ├── product-card.tsx │ │ ├── product-grid.tsx │ │ ├── seo.tsx │ │ ├── thumbnail.tsx │ │ └── under-the-fold.tsx │ ├── molecules │ │ ├── aware-builder-component.tsx │ │ ├── footer │ │ │ ├── footer.builder.ts │ │ │ └── footer.tsx │ │ ├── header │ │ │ ├── header.builder.ts │ │ │ └── header.tsx │ │ └── layout.tsx │ ├── organisms │ │ ├── all-products │ │ │ ├── all-products.builder.ts │ │ │ └── all-products.tsx │ │ ├── dev-404.tsx │ │ ├── latest-products │ │ │ ├── latest-products-no-ssr.builder.tsx │ │ │ ├── latest-products.builder.ts │ │ │ └── latest-products.tsx │ │ └── product-page-details │ │ │ ├── product-page-details.builder.ts │ │ │ └── product-page-details.tsx │ └── providers │ │ ├── all-products-provider.builder.tsx │ │ ├── all-products-provider.tsx │ │ └── state-provider.tsx ├── gatsby-plugin-theme-ui │ └── index.ts ├── hooks │ ├── use-all-products.ts │ ├── use-all-site-pages.ts │ ├── use-builder-footer.ts │ ├── use-builder-header.ts │ ├── use-recent-static-products.ts │ └── use-site-metadata.ts ├── html.tsx ├── pages │ ├── 404.tsx │ └── cart.tsx ├── templates │ ├── page.tsx │ └── product-page.tsx ├── typings.d.ts └── utils │ └── product.ts ├── tsconfig.json └── yarn.lock /.env.all: -------------------------------------------------------------------------------- 1 | GATSBY_SHOP_NAME=builder-io-demo 2 | GATSBY_SHOPIFY_ACCESS_TOKEN=dd0057d1e48d2d61ca8ec27b07d3c5e6 3 | GATSBY_STALL_RETRY_LIMIT=6 4 | GATSBY_STALL_TIMEOUT=60000 5 | GATSBY_CONNECTION_RETRY_LIMIT=10 6 | GATSBY_CONNECTION_TIMEOUT=60000 7 | BUILDER_API_KEY=6d39f4449e2b4e6792a793bb8c1d9615 8 | GATSBY_BUILDER_API_KEY=6d39f4449e2b4e6792a793bb8c1d9615 9 | ENABLE_GATSBY_REFRESH_ENDPOINT=1 10 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | GATSBY_SHOP_NAME=builder-io-demo 2 | GATSBY_SHOPIFY_ACCESS_TOKEN=dd0057d1e48d2d61ca8ec27b07d3c5e6 3 | GATSBY_STALL_RETRY_LIMIT=6 4 | GATSBY_STALL_TIMEOUT=60000 5 | GATSBY_CONNECTION_RETRY_LIMIT=10 6 | GATSBY_CONNECTION_TIMEOUT=60000 7 | BUILDER_API_KEY=bc2b10075b4046a7810143f7f3238f51 8 | GATSBY_BUILDER_API_KEY=bc2b10075b4046a7810143f7f3238f51 9 | ENABLE_GATSBY_REFRESH_ENDPOINT=1 10 | -------------------------------------------------------------------------------- /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "builder-shopify-starter" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional eslint cache 37 | .eslintcache 38 | 39 | # Optional REPL history 40 | .node_repl_history 41 | 42 | # Output of 'npm pack' 43 | *.tgz 44 | 45 | # dotenv environment variable files 46 | 47 | # gatsby files 48 | .cache 49 | public 50 | 51 | # Mac files 52 | .DS_Store 53 | 54 | # Yarn 55 | yarn-error.log 56 | .pnp 57 | .pnp.js 58 | # Yarn Integrity file 59 | .yarn-integrity 60 | 61 | .now 62 | 63 | # The TS auto-complete stuff 64 | apollo.config.js 65 | 66 | # GraphQl typegen 67 | __generated__ 68 | 69 | # firebase cache 70 | .firebase/ -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | coverage 2 | node_modules 3 | dist 4 | .next 5 | .cache-loader 6 | .cache 7 | docs 8 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "trailingComma": "es5", 4 | "singleQuote": true, 5 | "arrowParens": "avoid" 6 | } 7 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "./node_modules/typescript/lib", 3 | 4 | "files.exclude": { 5 | "**/.git": true, 6 | "**/.DS_Store": true, 7 | "**/.cache-loader": true, 8 | "**/dist": true, 9 | "**/.next": true, 10 | "**/cache": true 11 | }, 12 | "javascript.preferences.importModuleSpecifier": "relative", 13 | "typescript.preferences.importModuleSpecifier": "relative", 14 | "editor.formatOnSave": true 15 | } 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Builder.io 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Builder.io + Shopify + Gatsby Starter 2 | 3 | :heavy_check_mark: All the power of going headless, speed of Gatsby and, the customizability of [Builder.io](https://builder.io) 4 | 5 | :heavy_check_mark: Typescript + Theme-UI 6 | 7 | :heavy_check_mark: [gatsby-theme-shopify-manager](https://github.com/thetrevorharmon/gatsby-theme-shopify-manager) The easiest way to build a shopify store on gatsby. 8 | 9 | :heavy_check_mark: Analytics, A/B testing, product augmentation, and heatmaps out of the box. 10 |
11 |
12 | Editor example 13 | 14 | ## Get Started 15 | 16 | ### Install the Builder.io cli 17 | 18 | ``` 19 | npm install @builder.io/cli -g 20 | ``` 21 | 22 | ### Clone this repo 23 | 24 | using git 25 | 26 | ``` 27 | git clone https://github.com/BuilderIO/gatsby-builder-shopify 28 | ``` 29 | 30 | ### Generate your Builder.io space 31 | 32 | 33 | 34 | [Signup for Builder.io](builder.io/signup), then go to your [organization settings page](https://builder.io/account/organization?root=true), create a private key and copy it, then create your space and give it a name 35 | 36 | ``` 37 | cd gatsby-builder-shopify 38 | builder create -k [private-key] -n [space-name] -d 39 | ``` 40 | 41 | This command when done it'll print your new space's public api key, copy it and add as the value for `GATSBY_BUILDER_PUBLIC_KEY` into the .env files (`.env.production` and `.env.development`) 42 | 43 | ``` 44 | GATBY_BUILDER_PUBLIC_KEY=... 45 | ``` 46 | 47 | ### Connect Shopify 48 | 49 | Now you have a space clone matching the spec defined in this repo, you'll need to connect it to your shopify store. 50 | 51 | Create a [private app](https://help.shopify.com/en/manual/apps/private-apps) in your Shpoify store and generate both admin api keys and storefront API token. 52 | 53 | Access your newly created space, by selecting it from the [list of spaces](https://builder.io/spaces) in your organization, then from space settings, configure the `@builder.io/plugin-shopify` with the required details: admin api key / password, store domain, please feel free to ignore the `import your products/collections` step since it's not needed for this starter. 54 | 55 | Add your storefront api token to the .env files (`.env.all`) 56 | 57 | ``` 58 | GATSBY_SHOPIFY_ACCESS_TOKEN=... 59 | GATSBY_SHOP_NAME=... 60 | ``` 61 | 62 | ### Install dependencies 63 | 64 | ``` 65 | yarn 66 | ``` 67 | 68 | ### Run the dev server 69 | 70 | ``` 71 | yarn develop 72 | ``` 73 | 74 | It'll start a dev server at `http://localhost:8000` 75 | 76 | ### Deploy 77 | 78 | [![Deploy to Netlify](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/BuilderIO/gatsby-builder-shopify) 79 | For continuous deployment from netlify <> Builder.io : 80 | - Create a [build hook](https://docs.netlify.com/configure-builds/build-hooks/) in netlify 81 | - Add the build hook from last step to Builder.io global webhooks in your new [space settings](https://builder.io/account/space). 82 | 83 | 84 | ## 🧐 What's inside? 85 | 86 | This starter demonstrates: 87 | 88 | - creating product pages in builder.io for easier a/b testing and cusotm targeting. 89 | - shows how to pass context with the editor for binding components to dynamic state object for easier templating, for things like product pages, collection pages. 90 | - shows how can you customize and augment your data, for example a specific product in shopify you want to override it's description for an a/b test, that's as simple as setting a default binding, and allowing users to break the binding for a specific product handle. 91 | 92 | See: 93 | 94 | - [src/components/molecules/aware-builder-component.tsx](src/components/molecules/aware-builder-component.tsx) 95 | - [src/templates/product-page.tsx](src/templates/product-page.tsx) for using GraphQL to query and render Builder.io components 96 | - [@builder.io/gatsby](https://github.com/builderio/builder/tree/master/packages/gatsby) the plugin used in this starter to generate pages dynamically. 97 | 98 | ### Using your custom components in the editor 99 | 100 | > 👉**Tip: want to limit page building to only your components? Try [components only mode](https://builder.io/c/docs/guides/components-only-mode)** 101 | 102 | Register a component 103 | 104 | ```tsx 105 | import { Builder } from '@builder.io/react'; 106 | 107 | class SimpleText extends React.Component { 108 | render() { 109 | return

{this.props.text}

; 110 | } 111 | } 112 | 113 | Builder.registerComponent(SimpleText, { 114 | name: 'Simple Text', 115 | inputs: [{ name: 'text', type: 'string' }], 116 | }); 117 | ``` 118 | 119 | Then import it in the template entry point 120 | 121 | ```tsx 122 | import './components/simple-text'; 123 | // ... 124 | ``` 125 | 126 | See: 127 | 128 | - [design systems example](https://github.com/BuilderIO/builder/tree/master/examples/react-design-system) for lots of examples using your deisgn system + custom components 129 | 130 | ### Mixed Content errors when hosting on insecure http 131 | 132 | Our editor uses the preview URL you supply for live editing. Because the editor is on `https`, the preview might not work correctly if your development setup uses http. To fix this, change your development set up to serve using https. Or, as a workaround, on Chrome you can allow insecure content on localhost, by toggling the `insecure content` option here [chrome://settings/content/siteDetails?site=http%3A%2F%2Flocalhost%3A9009](chrome://settings/content/siteDetails?site=http%3A%2F%2Flocalhost%3A8000) 133 | 134 | ## Prerequisites 135 | 136 | - Node 137 | - [Gatsby CLI](https://www.gatsbyjs.org/docs/) 138 | 139 | ## Available scripts 140 | 141 | ### `build` 142 | 143 | Build the static files into the `public` folder 144 | 145 | #### Usage 146 | 147 | ```sh 148 | $ yarn build 149 | ``` 150 | 151 | ### `develop` or `start` 152 | 153 | Runs the `clean` script and starts the gatsby develop server using the command `gatsby develop`. 154 | 155 | #### Usage 156 | 157 | ```sh 158 | yarn develop 159 | ``` 160 | 161 | ### `format` 162 | 163 | Formats code and docs according to our style guidelines using `prettier` 164 | 165 | ## CONTRIBUTING 166 | 167 | Contributions are always welcome, no matter how large or small. 168 | 169 | ## Learn more 170 | 171 | - [Official docs](https://www.builder.io/c/docs/getting-started) 172 | -------------------------------------------------------------------------------- /builder/footer/footer.json: -------------------------------------------------------------------------------- 1 | { 2 | "@originContentId": "882089ce8e164dcb6034d183d631785a6c1f99ec93b9b54acd379fa1278cd66b", 3 | "@originModelId": "ec0f6f43702ca2ce531fac106bc15bf0701a97e691c6681171cf33cce9158623", 4 | "@originOrg": "6d39f4449e2b4e6792a793bb8c1d9615", 5 | "createdBy": "pldtOvKDa6fvdX4zCxWYmRh99ao1", 6 | "createdDate": 1603206518072, 7 | "data": { 8 | "cssCode": "/*\n* Custom CSS styles\n*\n* Global by default, but use `&` to scope to just this content, e.g.\n* \n* & .foo {\n* color: 'red'\n* }\n*/\n\np a {\n text-decoration: none;\n color: inherit;\n}", 9 | "customFonts": [ 10 | { 11 | "family": "Roboto", 12 | "isUserFont": true 13 | } 14 | ], 15 | "inputs": [], 16 | "state": { 17 | "deviceSize": "large", 18 | "location": { 19 | "path": "", 20 | "query": {} 21 | } 22 | }, 23 | "blocks": [ 24 | { 25 | "@type": "@builder.io/sdk:Element", 26 | "@version": 2, 27 | "id": "builder-2ffa01d4bebf46819ebf3814ef965145", 28 | "component": { 29 | "name": "Core:Section", 30 | "options": { 31 | "maxWidth": 1200 32 | } 33 | }, 34 | "children": [ 35 | { 36 | "@type": "@builder.io/sdk:Element", 37 | "@version": 2, 38 | "id": "builder-1c96c5bb44eb48c78383a24701b0169e", 39 | "children": [ 40 | { 41 | "@type": "@builder.io/sdk:Element", 42 | "@version": 2, 43 | "id": "builder-6aac5a546b874ac6b4aff9c8e7352c1b", 44 | "component": { 45 | "name": "Custom Code", 46 | "options": { 47 | "code": "\n \n\n" 48 | } 49 | }, 50 | "responsiveStyles": { 51 | "large": { 52 | "display": "flex", 53 | "flexDirection": "column", 54 | "alignItems": "stretch", 55 | "position": "relative", 56 | "flexShrink": "0", 57 | "boxSizing": "border-box" 58 | } 59 | } 60 | }, 61 | { 62 | "@type": "@builder.io/sdk:Element", 63 | "@version": 2, 64 | "id": "builder-4e521b5612634679bd601a1a21a57489", 65 | "component": { 66 | "name": "Text", 67 | "options": { 68 | "text": "

BUILDER

" 69 | } 70 | }, 71 | "responsiveStyles": { 72 | "large": { 73 | "display": "flex", 74 | "flexDirection": "column", 75 | "alignItems": "stretch", 76 | "position": "relative", 77 | "flexShrink": "0", 78 | "boxSizing": "border-box", 79 | "marginLeft": "20px", 80 | "lineHeight": "normal", 81 | "height": "auto", 82 | "textAlign": "center", 83 | "color": "rgba(255, 255, 255, 1)", 84 | "fontFamily": "Roboto, sans-serif", 85 | "fontSize": "29px", 86 | "letterSpacing": "4.3545px", 87 | "fontWeight": "300" 88 | } 89 | } 90 | } 91 | ], 92 | "responsiveStyles": { 93 | "large": { 94 | "display": "flex", 95 | "flexDirection": "row", 96 | "position": "relative", 97 | "flexShrink": "0", 98 | "boxSizing": "border-box", 99 | "justifyContent": "center" 100 | } 101 | } 102 | }, 103 | { 104 | "@type": "@builder.io/sdk:Element", 105 | "@version": 2, 106 | "id": "builder-a46117e9869e4a0f8db24e8bc19abb6e", 107 | "component": { 108 | "name": "Columns", 109 | "options": { 110 | "columns": [ 111 | { 112 | "blocks": [ 113 | { 114 | "@type": "@builder.io/sdk:Element", 115 | "@version": 2, 116 | "id": "builder-8c7c7eb25fb1473e9aa1f38b52de2336", 117 | "component": { 118 | "name": "Text", 119 | "options": { 120 | "text": "

Product

\n

\n Visual CMS\n

\n

\n Theme Studio for Shopify\n

\n" 121 | } 122 | }, 123 | "responsiveStyles": { 124 | "large": { 125 | "display": "flex", 126 | "flexDirection": "column", 127 | "alignItems": "stretch", 128 | "flexShrink": "0", 129 | "position": "relative", 130 | "marginTop": "10px", 131 | "textAlign": "center", 132 | "lineHeight": "normal", 133 | "height": "auto", 134 | "color": "rgba(255, 255, 255, 1)" 135 | } 136 | } 137 | } 138 | ] 139 | }, 140 | { 141 | "blocks": [ 142 | { 143 | "@type": "@builder.io/sdk:Element", 144 | "@version": 2, 145 | "id": "builder-f2811248f90044d5ad6733afcfa0a247", 146 | "component": { 147 | "name": "Text", 148 | "options": { 149 | "text": "

Featured Integrations

React

Angular

Next.js

Gatsby

" 150 | } 151 | }, 152 | "responsiveStyles": { 153 | "large": { 154 | "display": "flex", 155 | "flexDirection": "column", 156 | "alignItems": "stretch", 157 | "flexShrink": "0", 158 | "position": "relative", 159 | "marginTop": "10px", 160 | "textAlign": "center", 161 | "lineHeight": "normal", 162 | "height": "auto", 163 | "color": "rgba(255, 255, 255, 1)" 164 | } 165 | } 166 | } 167 | ] 168 | }, 169 | { 170 | "blocks": [ 171 | { 172 | "@type": "@builder.io/sdk:Element", 173 | "@version": 2, 174 | "id": "builder-cb7ecce2452040af9c69122f664352b4", 175 | "component": { 176 | "name": "Text", 177 | "options": { 178 | "text": "

Resources

User Guides

Developer Docs

Forum

Blog

Github

" 179 | } 180 | }, 181 | "responsiveStyles": { 182 | "large": { 183 | "display": "flex", 184 | "flexDirection": "column", 185 | "alignItems": "stretch", 186 | "flexShrink": "0", 187 | "position": "relative", 188 | "marginTop": "10px", 189 | "textAlign": "center", 190 | "lineHeight": "normal", 191 | "height": "auto", 192 | "color": "rgba(255, 255, 255, 1)" 193 | } 194 | } 195 | } 196 | ] 197 | }, 198 | { 199 | "blocks": [ 200 | { 201 | "@type": "@builder.io/sdk:Element", 202 | "@version": 2, 203 | "id": "builder-9224d8263a664bc696b0f82602e986ba", 204 | "component": { 205 | "name": "Text", 206 | "options": { 207 | "text": "

Get In Touch

Twitter

Linkedin

Careers

" 208 | } 209 | }, 210 | "responsiveStyles": { 211 | "large": { 212 | "display": "flex", 213 | "flexDirection": "column", 214 | "alignItems": "stretch", 215 | "flexShrink": "0", 216 | "position": "relative", 217 | "marginTop": "10px", 218 | "textAlign": "center", 219 | "lineHeight": "normal", 220 | "height": "auto", 221 | "color": "rgba(255, 255, 255, 1)" 222 | } 223 | } 224 | } 225 | ] 226 | } 227 | ], 228 | "space": 20, 229 | "stackColumnsAt": "tablet" 230 | } 231 | }, 232 | "responsiveStyles": { 233 | "large": { 234 | "display": "flex", 235 | "flexDirection": "column", 236 | "alignItems": "stretch", 237 | "position": "relative", 238 | "flexShrink": "0", 239 | "boxSizing": "border-box", 240 | "marginTop": "20px" 241 | } 242 | } 243 | } 244 | ], 245 | "responsiveStyles": { 246 | "large": { 247 | "display": "flex", 248 | "flexDirection": "column", 249 | "alignItems": "stretch", 250 | "position": "relative", 251 | "flexShrink": "0", 252 | "boxSizing": "border-box", 253 | "marginTop": "0px", 254 | "paddingLeft": "20px", 255 | "paddingRight": "20px", 256 | "paddingTop": "50px", 257 | "paddingBottom": "50px", 258 | "width": "100vw", 259 | "marginLeft": "calc(50% - 50vw)", 260 | "backgroundColor": "rgba(85,117,113,1)" 261 | } 262 | } 263 | }, 264 | { 265 | "@type": "@builder.io/sdk:Element", 266 | "@version": 2, 267 | "tagName": "img", 268 | "id": "builder-pixel-j7kyh3hcasi", 269 | "properties": { 270 | "src": "https://cdn.builder.io/api/v1/pixel?apiKey=bc2b10075b4046a7810143f7f3238f51", 271 | "role": "presentation", 272 | "width": "0", 273 | "height": "0" 274 | }, 275 | "responsiveStyles": { 276 | "large": { 277 | "height": "0", 278 | "width": "0", 279 | "display": "inline-block", 280 | "opacity": "0", 281 | "overflow": "hidden", 282 | "pointerEvents": "none" 283 | } 284 | } 285 | }, 286 | { 287 | "id": "builder-pixel-i0k85bluvcg", 288 | "@type": "@builder.io/sdk:Element", 289 | "tagName": "img", 290 | "properties": { 291 | "src": "https://cdn.builder.io/api/v1/pixel?apiKey=6d39f4449e2b4e6792a793bb8c1d9615", 292 | "role": "presentation", 293 | "width": "0", 294 | "height": "0" 295 | }, 296 | "responsiveStyles": { 297 | "large": { 298 | "height": "0", 299 | "width": "0", 300 | "display": "inline-block", 301 | "opacity": "0", 302 | "overflow": "hidden", 303 | "pointerEvents": "none" 304 | } 305 | } 306 | } 307 | ] 308 | }, 309 | "id": "930fc042157241c5963b5283604604bf", 310 | "lastUpdated": 1612502997817, 311 | "lastUpdatedBy": "4FFFg0MNRJT0z0nW4uUizDHfHJV2", 312 | "meta": { 313 | "hasLinks": false, 314 | "kind": "component", 315 | "needsHydration": false, 316 | "shopifyDomain": "builder-io-demo.myshopify.com/" 317 | }, 318 | "modelId": "f0943894ca9241bc9e9cd4967dc33ac6", 319 | "name": "footer", 320 | "published": "published", 321 | "query": [], 322 | "rev": "5lw5pfjdqwf", 323 | "screenshot": "https://cdn.builder.io/api/v1/image/assets%2F6d39f4449e2b4e6792a793bb8c1d9615%2Fc59c84fc97c54c8aa4341fe3f81b05ed", 324 | "testRatio": 1, 325 | "variations": {} 326 | } -------------------------------------------------------------------------------- /builder/footer/schema.model.json: -------------------------------------------------------------------------------- 1 | { 2 | "helperText": "Not deployed yet, run from localhost", 3 | "repeatable": false, 4 | "allowHeatmap": true, 5 | "componentsOnlyMode": false, 6 | "designerVersion": 1, 7 | "isPage": false, 8 | "webhooks": [], 9 | "requiredTargets": [], 10 | "defaultQuery": [], 11 | "sendToMongoDb": true, 12 | "kind": "component", 13 | "bigData": false, 14 | "sendToElasticSearch": false, 15 | "hideOptions": false, 16 | "injectWcAt": "", 17 | "publicWritable": false, 18 | "showAbTests": true, 19 | "id": "f0943894ca9241bc9e9cd4967dc33ac6", 20 | "subType": "", 21 | "showScheduling": true, 22 | "allowTests": true, 23 | "getSchemaFromPage": false, 24 | "publicReadable": true, 25 | "pathPrefix": "/", 26 | "showMetrics": true, 27 | "archived": false, 28 | "name": "footer", 29 | "strictPrivateWrite": false, 30 | "apiGenerated": true, 31 | "singleton": false, 32 | "hooks": {}, 33 | "createdDate": 1612487719274, 34 | "strictPrivateRead": false, 35 | "injectWcPosition": "", 36 | "schema": {}, 37 | "showTargeting": true, 38 | "allowMetrics": true, 39 | "individualEmbed": false, 40 | "lastUpdateBy": null, 41 | "autoTracked": true, 42 | "fields": [ 43 | { 44 | "required": true, 45 | "permissionsRequiredToEdit": "", 46 | "disallowRemove": false, 47 | "showIf": "", 48 | "autoFocus": false, 49 | "showTemplatePicker": true, 50 | "hideFromUI": false, 51 | "hidden": false, 52 | "mandatory": false, 53 | "model": "", 54 | "type": "uiBlocks", 55 | "simpleTextOnly": false, 56 | "advanced": false, 57 | "helperText": "", 58 | "copyOnAdd": true, 59 | "hideFromFieldsEditor": true, 60 | "noPhotoPicker": false, 61 | "@type": "@builder.io/core:Field", 62 | "name": "blocks", 63 | "onChange": "", 64 | "subFields": [] 65 | } 66 | ], 67 | "@originId": "ec0f6f43702ca2ce531fac106bc15bf0701a97e691c6681171cf33cce9158623", 68 | "hidden": false 69 | } -------------------------------------------------------------------------------- /builder/header/header.json: -------------------------------------------------------------------------------- 1 | { 2 | "@originContentId": "05bebde85a511d7f4d24022a3b39ed4898ee4aef5158ca924095e8cb90306581", 3 | "@originModelId": "91d31d75ea8ba088b415daa0ac4c264334e7d6e807d7097545bffe0565f56be9", 4 | "@originOrg": "6d39f4449e2b4e6792a793bb8c1d9615", 5 | "createdBy": "agZ9n5CUKRfbL9t6CaJOyVSK4Es2", 6 | "createdDate": 1602290403960, 7 | "data": { 8 | "cssCode": "/*\n* Custom CSS styles\n*\n* Global by default, but use `&` to scope to just this content, e.g.\n* \n* & .foo {\n* color: 'red'\n* }\n*/\n\n", 9 | "customFonts": [ 10 | { 11 | "family": "Roboto", 12 | "isUserFont": true 13 | } 14 | ], 15 | "inputs": [], 16 | "state": { 17 | "deviceSize": "large", 18 | "location": { 19 | "path": "", 20 | "query": {} 21 | } 22 | }, 23 | "blocks": [ 24 | { 25 | "@type": "@builder.io/sdk:Element", 26 | "@version": 2, 27 | "layerName": "Ribbon", 28 | "id": "builder-c3fa0cdb9630416699f19274de80cd38", 29 | "children": [ 30 | { 31 | "@type": "@builder.io/sdk:Element", 32 | "@version": 2, 33 | "id": "builder-413058d87b054c67a370e6570ffdda50", 34 | "component": { 35 | "name": "Text", 36 | "options": { 37 | "text": "

Build, target, schedule, a/b test, rinse, and repeat

" 38 | } 39 | }, 40 | "responsiveStyles": { 41 | "large": { 42 | "display": "flex", 43 | "flexDirection": "column", 44 | "alignItems": "stretch", 45 | "position": "relative", 46 | "flexShrink": "0", 47 | "boxSizing": "border-box", 48 | "marginTop": "5px", 49 | "lineHeight": "normal", 50 | "height": "auto", 51 | "textAlign": "center", 52 | "color": "rgba(238, 238, 238, 1)" 53 | } 54 | } 55 | } 56 | ], 57 | "responsiveStyles": { 58 | "large": { 59 | "display": "flex", 60 | "flexDirection": "column", 61 | "alignItems": "stretch", 62 | "position": "relative", 63 | "flexShrink": "0", 64 | "boxSizing": "border-box", 65 | "height": "30px", 66 | "backgroundColor": "rgba(85, 117, 113, 1)" 67 | } 68 | } 69 | }, 70 | { 71 | "@type": "@builder.io/sdk:Element", 72 | "@version": 2, 73 | "id": "builder-053ba089238a409db878ae9a3db7315e", 74 | "component": { 75 | "name": "Header", 76 | "options": { 77 | "siteTitle": "Builder.io Swag Store" 78 | } 79 | }, 80 | "responsiveStyles": { 81 | "large": { 82 | "display": "flex", 83 | "flexDirection": "column", 84 | "alignItems": "stretch", 85 | "position": "relative", 86 | "flexShrink": "0", 87 | "boxSizing": "border-box" 88 | } 89 | } 90 | }, 91 | { 92 | "@type": "@builder.io/sdk:Element", 93 | "@version": 2, 94 | "layerName": "Page made in Builder.io badge", 95 | "tagName": "a", 96 | "id": "builder-cc34685e1c794cfa97019911d885763e", 97 | "properties": { 98 | "target": "_blank", 99 | "href": "https://github.com/BuilderIO/builder/tree/master/examples/builder-io-site" 100 | }, 101 | "linkUrl": "https://github.com/BuilderIO/builder/tree/master/examples/builder-io-site", 102 | "animations": [ 103 | { 104 | "trigger": "pageLoad", 105 | "animation": "fadeInUp", 106 | "steps": [ 107 | { 108 | "id": "6222f4ca669f4d148a5d6cf57922d60d", 109 | "isStartState": false, 110 | "styles": { 111 | "opacity": "0", 112 | "transform": "translate3d(0, 20px, 0)" 113 | }, 114 | "delay": 0 115 | }, 116 | { 117 | "id": "bfbf9b7302084b32aa51f8974e67708c", 118 | "isStartState": false, 119 | "styles": { 120 | "opacity": "1", 121 | "transform": "none" 122 | }, 123 | "delay": 0 124 | } 125 | ], 126 | "delay": 1, 127 | "duration": 1, 128 | "easing": "cubic-bezier(.37,.01,0,.98)", 129 | "repeat": false 130 | } 131 | ], 132 | "component": { 133 | "name": "Text", 134 | "options": { 135 | "text": "

This page was made in Builder!

" 136 | } 137 | }, 138 | "responsiveStyles": { 139 | "large": { 140 | "display": "flex", 141 | "flexDirection": "column", 142 | "alignItems": "stretch", 143 | "position": "fixed", 144 | "flexShrink": "0", 145 | "boxSizing": "border-box", 146 | "textShadow": "0 1px 0 rgba(0, 0, 0, 0.08)", 147 | "fontWeight": "500", 148 | "backgroundImage": "linear-gradient(160deg, rgb(210, 238, 245) -25%, rgb(1, 185, 241) 29%, rgb(0, 126, 193) 115%)", 149 | "pointerEvents": "auto", 150 | "zIndex": "10", 151 | "fontSize": "14px", 152 | "boxShadow": "rgba(0, 0, 0, 0.2) 0px 5px 5px -3px, rgba(0, 0, 0, 0.14) 0px 8px 10px 1px, rgba(0, 0, 0, 0.12) 0px 3px 14px 2px", 153 | "borderColor": "rgba(0, 0, 0, 0.13)", 154 | "borderWidth": "1px", 155 | "borderStyle": "none", 156 | "color": "rgba(255, 255, 255, 1) !important", 157 | "borderRadius": "50px", 158 | "paddingRight": "20px", 159 | "paddingBottom": "10px", 160 | "paddingLeft": "20px", 161 | "paddingTop": "10px", 162 | "textAlign": "center", 163 | "lineHeight": "normal", 164 | "left": "20px", 165 | "bottom": "20px", 166 | "height": "auto", 167 | "marginTop": "20px", 168 | "cursor": "pointer" 169 | } 170 | } 171 | }, 172 | { 173 | "id": "builder-pixel-mymg1f1mo3s", 174 | "@type": "@builder.io/sdk:Element", 175 | "tagName": "img", 176 | "properties": { 177 | "src": "https://cdn.builder.io/api/v1/pixel?apiKey=bc2b10075b4046a7810143f7f3238f51", 178 | "role": "presentation", 179 | "width": "0", 180 | "height": "0" 181 | }, 182 | "responsiveStyles": { 183 | "large": { 184 | "height": "0", 185 | "width": "0", 186 | "display": "inline-block", 187 | "opacity": "0", 188 | "overflow": "hidden", 189 | "pointerEvents": "none" 190 | } 191 | } 192 | }, 193 | { 194 | "id": "builder-pixel-x8c9tlk551i", 195 | "@type": "@builder.io/sdk:Element", 196 | "tagName": "img", 197 | "properties": { 198 | "src": "https://cdn.builder.io/api/v1/pixel?apiKey=6d39f4449e2b4e6792a793bb8c1d9615", 199 | "role": "presentation", 200 | "width": "0", 201 | "height": "0" 202 | }, 203 | "responsiveStyles": { 204 | "large": { 205 | "height": "0", 206 | "width": "0", 207 | "display": "inline-block", 208 | "opacity": "0", 209 | "overflow": "hidden", 210 | "pointerEvents": "none" 211 | } 212 | } 213 | } 214 | ] 215 | }, 216 | "id": "43812700930546a4aa936f436bbcb742", 217 | "lastUpdated": 1607459580491, 218 | "lastUpdatedBy": "agZ9n5CUKRfbL9t6CaJOyVSK4Es2", 219 | "meta": { 220 | "hasLinks": true, 221 | "kind": "component", 222 | "needsHydration": false 223 | }, 224 | "modelId": "764f7e7e26734333966143c09073cf75", 225 | "name": "header", 226 | "published": "published", 227 | "query": [], 228 | "rev": "3tywl35h95b", 229 | "screenshot": "https://cdn.builder.io/api/v1/image/assets%2Fbc2b10075b4046a7810143f7f3238f51%2F1032575042964ddcacd51d813a4a7996", 230 | "testRatio": 1, 231 | "variations": {} 232 | } -------------------------------------------------------------------------------- /builder/header/schema.model.json: -------------------------------------------------------------------------------- 1 | { 2 | "getSchemaFromPage": false, 3 | "designerVersion": 1, 4 | "publicReadable": true, 5 | "webhooks": [], 6 | "hideOptions": false, 7 | "kind": "component", 8 | "@originId": "91d31d75ea8ba088b415daa0ac4c264334e7d6e807d7097545bffe0565f56be9", 9 | "hidden": false, 10 | "allowMetrics": true, 11 | "fields": [ 12 | { 13 | "@type": "@builder.io/core:Field", 14 | "name": "blocks", 15 | "onChange": "", 16 | "subFields": [], 17 | "hidden": false, 18 | "permissionsRequiredToEdit": "", 19 | "showTemplatePicker": true, 20 | "noPhotoPicker": false, 21 | "required": true, 22 | "copyOnAdd": true, 23 | "type": "uiBlocks", 24 | "simpleTextOnly": false, 25 | "disallowRemove": false, 26 | "hideFromUI": false, 27 | "model": "", 28 | "advanced": false, 29 | "hideFromFieldsEditor": true, 30 | "mandatory": false, 31 | "showIf": "", 32 | "autoFocus": false, 33 | "helperText": "" 34 | } 35 | ], 36 | "showAbTests": true, 37 | "allowTests": true, 38 | "subType": "", 39 | "apiGenerated": true, 40 | "defaultQuery": [], 41 | "isPage": false, 42 | "showMetrics": true, 43 | "hooks": {}, 44 | "schema": {}, 45 | "componentsOnlyMode": false, 46 | "injectWcAt": "", 47 | "requiredTargets": [], 48 | "individualEmbed": false, 49 | "autoTracked": true, 50 | "injectWcPosition": "", 51 | "strictPrivateRead": false, 52 | "sendToElasticSearch": false, 53 | "helperText": "Use for headers only, will be included in SSR", 54 | "strictPrivateWrite": false, 55 | "id": "764f7e7e26734333966143c09073cf75", 56 | "lastUpdateBy": null, 57 | "createdDate": 1612487719411, 58 | "allowHeatmap": true, 59 | "sendToMongoDb": true, 60 | "pathPrefix": "/", 61 | "name": "header", 62 | "singleton": false, 63 | "bigData": false, 64 | "archived": false, 65 | "repeatable": false, 66 | "showTargeting": true, 67 | "showScheduling": true, 68 | "publicWritable": false 69 | } -------------------------------------------------------------------------------- /builder/page/home.json: -------------------------------------------------------------------------------- 1 | { 2 | "@originContentId": "d9573746dfd2716a279baa9d44f324c0eb8d04df639d82619e5944ea2964a8d3", 3 | "@originModelId": "9ff7804e017c8ccfebb2a558f605acf8d2379ad11300a983cfa6440fcdad87a7", 4 | "@originOrg": "6d39f4449e2b4e6792a793bb8c1d9615", 5 | "createdBy": "4FFFg0MNRJT0z0nW4uUizDHfHJV2", 6 | "createdDate": 1601863087822, 7 | "data": { 8 | "customFonts": [ 9 | { 10 | "family": "Roboto", 11 | "isUserFont": true 12 | } 13 | ], 14 | "inputs": [], 15 | "state": { 16 | "deviceSize": "large", 17 | "location": { 18 | "path": "", 19 | "query": {} 20 | } 21 | }, 22 | "title": "home", 23 | "url": "/", 24 | "blocks": [ 25 | { 26 | "@type": "@builder.io/sdk:Element", 27 | "@version": 2, 28 | "id": "builder-8e1181919ee54731a1dec1d35450d951", 29 | "component": { 30 | "name": "Core:Section", 31 | "options": { 32 | "maxWidth": 1200 33 | } 34 | }, 35 | "children": [ 36 | { 37 | "@type": "@builder.io/sdk:Element", 38 | "@version": 2, 39 | "id": "builder-a4879dc540f24981b78cc86e6616444a", 40 | "children": [ 41 | { 42 | "@type": "@builder.io/sdk:Element", 43 | "@version": 2, 44 | "layerName": "Section", 45 | "id": "builder-d32263628c7049f09924babefaad44fa", 46 | "component": { 47 | "name": "Core:Section", 48 | "options": { 49 | "maxWidth": 1400 50 | } 51 | }, 52 | "children": [ 53 | { 54 | "@type": "@builder.io/sdk:Element", 55 | "@version": 2, 56 | "id": "builder-ed6139b93293492fb3a892b2e627469b", 57 | "component": { 58 | "name": "Columns", 59 | "options": { 60 | "space": 0, 61 | "columns": [ 62 | { 63 | "blocks": [ 64 | { 65 | "@type": "@builder.io/sdk:Element", 66 | "@version": 2, 67 | "id": "builder-9e474c2ad1f14f56a260c42640226ca4", 68 | "children": [ 69 | { 70 | "@type": "@builder.io/sdk:Element", 71 | "@version": 2, 72 | "id": "builder-77f9663a36e245968f3353afea0e1e8e", 73 | "component": { 74 | "name": "Image", 75 | "options": { 76 | "image": "https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F67e435a1efdc493cb9bbb8b54055d762?width=50", 77 | "backgroundSize": "contain", 78 | "backgroundPosition": "center", 79 | "aspectRatio": 0.92, 80 | "height": 46, 81 | "width": 50, 82 | "lazy": false, 83 | "sizes": "", 84 | "srcset": "https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F67e435a1efdc493cb9bbb8b54055d762?width=100 100w, https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F67e435a1efdc493cb9bbb8b54055d762?width=200 200w, https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F67e435a1efdc493cb9bbb8b54055d762?width=400 400w, https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F67e435a1efdc493cb9bbb8b54055d762?width=800 800w, https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F67e435a1efdc493cb9bbb8b54055d762?width=1200 1200w, https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F67e435a1efdc493cb9bbb8b54055d762?width=1600 1600w, https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F67e435a1efdc493cb9bbb8b54055d762?width=2000 2000w, https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F67e435a1efdc493cb9bbb8b54055d762?width=50 50w" 85 | } 86 | }, 87 | "responsiveStyles": { 88 | "large": { 89 | "display": "flex", 90 | "flexDirection": "column", 91 | "alignItems": "stretch", 92 | "position": "relative", 93 | "flexShrink": "0", 94 | "boxSizing": "border-box", 95 | "marginTop": "20px", 96 | "minHeight": "20px", 97 | "minWidth": "20px", 98 | "overflow": "hidden", 99 | "width": "50px", 100 | "height": "50px", 101 | "marginLeft": "auto", 102 | "marginRight": "auto" 103 | } 104 | } 105 | }, 106 | { 107 | "@type": "@builder.io/sdk:Element", 108 | "@version": 2, 109 | "id": "builder-fbd20fd1a76c412f80f034ff2a5d2ba0", 110 | "component": { 111 | "name": "Text", 112 | "options": { 113 | "text": "

\n The only visual and headless CMS and page builder for\n Gatsby\n

\n" 114 | } 115 | }, 116 | "responsiveStyles": { 117 | "large": { 118 | "display": "flex", 119 | "flexDirection": "column", 120 | "alignItems": "stretch", 121 | "position": "relative", 122 | "flexShrink": "0", 123 | "boxSizing": "border-box", 124 | "marginTop": "30px", 125 | "height": "auto", 126 | "textAlign": "center", 127 | "fontSize": "40px", 128 | "fontWeight": "900", 129 | "width": "auto", 130 | "paddingBottom": "41px", 131 | "alignSelf": "stretch" 132 | }, 133 | "small": { 134 | "fontSize": "32px", 135 | "width": "100%" 136 | } 137 | } 138 | } 139 | ], 140 | "responsiveStyles": { 141 | "large": { 142 | "display": "flex", 143 | "flexDirection": "column", 144 | "alignItems": "stretch", 145 | "position": "relative", 146 | "flexShrink": "0", 147 | "boxSizing": "border-box", 148 | "marginRight": "-4px", 149 | "marginTop": "70px" 150 | }, 151 | "small": { 152 | "marginTop": "0px" 153 | } 154 | } 155 | } 156 | ], 157 | "width": 41.667 158 | }, 159 | { 160 | "blocks": [ 161 | { 162 | "@type": "@builder.io/sdk:Element", 163 | "@version": 2, 164 | "id": "builder-b91638b92f6c4de08a1a923554b31ace", 165 | "children": [ 166 | { 167 | "@type": "@builder.io/sdk:Element", 168 | "@version": 2, 169 | "id": "builder-fc46886e71c846b2b26c4a28d1ff43c0", 170 | "children": [ 171 | { 172 | "@type": "@builder.io/sdk:Element", 173 | "@version": 2, 174 | "id": "builder-6589192e16fd4ede81d8f49b1f8892dd", 175 | "children": [ 176 | { 177 | "@type": "@builder.io/sdk:Element", 178 | "@version": 2, 179 | "id": "builder-bf788e2a6ba34798a102ad4eb351057a", 180 | "component": { 181 | "name": "Video", 182 | "options": { 183 | "video": "https://cdn.builder.io/api/v1/file/assets%2FYJIGb4i01jvw0SRdL5Bt%2F7da5321e64b04424a27d90017db44d63", 184 | "autoPlay": true, 185 | "muted": true, 186 | "loop": true, 187 | "playsInline": true, 188 | "fit": "cover", 189 | "position": "top", 190 | "aspectRatio": 0.7347, 191 | "width": null 192 | } 193 | }, 194 | "responsiveStyles": { 195 | "large": { 196 | "display": "flex", 197 | "flexDirection": "column", 198 | "alignItems": "stretch", 199 | "position": "relative", 200 | "flexShrink": "0", 201 | "boxSizing": "border-box", 202 | "marginLeft": "auto", 203 | "width": "100%", 204 | "borderRadius": "8px", 205 | "backgroundColor": "rgba(255, 255, 255, 1)", 206 | "boxShadow": "0px 5px 5px -3px rgba(0,0,0,0.2), 0px 8px 10px 1px rgba(0,0,0,0.14), 0px 3px 14px 2px rgba(0,0,0,0.12)", 207 | "borderStyle": "solid", 208 | "borderWidth": "1px", 209 | "borderColor": "rgba(0, 0, 0, 0.09)", 210 | "overflow": "hidden", 211 | "marginTop": "0px", 212 | "maxWidth": "px", 213 | "paddingLeft": "1px", 214 | "marginRight": "1.03px" 215 | }, 216 | "medium": { 217 | "marginTop": "30px" 218 | } 219 | } 220 | } 221 | ], 222 | "responsiveStyles": { 223 | "large": { 224 | "display": "flex", 225 | "flexDirection": "column", 226 | "alignItems": "stretch", 227 | "position": "relative", 228 | "flexShrink": "0", 229 | "boxSizing": "border-box", 230 | "width": "auto", 231 | "alignSelf": "stretch" 232 | }, 233 | "medium": { 234 | "marginLeft": "0px", 235 | "marginRight": "0px", 236 | "alignItems": "center", 237 | "marginTop": "23px", 238 | "paddingBottom": "44px", 239 | "marginBottom": "auto" 240 | }, 241 | "small": { 242 | "justifyContent": "flex-start", 243 | "alignItems": "center" 244 | } 245 | } 246 | } 247 | ], 248 | "responsiveStyles": { 249 | "large": { 250 | "display": "flex", 251 | "flexDirection": "column", 252 | "alignItems": "stretch", 253 | "position": "relative", 254 | "flexShrink": "0", 255 | "boxSizing": "border-box" 256 | }, 257 | "small": { 258 | "paddingBottom": "44px" 259 | } 260 | } 261 | } 262 | ], 263 | "responsiveStyles": { 264 | "large": { 265 | "display": "flex", 266 | "flexDirection": "column", 267 | "alignItems": "stretch", 268 | "position": "relative", 269 | "flexShrink": "0", 270 | "boxSizing": "border-box" 271 | } 272 | } 273 | } 274 | ], 275 | "width": 58.333 276 | } 277 | ], 278 | "stackColumnsAt": "tablet" 279 | } 280 | }, 281 | "responsiveStyles": { 282 | "large": { 283 | "marginTop": "0px", 284 | "paddingBottom": "0px", 285 | "backgroundColor": "rgba(0, 0, 0, 0.01)" 286 | } 287 | } 288 | } 289 | ], 290 | "responsiveStyles": { 291 | "large": { 292 | "display": "flex", 293 | "flexDirection": "column", 294 | "alignItems": "stretch", 295 | "position": "relative", 296 | "flexShrink": "0", 297 | "boxSizing": "border-box", 298 | "backgroundColor": "rgba(255, 255, 255, 0.1)", 299 | "opacity": "1" 300 | }, 301 | "medium": { 302 | "paddingBottom": "83px" 303 | }, 304 | "small": { 305 | "paddingTop": "30px", 306 | "paddingLeft": "14px", 307 | "paddingRight": "14px", 308 | "paddingBottom": "0px" 309 | } 310 | } 311 | } 312 | ], 313 | "responsiveStyles": { 314 | "large": { 315 | "display": "flex", 316 | "flexDirection": "column", 317 | "alignItems": "stretch", 318 | "position": "relative", 319 | "flexShrink": "0", 320 | "boxSizing": "border-box", 321 | "marginTop": "-2px", 322 | "height": "auto", 323 | "paddingBottom": "0px" 324 | } 325 | } 326 | } 327 | ], 328 | "responsiveStyles": { 329 | "large": { 330 | "display": "flex", 331 | "flexDirection": "column", 332 | "alignItems": "stretch", 333 | "position": "relative", 334 | "flexShrink": "0", 335 | "boxSizing": "border-box", 336 | "marginTop": "0px", 337 | "paddingLeft": "20px", 338 | "paddingRight": "20px", 339 | "paddingTop": "0px", 340 | "paddingBottom": "50px", 341 | "width": "100vw", 342 | "marginLeft": "calc(50% - 50vw)" 343 | } 344 | } 345 | }, 346 | { 347 | "@type": "@builder.io/sdk:Element", 348 | "@version": 2, 349 | "id": "builder-66f4582cb0ab46fc87edf707cde4a623", 350 | "component": { 351 | "name": "LatestProductsGridSSR", 352 | "options": {} 353 | }, 354 | "responsiveStyles": { 355 | "large": { 356 | "display": "flex", 357 | "flexDirection": "column", 358 | "alignItems": "stretch", 359 | "position": "relative", 360 | "flexShrink": "0", 361 | "boxSizing": "border-box", 362 | "marginTop": "20px" 363 | } 364 | } 365 | }, 366 | { 367 | "@type": "@builder.io/sdk:Element", 368 | "@version": 2, 369 | "id": "builder-404a381983fc4d79990245f7ea98259e", 370 | "children": [ 371 | { 372 | "@type": "@builder.io/sdk:Element", 373 | "@version": 2, 374 | "bindings": { 375 | "show": "var _virtual_index=Builder.isBrowser;return _virtual_index" 376 | }, 377 | "code": { 378 | "bindings": { 379 | "show": "Builder.isBrowser" 380 | } 381 | }, 382 | "id": "builder-3423c0ca09274ce8bdfad0b3811bf5eb", 383 | "children": [ 384 | { 385 | "@type": "@builder.io/sdk:Element", 386 | "@version": 2, 387 | "id": "builder-3a457d113ec641e380454d7e695d4621", 388 | "animations": [ 389 | { 390 | "trigger": "scrollInView", 391 | "animation": "fadeInUp", 392 | "steps": [ 393 | { 394 | "id": "ba6b1c4e91cd498ea555efd497cfdf81", 395 | "isStartState": false, 396 | "styles": { 397 | "opacity": "0", 398 | "transform": "translate3d(0, 20px, 0)" 399 | }, 400 | "delay": 0 401 | }, 402 | { 403 | "id": "7efa5aec2bec4bcca0b17169378a399c", 404 | "isStartState": false, 405 | "styles": { 406 | "opacity": "1", 407 | "transform": "none" 408 | }, 409 | "delay": 0 410 | } 411 | ], 412 | "delay": 0.2, 413 | "duration": 1, 414 | "easing": "cubic-bezier(.37,.01,0,.98)", 415 | "repeat": false 416 | } 417 | ], 418 | "children": [ 419 | { 420 | "@type": "@builder.io/sdk:Element", 421 | "@version": 2, 422 | "id": "builder-c2a9df06d6b148c990950e136e809ead", 423 | "class": "scrolling-showcase", 424 | "children": [ 425 | { 426 | "@type": "@builder.io/sdk:Element", 427 | "@version": 2, 428 | "id": "builder-1386a7c559f14f2ba4d8d4e1a4f0cd08", 429 | "children": [ 430 | { 431 | "@type": "@builder.io/sdk:Element", 432 | "@version": 2, 433 | "id": "builder-0b986e8e5b0c4b968250456822a04a74", 434 | "component": { 435 | "name": "Image", 436 | "options": { 437 | "image": "https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F2aabcbd098d24e46aa8004766854f2ce?width=310", 438 | "backgroundSize": "contain", 439 | "backgroundPosition": "center", 440 | "aspectRatio": 0.8832, 441 | "height": 736, 442 | "width": 656, 443 | "srcset": "https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F2aabcbd098d24e46aa8004766854f2ce?width=100 100w, https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F2aabcbd098d24e46aa8004766854f2ce?width=200 200w, https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F2aabcbd098d24e46aa8004766854f2ce?width=400 400w, https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F2aabcbd098d24e46aa8004766854f2ce?width=800 800w, https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F2aabcbd098d24e46aa8004766854f2ce?width=1200 1200w, https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F2aabcbd098d24e46aa8004766854f2ce?width=1600 1600w, https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F2aabcbd098d24e46aa8004766854f2ce?width=2000 2000w, https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F2aabcbd098d24e46aa8004766854f2ce?width=310 310w", 444 | "sizes": "", 445 | "lazy": true 446 | } 447 | }, 448 | "responsiveStyles": { 449 | "large": { 450 | "display": "flex", 451 | "flexDirection": "column", 452 | "alignItems": "stretch", 453 | "position": "relative", 454 | "flexShrink": "0", 455 | "boxSizing": "border-box", 456 | "height": "348.2109375px", 457 | "borderRadius": "4px", 458 | "backgroundColor": "rgba(255, 255, 255, 1)", 459 | "boxShadow": "0 2px 8px 0 rgba(0, 0, 0, 0.06)", 460 | "borderStyle": "solid", 461 | "borderWidth": "1px", 462 | "borderColor": "rgba(0, 0, 0, 0.09)" 463 | } 464 | } 465 | }, 466 | { 467 | "@type": "@builder.io/sdk:Element", 468 | "@version": 2, 469 | "id": "builder-47885fd4d1264643b10df5048128685f", 470 | "component": { 471 | "name": "Text", 472 | "options": { 473 | "text": "

Content Pages

" 474 | } 475 | }, 476 | "responsiveStyles": { 477 | "large": { 478 | "display": "flex", 479 | "flexDirection": "column", 480 | "alignItems": "stretch", 481 | "position": "relative", 482 | "flexShrink": "0", 483 | "boxSizing": "border-box", 484 | "marginTop": "20px", 485 | "lineHeight": "normal", 486 | "height": "auto", 487 | "textAlign": "center", 488 | "opacity": "0.63" 489 | } 490 | } 491 | } 492 | ], 493 | "responsiveStyles": { 494 | "large": { 495 | "display": "flex", 496 | "flexDirection": "column", 497 | "alignItems": "stretch", 498 | "position": "relative", 499 | "flexShrink": "0", 500 | "boxSizing": "border-box", 501 | "marginTop": "29px", 502 | "height": "392.2109375px", 503 | "borderRadius": "4px", 504 | "backgroundColor": "rgba(255, 255, 255, 1)", 505 | "borderStyle": "none", 506 | "borderWidth": "1px", 507 | "borderColor": "rgba(0, 0, 0, 0.09)", 508 | "width": "310px", 509 | "marginLeft": "25px" 510 | } 511 | } 512 | }, 513 | { 514 | "@type": "@builder.io/sdk:Element", 515 | "@version": 2, 516 | "id": "builder-2da638d86c8a4c0386fd6d89809040c7", 517 | "children": [ 518 | { 519 | "@type": "@builder.io/sdk:Element", 520 | "@version": 2, 521 | "id": "builder-4a327e7cbbc14d43825a97924ff5c45a", 522 | "component": { 523 | "name": "Image", 524 | "options": { 525 | "image": "https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F816191e35bc048259d8034a0deaea3a2?width=310", 526 | "backgroundSize": "contain", 527 | "backgroundPosition": "center", 528 | "aspectRatio": 0.8832, 529 | "height": 736, 530 | "width": 656, 531 | "srcset": "https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F816191e35bc048259d8034a0deaea3a2?width=100 100w, https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F816191e35bc048259d8034a0deaea3a2?width=200 200w, https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F816191e35bc048259d8034a0deaea3a2?width=400 400w, https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F816191e35bc048259d8034a0deaea3a2?width=800 800w, https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F816191e35bc048259d8034a0deaea3a2?width=1200 1200w, https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F816191e35bc048259d8034a0deaea3a2?width=1600 1600w, https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F816191e35bc048259d8034a0deaea3a2?width=2000 2000w, https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F816191e35bc048259d8034a0deaea3a2?width=310 310w", 532 | "sizes": "", 533 | "lazy": true 534 | } 535 | }, 536 | "responsiveStyles": { 537 | "large": { 538 | "display": "flex", 539 | "flexDirection": "column", 540 | "alignItems": "stretch", 541 | "position": "relative", 542 | "flexShrink": "0", 543 | "boxSizing": "border-box", 544 | "height": "348.2109375px", 545 | "borderRadius": "4px", 546 | "backgroundColor": "rgba(255, 255, 255, 1)", 547 | "boxShadow": "0 2px 8px 0 rgba(0, 0, 0, 0.06)", 548 | "borderStyle": "solid", 549 | "borderWidth": "1px", 550 | "borderColor": "rgba(0, 0, 0, 0.09)" 551 | } 552 | } 553 | }, 554 | { 555 | "@type": "@builder.io/sdk:Element", 556 | "@version": 2, 557 | "id": "builder-c4c6c02af40d438097c70702e381e492", 558 | "component": { 559 | "name": "Text", 560 | "options": { 561 | "text": "

Promotions

" 562 | } 563 | }, 564 | "responsiveStyles": { 565 | "large": { 566 | "display": "flex", 567 | "flexDirection": "column", 568 | "alignItems": "stretch", 569 | "position": "relative", 570 | "flexShrink": "0", 571 | "boxSizing": "border-box", 572 | "marginTop": "20px", 573 | "lineHeight": "normal", 574 | "height": "auto", 575 | "textAlign": "center", 576 | "opacity": "0.63" 577 | } 578 | } 579 | } 580 | ], 581 | "responsiveStyles": { 582 | "large": { 583 | "display": "flex", 584 | "flexDirection": "column", 585 | "alignItems": "stretch", 586 | "position": "relative", 587 | "flexShrink": "0", 588 | "boxSizing": "border-box", 589 | "marginTop": "29px", 590 | "height": "392.2109375px", 591 | "borderRadius": "4px", 592 | "backgroundColor": "rgba(255, 255, 255, 1)", 593 | "borderStyle": "none", 594 | "borderWidth": "1px", 595 | "borderColor": "rgba(0, 0, 0, 0.09)", 596 | "width": "310px", 597 | "marginLeft": "25px" 598 | } 599 | } 600 | }, 601 | { 602 | "@type": "@builder.io/sdk:Element", 603 | "@version": 2, 604 | "id": "builder-540ebe336b3f4f5aa9d35309ecb64142", 605 | "children": [ 606 | { 607 | "@type": "@builder.io/sdk:Element", 608 | "@version": 2, 609 | "id": "builder-a7f10279907148d78d6aa6f24caa6a39", 610 | "component": { 611 | "name": "Image", 612 | "options": { 613 | "image": "https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F9266956592b642928008bc9652969ca1?width=310", 614 | "backgroundSize": "contain", 615 | "backgroundPosition": "center", 616 | "aspectRatio": 0.8832, 617 | "height": 736, 618 | "width": 656, 619 | "srcset": "https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F9266956592b642928008bc9652969ca1?width=100 100w, https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F9266956592b642928008bc9652969ca1?width=200 200w, https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F9266956592b642928008bc9652969ca1?width=400 400w, https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F9266956592b642928008bc9652969ca1?width=800 800w, https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F9266956592b642928008bc9652969ca1?width=1200 1200w, https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F9266956592b642928008bc9652969ca1?width=1600 1600w, https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F9266956592b642928008bc9652969ca1?width=2000 2000w, https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F9266956592b642928008bc9652969ca1?width=310 310w", 620 | "sizes": "", 621 | "lazy": true 622 | } 623 | }, 624 | "responsiveStyles": { 625 | "large": { 626 | "display": "flex", 627 | "flexDirection": "column", 628 | "alignItems": "stretch", 629 | "position": "relative", 630 | "flexShrink": "0", 631 | "boxSizing": "border-box", 632 | "height": "348.2109375px", 633 | "borderRadius": "4px", 634 | "backgroundColor": "rgba(255, 255, 255, 1)", 635 | "boxShadow": "0 2px 8px 0 rgba(0, 0, 0, 0.06)", 636 | "borderStyle": "solid", 637 | "borderWidth": "1px", 638 | "borderColor": "rgba(0, 0, 0, 0.09)" 639 | } 640 | } 641 | }, 642 | { 643 | "@type": "@builder.io/sdk:Element", 644 | "@version": 2, 645 | "id": "builder-7d9ec08271b34a8686461eba91ef5833", 646 | "component": { 647 | "name": "Text", 648 | "options": { 649 | "text": "

Online Stores

" 650 | } 651 | }, 652 | "responsiveStyles": { 653 | "large": { 654 | "display": "flex", 655 | "flexDirection": "column", 656 | "alignItems": "stretch", 657 | "position": "relative", 658 | "flexShrink": "0", 659 | "boxSizing": "border-box", 660 | "marginTop": "20px", 661 | "lineHeight": "normal", 662 | "height": "auto", 663 | "textAlign": "center", 664 | "opacity": "0.63" 665 | } 666 | } 667 | } 668 | ], 669 | "responsiveStyles": { 670 | "large": { 671 | "display": "flex", 672 | "flexDirection": "column", 673 | "alignItems": "stretch", 674 | "position": "relative", 675 | "flexShrink": "0", 676 | "boxSizing": "border-box", 677 | "marginTop": "29px", 678 | "height": "392.2109375px", 679 | "borderRadius": "4px", 680 | "backgroundColor": "rgba(255, 255, 255, 1)", 681 | "borderStyle": "none", 682 | "borderWidth": "1px", 683 | "borderColor": "rgba(0, 0, 0, 0.09)", 684 | "width": "310px", 685 | "marginLeft": "25px" 686 | } 687 | } 688 | }, 689 | { 690 | "@type": "@builder.io/sdk:Element", 691 | "@version": 2, 692 | "id": "builder-2029446e82634521ac8c19f8bac97b0c", 693 | "children": [ 694 | { 695 | "@type": "@builder.io/sdk:Element", 696 | "@version": 2, 697 | "id": "builder-cbf7c95787f444b086b6c872308b00ae", 698 | "component": { 699 | "name": "Image", 700 | "options": { 701 | "image": "https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2Fb60dc35ec02c48b19f8a181939b3e26e?width=310", 702 | "backgroundSize": "contain", 703 | "backgroundPosition": "center", 704 | "aspectRatio": 0.8832, 705 | "height": 736, 706 | "width": 656, 707 | "srcset": "https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2Fb60dc35ec02c48b19f8a181939b3e26e?width=100 100w, https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2Fb60dc35ec02c48b19f8a181939b3e26e?width=200 200w, https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2Fb60dc35ec02c48b19f8a181939b3e26e?width=400 400w, https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2Fb60dc35ec02c48b19f8a181939b3e26e?width=800 800w, https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2Fb60dc35ec02c48b19f8a181939b3e26e?width=1200 1200w, https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2Fb60dc35ec02c48b19f8a181939b3e26e?width=1600 1600w, https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2Fb60dc35ec02c48b19f8a181939b3e26e?width=2000 2000w, https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2Fb60dc35ec02c48b19f8a181939b3e26e?width=310 310w", 708 | "sizes": "", 709 | "lazy": true 710 | } 711 | }, 712 | "responsiveStyles": { 713 | "large": { 714 | "display": "flex", 715 | "flexDirection": "column", 716 | "alignItems": "stretch", 717 | "position": "relative", 718 | "flexShrink": "0", 719 | "boxSizing": "border-box", 720 | "height": "348.2109375px", 721 | "borderRadius": "4px", 722 | "backgroundColor": "rgba(255, 255, 255, 1)", 723 | "boxShadow": "0 2px 8px 0 rgba(0, 0, 0, 0.06)", 724 | "borderStyle": "solid", 725 | "borderWidth": "1px", 726 | "borderColor": "rgba(0, 0, 0, 0.09)" 727 | } 728 | } 729 | }, 730 | { 731 | "@type": "@builder.io/sdk:Element", 732 | "@version": 2, 733 | "id": "builder-615b508bef3e4fe383cb8e18da91df2c", 734 | "component": { 735 | "name": "Text", 736 | "options": { 737 | "text": "

Blogs

" 738 | } 739 | }, 740 | "responsiveStyles": { 741 | "large": { 742 | "display": "flex", 743 | "flexDirection": "column", 744 | "alignItems": "stretch", 745 | "position": "relative", 746 | "flexShrink": "0", 747 | "boxSizing": "border-box", 748 | "marginTop": "20px", 749 | "lineHeight": "normal", 750 | "height": "auto", 751 | "textAlign": "center", 752 | "opacity": "0.63" 753 | } 754 | } 755 | } 756 | ], 757 | "responsiveStyles": { 758 | "large": { 759 | "display": "flex", 760 | "flexDirection": "column", 761 | "alignItems": "stretch", 762 | "position": "relative", 763 | "flexShrink": "0", 764 | "boxSizing": "border-box", 765 | "marginTop": "29px", 766 | "height": "392.2109375px", 767 | "borderRadius": "4px", 768 | "backgroundColor": "rgba(255, 255, 255, 1)", 769 | "borderStyle": "none", 770 | "borderWidth": "1px", 771 | "borderColor": "rgba(0, 0, 0, 0.09)", 772 | "width": "310px", 773 | "marginLeft": "25px" 774 | } 775 | } 776 | }, 777 | { 778 | "@type": "@builder.io/sdk:Element", 779 | "@version": 2, 780 | "id": "builder-0590021486154213a14eeb353fae0c95", 781 | "children": [ 782 | { 783 | "@type": "@builder.io/sdk:Element", 784 | "@version": 2, 785 | "id": "builder-be3db37a90d14bc790845189667cf0d2", 786 | "component": { 787 | "name": "Image", 788 | "options": { 789 | "image": "https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F231866fb6d9846f886ee912be3f958a8?width=310", 790 | "backgroundSize": "contain", 791 | "backgroundPosition": "center", 792 | "aspectRatio": 0.8832, 793 | "height": 736, 794 | "width": 656, 795 | "srcset": "https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F231866fb6d9846f886ee912be3f958a8?width=100 100w, https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F231866fb6d9846f886ee912be3f958a8?width=200 200w, https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F231866fb6d9846f886ee912be3f958a8?width=400 400w, https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F231866fb6d9846f886ee912be3f958a8?width=800 800w, https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F231866fb6d9846f886ee912be3f958a8?width=1200 1200w, https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F231866fb6d9846f886ee912be3f958a8?width=1600 1600w, https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F231866fb6d9846f886ee912be3f958a8?width=2000 2000w, https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F231866fb6d9846f886ee912be3f958a8?width=310 310w", 796 | "sizes": "", 797 | "lazy": true 798 | } 799 | }, 800 | "responsiveStyles": { 801 | "large": { 802 | "display": "flex", 803 | "flexDirection": "column", 804 | "alignItems": "stretch", 805 | "position": "relative", 806 | "flexShrink": "0", 807 | "boxSizing": "border-box", 808 | "height": "348.2109375px", 809 | "borderRadius": "4px", 810 | "backgroundColor": "rgba(255, 255, 255, 1)", 811 | "boxShadow": "0 2px 8px 0 rgba(0, 0, 0, 0.06)", 812 | "borderStyle": "solid", 813 | "borderWidth": "1px", 814 | "borderColor": "rgba(0, 0, 0, 0.09)" 815 | } 816 | } 817 | }, 818 | { 819 | "@type": "@builder.io/sdk:Element", 820 | "@version": 2, 821 | "id": "builder-ffeb9c0e9d4548a0954bb97d9a9ddbc4", 822 | "component": { 823 | "name": "Text", 824 | "options": { 825 | "text": "

Landing Pages

" 826 | } 827 | }, 828 | "responsiveStyles": { 829 | "large": { 830 | "display": "flex", 831 | "flexDirection": "column", 832 | "alignItems": "stretch", 833 | "position": "relative", 834 | "flexShrink": "0", 835 | "boxSizing": "border-box", 836 | "marginTop": "20px", 837 | "lineHeight": "normal", 838 | "height": "auto", 839 | "textAlign": "center", 840 | "opacity": "0.63" 841 | } 842 | } 843 | } 844 | ], 845 | "responsiveStyles": { 846 | "large": { 847 | "display": "flex", 848 | "flexDirection": "column", 849 | "alignItems": "stretch", 850 | "position": "relative", 851 | "flexShrink": "0", 852 | "boxSizing": "border-box", 853 | "marginTop": "29px", 854 | "height": "392.2109375px", 855 | "borderRadius": "4px", 856 | "backgroundColor": "rgba(255, 255, 255, 1)", 857 | "borderStyle": "none", 858 | "borderWidth": "1px", 859 | "borderColor": "rgba(0, 0, 0, 0.09)", 860 | "width": "310px", 861 | "marginLeft": "25px" 862 | } 863 | } 864 | }, 865 | { 866 | "@type": "@builder.io/sdk:Element", 867 | "@version": 2, 868 | "id": "builder-74a87903c85d4a1dbe76105d1de286f0", 869 | "children": [ 870 | { 871 | "@type": "@builder.io/sdk:Element", 872 | "@version": 2, 873 | "id": "builder-1a793eac87be4900b56a0d8fc67de95e", 874 | "component": { 875 | "name": "Image", 876 | "options": { 877 | "image": "https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F7be5c5de09f4478fb4c5e7bf2723ad66?width=310", 878 | "backgroundSize": "contain", 879 | "backgroundPosition": "center", 880 | "aspectRatio": 0.8832, 881 | "height": 736, 882 | "width": 656, 883 | "srcset": "https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F7be5c5de09f4478fb4c5e7bf2723ad66?width=100 100w, https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F7be5c5de09f4478fb4c5e7bf2723ad66?width=200 200w, https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F7be5c5de09f4478fb4c5e7bf2723ad66?width=400 400w, https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F7be5c5de09f4478fb4c5e7bf2723ad66?width=800 800w, https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F7be5c5de09f4478fb4c5e7bf2723ad66?width=1200 1200w, https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F7be5c5de09f4478fb4c5e7bf2723ad66?width=1600 1600w, https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F7be5c5de09f4478fb4c5e7bf2723ad66?width=2000 2000w, https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F7be5c5de09f4478fb4c5e7bf2723ad66?width=310 310w", 884 | "sizes": "", 885 | "lazy": true 886 | } 887 | }, 888 | "responsiveStyles": { 889 | "large": { 890 | "display": "flex", 891 | "flexDirection": "column", 892 | "alignItems": "stretch", 893 | "position": "relative", 894 | "flexShrink": "0", 895 | "boxSizing": "border-box", 896 | "height": "348.2109375px", 897 | "borderRadius": "4px", 898 | "backgroundColor": "rgba(255, 255, 255, 1)", 899 | "boxShadow": "0 2px 8px 0 rgba(0, 0, 0, 0.06)", 900 | "borderStyle": "solid", 901 | "borderWidth": "1px", 902 | "borderColor": "rgba(0, 0, 0, 0.09)" 903 | } 904 | } 905 | }, 906 | { 907 | "@type": "@builder.io/sdk:Element", 908 | "@version": 2, 909 | "id": "builder-b6b02aa4ad7b40f2a5bad6dd82d60f1c", 910 | "component": { 911 | "name": "Text", 912 | "options": { 913 | "text": "

Docs and Guides

" 914 | } 915 | }, 916 | "responsiveStyles": { 917 | "large": { 918 | "display": "flex", 919 | "flexDirection": "column", 920 | "alignItems": "stretch", 921 | "position": "relative", 922 | "flexShrink": "0", 923 | "boxSizing": "border-box", 924 | "marginTop": "20px", 925 | "lineHeight": "normal", 926 | "height": "auto", 927 | "textAlign": "center", 928 | "opacity": "0.63" 929 | } 930 | } 931 | } 932 | ], 933 | "responsiveStyles": { 934 | "large": { 935 | "display": "flex", 936 | "flexDirection": "column", 937 | "alignItems": "stretch", 938 | "position": "relative", 939 | "flexShrink": "0", 940 | "boxSizing": "border-box", 941 | "marginTop": "29px", 942 | "height": "392.2109375px", 943 | "borderRadius": "4px", 944 | "backgroundColor": "rgba(255, 255, 255, 1)", 945 | "borderStyle": "none", 946 | "borderWidth": "1px", 947 | "borderColor": "rgba(0, 0, 0, 0.09)", 948 | "width": "310px", 949 | "marginLeft": "25px" 950 | } 951 | } 952 | } 953 | ], 954 | "responsiveStyles": { 955 | "large": { 956 | "display": "flex", 957 | "flexDirection": "row", 958 | "position": "relative", 959 | "flexShrink": "0", 960 | "boxSizing": "border-box", 961 | "paddingLeft": "43px" 962 | } 963 | } 964 | } 965 | ], 966 | "responsiveStyles": { 967 | "large": { 968 | "position": "relative", 969 | "flexShrink": "0", 970 | "boxSizing": "border-box", 971 | "display": "flex", 972 | "flexDirection": "row", 973 | "overflow": "auto", 974 | "paddingBottom": "20px", 975 | "width": "100vw", 976 | "marginLeft": "calc(50% - 50vw)", 977 | "paddingLeft": "11px", 978 | "paddingRight": "0px", 979 | "marginTop": "9px" 980 | } 981 | } 982 | } 983 | ], 984 | "responsiveStyles": { 985 | "large": { 986 | "display": "flex", 987 | "flexDirection": "column", 988 | "alignItems": "stretch", 989 | "position": "relative", 990 | "flexShrink": "0", 991 | "boxSizing": "border-box", 992 | "marginTop": "-2px", 993 | "height": "auto", 994 | "paddingBottom": "0px" 995 | } 996 | } 997 | } 998 | ], 999 | "responsiveStyles": { 1000 | "large": { 1001 | "display": "flex", 1002 | "flexDirection": "column", 1003 | "alignItems": "stretch", 1004 | "position": "relative", 1005 | "flexShrink": "0", 1006 | "boxSizing": "border-box", 1007 | "marginTop": "20px" 1008 | } 1009 | } 1010 | }, 1011 | { 1012 | "id": "builder-pixel-hc27e6wbriw", 1013 | "@type": "@builder.io/sdk:Element", 1014 | "tagName": "img", 1015 | "properties": { 1016 | "src": "https://cdn.builder.io/api/v1/pixel?apiKey=bc2b10075b4046a7810143f7f3238f51", 1017 | "role": "presentation", 1018 | "width": "0", 1019 | "height": "0" 1020 | }, 1021 | "responsiveStyles": { 1022 | "large": { 1023 | "height": "0", 1024 | "width": "0", 1025 | "display": "inline-block", 1026 | "opacity": "0", 1027 | "overflow": "hidden", 1028 | "pointerEvents": "none" 1029 | } 1030 | } 1031 | }, 1032 | { 1033 | "id": "builder-pixel-w31cq5pszsb", 1034 | "@type": "@builder.io/sdk:Element", 1035 | "tagName": "img", 1036 | "properties": { 1037 | "src": "https://cdn.builder.io/api/v1/pixel?apiKey=6d39f4449e2b4e6792a793bb8c1d9615", 1038 | "role": "presentation", 1039 | "width": "0", 1040 | "height": "0" 1041 | }, 1042 | "responsiveStyles": { 1043 | "large": { 1044 | "height": "0", 1045 | "width": "0", 1046 | "display": "inline-block", 1047 | "opacity": "0", 1048 | "overflow": "hidden", 1049 | "pointerEvents": "none" 1050 | } 1051 | } 1052 | } 1053 | ] 1054 | }, 1055 | "id": "ca24094e6bca4be885bf943d9e00686d", 1056 | "lastUpdated": 1607459753745, 1057 | "lastUpdatedBy": "agZ9n5CUKRfbL9t6CaJOyVSK4Es2", 1058 | "meta": { 1059 | "hasLinks": false, 1060 | "kind": "page", 1061 | "needsHydration": false 1062 | }, 1063 | "modelId": "7cdf9fb295ef4d53a46ecd8e27a6716d", 1064 | "name": "home", 1065 | "published": "published", 1066 | "query": [ 1067 | { 1068 | "operator": "is", 1069 | "property": "urlPath", 1070 | "value": "/" 1071 | } 1072 | ], 1073 | "rev": "bdlh3cduzy", 1074 | "screenshot": "https://cdn.builder.io/api/v1/image/assets%2Fbc2b10075b4046a7810143f7f3238f51%2Fbabcc34057f1424886c2b331afe25918", 1075 | "testRatio": 1, 1076 | "variations": {} 1077 | } 1078 | -------------------------------------------------------------------------------- /builder/page/schema.model.json: -------------------------------------------------------------------------------- 1 | { 2 | "autoTracked": true, 3 | "injectWcAt": "", 4 | "createdDate": 1612487720327, 5 | "webhooks": [], 6 | "hidden": false, 7 | "getSchemaFromPage": false, 8 | "@originId": "9ff7804e017c8ccfebb2a558f605acf8d2379ad11300a983cfa6440fcdad87a7", 9 | "kind": "page", 10 | "publicReadable": true, 11 | "requiredTargets": [], 12 | "sendToElasticSearch": false, 13 | "designerVersion": 1, 14 | "strictPrivateWrite": false, 15 | "showAbTests": true, 16 | "pathPrefix": "/", 17 | "strictPrivateRead": false, 18 | "componentsOnlyMode": false, 19 | "singleton": false, 20 | "hideOptions": false, 21 | "hooks": {}, 22 | "helperText": "", 23 | "showScheduling": true, 24 | "allowMetrics": true, 25 | "showTargeting": true, 26 | "subType": "", 27 | "bigData": false, 28 | "sendToMongoDb": true, 29 | "individualEmbed": false, 30 | "publicWritable": false, 31 | "id": "7cdf9fb295ef4d53a46ecd8e27a6716d", 32 | "repeatable": false, 33 | "fields": [ 34 | { 35 | "model": "", 36 | "name": "blocks", 37 | "permissionsRequiredToEdit": "", 38 | "subFields": [], 39 | "simpleTextOnly": false, 40 | "hidden": true, 41 | "hideFromUI": false, 42 | "type": "uiBlocks", 43 | "showIf": "", 44 | "hideFromFieldsEditor": false, 45 | "autoFocus": false, 46 | "showTemplatePicker": true, 47 | "disallowRemove": false, 48 | "onChange": "", 49 | "noPhotoPicker": false, 50 | "advanced": false, 51 | "mandatory": false, 52 | "helperText": "", 53 | "required": false, 54 | "copyOnAdd": true, 55 | "@type": "@builder.io/core:Field" 56 | }, 57 | { 58 | "required": false, 59 | "advanced": false, 60 | "hideFromUI": false, 61 | "showIf": "", 62 | "type": "text", 63 | "hidden": false, 64 | "showTemplatePicker": true, 65 | "@type": "@builder.io/core:Field", 66 | "subFields": [], 67 | "onChange": "", 68 | "name": "title", 69 | "autoFocus": false, 70 | "model": "", 71 | "hideFromFieldsEditor": false, 72 | "disallowRemove": false, 73 | "mandatory": false, 74 | "copyOnAdd": true, 75 | "permissionsRequiredToEdit": "", 76 | "noPhotoPicker": false, 77 | "helperText": "SEO page title", 78 | "simpleTextOnly": false 79 | }, 80 | { 81 | "disallowRemove": false, 82 | "required": false, 83 | "@type": "@builder.io/core:Field", 84 | "showTemplatePicker": true, 85 | "advanced": false, 86 | "mandatory": false, 87 | "name": "description", 88 | "noPhotoPicker": false, 89 | "hideFromUI": false, 90 | "type": "longText", 91 | "simpleTextOnly": false, 92 | "model": "", 93 | "subFields": [], 94 | "showIf": "", 95 | "hideFromFieldsEditor": false, 96 | "copyOnAdd": true, 97 | "helperText": "SEO page description", 98 | "hidden": false, 99 | "onChange": "", 100 | "permissionsRequiredToEdit": "", 101 | "autoFocus": false 102 | } 103 | ], 104 | "injectWcPosition": "", 105 | "allowTests": true, 106 | "name": "page", 107 | "allowHeatmap": true, 108 | "apiGenerated": true, 109 | "defaultQuery": [], 110 | "archived": false, 111 | "lastUpdateBy": null, 112 | "isPage": false, 113 | "schema": {}, 114 | "showMetrics": true 115 | } -------------------------------------------------------------------------------- /builder/product-page/product-page-template.json: -------------------------------------------------------------------------------- 1 | { 2 | "createdBy": "4FFFg0MNRJT0z0nW4uUizDHfHJV2", 3 | "createdDate": 1601346290008, 4 | "data": { 5 | "inputs": [], 6 | "productPreview": { 7 | "@type": "@builder.io/core:Request", 8 | "options": { 9 | "product": 6206263787699 10 | }, 11 | "request": { 12 | "method": "GET", 13 | "url": "https://cdn.builder.io/api/v1/shopify/products/{{this.options.product}}.json?apiKey=6d39f4449e2b4e6792a793bb8c1d9615" 14 | } 15 | }, 16 | "state": { 17 | "deviceSize": "large", 18 | "location": { 19 | "path": "", 20 | "query": {} 21 | } 22 | }, 23 | "blocks": [ 24 | { 25 | "@type": "@builder.io/sdk:Element", 26 | "@version": 2, 27 | "bindings": { 28 | "component.options.product": "state.product", 29 | "component.options.title": "state.product.title", 30 | "component.options.description": "state.product.descriptionHtml" 31 | }, 32 | "id": "builder-aa9150236c0a4b2fa01fc3ec02ba1301", 33 | "component": { 34 | "name": "ProductPageDetails", 35 | "options": {} 36 | }, 37 | "responsiveStyles": { 38 | "large": { 39 | "display": "flex", 40 | "flexDirection": "column", 41 | "alignItems": "stretch", 42 | "position": "relative", 43 | "flexShrink": "0", 44 | "boxSizing": "border-box", 45 | "marginTop": "2px", 46 | "paddingTop": "0px" 47 | } 48 | } 49 | }, 50 | { 51 | "@type": "@builder.io/sdk:Element", 52 | "@version": 2, 53 | "id": "builder-0eecfc7be3cc451f9d5379007523b142", 54 | "component": { 55 | "name": "LatestProductsGridNoSSR", 56 | "options": {} 57 | }, 58 | "responsiveStyles": { 59 | "large": { 60 | "display": "flex", 61 | "flexDirection": "column", 62 | "alignItems": "stretch", 63 | "position": "relative", 64 | "flexShrink": "0", 65 | "boxSizing": "border-box", 66 | "marginTop": "20px" 67 | } 68 | } 69 | }, 70 | { 71 | "id": "builder-pixel-68kmwlcim9n", 72 | "@type": "@builder.io/sdk:Element", 73 | "tagName": "img", 74 | "properties": { 75 | "src": "https://cdn.builder.io/api/v1/pixel?apiKey=6d39f4449e2b4e6792a793bb8c1d9615", 76 | "role": "presentation", 77 | "width": "0", 78 | "height": "0" 79 | }, 80 | "responsiveStyles": { 81 | "large": { 82 | "height": "0", 83 | "width": "0", 84 | "display": "inline-block", 85 | "opacity": "0", 86 | "overflow": "hidden", 87 | "pointerEvents": "none" 88 | } 89 | } 90 | } 91 | ] 92 | }, 93 | "id": "91b04b5f7a594909bcb6367372a90e22", 94 | "lastUpdated": 1612504890353, 95 | "lastUpdatedBy": "4FFFg0MNRJT0z0nW4uUizDHfHJV2", 96 | "meta": { 97 | "hasLinks": false, 98 | "kind": "component", 99 | "needsHydration": false, 100 | "shopifyDomain": "builder-io-demo.myshopify.com/", 101 | "winningTest": null 102 | }, 103 | "modelId": "37e2bb9c9af7414b83a142c792be2485", 104 | "name": "product page template", 105 | "published": "published", 106 | "query": [], 107 | "screenshot": "https://cdn.builder.io/api/v1/image/assets%2F6d39f4449e2b4e6792a793bb8c1d9615%2Ff24c95db1ce84232b652f7367e06aaa2", 108 | "testRatio": 1, 109 | "variations": {}, 110 | "rev": "jmnlsxgzrk" 111 | } 112 | -------------------------------------------------------------------------------- /builder/product-page/schema.model.json: -------------------------------------------------------------------------------- 1 | { 2 | "publicReadable": true, 3 | "publicWritable": false, 4 | "useQueryParamTargetingClientSide": false, 5 | "showAbTests": true, 6 | "id": "37e2bb9c9af7414b83a142c792be2485", 7 | "strictPrivateRead": false, 8 | "name": "product-page", 9 | "webhooks": [], 10 | "allowTests": true, 11 | "showMetrics": true, 12 | "hooks": {}, 13 | "individualEmbed": false, 14 | "subType": "", 15 | "allowMetrics": true, 16 | "kind": "component", 17 | "isPage": false, 18 | "lastUpdateBy": null, 19 | "injectWcPosition": "", 20 | "showScheduling": true, 21 | "showTargeting": true, 22 | "allowHeatmap": true, 23 | "hideOptions": false, 24 | "singleton": false, 25 | "bigData": false, 26 | "defaultQuery": [], 27 | "autoTracked": true, 28 | "injectWcAt": "", 29 | "hidden": false, 30 | "sendToElasticSearch": false, 31 | "helperText": "", 32 | "schema": {}, 33 | "pathPrefix": "/", 34 | "fields": [ 35 | { 36 | "noPhotoPicker": false, 37 | "@type": "@builder.io/core:Field", 38 | "advanced": false, 39 | "model": "", 40 | "disallowRemove": false, 41 | "name": "blocks", 42 | "helperText": "", 43 | "mandatory": false, 44 | "copyOnAdd": true, 45 | "permissionsRequiredToEdit": "", 46 | "required": true, 47 | "showIf": "", 48 | "autoFocus": false, 49 | "showTemplatePicker": true, 50 | "hideFromFieldsEditor": true, 51 | "type": "uiBlocks", 52 | "hideFromUI": false, 53 | "onChange": "", 54 | "simpleTextOnly": false, 55 | "hidden": false, 56 | "subFields": [] 57 | }, 58 | { 59 | "defaultValue": { 60 | "@type": "@builder.io/core:Request", 61 | "request": { 62 | "url": "https://cdn.builder.io/api/v1/shopify/products/{{this.options.product}}.json?apiKey=6d39f4449e2b4e6792a793bb8c1d9615", 63 | "method": "GET" 64 | }, 65 | "options": { 66 | "product": 6206263525555 67 | } 68 | }, 69 | "disallowRemove": false, 70 | "permissionsRequiredToEdit": "", 71 | "hideFromUI": false, 72 | "autoFocus": false, 73 | "simpleTextOnly": false, 74 | "@type": "@builder.io/core:Field", 75 | "subFields": [], 76 | "required": false, 77 | "mandatory": false, 78 | "showTemplatePicker": true, 79 | "showIf": "", 80 | "name": "productPreview", 81 | "copyOnAdd": true, 82 | "advanced": false, 83 | "noPhotoPicker": false, 84 | "hidden": false, 85 | "hideFromFieldsEditor": false, 86 | "type": "ShopifyProductPreview", 87 | "model": "", 88 | "onChange": "", 89 | "helperText": "" 90 | } 91 | ], 92 | "getSchemaFromPage": false, 93 | "sendToMongoDb": true, 94 | "examplePageUrl": "${space.siteUrl}/product/${previewProduct.handle}", 95 | "requiredTargets": [], 96 | "designerVersion": 1, 97 | "componentsOnlyMode": false, 98 | "strictPrivateWrite": false, 99 | "hideFromUI": false, 100 | "archived": false, 101 | "repeatable": false, 102 | "@originId": "7c834559dcc183dd381b13ae41cd7bd325ecceba05de9f24b61fd3428c583fc9" 103 | } -------------------------------------------------------------------------------- /builder/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "hasIntegrated": "remote", 3 | "name": "gatsby-shopufy-space-template-take-2", 4 | "@version": 5, 5 | "settings": { 6 | "componentsOnlyMode": false, 7 | "editorShowShopifyBlocks": true, 8 | "plugins": { 9 | "@builder.io/plugin-shopify": { 10 | "hasConnected": true, 11 | "apiPassword": "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", 12 | "storefrontAccessToken": "dd0057d1e48d2d61ca8ec27b07d3c5e6", 13 | "syncPreviewUrlWithTargeting": true, 14 | "apiKey": "14f064d7f94eeaadf93bd8690fca1207", 15 | "storeDomain": "builder-io-demo.myshopify.com/" 16 | } 17 | }, 18 | "allowBuiltInComponents": true 19 | }, 20 | "siteUrl": "http://localhost:8000", 21 | "customTargetingAttributes": { 22 | "productHandle": { 23 | "type": "ShopifyProductHandle" 24 | } 25 | }, 26 | "loadPlugins": [ 27 | "@builder.io/plugin-shopify@dev" 28 | ], 29 | "cloneInfo": { 30 | "contentIdMap": { 31 | "05bebde85a511d7f4d24022a3b39ed4898ee4aef5158ca924095e8cb90306581": "43812700930546a4aa936f436bbcb742", 32 | "d9573746dfd2716a279baa9d44f324c0eb8d04df639d82619e5944ea2964a8d3": "ca24094e6bca4be885bf943d9e00686d", 33 | "882089ce8e164dcb6034d183d631785a6c1f99ec93b9b54acd379fa1278cd66b": "930fc042157241c5963b5283604604bf" 34 | }, 35 | "modelIdMap": { 36 | "7c834559dcc183dd381b13ae41cd7bd325ecceba05de9f24b61fd3428c583fc9": "37e2bb9c9af7414b83a142c792be2485", 37 | "91d31d75ea8ba088b415daa0ac4c264334e7d6e807d7097545bffe0565f56be9": "764f7e7e26734333966143c09073cf75", 38 | "9ff7804e017c8ccfebb2a558f605acf8d2379ad11300a983cfa6440fcdad87a7": "7cdf9fb295ef4d53a46ecd8e27a6716d", 39 | "ec0f6f43702ca2ce531fac106bc15bf0701a97e691c6681171cf33cce9158623": "f0943894ca9241bc9e9cd4967dc33ac6" 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosting": { 3 | "public": "public", 4 | "ignore": ["firebase.json", "**/.*", "**/node_modules/**"], 5 | "headers": [ 6 | { 7 | "source": "**/*.@(eot|otf|ttf|ttc|woff|font.css)", 8 | "headers": [ 9 | { 10 | "key": "Access-Control-Allow-Origin", 11 | "value": "*" 12 | } 13 | ] 14 | }, 15 | { 16 | "source": "**/*.@(jpg|jpeg|gif|png|webp|js)", 17 | "headers": [ 18 | { 19 | "key": "Cache-Control", 20 | "value": "max-age=31536000" 21 | } 22 | ] 23 | }, 24 | { 25 | "source": "404.html", 26 | "headers": [ 27 | { 28 | "key": "Cache-Control", 29 | "value": "max-age=300" 30 | } 31 | ] 32 | }, 33 | { 34 | "source": "**/*", 35 | "headers": [ 36 | { 37 | "key": "Cache-Control", 38 | "value": "public, max-age=600, s-maxage=2628000, stale-while-revalidate=2628000, stale-if-error=2628000" 39 | } 40 | ] 41 | } 42 | ] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /gatsby-config.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config({ 2 | path: `.env.all`, 3 | }); 4 | 5 | const path = require('path'); 6 | 7 | module.exports = { 8 | siteMetadata: { 9 | title: `Builder.io`, 10 | description: ``, 11 | author: `Aziz Abbas`, 12 | }, 13 | plugins: [ 14 | `gatsby-plugin-typescript`, 15 | `gatsby-plugin-react-helmet`, 16 | { 17 | resolve: `gatsby-plugin-layout`, 18 | options: { 19 | component: require.resolve(`./src/components/molecules/layout.tsx`), 20 | }, 21 | }, 22 | { 23 | resolve: `gatsby-theme-shopify-manager`, 24 | options: { 25 | shopName: process.env.GATSBY_SHOP_NAME, 26 | accessToken: process.env.GATSBY_SHOPIFY_ACCESS_TOKEN, 27 | }, 28 | }, 29 | `gatsby-plugin-theme-ui`, 30 | { 31 | resolve: 'gatsby-theme-style-guide', 32 | options: { 33 | // sets path for generated page 34 | basePath: '/design-system', 35 | }, 36 | }, 37 | 38 | 'gatsby-transformer-sharp', 39 | 'gatsby-plugin-sharp', 40 | { 41 | resolve: '@builder.io/gatsby', 42 | options: { 43 | publicAPIKey: process.env.BUILDER_API_KEY, 44 | /* to allow live preview editing on localhost*/ 45 | custom404Dev: path.resolve('./src/pages/404.tsx'), 46 | templates: { 47 | /* Render every `page` model as a new page using the /page.tsx template 48 | /* based on the URL provided in Builder.io 49 | */ 50 | page: path.resolve('./src/templates/page.tsx'), 51 | }, 52 | }, 53 | }, 54 | { 55 | resolve: `gatsby-plugin-typegen`, 56 | options: { 57 | emitSchema: { 58 | './src/__generated__/gatsby-schema.graphql': true, 59 | './src/__generated__/gatsby-introspection.json': true, 60 | }, 61 | emitPluginDocuments: { 62 | './src/__generated__/gatsby-plugin-documents.graphql': true, 63 | }, 64 | }, 65 | }, 66 | { 67 | resolve: `gatsby-plugin-loadable-components-ssr`, 68 | options: { 69 | // Whether replaceHydrateFunction should call ReactDOM.hydrate or ReactDOM.render 70 | // Defaults to ReactDOM.render on develop and ReactDOM.hydrate on build 71 | useHydrate: true, 72 | }, 73 | }, 74 | ], 75 | }; 76 | -------------------------------------------------------------------------------- /gatsby-node.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | exports.createPages = ({ graphql, actions }) => { 4 | const { createPage } = actions; 5 | return graphql(` 6 | { 7 | allShopifyProduct(limit: 70) { 8 | edges { 9 | node { 10 | handle 11 | } 12 | } 13 | } 14 | } 15 | `).then(result => { 16 | result.data.allShopifyProduct.edges.forEach(({ node }) => { 17 | createPage({ 18 | path: `/product/${node.handle}/`, 19 | component: path.resolve(`./src/templates/product-page.tsx`), 20 | context: { 21 | handle: node.handle, 22 | }, 23 | }); 24 | }); 25 | }); 26 | }; 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gatsby-builder-shopify", 3 | "scripts": { 4 | "prettier": "prettier", 5 | "prettier-staged": "git diff --staged --name-only | xargs prettier --write", 6 | "build": "gatsby build", 7 | "develop": "gatsby develop", 8 | "serve": "gatsby serve", 9 | "clean": "gatsby clean", 10 | "deploy": "firebase deploy" 11 | }, 12 | "dependencies": { 13 | "@builder.io/gatsby": "^1.0.10", 14 | "@builder.io/react": "^1.1.34-10", 15 | "@builder.io/widgets": "^1.2.17", 16 | "@emotion/cache": "^10.0.29", 17 | "@emotion/core": "^10.0.35", 18 | "@loadable/component": "^5.13.2", 19 | "@theme-ui/components": "^0.3.1", 20 | "@theme-ui/preset-base": "^0.3.0", 21 | "@types/cheerio": "^0.22.22", 22 | "@types/react-visibility-sensor": "^5.1.0", 23 | "cheerio": "^1.0.0-rc.3", 24 | "gatsby": "^2.20.12", 25 | "gatsby-image": "^2.3.1", 26 | "gatsby-link": "^2.4.15", 27 | "gatsby-plugin-google-fonts": "^1.0.1", 28 | "gatsby-plugin-layout": "^1.2.1", 29 | "gatsby-plugin-loadable-components-ssr": "^2.1.0", 30 | "gatsby-plugin-offline": "^3.2.13", 31 | "gatsby-plugin-react-helmet": "^3.3.6", 32 | "gatsby-plugin-sharp": "^2.6.36", 33 | "gatsby-plugin-theme-ui": "^0.3.0", 34 | "gatsby-plugin-typegen": "^2.0.0", 35 | "gatsby-plugin-typescript": "^2.3.1", 36 | "gatsby-source-graphql": "^2.1.32", 37 | "gatsby-source-shopify": "^3.2.32", 38 | "gatsby-theme-shopify-manager": "^0.1.8", 39 | "gatsby-theme-style-guide": "^0.3.1", 40 | "gatsby-transformer-sharp": "^2.5.15", 41 | "graphql": "14.6.0", 42 | "normalize.css": "^8.0.1", 43 | "preact": "^10.5.5", 44 | "react": "^16.13.1", 45 | "react-dom": "^16.13.1", 46 | "react-helmet": "^6.1.0", 47 | "react-visibility-sensor": "^5.1.1", 48 | "theme-ui": "^0.3.1" 49 | }, 50 | "resolutions": { 51 | "graphql": "14.6.0" 52 | }, 53 | "devDependencies": { 54 | "@types/node": "^13.11.0", 55 | "@types/react": "^16.9.32", 56 | "@types/react-dom": "^16.9.6", 57 | "@types/react-helmet": "^6.0.0", 58 | "@types/theme-ui": "^0.3.1", 59 | "gatsby-plugin-ts-config": "^1.1.0", 60 | "gatsby-plugin-tslint": "^0.0.2", 61 | "prettier": "^2.0.2", 62 | "typescript": "^3.8.3", 63 | "yarn": "^1.22.5" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/components/atoms/link.tsx: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from 'theme-ui'; 3 | import { Link as GatsbyLink } from 'gatsby'; 4 | 5 | export interface LinkProps { 6 | isButton?: boolean; 7 | url: string; 8 | } 9 | 10 | const Link: React.FC = ({ isButton, url, children, ...props }) => { 11 | return isButton ? ( 12 | 31 | {children} 32 | 33 | ) : ( 34 | 45 | {children} 46 | 47 | ); 48 | }; 49 | 50 | export default Link; 51 | -------------------------------------------------------------------------------- /src/components/atoms/no-ssr.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | 3 | const NoSSR: React.FC<{ skeleton?: React.ReactNode }> = ({ children, skeleton }) => { 4 | const [render, setRender] = useState(false); 5 | useEffect(() => setRender(true), []); 6 | if (render) { 7 | return <>{children}; 8 | } 9 | if (skeleton) { 10 | return <>{skeleton}; 11 | } 12 | return null; 13 | }; 14 | export default NoSSR; 15 | -------------------------------------------------------------------------------- /src/components/atoms/option-picker.tsx: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from 'theme-ui'; 3 | import { Select, Label } from '@theme-ui/components'; 4 | export interface OptionPickerProps { 5 | name: string; 6 | options?: Readonly>; 7 | onChange: React.ChangeEventHandler; 8 | selected: string; 9 | } 10 | 11 | const OptionPicker: React.FC = ({ name, options, onChange, selected }) => { 12 | return ( 13 |
14 | 15 | 22 |
23 | ); 24 | }; 25 | 26 | export default OptionPicker; 27 | -------------------------------------------------------------------------------- /src/components/atoms/product-card.tsx: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import React from 'react'; 3 | import { Styled, jsx } from 'theme-ui'; 4 | import Img from 'gatsby-image'; 5 | import { Card, Text } from '@theme-ui/components'; 6 | import Link from './link'; 7 | import { getPrice } from '../../utils/product'; 8 | 9 | export interface ProductCardProps { 10 | title?: string; 11 | slug?: string; 12 | price: number | string; 13 | image: any; 14 | currency?: string; 15 | imageLoading?: `auto` | `lazy` | `eager`; 16 | } 17 | 18 | const ProductCard: React.FC = ({ 19 | title, 20 | slug, 21 | price, 22 | image, 23 | currency, 24 | imageLoading = 'eager', 25 | }) => { 26 | return ( 27 | 35 |
36 | {image && } 37 |
38 | {title} 39 | {getPrice(String(price), currency || 'USD')} 40 | 41 | View 42 | 43 |
44 | ); 45 | }; 46 | 47 | export default ProductCard; 48 | -------------------------------------------------------------------------------- /src/components/atoms/product-grid.tsx: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from 'theme-ui'; 3 | import { Grid } from '@theme-ui/components'; 4 | import ProductCard from './product-card'; 5 | 6 | export interface ProductGridProps { 7 | products: Readonly; 8 | imageLoading?: 'lazy' | 'eager' | 'auto'; 9 | } 10 | const ProductGrid: React.FC = ({ products, imageLoading }) => ( 11 | 19 | {products.map(product => ( 20 | 29 | ))} 30 | 31 | ); 32 | 33 | export default ProductGrid; 34 | -------------------------------------------------------------------------------- /src/components/atoms/seo.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * SEO component that queries for data with 3 | * See: https://www.gatsbyjs.org/docs/use-static-query/ 4 | */ 5 | 6 | import React from 'react'; 7 | import { Helmet, HelmetProps, MetaProps } from 'react-helmet'; 8 | import useSiteMetadata from '../../hooks/use-site-metadata'; 9 | 10 | export interface SEOProps extends HelmetProps { 11 | description?: string; 12 | lang?: string; 13 | image?: string; 14 | } 15 | 16 | const SEO: React.FC = ({ lang, description, image, meta = [], ...rest }) => { 17 | const siteMetadata = useSiteMetadata(); 18 | const metaDescription = description || siteMetadata?.description; 19 | const helmetProps: HelmetProps = { 20 | titleTemplate: `%s | ${siteMetadata?.title}`, 21 | htmlAttributes: { 22 | lang: lang || 'en', 23 | }, 24 | ...rest, 25 | }; 26 | const helmetMeta: MetaProps[] = [ 27 | { 28 | name: `description`, 29 | content: metaDescription, 30 | }, 31 | { 32 | property: `og:title`, 33 | content: rest.title, 34 | }, 35 | { 36 | property: `og:description`, 37 | content: metaDescription, 38 | }, 39 | { 40 | property: `og:type`, 41 | content: `website`, 42 | }, 43 | ...(image 44 | ? [ 45 | { 46 | itemProp: 'image', 47 | content: image, 48 | }, 49 | { 50 | property: 'og:image', 51 | content: image, 52 | }, 53 | { 54 | name: 'twitter:image', 55 | content: image, 56 | }, 57 | ] 58 | : []), 59 | ...meta, 60 | ]; 61 | return ; 62 | }; 63 | 64 | export default SEO; 65 | -------------------------------------------------------------------------------- /src/components/atoms/thumbnail.tsx: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from 'theme-ui'; 3 | import Img from 'gatsby-image'; 4 | 5 | export interface ThumbnailProps { 6 | src: any; // for now; 7 | onClick?: React.MouseEventHandler; 8 | name: string; 9 | } 10 | 11 | const Thumbnail: React.FC = ({ src, onClick, name }) => { 12 | return ( 13 | 28 | ); 29 | }; 30 | 31 | export default Thumbnail; 32 | -------------------------------------------------------------------------------- /src/components/atoms/under-the-fold.tsx: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { useRef } from 'react'; 3 | import { Spinner, jsx } from 'theme-ui'; 4 | import VisibilitySensor from 'react-visibility-sensor'; 5 | interface Shape { 6 | top?: number; 7 | left?: number; 8 | bottom?: number; 9 | right?: number; 10 | } 11 | 12 | export interface UnderTheFoldProps { 13 | onChange?: (isVisible: boolean) => void; 14 | active?: boolean; 15 | partialVisibility?: boolean; 16 | offset?: Shape; 17 | minTopValue?: number; 18 | intervalCheck?: boolean; 19 | intervalDelay?: number; 20 | scrollCheck?: boolean; 21 | scrollDelay?: number; 22 | scrollThrottle?: number; 23 | resizeCheck?: boolean; 24 | resizeDelay?: number; 25 | resizeThrottle?: number; 26 | containment?: any; 27 | delayedCall?: boolean; 28 | children: (args: { isVisible: boolean; visibilityRect?: Shape }) => React.ReactNode; 29 | } 30 | 31 | const UnderTheFold: React.FC = props => { 32 | const seenRef = useRef(); 33 | return ( 34 | 35 | {({ isVisible, visibilityRect }) => { 36 | if (isVisible || seenRef.current) { 37 | seenRef.current = true; 38 | return props.children({ isVisible, visibilityRect }); 39 | } 40 | return ( 41 |
49 | 50 |
51 | ); 52 | }} 53 |
54 | ); 55 | }; 56 | export default UnderTheFold; 57 | -------------------------------------------------------------------------------- /src/components/molecules/aware-builder-component.tsx: -------------------------------------------------------------------------------- 1 | import { Builder, builder, BuilderComponent } from '@builder.io/react'; 2 | import { BuilderPageProps } from '@builder.io/react/src/components/builder-page.component'; 3 | import '@builder.io/widgets'; 4 | import React from 'react'; 5 | import Link from '../atoms/link'; 6 | import { useCartCount, useAddItemToCart } from 'gatsby-theme-shopify-manager/src'; 7 | 8 | const apiKey = process.env.GATSBY_BUILDER_API_KEY; 9 | builder.init(apiKey!); 10 | Builder.isStatic = true; 11 | 12 | const AwareBuilderComponent: React.FC> = props => { 13 | const cartCount = useCartCount(); 14 | const addItem = useAddItemToCart(); 15 | return ( 16 | { 18 | const internal = props.target !== '_blank' && /^\/(?!\/)/.test(props.href!); 19 | if (internal) { 20 | return ; 21 | } 22 | return ; 23 | }} 24 | context={{ cartCount, addItem }} 25 | {...props} 26 | /> 27 | ); 28 | }; 29 | 30 | export default AwareBuilderComponent; 31 | -------------------------------------------------------------------------------- /src/components/molecules/footer/footer.builder.ts: -------------------------------------------------------------------------------- 1 | import { Builder, builder } from '@builder.io/react'; 2 | import loadable from '@loadable/component'; 3 | 4 | builder.init(process.env.GATSBY_BUILDER_API_KEY!); 5 | 6 | const LazyFooter = loadable(() => import('./footer')); 7 | 8 | Builder.registerComponent(LazyFooter, { 9 | name: 'Footer', 10 | description: 'Used at the bottom of the page', 11 | inputs: [ 12 | { 13 | name: 'footerTitle', 14 | type: 'text', 15 | defaultValue: 'Builder.io footer', 16 | }, 17 | ], 18 | }); 19 | -------------------------------------------------------------------------------- /src/components/molecules/footer/footer.tsx: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { Box, Styled, jsx } from 'theme-ui'; 3 | 4 | const Footer: React.FC = () => ( 5 | 6 |
7 | 8 | © {new Date().getFullYear()} Built with 9 | {` `} 10 | Builder.io 11 | {` and `} 12 | Gatsby 13 | {` and `} 14 | Shopify. 15 | 16 |
17 |
18 | ); 19 | 20 | export default Footer; 21 | -------------------------------------------------------------------------------- /src/components/molecules/header/header.builder.ts: -------------------------------------------------------------------------------- 1 | import { Builder, builder } from '@builder.io/react'; 2 | import loadable from '@loadable/component'; 3 | 4 | builder.init(process.env.GATSBY_BUILDER_API_KEY!); 5 | 6 | const LazyHeader = loadable(() => import('./header')); 7 | 8 | Builder.registerComponent(LazyHeader, { 9 | name: 'Header', 10 | description: 'used on top of the page, included in SSR and affects SEO', 11 | inputs: [ 12 | { 13 | name: 'siteTitle', 14 | type: 'text', 15 | defaultValue: 'Builder.io Swag Store', 16 | }, 17 | ], 18 | }); 19 | -------------------------------------------------------------------------------- /src/components/molecules/header/header.tsx: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx, Styled } from 'theme-ui'; 3 | import Link from '../../atoms/link'; 4 | import { useCartCount } from 'gatsby-theme-shopify-manager/src'; 5 | 6 | const Header: React.FC<{ 7 | siteTitle: string; 8 | }> = ({ siteTitle }) => { 9 | const count = useCartCount(); 10 | return ( 11 | 12 |
23 | 24 | 36 | {siteTitle} 37 | 38 | 39 | 40 | Cart{count ? '*' : ''} 41 | 42 |
43 |
44 | ); 45 | }; 46 | 47 | export default Header; 48 | -------------------------------------------------------------------------------- /src/components/molecules/layout.tsx: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import React from 'react'; 3 | import { Button, ThemeProvider, jsx } from 'theme-ui'; 4 | import { Helmet } from 'react-helmet'; 5 | import theme from '../../gatsby-plugin-theme-ui'; 6 | import './footer/footer.builder'; 7 | import useBuilderHeader from '../../hooks/use-builder-header'; 8 | import AwareBuilderComponent from './aware-builder-component'; 9 | import './header/header.builder'; 10 | import 'normalize.css'; 11 | import useBuilderFooter from '../../hooks/use-builder-footer'; 12 | const Layout: React.FunctionComponent = ({ children }) => { 13 | const header = useBuilderHeader(); 14 | const footer = useBuilderFooter(); 15 | 16 | return ( 17 | 18 | 19 | 20 | 21 | 22 |
30 |
{children}
31 | 32 |
33 |
34 | ); 35 | }; 36 | 37 | export default Layout; 38 | -------------------------------------------------------------------------------- /src/components/organisms/all-products/all-products.builder.ts: -------------------------------------------------------------------------------- 1 | import { Builder, builder } from '@builder.io/react'; 2 | import loadable from '@loadable/component'; 3 | builder.init(process.env.GATSBY_BUILDER_API_KEY!); 4 | 5 | const LazyAllProducts = loadable(() => import('./all-products')); 6 | 7 | Builder.registerComponent(LazyAllProducts, { 8 | name: 'AllProductsGridSSR', 9 | description: 'Contains all products in store, will be included on the page in SSR', 10 | }); 11 | -------------------------------------------------------------------------------- /src/components/organisms/all-products/all-products.tsx: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { Box, Heading, jsx } from 'theme-ui'; 3 | import useAllStaticProducts from '../../../hooks/use-all-products'; 4 | import ProductGrid from '../../atoms/product-grid'; 5 | 6 | const AllProducts = () => { 7 | const products = useAllStaticProducts(); 8 | 9 | return ( 10 | 11 | 12 | All Products 13 | 14 | 15 | 16 | ); 17 | }; 18 | 19 | export default AllProducts; 20 | -------------------------------------------------------------------------------- /src/components/organisms/dev-404.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link as GatsbyLink } from 'gatsby'; 3 | import useAllSitePages from '../../hooks/use-all-site-pages'; 4 | 5 | const Dev404 = () => { 6 | const allPages = useAllSitePages(); 7 | 8 | return ( 9 | 10 |

Custom 404 page

11 |

This is a development only page

12 |
    13 | {allPages.map(item => ( 14 |
  • 15 | 16 | {' '} 17 | {item.path}{' '} 18 | 19 |
  • 20 | ))} 21 |
22 |
23 | ); 24 | }; 25 | 26 | export default Dev404; 27 | -------------------------------------------------------------------------------- /src/components/organisms/latest-products/latest-products-no-ssr.builder.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Builder, builder } from '@builder.io/react'; 3 | import loadable from '@loadable/component'; 4 | import UnderTheFold from '../../atoms/under-the-fold'; 5 | builder.init(process.env.GATSBY_BUILDER_API_KEY!); 6 | 7 | const LazyLatesProducts = loadable(() => import('./latest-products'), { ssr: false }); 8 | 9 | const UnderTheFoldProducts: React.FC = () => ( 10 | {() => } 11 | ); 12 | 13 | Builder.registerComponent(UnderTheFoldProducts, { 14 | name: 'LatestProductsGridNoSSR', 15 | description: 16 | 'Contains latest products in store, will not included on the page in SSR (for better performance)', 17 | }); 18 | -------------------------------------------------------------------------------- /src/components/organisms/latest-products/latest-products.builder.ts: -------------------------------------------------------------------------------- 1 | import { Builder, builder } from '@builder.io/react'; 2 | import loadable from '@loadable/component'; 3 | builder.init(process.env.GATSBY_BUILDER_API_KEY!); 4 | 5 | const LazyAllProducts = loadable(() => import('./latest-products')); 6 | 7 | Builder.registerComponent(LazyAllProducts, { 8 | name: 'LatestProductsGridSSR', 9 | description: 'Contains latest products in store, will be included on the page in SSR', 10 | }); 11 | -------------------------------------------------------------------------------- /src/components/organisms/latest-products/latest-products.tsx: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { Box, Heading, jsx } from 'theme-ui'; 3 | import useRecentStaticProducts from '../../../hooks/use-recent-static-products'; 4 | import ProductGrid from '../../atoms/product-grid'; 5 | 6 | const LatestProducts = () => { 7 | const products = useRecentStaticProducts(); 8 | 9 | return ( 10 | 11 | Latest Products 12 | 13 | 14 | ); 15 | }; 16 | 17 | export default LatestProducts; 18 | -------------------------------------------------------------------------------- /src/components/organisms/product-page-details/product-page-details.builder.ts: -------------------------------------------------------------------------------- 1 | import { Builder, builder } from '@builder.io/react'; 2 | import loadable from '@loadable/component'; 3 | builder.init(process.env.GATSBY_BUILDER_API_KEY!); 4 | 5 | const LazyProductPageDetails = loadable(() => import('./product-page-details')); 6 | 7 | Builder.registerComponent(LazyProductPageDetails, { 8 | name: 'ProductPageDetails', 9 | description: 'Dynamic product details, included in SSR, should only be used in product pages', 10 | defaults: { 11 | bindings: { 12 | 'component.options.product': 'state.product', 13 | 'component.options.title': 'state.product.title', 14 | 'component.options.description': 'state.product.descriptionHtml', 15 | }, 16 | }, 17 | inputs: [ 18 | { 19 | name: 'description', 20 | type: 'richText', 21 | }, 22 | { 23 | name: 'title', 24 | type: 'text', 25 | }, 26 | ], 27 | }); 28 | -------------------------------------------------------------------------------- /src/components/organisms/product-page-details/product-page-details.tsx: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { useState, useEffect, useMemo, Fragment } from 'react'; 3 | import { Styled, jsx } from 'theme-ui'; 4 | import Img from 'gatsby-image'; 5 | import { Grid, Button, Alert, Close } from '@theme-ui/components'; 6 | import SEO from '../../atoms/seo'; 7 | import Thumbnail from '../../atoms/thumbnail'; 8 | import OptionPicker from '../../atoms/option-picker'; 9 | import { 10 | prepareVariantsWithOptions, 11 | prepareVariantsImages, 12 | getPrice, 13 | } from '../../../utils/product'; 14 | import { useAddItemToCart } from 'gatsby-theme-shopify-manager/src'; 15 | 16 | export interface ProductPageDetailsProps { 17 | product: GatsbyTypes.ShopifyProduct; 18 | title: string; 19 | description: string; 20 | } 21 | 22 | const ProductPageDetails: React.FC = ({ product, title, description }) => { 23 | const colors = product?.options?.find(option => option?.name?.toLowerCase() === 'color')?.values!; 24 | const sizes = product?.options?.find(option => option?.name?.toLowerCase() === 'size')?.values; 25 | 26 | const variants = useMemo(() => prepareVariantsWithOptions(product!.variants! as any), [ 27 | product.variants, 28 | ]); 29 | const images = useMemo(() => prepareVariantsImages(variants, 'color'), [variants]); 30 | 31 | if (images.length < 1) { 32 | throw new Error('Must have at least one product image!'); 33 | } 34 | 35 | const addItemToCart = useAddItemToCart(); 36 | const [variant, setVariant] = useState(variants[0]); 37 | const [color, setColor] = useState(variant.color); 38 | const [size, setSize] = useState(variant.size); 39 | const [addedToCartMessage, setAddedToCartMessage] = useState(''); 40 | 41 | useEffect(() => { 42 | const newVariant = variants.find(variant => { 43 | return variant.size === size && variant.color === color; 44 | }); 45 | 46 | if (variant.shopifyId !== newVariant.shopifyId) { 47 | setVariant(newVariant); 48 | } 49 | }, [size, color, variants, variant.shopifyId]); 50 | 51 | const gallery = 52 | images.length > 1 ? ( 53 | 54 | {images.map(({ src, color }) => ( 55 | setColor(color)} /> 56 | ))} 57 | 58 | ) : null; 59 | 60 | async function handleAddToCart() { 61 | try { 62 | await addItemToCart(variant.shopifyId, 1); 63 | setAddedToCartMessage('🛒 Added to your cart!'); 64 | } catch (e) { 65 | setAddedToCartMessage('There was a problem adding this to your cart'); 66 | } 67 | } 68 | 69 | return ( 70 | 71 | 75 | {addedToCartMessage ? ( 76 | 77 | {addedToCartMessage} 78 | setAddedToCartMessage('')} 87 | /> 88 | 89 | ) : null} 90 | 91 |
92 |
99 | 104 |
105 | {gallery} 106 |
107 |
108 | 109 | {title || product.title} 110 | 111 | { 112 | /** 113 | * TODO: load this from api client side for selected variant 114 | */ 115 | getPrice( 116 | product.priceRange?.maxVariantPrice?.amount!, 117 | product.priceRange?.maxVariantPrice?.currencyCode! 118 | ) 119 | } 120 | 121 | 122 |
123 |
124 | 125 | {colors?.length && ( 126 | setColor(event.target.value)} 132 | /> 133 | )} 134 | {sizes?.length && ( 135 | setSize(event.target.value)} 141 | /> 142 | )} 143 | 144 |
145 | 148 |
149 | 150 | 151 | ); 152 | }; 153 | 154 | export default ProductPageDetails; 155 | -------------------------------------------------------------------------------- /src/components/providers/all-products-provider.builder.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Builder } from '@builder.io/react'; 3 | import loadable from '@loadable/component'; 4 | import StateProvider, { StateProviderProps } from './state-provider'; 5 | const AllProducts: typeof import('./all-products-provider').default = loadable( 6 | () => import('./all-products-provider') 7 | ); 8 | 9 | const AllProductsLazyProvider = ({ state, ...rest }: StateProviderProps) => { 10 | return ( 11 | 12 | {allProducts => } 13 | 14 | ); 15 | }; 16 | 17 | Builder.registerComponent(AllProductsLazyProvider, { 18 | name: 'All Product State Provider', 19 | canHaveChildren: true, 20 | noWrap: true, 21 | }); 22 | -------------------------------------------------------------------------------- /src/components/providers/all-products-provider.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import useAllStaticProducts from '../../hooks/use-all-products'; 3 | 4 | export const AllProductsWrapper: React.FC<{ 5 | children: (products: GatsbyTypes.ShopifyProduct[]) => React.ReactElement; 6 | }> = ({ children }) => { 7 | const products = useAllStaticProducts(); 8 | 9 | return children(products.slice()) || null; 10 | }; 11 | 12 | export default AllProductsWrapper; 13 | -------------------------------------------------------------------------------- /src/components/providers/state-provider.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { BuilderBlockComponent, BuilderStoreContext, BuilderElement } from '@builder.io/react'; 3 | 4 | export interface StateProviderProps { 5 | state: any; 6 | builderBlock: BuilderElement; 7 | [key: string]: any; 8 | } 9 | 10 | const StateProvider: React.FC = props => ( 11 | 12 | {state => ( 13 | 26 | {props.builderBlock && 27 | props.builderBlock.children && 28 | props.builderBlock.children.map((block, index) => ( 29 | 30 | ))} 31 | {props.children} 32 | 33 | )} 34 | 35 | ); 36 | 37 | export default StateProvider; 38 | -------------------------------------------------------------------------------- /src/gatsby-plugin-theme-ui/index.ts: -------------------------------------------------------------------------------- 1 | import base from '@theme-ui/preset-base'; 2 | 3 | export default { 4 | initialColorModeName: 'light', 5 | ...base, 6 | colors: { 7 | text: '#557571', 8 | background: '#eeeeee', 9 | primary: '#596e79', 10 | secondary: '#3b6978', 11 | }, 12 | styles: { 13 | ...base.styles, 14 | a: { 15 | color: 'text', 16 | textDecoration: 'none', 17 | '&:hover': { 18 | color: 'secondary', 19 | textDecoration: 'underline', 20 | }, 21 | }, 22 | hr: { 23 | display: 'block', 24 | height: '1px', 25 | border: 0, 26 | borderTop: '1px solid', 27 | borderColor: 'secondary', 28 | opacity: '0.3', 29 | }, 30 | }, 31 | fontWeights: { 32 | medium: 600, 33 | bold: 800, 34 | }, 35 | text: { 36 | bold: { 37 | fontWeight: 600, 38 | }, 39 | }, 40 | alerts: { 41 | primary: { 42 | border: '1px solid', 43 | borderColor: 'text', 44 | color: 'background', 45 | bg: 'text', 46 | fontWeight: 'normal', 47 | }, 48 | }, 49 | cards: { 50 | primary: { 51 | padding: 2, 52 | borderRadius: 4, 53 | }, 54 | compact: { 55 | padding: 1, 56 | borderRadius: 2, 57 | border: '1px solid', 58 | borderColor: 'text', 59 | }, 60 | }, 61 | buttons: { 62 | primary: { 63 | color: 'background', 64 | bg: 'primary', 65 | fontWeight: 600, 66 | '&:hover': { 67 | bg: 'secondary', 68 | cursor: 'pointer', 69 | }, 70 | }, 71 | secondary: { 72 | color: 'background', 73 | bg: 'primary', 74 | }, 75 | link: { 76 | color: 'text', 77 | textDecoration: 'none', 78 | padding: 0, 79 | background: 'transparent', 80 | '&:hover': { 81 | textDecoration: 'underline', 82 | color: 'secondary', 83 | cursor: 'pointer', 84 | }, 85 | }, 86 | }, 87 | }; 88 | -------------------------------------------------------------------------------- /src/hooks/use-all-products.ts: -------------------------------------------------------------------------------- 1 | import { useStaticQuery, graphql } from 'gatsby'; 2 | const productStaticQuery = graphql` 3 | query productQuery { 4 | allShopifyProduct(limit: 50) { 5 | nodes { 6 | title 7 | handle 8 | images { 9 | originalSrc 10 | localFile { 11 | childImageSharp { 12 | fluid(maxWidth: 290) { 13 | ...GatsbyImageSharpFluid_withWebp_noBase64 14 | } 15 | } 16 | } 17 | } 18 | priceRange { 19 | maxVariantPrice { 20 | amount 21 | currencyCode 22 | } 23 | } 24 | } 25 | } 26 | } 27 | `; 28 | 29 | const useAllStaticProducts = () => { 30 | const data = useStaticQuery(productStaticQuery); 31 | 32 | return data.allShopifyProduct.nodes; 33 | }; 34 | 35 | export default useAllStaticProducts; 36 | -------------------------------------------------------------------------------- /src/hooks/use-all-site-pages.ts: -------------------------------------------------------------------------------- 1 | import { useStaticQuery, graphql } from 'gatsby'; 2 | 3 | const allSitePagesQuery = graphql` 4 | query PagesQuery { 5 | allSitePage(filter: { path: { ne: "/dev-404-page/" } }) { 6 | nodes { 7 | path 8 | component 9 | } 10 | } 11 | } 12 | `; 13 | 14 | const useAllSitePages = () => { 15 | const data = useStaticQuery(allSitePagesQuery); 16 | 17 | return data.allSitePage.nodes; 18 | }; 19 | 20 | export default useAllSitePages; 21 | -------------------------------------------------------------------------------- /src/hooks/use-builder-footer.ts: -------------------------------------------------------------------------------- 1 | import { useStaticQuery, graphql } from 'gatsby'; 2 | 3 | const builderFooterQuery = graphql` 4 | query Footer { 5 | allBuilderModels { 6 | oneFooter(options: { noTraverse: false }) { 7 | content 8 | } 9 | } 10 | } 11 | `; 12 | 13 | const useBuilderFooter = () => { 14 | const data = useStaticQuery(builderFooterQuery); 15 | 16 | return data.allBuilderModels.oneFooter?.content; 17 | }; 18 | 19 | export default useBuilderFooter; 20 | -------------------------------------------------------------------------------- /src/hooks/use-builder-header.ts: -------------------------------------------------------------------------------- 1 | import { useStaticQuery, graphql } from 'gatsby'; 2 | 3 | const builderHeaderQuery = graphql` 4 | query Header { 5 | allBuilderModels { 6 | oneHeader(options: { noTraverse: false }) { 7 | content 8 | } 9 | } 10 | } 11 | `; 12 | 13 | const useBuilderHeader = () => { 14 | const data = useStaticQuery(builderHeaderQuery); 15 | 16 | return data.allBuilderModels.oneHeader?.content; 17 | }; 18 | 19 | export default useBuilderHeader; 20 | -------------------------------------------------------------------------------- /src/hooks/use-recent-static-products.ts: -------------------------------------------------------------------------------- 1 | import { useStaticQuery, graphql } from 'gatsby'; 2 | const latestStaticQuery = graphql` 3 | query latest { 4 | allShopifyProduct(sort: { fields: [createdAt], order: DESC }, limit: 9) { 5 | nodes { 6 | title 7 | handle 8 | images { 9 | localFile { 10 | childImageSharp { 11 | fluid(maxWidth: 290) { 12 | ...GatsbyImageSharpFluid_withWebp_noBase64 13 | } 14 | } 15 | } 16 | } 17 | priceRange { 18 | maxVariantPrice { 19 | amount 20 | currencyCode 21 | } 22 | } 23 | } 24 | } 25 | } 26 | `; 27 | 28 | const useRecentStaticProducts = () => { 29 | const data = useStaticQuery(latestStaticQuery); 30 | 31 | return data.allShopifyProduct.nodes; 32 | }; 33 | 34 | export default useRecentStaticProducts; 35 | -------------------------------------------------------------------------------- /src/hooks/use-site-metadata.ts: -------------------------------------------------------------------------------- 1 | import { useStaticQuery, graphql } from 'gatsby'; 2 | 3 | const siteMetadataStaticQuery = graphql` 4 | query metadata { 5 | site { 6 | siteMetadata { 7 | title 8 | description 9 | author 10 | } 11 | } 12 | } 13 | `; 14 | 15 | const useSiteMetadata = () => { 16 | const data = useStaticQuery(siteMetadataStaticQuery); 17 | 18 | return data.site?.siteMetadata; 19 | }; 20 | 21 | export default useSiteMetadata; 22 | -------------------------------------------------------------------------------- /src/html.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from 'react'; 2 | import cheerio from 'cheerio'; 3 | 4 | /** 5 | * 6 | * Gatsby does a two-pass render for HTML. It loops through your pages first 7 | * rendering only the body and then takes the result body HTML string and 8 | * passes it as the `body` prop to this component to complete the render. 9 | */ 10 | 11 | const postProcess = (body: string) => { 12 | let globalStyles = ''; 13 | 14 | if (body.includes(' { 19 | const str = $(element).html(); 20 | const styles = cheerio.load(String(str))('style'); 21 | globalStyles += styles 22 | .toArray() 23 | .map(el => $(el).html()) 24 | .join(' '); 25 | }); 26 | } 27 | return { body, globalStyles }; 28 | }; 29 | 30 | interface HTMLProps { 31 | htmlAttributes: {}; 32 | headComponents: ReactNode[]; 33 | bodyAttributes: {}; 34 | preBodyComponents: ReactNode[]; 35 | body: string; 36 | postBodyComponents: ReactNode[]; 37 | } 38 | const HTML: React.FC = props => { 39 | const { body, globalStyles } = postProcess(props.body); 40 | return ( 41 | 42 | 43 | 44 | 45 | 46 | {props.headComponents} 47 | 48 | 49 | {props.preBodyComponents} 50 | 51 |
52 | {props.postBodyComponents} 53 | 54 | 55 | ); 56 | }; 57 | 58 | export default HTML; 59 | -------------------------------------------------------------------------------- /src/pages/404.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Container } from 'theme-ui'; 3 | import SEO from '../components/atoms/seo'; 4 | import { Builder } from '@builder.io/react'; 5 | import loadable from '@loadable/component'; 6 | import NoSSR from '../components/atoms/no-ssr'; 7 | import PageTemplate from '../templates/page'; 8 | 9 | const AsyncDev404 = loadable(() => import('../components/organisms/dev-404')); 10 | 11 | const NotFound: React.FC = () => ( 12 |
13 | 14 | 15 |

Page not found :(

16 |
17 |
18 | ); 19 | 20 | const FourOhFour: React.FC = () => { 21 | if (Builder.isEditing || Builder.isPreviewing) { 22 | return ; 23 | } 24 | return {process.env.NODE_ENV === 'development' ? : }; 25 | }; 26 | 27 | export default FourOhFour; 28 | -------------------------------------------------------------------------------- /src/pages/cart.tsx: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { Styled, jsx } from 'theme-ui'; 3 | import React from 'react'; 4 | import { Grid, Divider, Button, Card, Text, Image } from '@theme-ui/components'; 5 | import Link from '../components/atoms/link'; 6 | import SEO from '../components/atoms/seo'; 7 | import { 8 | useCartItems, 9 | useCheckoutUrl, 10 | useCart, 11 | useUpdateItemQuantity, 12 | } from 'gatsby-theme-shopify-manager/src'; 13 | import NoSSR from '../components/atoms/no-ssr'; 14 | import builder from '@builder.io/react'; 15 | 16 | const CartPage = () => { 17 | const lineItems = useCartItems(); 18 | const updateItemQuantity = useUpdateItemQuantity(); 19 | // handing over session ID will allow you to do conversion tracking cross domains in Builder 20 | // otherwise not necessary 21 | const checkoutUrl = useCheckoutUrl() + `&builder.overrideSessionId=${builder.sessionId}`; 22 | const cart = useCart(); 23 | const { tax, total } = getCartTotals(cart); 24 | 25 | function getCartTotals(cart: ShopifyBuy.Cart | null) { 26 | if (cart == null) { 27 | return { tax: '-', total: '-' }; 28 | } 29 | 30 | const total = cart.subtotalPrice ? `$${Number(cart.subtotalPrice).toFixed(2)}` : '-'; 31 | 32 | return { 33 | tax: '-', 34 | total, 35 | }; 36 | } 37 | 38 | async function removeFromCart(variantId: string) { 39 | try { 40 | await updateItemQuantity(variantId, 0); 41 | } catch (e) { 42 | console.log(e); 43 | } 44 | } 45 | 46 | const getPrice = (price: any, currency: string) => 47 | Intl.NumberFormat(undefined, { 48 | currency, 49 | minimumFractionDigits: 2, 50 | style: 'currency', 51 | }).format(parseFloat(price ? price : 0)); 52 | 53 | const LineItem: React.FC<{ item: /*ShopifyBuy.LineItem todo: check if updated types*/ any }> = ({ 54 | item, 55 | }) => ( 56 | 57 |
58 |
59 | {item.variant.image.altText} 60 |
61 |
62 |
63 | 67 | {item.title} 68 | 69 | 70 |
  • 71 | Quantity: 72 | {item.quantity} 73 |
  • 74 | {item.variant.selectedOptions.map((option: any) => ( 75 |
  • 76 | {option.name}:{option.value} 77 |
  • 78 | ))} 79 |
    80 |
    81 | 88 | 95 | {getPrice(item.variant.priceV2.amount, item.variant.priceV2.currencyCode || 'USD')} 96 | 97 |
    98 | ); 99 | 100 | const emptyCart = ( 101 | 102 | 103 | Cart 104 | Your shopping cart is empty. 105 | 106 | ); 107 | 108 | const skeleton = todo; 109 | 110 | return ( 111 | 112 | {lineItems.length < 1 ? ( 113 | emptyCart 114 | ) : ( 115 | 116 | 117 | Cart 118 | {lineItems.map(item => ( 119 | 120 | 121 | 122 | 123 | ))} 124 |
    158 | 159 | )} 160 | 161 | ); 162 | }; 163 | 164 | export default CartPage; 165 | -------------------------------------------------------------------------------- /src/templates/page.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { graphql } from 'gatsby'; 3 | import SEO from '../components/atoms/seo'; 4 | import AwareBuilderComponent from '../components/molecules/aware-builder-component'; 5 | import '../components/organisms/all-products/all-products.builder'; 6 | import '../components/organisms/latest-products/latest-products.builder'; 7 | import '../components/organisms/latest-products/latest-products-no-ssr.builder'; 8 | import '../components/providers/all-products-provider.builder'; 9 | 10 | export interface PageProps { 11 | data?: GatsbyTypes.Query; 12 | } 13 | 14 | const defaultDescription = 'Edit this in your entry for a better SEO'; 15 | const defaultTitle = 'Builder: Drag and Drop Page Building for Any Site'; 16 | 17 | const PageTemplate: React.FC = ({ data, ...rest }) => { 18 | const builderPage = data?.allBuilderModels?.onePage?.content; 19 | const seo = { 20 | title: (builderPage && builderPage.data.title) || defaultTitle, 21 | description: (builderPage && builderPage.data.description) || defaultDescription, 22 | keywords: (builderPage && builderPage.data.keywords) || [], 23 | image: builderPage && builderPage.data.image, 24 | }; 25 | 26 | return ( 27 | <> 28 | 29 | 30 | 31 | ); 32 | }; 33 | 34 | export default PageTemplate; 35 | 36 | export const query = graphql` 37 | query onePage($path: String!) { 38 | allBuilderModels { 39 | onePage(target: { urlPath: $path }, options: { cachebust: true, noTraverse: false }) { 40 | content 41 | } 42 | } 43 | } 44 | `; 45 | -------------------------------------------------------------------------------- /src/templates/product-page.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { graphql } from 'gatsby'; 3 | import AwareBuilderComponent from '../components/molecules/aware-builder-component'; 4 | import '../components/organisms/product-page-details/product-page-details.builder'; 5 | import '../components/organisms/latest-products/latest-products-no-ssr.builder'; 6 | import '../components/organisms/latest-products/latest-products.builder'; 7 | 8 | export interface ProductPageProps { 9 | data: GatsbyTypes.Query; 10 | } 11 | 12 | const ProductPage: React.FC = ({ 13 | data: { shopifyProduct, allBuilderModels }, 14 | }) => { 15 | const product = shopifyProduct!; 16 | const content = allBuilderModels?.oneProductPageTemplate?.content; 17 | return ; 18 | }; 19 | 20 | export default ProductPage; 21 | 22 | export const ProductPageQuery = graphql` 23 | query productPage($handle: String!) { 24 | allBuilderModels { 25 | oneProductPage( 26 | target: { productHandle: $handle } 27 | options: { cachebust: true, noTraverse: false } 28 | ) { 29 | content 30 | } 31 | } 32 | shopifyProduct(handle: { eq: $handle }) { 33 | id 34 | title 35 | description 36 | priceRange { 37 | maxVariantPrice { 38 | amount 39 | currencyCode 40 | } 41 | } 42 | descriptionHtml 43 | options { 44 | name 45 | values 46 | } 47 | variants { 48 | shopifyId 49 | selectedOptions { 50 | name 51 | value 52 | } 53 | image { 54 | localFile { 55 | childImageSharp { 56 | fluid(maxWidth: 446) { 57 | ...GatsbyImageSharpFluid_withWebp_noBase64 58 | } 59 | } 60 | } 61 | } 62 | } 63 | } 64 | } 65 | `; 66 | -------------------------------------------------------------------------------- /src/typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.svg'; 2 | declare module '*.gif'; 3 | declare module '*.png'; 4 | declare module '*.jpg'; 5 | declare module '@loadable/component'; 6 | declare module '@theme-ui/preset-base' { 7 | import { Theme } from 'theme-ui'; 8 | 9 | export default base as Theme; 10 | } 11 | -------------------------------------------------------------------------------- /src/utils/product.ts: -------------------------------------------------------------------------------- 1 | /* 2 | prepareVariantsWithOptions() 3 | 4 | This function changes the structure of the variants to 5 | more easily get at their options. The original data 6 | structure looks like this: 7 | 8 | { 9 | "shopifyId": "Z2lkOi8vc2hvcGlmeS9Qcm9kdWN0VmFyaWFudC8zMTc4NDQ4MTAzMDE4OA==", 10 | "selectedOptions": [ 11 | { 12 | "name": "Color", 13 | "value": "Red" 14 | }, 15 | { 16 | "name": "Size", 17 | "value": "Small" 18 | } 19 | ] 20 | }, 21 | 22 | This function accepts that and outputs a data structure that looks like this: 23 | 24 | { 25 | "shopifyId": "Z2lkOi8vc2hvcGlmeS9Qcm9kdWN0VmFyaWFudC8zMTc4NDQ4MTAzMDE4OA==", 26 | "color": "Red", 27 | "size": "Small" 28 | }, 29 | */ 30 | 31 | export function prepareVariantsWithOptions( 32 | variants: Readonly 33 | ) { 34 | return variants.map(variant => { 35 | // convert the options to a dictionary instead of an array 36 | const optionsDictionary = variant.selectedOptions?.reduce>( 37 | (options, option) => { 38 | options[`${option?.name?.toLowerCase()}`] = option?.value; 39 | return options; 40 | }, 41 | {} 42 | ); 43 | 44 | // return an object with all of the variant properties + the options at the top level 45 | return { 46 | ...optionsDictionary, 47 | ...variant, 48 | }; 49 | }) as any[]; 50 | } 51 | 52 | export const getPrice = (price: string, currency: string) => 53 | Intl.NumberFormat(undefined, { 54 | currency, 55 | minimumFractionDigits: 2, 56 | style: 'currency', 57 | }).format(parseFloat(price ? price : '0')); 58 | 59 | /* 60 | prepareVariantsImages() 61 | 62 | This function distills the variants images into a non-redundant 63 | group that includes an option 'key' (most likely color). The 64 | datastructure coming into this function looks like this: 65 | 66 | { 67 | "shopifyId": "Z2lkOi8vc2hvcGlmeS9Qcm9kdWN0VmFyaWFudC8zMTc4NDQ4MTAzMDE4OA==", 68 | "image": image1, 69 | "color": "Red", 70 | "size": "Small" 71 | }, 72 | { 73 | "shopifyId": "Z2lkOi8vc2hvcGlmeS9Qcm9kdWN0VmFyaW1l2C8zMTc4NDQ4MTAzMDE4OA==", 74 | "image": image1, 75 | "color": "Red", 76 | "size": "Medium" 77 | }, 78 | 79 | And condenses them so that there is only one unique 80 | image per key value: 81 | 82 | { 83 | "image": image1, 84 | "color": "Red", 85 | }, 86 | */ 87 | 88 | export function prepareVariantsImages( 89 | variants: any[], 90 | // variants: Readonly, 91 | optionKey: any 92 | ): any[] { 93 | // Go through the variants and reduce them into non-redundant 94 | // images by optionKey. Output looks like this: 95 | // { 96 | // [optionKey]: image 97 | // } 98 | const imageDictionary = variants.reduce>((images, variant) => { 99 | images[variant[optionKey]] = variant.image; 100 | return images; 101 | }, {}); 102 | 103 | // prepare an array of image objects that include both the image 104 | // and the optionkey value. 105 | const images = Object.keys(imageDictionary).map(key => { 106 | return { 107 | [optionKey]: key, 108 | src: imageDictionary[key], 109 | }; 110 | }); 111 | 112 | return images; 113 | } 114 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "node", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "baseUrl": "src", 15 | "jsx": "preserve" 16 | }, 17 | 18 | "exclude": ["node_modules"], 19 | "include": [ 20 | "src/**/*" 21 | ] 22 | } 23 | --------------------------------------------------------------------------------