Simply copy and paste the following command line in your terminal to create your first Strapi project.
npx create-strapi-app
my-project
This tutorial is part of the « E-commerce website with Strapi, Nuxt.js, GraphQL and Stripe
Note: The source code is available on Github.
All of these dishes look so tasty! What if you could add some of them in a shopping cart?
Let’s create a new Vuex store to manage the shopping cart.
Vuex is a state management pattern + library for Vue.js applications. It serves as a centralized store for all the components in an application, with rules ensuring that the state can only be mutated in a predictable fashion. It also integrates with Vue's official devtools extension to provide advanced features such as zero-config, time-travel debugging, and state snapshot export / import. Learn more about Vuex at What is Vuex?.
Create a new store named cart.js
:
1
2
3
4
5
6
7
// Folder structure
deliveroo-clone-tutorial
└── frontend
└── store
├── cart.js // new store
├── index.js
└── README.md
Then, copy/paste the following code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// store/cart.js
import Cookies from 'js-cookie'
export const state = () => ({
items: [],
})
export const mutations = {
setItems(state, items) {
state.items = items
},
add(state, item) {
const record = state.items.find((i) => i.id === item.id)
if (!record) {
state.items.push({
quantity: 1,
...item,
})
} else {
record.quantity++
}
Cookies.set('cart', JSON.stringify(state.items))
},
remove(state, item) {
const record = state.items.find((i) => i.id === item.id)
if (record.quantity > 1) {
record.quantity--
} else {
const index = state.items.findIndex((i) => i.id === item.id)
state.items.splice(index, 1)
}
Cookies.set('cart', JSON.stringify(state.items))
},
emptyList(state) {
state.items = []
Cookies.set('cart', JSON.stringify(state.items))
},
}
export const getters = {
items: (state) => {
return state.items
},
price: (state) => {
return state.items.reduce(
(accumulator, item) =>
accumulator + item.attributes.price * item.quantity,
0
)
},
numberOfItems: (state) => {
return state.items.reduce(
(accumulator, item) => accumulator + item.quantity,
0
)
},
}
If you are new to Vuex, the terms state, mutations, and actions might be confusing. However, the concept behind each is quite simple. We create a state object that holds the initial state an empty list of items. Next we create an object mutations for storing our add, remove, empty events. Finally, we create an object actions to store functions that dispatch mutations.
For this tutorial, you will store cart data in browser cookies. Let’s install some libraries for handling browser cookies:
1
2
# Ctrl + C to close process
yarn add js-cookie cookieparser
Now you want to add the cart to your pages. To do so you are going to create a Cart
component that will be used in our Header.vue
component.
Let’s create a new Cart component to display user stored dishes. Create a new Cart.vue
file inside the components
directory.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// components/Cart.vue
<template>
<div>
<div v-if="price > 0">
<div
v-for="dish in selectedDishes"
:key="dish.id"
class="uk-grid-small uk-flex"
uk-grid
>
<div class="uk-width-expand">
<p class="uk-margin-remove-bottom">{{ dish.attributes.name }}</p>
<p class="uk-text-meta uk-margin-remove-top">
{{ dish.quantity }} × {{ dish.attributes.price }}€
</p>
</div>
<div v-if="checkout" class="uk-width-auto">
<button type="button" uk-close @click="removeFromCart(dish)"></button>
</div>
</div>
<div class="uk-grid-small uk-flex" uk-grid>
<div class="uk-width-expand">Subtotal</div>
<div>{{ price }}€</div>
</div>
<div v-if="checkout">
<NuxtLink
class="uk-button uk-button-secondary uk-width-1-1"
to="/checkout"
>
Checkout
</NuxtLink>
</div>
</div>
<div v-else class="uk-text-meta">Empty</div>
</div>
</template>
<script>
import { mapGetters, mapMutations } from 'vuex'
export default {
props: {
checkout: {
type: Boolean,
default: true,
},
},
computed: {
...mapGetters({
selectedDishes: 'cart/items',
price: 'cart/price',
}),
},
methods: {
...mapMutations({
addToCart: 'cart/add',
removeFromCart: 'cart/remove',
}),
},
}
</script>
Card component will receive the dishes selected by the user, v-for
attribute will loop through the selected dishes and list them as cards in the dropdown. removeFromCart
method will be in charge of removing a dish from the shopping cart.
The mapGetters
helper simply maps store getters to local computed properties.
The mapMutations
helper maps component methods to store.commit
calls.
Next, let’s update our pages/restaurants/_id.vue
, and copy/paste the following code in it.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// pages/restaurants/_id.vue
<template>
<div class="uk-container uk-container-xsmall">
<span class="uk-heading-small">
// Link to go back to the previous page
<NuxtLink class="uk-button uk-button-text" to="/">
<span uk-icon="arrow-left"></span> go back
</NuxtLink>
</span>
// Displaying dishes
<div v-for="dish in dishes" :key="dish.id">
<div class="uk-card uk-card-default uk-child-width-1-2 uk-margin" uk-grid>
<div class="uk-card-body uk-card-small">
<h2 class="uk-card-title">{{ dish.attributes.name }}</h2>
<p>{{ restaurant.data.attributes.name }}</p>
<p>{{ dish.attributes.price }} €</p>
<button class="uk-button uk-button-primary uk-margin-xlarge-top" @click="addToCart(dish)">
Add to cart
</button>
</div>
<figure class="uk-card-media-right uk-cover-container">
<img
:src="getStrapiMedia(dish.attributes.image.data.attributes.url)"
:alt="dish.attributes.image.data.attributes.alternativeText"
/>
</figure>
</div>
</div>
</div>
</template>
<script>
import { mapMutations } from 'vuex'
import { getStrapiMedia } from '@/utils/media'
import restaurantQuery from '@/apollo/queries/restaurant'
export default {
data() {
return {
restaurant: Object,
}
},
apollo: {
restaurant: {
prefetch: true,
query: restaurantQuery,
variables() {
return { id: this.$route.params.id }
},
},
},
computed: {
dishes() {
if (!this.restaurant?.data) return []
return this.restaurant.data.attributes.dishes.data
},
},
methods: {
getStrapiMedia,
...mapMutations({
addToCart: 'cart/add',
removeFromCart: 'cart/remove',
}),
},
}
</script>
We called a new mapMutations
, addToCart
to receive the dish
as an argument and perfrom a change in the Vuex store. It adds the selected dish to the card component automatically.
As you may see, you created a full featured shopping card component. In fact you want to reuse this component in two pages: pages/r``estaurants/index.vue
and pages/o``rders/checkout.vue
that you'll create soon.
🔐 In the next section, you will learn how to authenticate users in your app.
Pierre created Strapi with Aurélien and Jim back in 2015. He's a strong believer in open-source, remote and people-first organizations. You can also find him regularly windsurfing or mountain-biking!