Build a JamStack food ordering application with Strapi, Gridsome and Snipcart (2)

Ekene Eze (Kenny) - Dec 13 '20 - - Dev Community

In the first part, we walked through the process of setting up a Strapi application and creating products for our application. In this part, we will concentrate more on how to set up the Gridsome application. Afterwards, we will set up some components, pages and layouts to get the application ready.

Table of Contents:

  1. Part 1 - Generating a Strapi app and creating products
  2. Part 2 - Setting up a Gridsome project
  3. Part 3 - Consuming products with Gridsome and GraphQL
  4. Part 4 - Creating single product views with Gridsome templates
  5. Part 5 - Implementing cart and checkout with Snipcart
  6. Part 6 - Deploying the apps Setting up a new Gridsome application.

Before we get started, here are some prerequisites:

  • Node v8.0 or higher
  • Working knowledge of JavaScript and Vue

Installing Gridsome

With Node installed, you can install the Gridsome CLI by running the command below:

    yarn global add @gridsome/cli
    #OR
    npm install --global @gridsome/cli
Enter fullscreen mode Exit fullscreen mode

Having installed the Gridsome CLI, you can use it to create a new Gridsome project by running the following command:

    gridsome create mealzers
Enter fullscreen mode Exit fullscreen mode

The above command will create a Gridsome project named mealzers. Navigate into to the created project folder with:

    cd mealzers
Enter fullscreen mode Exit fullscreen mode

And start the project development server with:

    gridsome develop
Enter fullscreen mode Exit fullscreen mode

The project will be available for preview on the browser. Navigate to localhost:8080 to view the app.

There, you have a Gridsome application running on the browser in minutes. Next, let's add Vuetify for styling.

Setting up Vuetify

Vuetify is a UI framework built on top of Vue.js. Whenever I'm working on a Vue project, Vuetify is by default my goto UI framework for styling. It is the most popular design framework for Vue.js and it comes packaged with tons of prebuilt components.

Amongst other things, it is mobile-first which ensures that my applications perform responsively irrespective of device or orientation. It is also simple to understand and use. Let's quickly add Vuetify to our Gridsome application by running the installation command below:

    # npm
    npm install vuetify --save
    #OR
    # yarn
    yarn add vuetify
Enter fullscreen mode Exit fullscreen mode

Now that you've installed Vuetify, you need to register it as a plugin in your Gridsome application. You will also need to add Vuetify CSS file and a link to Google's material design icons to your app. To do that, copy the code snippet below into your project's main.js file and save:

    import Vuetify from 'vuetify'
    import 'vuetify/dist/vuetify.min.css'
    import DefaultLayout from '~/layouts/Default.vue'

    export default function (Vue, { appOptions, head }) {
      head.link.push({
        rel: 'stylesheet',
        href: 'https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css',
      })

      head.link.push({
        rel: 'stylesheet',
        href: 'https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900',
      });
      Vue.use(Vuetify)
      const opts = {}; //opts includes, vuetify themes, icons, etc.
      appOptions.vuetify = new Vuetify(opts);

      // Set default layout as a global component
      Vue.component('Layout', DefaultLayout)
    }

Enter fullscreen mode Exit fullscreen mode

Next, you need to whitelist Vuetify in Webpack so that your application can build properly. To do that, first, you need to install the webpack-node-externals plugin with the command below:

    npm install webpack-node-externals --save-dev
Enter fullscreen mode Exit fullscreen mode

Then replace your project's gridsome.server.js file with the snippet below :

    const nodeExternals = require('webpack-node-externals')

    module.exports = function (api) {
      api.chainWebpack((config, { isServer }) => {
        if (isServer) {
          config.externals([
            nodeExternals({
              allowlist: [/^vuetify/]
            })
          ])
        }
      })

      api.loadSource(store => {
        // Use the Data store API here: https://gridsome.org/docs/data-store-api
      })
    }
Enter fullscreen mode Exit fullscreen mode

Great! so we've just added Vuetify to our Gridsome project. But don't take my words for it. Let's quickly run the Gridsome app again and see if we observe any changes in the UI:

And we do. The presence of Vuetify in this application has already impacted the existing style which is why the app looks all jumbled up. Next, let's create a default layout that will cater to the applications Header and Footer needs. Let's start with the footer.

Footer component

Create a src/components/Footer.vue file and add the following snippet to it:


     <!-- src/components/Footer.vue -->
    <template>
      <v-footer absolute padless>
        <v-card flat tile class="orange lighten-1 white--text text-center">
          <v-card-text>
            <v-btn v-for="icon in icons" :key="icon" class="mx-4 white--text" icon>
              <v-icon size="24px">{{ icon }}</v-icon>
            </v-btn>
          </v-card-text>
          <v-card-text class="white--text pt-0">
            Phasellus feugiat arcu sapien, et iaculis ipsum elementum sit amet.
            Mauris cursus commodo interdum. Praesent ut risus eget metus luctus
            accumsan id ultrices nunc. Sed at orci sed massa consectetur dignissim a
            sit amet dui. Duis commodo vitae velit et faucibus. Morbi vehicula
            lacinia malesuada. Phasellus feugiat arcu sapien, et iaculis ipsum
            elementum sit amet. Mauris cursus commodo interdum. Praesent ut risus
            eget metus luctus accumsan id ultrices nunc. Sed at orci sed massa
            consectetur dignissim a sit amet dui. Duis commodo vitae velit et
            faucibus. Morbi vehicula lacinia malesuada.
          </v-card-text>
          <v-divider></v-divider>
          <v-card-text class="white--text">
            {{ new Date().getFullYear() }}  <strong>Mealsers</strong>
          </v-card-text>
        </v-card>
      </v-footer>
    </template>
    <script>
    export default {
      data: () => ({
        icons: ["mdi-facebook", "mdi-twitter", "mdi-linkedin", "mdi-instagram"],
      }),
    };
    </script>

Enter fullscreen mode Exit fullscreen mode

Here, we defined a basic Vuetify footer component with a bunch of dummy text and social media icons. The icons are made available through the material design icons link we added in the applications main.js file.

Default layout

Next, open the src/layouts/Default.vue file and update it with the code snippet below:

     <!-- src/layouts/Default.vue -->
      <template>
        <v-app>
          <div>
            <v-app-bar
              absolute color="orange"
              dark shrink-on-scroll
              scroll-target="#scrolling-techniques-2"
            >
              <v-app-bar-nav-icon class="mt-5" @click="drawer = !drawer">
              </v-app-bar-nav-icon>
              <g-link
                class="mt-7 ml-3"
                style="text-decoration: none; color: inherit"
                to="/"
                >Mealzers</g-link
              >
              <v-spacer></v-spacer>
              <v-btn outlined rounded dense class="mt-5 mr-3">
                <g-link style="text-decoration: none; color: inherit" to="/shop/"
                  >Shop</g-link
                >
              </v-btn>
              <v-btn outlined rounded dense class="mt-5 mr-3">
                <g-link style="text-decoration: none; color: inherit" to="/support/"
                  >Support</g-link
                >
              </v-btn>
              <v-text-field
                v-model="searchText" @click:clear="searchText = ''"
                placeholder="Search products ..." class="mt-5 mr-4"
                style="max-width: 350px" prepend-inner-icon="mdi-magnify"
                clearable outlined rounded dense hide-details
              />
              <v-btn class="mt-4 mr-4 ml-4 snipcart-checkout" icon>
                <v-icon>mdi-cart</v-icon>
                <span class="snipcart-total-items"></span>
                <span class="snipcart-total-price">{{ this.totalPrice }}</span>
              </v-btn>
            </v-app-bar>
            <v-navigation-drawer v-model="drawer" absolute bottom temporary>
              <v-list nav dense>
                <v-list-item-group
                  v-model="group"
                  active-class="deep-purple--text text--accent-4"
                >
                  <v-list-item>
                    <v-list-item-title>
                      <v-btn elevation="0" rounded>
                        <g-link
                          style="text-decoration: none; color: orange"
                          to="/shop/"
                          >Shop</g-link
                        >
                      </v-btn>
                    </v-list-item-title>
                  </v-list-item>
                  <v-list-item>
                    <v-list-item-title>
                      <v-btn elevation="0" rounded dense>
                        <g-link
                          style="text-decoration: none; color: orange"
                          to="/support/"
                          >Support</g-link
                        >
                      </v-btn>
                    </v-list-item-title>
                  </v-list-item>
                </v-list-item-group>
              </v-list>
            </v-navigation-drawer>
            <v-sheet
              id="scrolling-techniques-2"
              class="overflow-y-auto"
              max-height="600"
            >
              <v-container style="height: 120px"></v-container>
            </v-sheet>
            <slot />
          </div>
          <Footer />
        </v-app>
      </template>

Enter fullscreen mode Exit fullscreen mode

In the template section above, we've defined a basic Vuetify header that features a navigation drawer and a few buttons to switch pages. We are using the custom Gridsome <g-link> tag to handle navigation between pages.

At the moment, we have a shop page, and a support page, which we'll create later in the next part of this series. So don't bother about them just yet. Next, let set up the script section of the layout file above using the code snippet below:


     <!-- src/layouts/Default.vue -->
    <script>
    import Footer from "@/components/Footer.vue";
    export default {
      components: {
        Footer,
      },
      data() {
        const drawer = false;
        const group = null;
        return {
          drawer,
          group
        };
      },
      watch: {
        group() {
          this.drawer = false;
        },
      },

    };
    </script>
    <static-query>
    query {
      metadata {
        siteName
      }
    }
    </static-query>
Enter fullscreen mode Exit fullscreen mode

If you save the changes and run the application again, you should get the following output on the browser where the app is running:

At the moment, the buttons and navigation on the app will lead to a 404 page. This is because we haven't created the /shop and /support pages yet. We'll get to that eventually in the next part of this series, but in the meantime, let's add some Hero images to make this app look even more like an e-comnerce application.

Hero

Next, update your src/pages/index.vue file with the snippet below:


    <template>
      <Layout>
        <template>
          <v-carousel
            cycle
            height="400"
            hide-delimiter-background
            :show-arrows="false"
          >
            <v-carousel-item v-for="(image, i) in images" :key="i">
              <v-sheet height="100%">
                <v-row class="fill-height" align="center" justify="center">
                  <div class="display-3">
                    <img :src="image" />
                  </div>
                </v-row>
              </v-sheet>
            </v-carousel-item>
          </v-carousel>
          <div class="separator"></div>
        </template>
      </Layout>
    </template>
    <script>
    export default {
      metaInfo: {
        title: "Mealzers",
      },
      data() {
        return {
          images: [
            "https://bit.ly/33Ehev5",
            "https://bit.ly/33Ehev5",
            "https://bit.ly/33Ehev5",
            "https://bit.ly/33Ehev5",
          ],
        };
      },
    };
    </script>
Enter fullscreen mode Exit fullscreen mode

Here, we define an array of 5 images and loop through them to display each one in the Vuetify v-carousel UI component we specified in the component template. If you save this snippet and check back on the browser, you should get an updated view like so:

Conclusion

We've come to the end of this part. Before we jump on to the next part, here's a quick
recap on what we've done. In the first part, we set up a Strapi application and created a bunch of products we'll sell on our store. In this part, we just set up a Gridsome application and modified the UI to get it ready. In the next part, we'll connect this application to display the products we created with Strapi using Gridsome's GraphQL data layer. See you on the next one!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .