├── .github └── FUNDING.yml ├── src ├── favicon.png ├── styles │ ├── main.scss │ ├── _custom.scss │ ├── _notification.scss │ └── _variables.scss ├── components │ ├── README.md │ └── Navbar.vue ├── layouts │ ├── README.md │ └── Default.vue ├── pages │ ├── README.md │ ├── About.vue │ ├── Collections.vue │ ├── Index.vue │ └── Cart.vue ├── templates │ ├── README.md │ ├── Collection.vue │ └── Product.vue └── main.js ├── .gitignore ├── static └── README.md ├── .eslintrc.js ├── gridsome.server.js ├── package.json ├── LICENSE ├── gridsome.config.js └── README.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: https://paypal.me/travis97 2 | -------------------------------------------------------------------------------- /src/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/travis-r6s/gridsome-starter-shopify/HEAD/src/favicon.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .cache 3 | .DS_Store 4 | src/.temp 5 | node_modules 6 | dist 7 | .env* 8 | .env.* 9 | static 10 | -------------------------------------------------------------------------------- /src/styles/main.scss: -------------------------------------------------------------------------------- 1 | @import "./variables"; 2 | @import "./custom"; 3 | @import "bulma/bulma"; 4 | 5 | // Plugins 6 | @import "./notification" 7 | -------------------------------------------------------------------------------- /src/components/README.md: -------------------------------------------------------------------------------- 1 | Add components that will be imported to Pages and Layouts to this folder. 2 | Learn more about components here: https://gridsome.org/docs/components/ 3 | 4 | You can delete this file. 5 | -------------------------------------------------------------------------------- /static/README.md: -------------------------------------------------------------------------------- 1 | Add static files here. Files in this directory will be copied directly to `dist` folder during build. For example, /static/robots.txt will be located at https://yoursite.com/robots.txt. 2 | 3 | This file should be deleted. -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['travisreynolds-vue'], 3 | rules: { 4 | 'vue-a11y/label-has-for': [ 2, { 5 | required: { 6 | some: [ 'nesting', 'id' ] 7 | }, 8 | allowChildren: false 9 | }] 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/layouts/README.md: -------------------------------------------------------------------------------- 1 | Layout components are used to wrap pages and templates. Layouts should contain components like headers, footers or sidebars that will be used across the site. 2 | 3 | Learn more about Layouts: https://gridsome.org/docs/layouts/ 4 | 5 | You can delete this file. 6 | -------------------------------------------------------------------------------- /src/pages/README.md: -------------------------------------------------------------------------------- 1 | Pages are usually used for normal pages or for listing items from a GraphQL collection. 2 | Add .vue files here to create pages. For example **About.vue** will be **site.com/about**. 3 | Learn more about pages: https://gridsome.org/docs/pages/ 4 | 5 | You can delete this file. 6 | -------------------------------------------------------------------------------- /src/templates/README.md: -------------------------------------------------------------------------------- 1 | Templates for **GraphQL collections** should be added here. 2 | To create a template for a collection called `WordPressPost` 3 | create a file named `WordPressPost.vue` in this folder. 4 | 5 | Learn more: https://gridsome.org/docs/templates/ 6 | 7 | You can delete this file. 8 | -------------------------------------------------------------------------------- /src/styles/_custom.scss: -------------------------------------------------------------------------------- 1 | // Button 2 | .button { 3 | font-weight: $weight-semibold; 4 | text-transform: uppercase; 5 | font-size: $size-small !important; 6 | height: 50px !important; 7 | } 8 | 9 | // Navbar { 10 | .navbar { 11 | text-transform: uppercase; 12 | font-size: 0.8rem; 13 | } 14 | -------------------------------------------------------------------------------- /gridsome.server.js: -------------------------------------------------------------------------------- 1 | // Server API makes it possible to hook into various parts of Gridsome 2 | // on server-side and add custom data to the GraphQL data layer. 3 | // Learn more: https://gridsome.org/docs/server-api 4 | 5 | // Changes here require a server restart. 6 | // To restart press CTRL + C in terminal and run `gridsome develop` 7 | -------------------------------------------------------------------------------- /src/layouts/Default.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 17 | -------------------------------------------------------------------------------- /src/pages/About.vue: -------------------------------------------------------------------------------- 1 | 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gridsome-starter-shopify", 3 | "private": true, 4 | "scripts": { 5 | "build": "gridsome build", 6 | "develop": "gridsome develop", 7 | "explore": "gridsome explore" 8 | }, 9 | "dependencies": { 10 | "apollo-boost": "0.4.9", 11 | "bulma": "^0.9.3", 12 | "currency.js": "^2.0.4", 13 | "graphql-tag": "^2.12.5", 14 | "gridsome": "^0.7.23", 15 | "gridsome-plugin-flexsearch": "^2.0.1", 16 | "gridsome-source-shopify": "^0.2.6", 17 | "isomorphic-fetch": "^3.0.0", 18 | "typeface-karla": "^1.1.13", 19 | "typeface-prata": "^1.1.13", 20 | "vue-apollo": "^3.0.7", 21 | "vue-notification": "^1.3.20", 22 | "vuex": "^3.6.2" 23 | }, 24 | "devDependencies": { 25 | "eslint-config-travisreynolds-vue": "^1.2.0", 26 | "node-sass": "^6.0.1", 27 | "sass-loader": "^10.2.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Travis Reynolds 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 | -------------------------------------------------------------------------------- /src/styles/_notification.scss: -------------------------------------------------------------------------------- 1 | .vue-notification-group { 2 | display: block; 3 | position: fixed; 4 | z-index: 5000; 5 | } 6 | 7 | .vue-notification-wrapper { 8 | display: block; 9 | overflow: hidden; 10 | width: 100%; 11 | margin: 0; 12 | padding: 0; 13 | } 14 | 15 | .notification-title { 16 | font-weight: 600; 17 | } 18 | 19 | .vue-notification-template { 20 | display: block; 21 | box-sizing: border-box; 22 | background: white; 23 | text-align: left; 24 | } 25 | 26 | .vue-notification { 27 | display: block; 28 | box-sizing: border-box; 29 | text-align: left; 30 | font-size: 12px; 31 | padding: 10px; 32 | margin: 0 5px 5px; 33 | 34 | color: white; 35 | background: #44A4FC; 36 | border-left: 5px solid #187FE7; 37 | } 38 | 39 | .vue-notification.primary { 40 | background: $primary; 41 | border-left-color: $primary; 42 | } 43 | 44 | .vue-notification.warn { 45 | background: #ffb648; 46 | border-left-color: #f48a06; 47 | } 48 | 49 | .vue-notification.error { 50 | background: #E54D42; 51 | border-left-color: #B82E24; 52 | } 53 | 54 | .vue-notification.success { 55 | background: #68CD86; 56 | border-left-color: #42A85F; 57 | } 58 | 59 | .vn-fade-enter-active, .vn-fade-leave-active, .vn-fade-move { 60 | transition: all .5s; 61 | } 62 | 63 | .vn-fade-enter, .vn-fade-leave-to { 64 | opacity: 0; 65 | } 66 | -------------------------------------------------------------------------------- /gridsome.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | siteName: 'Gridsome + Shopify 😍', 3 | configureWebpack: { 4 | module: { 5 | rules: [ // fixes https://github.com/graphql/graphql-js/issues/1272 6 | { 7 | test: /\.mjs$/, 8 | include: /node_modules/, 9 | type: 'javascript/auto' 10 | } 11 | ] 12 | } 13 | }, 14 | templates: { 15 | ShopifyProduct: [ 16 | { 17 | path: '/product/:handle', 18 | component: './src/templates/Product.vue' 19 | } 20 | ], 21 | ShopifyCollection: [ 22 | { 23 | path: '/collection/:handle', 24 | component: './src/templates/Collection.vue' 25 | } 26 | ] 27 | }, 28 | plugins: [ 29 | { 30 | use: 'gridsome-source-shopify', 31 | options: { 32 | storeName: process.env.GRIDSOME_SHOPIFY_STOREFRONT, 33 | storefrontToken: process.env.GRIDSOME_SHOPIFY_STOREFRONT_TOKEN 34 | } 35 | }, 36 | { 37 | use: 'gridsome-plugin-flexsearch', 38 | options: { 39 | flexsearch: { 40 | profile: 'match' 41 | }, 42 | collections: [ 43 | { 44 | typeName: 'ShopifyProduct', 45 | indexName: 'Product', 46 | fields: ['title', 'path'] 47 | }, 48 | { 49 | typeName: 'ShopifyCollection', 50 | indexName: 'Collection', 51 | fields: ['title', 'path'] 52 | } 53 | ], 54 | searchFields: ['title', 'handle', 'tags'] 55 | } 56 | } 57 | ] 58 | } 59 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | // This is the main.js file. Import global CSS and scripts here. 2 | // The Client API can be used here. Learn more: gridsome.org/docs/client-api 3 | 4 | // Layouts 5 | import DefaultLayout from '~/layouts/Default.vue' 6 | 7 | // Plugins 8 | import Vuex from 'vuex' 9 | import VueApollo from 'vue-apollo' 10 | import Notifications from 'vue-notification/dist/ssr.js' 11 | 12 | // Dependencies 13 | import ApolloClient from 'apollo-boost' 14 | import fetch from 'isomorphic-fetch' 15 | 16 | // Styles 17 | import '~/styles/main.scss' 18 | import 'typeface-karla' 19 | import 'typeface-prata' 20 | 21 | export default function (Vue, { appOptions }) { 22 | // Set default layout as a global component 23 | Vue.component('Layout', DefaultLayout) 24 | // Import global plugins 25 | Vue.use(Vuex) 26 | Vue.use(VueApollo) 27 | Vue.use(Notifications) 28 | 29 | // Create Apollo client 30 | const apolloClient = new ApolloClient({ 31 | fetch, 32 | uri: `https://${process.env.GRIDSOME_SHOPIFY_STOREFRONT}.myshopify.com/api/2019-07/graphql.json`, 33 | headers: { 34 | 'X-Shopify-Storefront-Access-Token': process.env.GRIDSOME_SHOPIFY_STOREFRONT_TOKEN 35 | } 36 | }) 37 | 38 | // Add client to vue-apollo provider 39 | const apolloProvider = new VueApollo({ 40 | defaultClient: apolloClient 41 | }) 42 | 43 | // Add provider to vue app 44 | appOptions.apolloProvider = apolloProvider 45 | 46 | // Create Vuex store 47 | appOptions.store = new Vuex.Store({ 48 | state: { 49 | cart: [] 50 | }, 51 | mutations: { 52 | addToCart: (state, newItem) => { 53 | const itemExists = state.cart.find(item => item.variantId === newItem.variantId) 54 | 55 | if (itemExists) itemExists.qty += newItem.qty 56 | else state.cart.push(newItem) 57 | }, 58 | removeFromCart: (state, itemId) => { 59 | const updatedCart = state.cart.filter(item => item.variantId !== itemId) 60 | state.cart = updatedCart 61 | } 62 | } 63 | }) 64 | } 65 | -------------------------------------------------------------------------------- /src/pages/Collections.vue: -------------------------------------------------------------------------------- 1 | 52 | 53 | 60 | 61 | 62 | query ShopifyProducts { 63 | allShopifyCollection (limit: 100) { 64 | edges { 65 | node { 66 | id 67 | handle 68 | title 69 | descriptionHtml 70 | image { 71 | altText 72 | src: transformedSrc(maxWidth: 400, maxHeight: 300, crop: CENTER) 73 | } 74 | } 75 | } 76 | } 77 | } 78 | 79 | 80 | 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Shopify starter for Gridsome 2 | 3 | This is a boilerplate starter, aiming to showcase how Gridsome and Shopify can work together, to create an amazingly fast shopping experience for consumers. [View Demo](https://gridsome-shopify-starter.netlify.app) 4 | 5 | This starter uses a source plugin ([`gridsome-source-shopify`](https://gridsome.org/plugins/gridsome-source-shopify)) to pull data from Shopify's Storefront API, and load it into Gridsome's data store - which you can then use to create pages (note that this only runs at build time). 6 | It also uses the [Vue Apollo](https://apollo.vuejs.org) plugin client-side, to send queries/mutations (i.e. `createCheckout`) to the [Storefront API](https://help.shopify.com/en/api/storefront-api) when running in the browser. 7 | 8 | > Looking for more? https://github.com/travis-r6s/gridsome-starter-shopify-advanced 9 | 10 | ## Shopify Setup 11 | 12 | You will need to create a private app in Shopify, and give it access to the Storefront API - the default permissions should be fine for this starter. 13 | Make sure to note the Storefront API key, and your store name. 14 | 15 | ## Installation 16 | 17 | Install the Gridsome CLI. 18 | 19 | ```bash 20 | npm install -g @gridsome/cli # or 21 | yarn global add @gridsome/cli 22 | ``` 23 | 24 | ## Create Project 25 | 26 | You can either directly download this repository, or use Gridsome's CLI to download and install dependencies for you. 27 | 28 | ```bash 29 | # Clone repository 30 | git clone https://github.com/travis-r6s/gridsome-starter-shopify.git 31 | npm install # or 32 | yarn install 33 | 34 | # Download with CLI 35 | gridsome create my-gridsome-site travis-r6s/gridsome-starter-shopify 36 | ``` 37 | 38 | ## Developing 39 | 40 | You will need to add your Storefront API shop name (https://\.myshopify.com) & token to an env file before running this starter. 41 | I also recommend you add some collections in Shopify to best show off this starter. 42 | 43 | ``` 44 | # Note env's are prefixed with GRIDSOME_ to make them available to apollo client side 45 | GRIDSOME_SHOPIFY_STOREFRONT= 46 | GRIDSOME_SHOPIFY_STOREFRONT_TOKEN= 47 | ``` 48 | 49 | Or you can manually edit the [Shopify Source Plugin's](https://gridsome.org/plugins/gridsome-source-shopify) configurations in `gridsome.config.js`. 50 | 51 | Enter the site folder, and run `gridsome develop` to start a local development server. 52 | 53 | Happy coding! 54 | -------------------------------------------------------------------------------- /src/templates/Collection.vue: -------------------------------------------------------------------------------- 1 | 54 | 55 | 62 | 63 | 64 | query Collection ($id: ID!) { 65 | shopifyCollection (id: $id) { 66 | id 67 | title 68 | descriptionHtml 69 | products { 70 | id 71 | title 72 | handle 73 | descriptionHtml 74 | priceRange { 75 | minVariantPrice { 76 | amount(format: true, currency: "GBP") 77 | } 78 | } 79 | images (limit: 1) { 80 | id 81 | altText 82 | src: transformedSrc (maxWidth: 400, maxHeight: 300, crop: CENTER) 83 | } 84 | } 85 | } 86 | } 87 | 88 | -------------------------------------------------------------------------------- /src/styles/_variables.scss: -------------------------------------------------------------------------------- 1 | @import "bulma/sass/utilities/initial-variables"; 2 | @import "bulma/sass/utilities/functions"; 3 | 4 | $primary: #161616; 5 | 6 | $info: $cyan; 7 | $success: $green; 8 | $warning: $yellow; 9 | $danger: $red; 10 | 11 | $light: $white-ter; 12 | $dark: $grey-darker; 13 | 14 | // Invert colors 15 | 16 | $orange-invert: findColorInvert($orange); 17 | $yellow-invert: findColorInvert($yellow); 18 | $green-invert: findColorInvert($green); 19 | $turquoise-invert: findColorInvert($turquoise); 20 | $cyan-invert: findColorInvert($cyan); 21 | $blue-invert: findColorInvert($blue); 22 | $purple-invert: findColorInvert($purple); 23 | $red-invert: findColorInvert($red); 24 | 25 | $primary-invert: $turquoise-invert; 26 | $info-invert: $cyan-invert; 27 | $success-invert: $green-invert; 28 | $warning-invert: $yellow-invert; 29 | $danger-invert: $red-invert; 30 | $light-invert: $dark; 31 | $dark-invert: $light; 32 | 33 | // General colors 34 | 35 | $background: $white-ter; 36 | 37 | $border: $grey-lighter; 38 | $border-hover: $grey-light; 39 | 40 | // Text colors 41 | 42 | $text: $black-bis; 43 | $text-invert: findColorInvert($text); 44 | $text-light: $grey-dark; 45 | $text-strong: $black; 46 | 47 | // Code colors 48 | 49 | $code: $red; 50 | $code-background: $background; 51 | 52 | $pre: $text; 53 | $pre-background: $background; 54 | 55 | // Link colors 56 | 57 | $link: $text; 58 | $link-invert: $text-invert; 59 | $link-visited: $text; 60 | 61 | $link-hover: $grey-light; 62 | $link-hover-border: $grey-light; 63 | 64 | $link-focus: $grey-darker; 65 | $link-focus-border: $grey-darker; 66 | 67 | $link-active: $grey-darker; 68 | $link-active-border: $grey-dark; 69 | 70 | // Typography 71 | 72 | $family-primary: "Karla", $family-sans-serif; 73 | $family-secondary: "Prata", $family-sans-serif; 74 | $family-code: $family-monospace; 75 | 76 | $size-small: $size-7; 77 | $size-normal: $size-6; 78 | $size-medium: $size-5; 79 | $size-large: $size-4; 80 | 81 | // Lists and maps 82 | $custom-colors: null; 83 | $custom-shades: null; 84 | 85 | $colors: mergeColorMaps(("white": ($white, $black), "black": ($black, $white), "light": ($light, $light-invert), "dark": ($dark, $dark-invert), "primary": ($primary, $primary-invert), "link": ($link, $link-invert), "info": ($info, $info-invert), "success": ($success, $success-invert), "warning": ($warning, $warning-invert), "danger": ($danger, $danger-invert)), $custom-colors); 86 | $shades: mergeColorMaps(("black-bis": $black-bis, "black-ter": $black-ter, "grey-darker": $grey-darker, "grey-dark": $grey-dark, "grey": $grey, "grey-light": $grey-light, "grey-lighter": $grey-lighter, "white-ter": $white-ter, "white-bis": $white-bis), $custom-shades); 87 | 88 | $sizes: $size-1 $size-2 $size-3 $size-4 $size-5 $size-6 $size-7; 89 | 90 | // Miscellaneous 91 | $radius: 0px; 92 | 93 | // Button 94 | $button-padding-horizontal: 2.8rem; 95 | -------------------------------------------------------------------------------- /src/components/Navbar.vue: -------------------------------------------------------------------------------- 1 | 90 | 91 | 112 | 113 | 119 | -------------------------------------------------------------------------------- /src/pages/Index.vue: -------------------------------------------------------------------------------- 1 | 87 | 88 | 99 | 100 | 101 | query ShopifyProducts { 102 | allShopifyCollection (limit: 1) { 103 | edges { 104 | node { 105 | id 106 | handle 107 | title 108 | descriptionHtml 109 | image { 110 | altText 111 | src: transformedSrc(maxWidth: 800, maxHeight: 800, crop: CENTER) 112 | } 113 | } 114 | } 115 | } 116 | allShopifyProduct (limit: 6) { 117 | edges { 118 | node { 119 | id 120 | title 121 | handle 122 | descriptionHtml 123 | priceRange { 124 | minVariantPrice { 125 | amount(format: true) 126 | } 127 | } 128 | images (limit: 1) { 129 | id 130 | altText 131 | src: transformedSrc (maxWidth: 400, maxHeight: 300, crop: CENTER) 132 | } 133 | } 134 | } 135 | } 136 | } 137 | 138 | 139 | 148 | -------------------------------------------------------------------------------- /src/pages/Cart.vue: -------------------------------------------------------------------------------- 1 | 108 | 109 | 177 | 178 | 183 | -------------------------------------------------------------------------------- /src/templates/Product.vue: -------------------------------------------------------------------------------- 1 | 93 | 94 | 147 | 148 | 149 | query Product ($id: ID!) { 150 | shopifyProduct (id: $id) { 151 | id 152 | descriptionHtml 153 | title 154 | tags 155 | images(limit: 4) { 156 | id 157 | altText 158 | src: transformedSrc(maxWidth: 600, maxHeight: 400, crop: CENTER) 159 | thumbnail: transformedSrc(maxWidth: 150, maxHeight: 150, crop: CENTER) 160 | } 161 | options { 162 | id 163 | name 164 | values 165 | } 166 | variants { 167 | id 168 | title 169 | price { 170 | amount(format: true) 171 | } 172 | selectedOptions { 173 | name 174 | value 175 | } 176 | image { 177 | id 178 | altText 179 | thumbnail: transformedSrc(maxWidth: 150, maxHeight: 150, crop: CENTER) 180 | } 181 | } 182 | } 183 | } 184 | 185 | 186 | 202 | --------------------------------------------------------------------------------