Tutorial updated by Fredrick Emmanuel and Paul Bratslavsky.
This tutorial is part of the « Cooking a Deliveroo clone with Next.js (React), GraphQL, Strapi and Stripe » tutorial series.
Table of contents
Note: The source code is available on GitHub here
Congratulations, you successfully displayed the list of restaurants!
Every restaurant sells dishes, which also must be stored in the database. Now a new Content Type is needed named dish
. Create a new Content Type the same way with the following attributes:
name
with type Text
.description
with type Text (Long Text)
.image
with type Media (Single media)
.price
with type Number
(Decimal).restaurant
with type Relation
: this one is a bit more specific. Our purpose here is to tell Strapi that every dish can be related to a restaurant. To do so, create a one-to-many relation, as below.I decided to represent the price in cents to avoid rounding errors.
Relations in Strapi are shown below.
Here is the final result:
Don’t forget to set up Roles and Permission for the Dishes Content type
Then, add some dishes from the Content Manager: http://localhost:1337/admin/plugins/content-manager/dish. Make sure they all have an image and are linked to a restaurant. As shown below
A new route called /restaurant
will be used to display the dishes for a particular restaurant using its id
.
Now create a new folder in the pages folder named restaurant and create a file named id.js.
This file named id.js is responsible for retrieving the restaurant’s id from the URL and using this id to get the dishes for that restaurant.
Path: frontend/pages/restaurant/[id].js
1import { gql, useQuery } from "@apollo/client";
2import { centsToDollars } from "@/utils/centsToDollars";
3import { useRouter } from "next/router";
4
5import Image from "next/image";
6import Loader from "@/components/Loader";
7
8const GET_RESTAURANT_DISHES = gql`
9 query ($id: ID!) {
10 restaurant(id: $id) {
11 data {
12 id
13 attributes {
14 name
15 dishes {
16 data {
17 id
18 attributes {
19 name
20 description
21 priceInCents
22 image {
23 data {
24 attributes {
25 url
26 }
27 }
28 }
29 }
30 }
31 }
32 }
33 }
34 }
35 }
36`;
37
38function DishCard({ data }) {
39 function handleAddItem() {
40 // will add some logic here
41 }
42
43 return (
44 <div className="w-full md:w-1/2 lg:w-1/3 p-4">
45 <div className="h-full bg-gray-100 rounded-2xl">
46 <Image
47 className="w-full rounded-2xl"
48 height={300}
49 width={300}
50 src={`${process.env.STRAPI_URL || "http://127.0.0.1:1337"}${
51 data.attributes.image.data.attributes.url
52 }`}
53 alt=""
54 />
55 <div className="p-8">
56 <div className="group inline-block mb-4" href="#">
57 <h3 className="font-heading text-xl text-gray-900 hover:text-gray-700 group-hover:underline font-black">
58 {data.attributes.name}
59 </h3>
60 <h2>${centsToDollars(data.attributes.priceInCents)}</h2>
61 </div>
62 <p className="text-sm text-gray-500 font-bold">
63 {data.attributes.description}
64 </p>
65 <div className="flex flex-wrap md:justify-center -m-2">
66 <div className="w-full md:w-auto p-2 my-6">
67 <button
68 className="block w-full px-12 py-3.5 text-lg text-center text-white font-bold bg-gray-900 hover:bg-gray-800 focus:ring-4 focus:ring-gray-600 rounded-full"
69 onClick={handleAddItem}
70 >
71 + Add to Cart
72 </button>
73 </div>
74 </div>
75 </div>
76 </div>
77 </div>
78 );
79}
80
81export default function Restaurant() {
82 const router = useRouter();
83 const { loading, error, data } = useQuery(GET_RESTAURANT_DISHES, {
84 variables: { id: router.query.id },
85 });
86
87 if (error) return "Error Loading Dishes";
88 if (loading) return <Loader />;
89 if (data.restaurant.data.attributes.dishes.data.length) {
90 const { restaurant } = data;
91
92 return (
93 <div className="py-6">
94 <h1 className="text-4xl font-bold text-green-600">
95 {restaurant.data.attributes.name}
96 </h1>
97 <div className="py-16 px-8 bg-white rounded-3xl">
98 <div className="max-w-7xl mx-auto">
99 <div className="flex flex-wrap -m-4 mb-6">
100 {restaurant.data.attributes.dishes.data.map((res) => {
101 return <DishCard key={res.id} data={res} />;
102 })}
103 </div>
104 </div>
105 </div>
106 </div>
107 );
108 } else {
109 return <h1>No Dishes Found</h1>;
110 }
111}
In the code above we are using a util function centsToDollars
so let's create now.
Create a utils folder and inside create a file called centsToDollars.js and paste the following code:
1export function centsToDollars(cents) {
2 const dollars = Math.floor(cents / 100);
3 const centsRemainder = cents % 100;
4 return dollars + "." + centsRemainder.toString().padStart(2, "0");
5}
Once you’ve added the lines of code and hit save, head on to localhost:3000 to try out the application. You should be able to view the dishes attached to specific restaurants as seen in the GIF below.
🔐 In the next section, you will learn how to authenticate users in your app (register, logout & login): https://strapi.io/blog/nextjs-react-hooks-strapi-auth-4
Ryan is an active member of the Strapi community and he's been contributing at a very early stage by writing awesome tutorial series to help fellow Strapier grow and learn.