20 |
21 |
27 |
28 |
29 |
30 |
31 | )
32 |
33 | Header.propTypes = {
34 | siteTitle: PropTypes.string,
35 | }
36 |
37 | Header.defaultProps = {
38 | siteTitle: ``,
39 | }
40 |
41 | export default Header
42 |
--------------------------------------------------------------------------------
/src/components/image.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { useStaticQuery, graphql } from "gatsby"
3 | import Img from "gatsby-image"
4 |
5 | /*
6 | * This component is built using `gatsby-image` to automatically serve optimized
7 | * images with lazy loading and reduced file sizes. The image is loaded using a
8 | * `useStaticQuery`, which allows us to load the image from directly within this
9 | * component, rather than having to pass the image data down from pages.
10 | *
11 | * For more information, see the docs:
12 | * - `gatsby-image`: https://gatsby.dev/gatsby-image
13 | * - `useStaticQuery`: https://www.gatsbyjs.org/docs/use-static-query/
14 | */
15 |
16 | const Image = () => {
17 | const data = useStaticQuery(graphql`
18 | query {
19 | placeholderImage: file(relativePath: { eq: "gatsby-astronaut.png" }) {
20 | childImageSharp {
21 | fluid(maxWidth: 300) {
22 | ...GatsbyImageSharpFluid
23 | }
24 | }
25 | }
26 | }
27 | `)
28 |
29 | return
30 | }
31 |
32 | export default Image
33 |
--------------------------------------------------------------------------------
/src/components/landing/TechStack.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import HeadingTwo from "../typography/HeadingTwo"
3 | import HeadingThree from "../typography/HeadingThree"
4 | import BodyOne from "../typography/BodyOne"
5 | import StackCard from "../cards/StackCard"
6 | import constants from "../../constants"
7 | const TechStack = () => (
8 | <>
9 |
10 | Tech stack
11 |
12 | Frontend
13 |
14 |
15 | On the frontend, you can create the Lunar Tour in either Vue or React.
16 | On the Vue side you uncover how to persist state in multiple components.
17 | While in the React course, you can learn how to use Apollo's Local Cache
18 | as an alternative for Rdux.
19 |
20 |
25 |
26 |
27 | Backend
28 |
29 |
30 | Using the Serverless Framework, you will be able to create a GraphQL
31 | Lambda powered by Apollo Server.
32 |
33 |
39 | >
40 | )
41 |
42 | export default TechStack
43 |
--------------------------------------------------------------------------------
/src/components/landing/hero.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { Link } from "gatsby"
3 | import HeadingOne from "../typography/HeadingOne"
4 | import BodyOne from "../typography/BodyOne"
5 | import RedBlockButton from "../buttons/RedBlockButton"
6 | import RedOutlineButton from "../buttons/RedOutlineButton"
7 | import constants from "../../constants"
8 | const Hero = () => (
9 | <>
10 |
11 |
12 | {/*
13 |
18 |
*/}
19 |
20 | Build a fullstack app that books vacations to the moon
21 |
22 |
23 |
24 | This course like resource will you teach how to use GraphQL with AWS
25 | Lambda and DynamoDB with JavaScript on the backend. While you can
26 | choose your own adventure on the frontend side of things with{" "}
27 |
33 | Vue
34 | {" "}
35 | or{" "}
36 |
42 | React
43 |
44 | . Both tracks will cover use of their respective Apollo Client
45 | libraries, while using Stripe for payments.
46 |
47 |
48 | Learn now!
49 |
50 |
51 | constants.track("App.RedirctToDemo")}
54 | >
55 |
61 | What you'll build
62 |
63 |
64 |
8 | )
9 |
10 | HeadingTwo.propTypes = {
11 | children: PropTypes.string,
12 | className: PropTypes.string,
13 | }
14 |
15 | HeadingTwo.defaultProps = {
16 | className: ``,
17 | }
18 | export default HeadingTwo
19 |
--------------------------------------------------------------------------------
/src/constants.js:
--------------------------------------------------------------------------------
1 | import mixpanel from "mixpanel-browser"
2 |
3 | import VueLogo from "./images/vue.svg"
4 | import ApolloLogo from "./images/apollo.svg"
5 | import AntLogo from "./images/ant.svg"
6 | import GraphQLLogo from "./images/graphql.svg"
7 | import DynamoLogo from "./images/aws-dynamodb.svg"
8 | import LambdaLogo from "./images/aws-lambda.svg"
9 | import StripLogo from "./images/stripe.svg"
10 | import TailwindLogo from "./images/tailwind.svg"
11 | import ServerlessLogo from "./images/serverless.svg"
12 | import SeedLogo from "./images/seed.png"
13 | import airtable from "./images/airtable.svg"
14 | import aws from "./images/aws.svg"
15 | import figma from "./images/figma.svg"
16 | import github from "./images/github.svg"
17 | import netlify from "./images/netlify.svg"
18 | import miro from "./images/miro.svg"
19 | import seed from "./images/seed.svg"
20 |
21 | mixpanel.init(process.env.GATSBY_MIXPANEL)
22 | const partners = [
23 | {
24 | logo: github,
25 | use: "Hosting of repos",
26 | url: "https://github.com/",
27 | },
28 | {
29 | logo: aws,
30 | use: "Cloud provider",
31 | url: "https://aws.amazon.com/",
32 | },
33 | {
34 | logo: seed,
35 | use: "CI/CD provider",
36 | url: "https://seed.run/",
37 | },
38 |
39 | {
40 | logo: figma,
41 | use: "Prototyping",
42 | url: "https://www.figma.com/",
43 | },
44 | {
45 | logo: airtable,
46 | use: "Product management",
47 | url: "https://airtable.com/",
48 | },
49 | {
50 | logo: netlify,
51 | use: "Hosting/CMS",
52 | url: "https://netlify.com/",
53 | },
54 | {
55 | logo: miro,
56 | use: "Product management",
57 | url: "https://miro.com/",
58 | },
59 | ]
60 | const techStack = [
61 | {
62 | logo: VueLogo,
63 | technology: "Vue",
64 | url: "https://vuejs.org/",
65 | },
66 | {
67 | logo: ApolloLogo,
68 | technology: "Vue Apollo",
69 | url: "https://vue-apollo.netlify.com/",
70 | },
71 | {
72 | logo: ApolloLogo,
73 | technology: "Apollo Client",
74 | url: "https://www.apollographql.com/docs/react/",
75 | },
76 | {
77 | logo: TailwindLogo,
78 | technology: "Tailwind CSS",
79 | url: "https://tailwindcss.com/",
80 | },
81 | {
82 | logo: AntLogo,
83 | technology: "Ant Design",
84 | url: "https://www.antdv.com/",
85 | },
86 | ]
87 |
88 | const backendTechStack = [
89 | {
90 | logo: ServerlessLogo,
91 | technology: "Serverless",
92 | url: "https://serverless.com/",
93 | },
94 | {
95 | logo: ApolloLogo,
96 | technology: "Apollo Server",
97 | url: "https://www.apollographql.com/docs/apollo-server/",
98 | },
99 | {
100 | logo: LambdaLogo,
101 | technology: "AWS Lambda",
102 | url: "https://aws.amazon.com/lambda",
103 | },
104 | {
105 | logo: DynamoLogo,
106 | technology: "AWS DynamoDB",
107 | url: "https://aws.amazon.com/dynamodb/",
108 | },
109 | {
110 | logo: StripLogo,
111 | technology: "Stripe",
112 | url: "https://stripe.com/",
113 | },
114 | { logo: GraphQLLogo, technology: "GraphQL", url: "https://graphql.org" },
115 | {
116 | logo: SeedLogo,
117 | technology: "Seed",
118 | url: "http://seed.run/",
119 | },
120 | ]
121 |
122 | const track = (name, props) => {
123 | mixpanel.track(name, props)
124 | }
125 | const filterByPart = (array, path) => {
126 | const result = array
127 | .filter(i => i.node.frontmatter.part.trim() === path)
128 | .sort(
129 | (a, b) => a.node.frontmatter.postnumber - b.node.frontmatter.postnumber
130 | )
131 | return result
132 | }
133 |
134 | const filterByChapter = (array, path) => {
135 | const result = array
136 | .filter(i => i.node.frontmatter.chapter.trim() === path)
137 | .sort(
138 | (a, b) => a.node.frontmatter.postnumber - b.node.frontmatter.postnumber
139 | )
140 | return result
141 | }
142 | const filterByFramework = (array, path, framework) => {
143 | const result = array
144 | .filter(
145 | i =>
146 | i.node.frontmatter.chapter.trim() === path &&
147 | i.node.frontmatter.framework === framework
148 | )
149 | .sort(
150 | (a, b) => a.node.frontmatter.postnumber - b.node.frontmatter.postnumber
151 | )
152 | return result
153 | }
154 |
155 | export default {
156 | techStack,
157 | backendTechStack,
158 | filterByPart,
159 | filterByChapter,
160 | filterByFramework,
161 | partners,
162 | track,
163 | }
164 |
--------------------------------------------------------------------------------
/src/content/a-case-for-graphql.md:
--------------------------------------------------------------------------------
1 | ---
2 | path: /a-case-for-graphql
3 | title: A Case For Graphql
4 | tag: Epilouge
5 | date: 2020-04-13T00:00:00.000Z
6 | part: Epilouge
7 | chapter: "Epilouge"
8 | postnumber: 2
9 | ---
10 |
11 | GraphQL is a query language for your API that allows you to specify exactly in what shape and form you want your data to be queried or mutated on your API. GraphQL has nothing to do with your database schema nor does it forces you to use a graph database.
12 |
13 | ## What problem does GraphQL solve ?
14 |
15 | GraphQL prevents you from developing multiple endpoints to collect data. Let us say you had an API that does operations on Stocks, Companies & Currencies. You would have had to make the following endpoints in the backend:
16 |
17 | get all stocks
18 |
19 | ```
20 | /stocks
21 | ```
22 |
23 | to get a stock
24 |
25 | ```
26 | /stocks/{stock_id}
27 | ```
28 |
29 | get a company
30 |
31 | ```
32 | /companies/{company_id}
33 | ```
34 |
35 | I think you get the pattern. whereas with GraphQL our endpoint could be : `/graphql`
36 |
37 | with the following schema:
38 |
39 | ```graphql
40 | type Company {
41 | ID: Int!
42 | name: String
43 | }
44 |
45 | type Stock {
46 | ID: Int!
47 | name: String
48 | comapany: [Company]
49 | }
50 |
51 | type Query {
52 | getStocksAndCompanies(ID: Int): [Stock]
53 | }
54 | ```
55 |
56 | now to query the data using GraphQL you can simply do this:
57 |
58 | ```graphql
59 | {
60 | getStocksAndCompanies(ID: 2) {
61 | ID
62 | name
63 | company {
64 | name
65 | }
66 | }
67 | }
68 | ```
69 |
70 | Which will get all the companies associated with that stock for you.
71 |
72 | ## What are the advantages of using GraphQL?
73 |
74 | GraphQL has a better developer experience because once you create the schema on the back-end, a front-end developer can easily see the schema and understand how they need to structure their tasks on the front-end to access specific data. A GraphQL API is self-documenting.
75 |
76 | Server-side graphQL libraries come with Playground or GraphQil for you to test your API, so no more fiddling with Postman.
77 |
78 | GraphQL also comes with subscriptions which are real-time connections to the server for use cases like messaging, notifications etc. All without using other libraries for this use case.
79 |
--------------------------------------------------------------------------------
/src/content/adding-listings-to-dynamo.md:
--------------------------------------------------------------------------------
1 | ---
2 | path: /adding-listings-to-dynamo
3 | title: Adding listings to Dynamo
4 | tag: backend
5 | date: 2020-05-11T18:19:27.334Z
6 | part: Building backend
7 | chapter: Adding libraries
8 | postnumber: 9
9 | ---
10 |
11 | In this post we will manually add listings to DynamoDB. Usually we could create a mutation to add listings for someone. But for the sake of this course I've created all the listings for you.
12 |
13 | First off lets create a folder called `/seed-data` and create file called `listings.json`. Then head over to this [gist](https://gist.github.com/AmoDinho/49fe02084d5634ce4bf76ff544042196) and copy the contents of it into the file you've just created.
14 |
15 | Then create a file called `insertData.js` and add the following:
16 |
17 | ```javascript
18 | /*
19 |
20 | this function will allow us to add all the listings into Dynamo dynamically
21 | */
22 |
23 | //set up the aws-sdk
24 | const AWS = require("aws-sdk")
25 | AWS.config.update({ region: "us-east-1" })
26 | const docClient = new AWS.DynamoDB.DocumentClient()
27 |
28 | //import the listings json
29 | const listings = require("./listings.json")
30 | console.log("Listings.Init", listings)
31 |
32 | //lets inseert them into the table
33 | //loop over the listings
34 | listings.map(l => {
35 | //create params object
36 | listingParams = {
37 | TableName: "dev-lunar-listings",
38 | Item: {
39 | coverPhoto: l.coverPhoto,
40 | guide: {
41 | avatar: l.guide.avatar,
42 | bio: l.guide.bio,
43 | name: l.guide.name,
44 | },
45 | listingActivities: l.listingActivities,
46 | listingDescription: l.listingDescription,
47 | listingId: l.listingId,
48 | listingLocation: l.listingLocation,
49 | listingName: l.listingName,
50 | listingType: l.listingType,
51 | numberOfDays: l.numberOfDays,
52 | price: l.price,
53 | rating: l.rating,
54 | specialAmount: l.specialAmount,
55 | specialType: l.specialType,
56 | },
57 | }
58 |
59 | //put the data into the table
60 |
61 | docClient.put(listingParams, function(err, data) {
62 | if (err) {
63 | console.error(
64 | "Unable to add listing",
65 | user.name,
66 | ". Error JSON:",
67 | JSON.stringify(err, null, 2)
68 | )
69 | } else {
70 | console.log("PutItem succeeded:")
71 | }
72 | })
73 | })
74 | ```
75 |
76 | 🌶️ First off we setup the AWS SDK
77 |
78 | 🌶️ Then we import the JSON file with the listings.
79 |
80 | 🌶️ Loop over them to create a record in the table.
81 |
--------------------------------------------------------------------------------
/src/content/adding-the-aws-sdk.md:
--------------------------------------------------------------------------------
1 | ---
2 | path: /adding-aws-sdk
3 | title: Adding the AWS SDK
4 | tag: backend
5 | date: 2020-05-11T18:08:17.066Z
6 | part: Building backend
7 | chapter: Adding libraries
8 | postnumber: 7
9 | ---
10 |
11 | Here we will set up the AWS SDK to be able to interact with DynamoDDB.
12 |
13 | First do the following in your terminal:
14 |
15 | ```bash
16 | $ yarn add aws-sdk
17 | ```
18 |
19 | Then go ahead and create a `libs` directory in the root of the folder and create a file called `dynamodb-lib.js`.
20 |
21 | Lets now work on a wrapper around the `AWS-SDK` for Dynamo and add the following to `dynamodb-lib.js`:
22 |
23 | ```javascript
24 | import AWS from "aws-sdk"
25 |
26 | const client = new AWS.DynamoDB.DocumentClient()
27 |
28 | export default {
29 | get: params => client.get(params).promise(),
30 | put: params => client.put(params).promise(),
31 | query: params => client.query(params).promise(),
32 | update: params => client.update(params).promise(),
33 | delete: params => client.delete(params).promise(),
34 | scan: params => client.scan(params).promise(),
35 | }
36 | ```
37 |
38 | The following is happening:
39 |
40 | 🎯 the call function has two parameters: action and params. Action will specifiy what type of DynamoDB operation is taking place, while params will be the data or expressions we are sending to Dynamo like whether to tell it to find something by a specific attribute etc.
41 |
42 | 🎯 Then we return a promise with the action and params attached to our new instance of DynamoDB.
43 |
44 | Next we will go ahead and populate Dynamo with our listings!
45 |
--------------------------------------------------------------------------------
/src/content/complete-get-a-listing-query.md:
--------------------------------------------------------------------------------
1 | ---
2 | path: /complete-get-a-listing-query
3 | title: Complete Get A Listing Query
4 | tag: backend
5 | date: 2020-05-14T18:07:22.925Z
6 | part: Building backend
7 | chapter: Get A Listing Query
8 | postnumber: 14
9 | ---
10 |
11 | Now lets do what we did in the previous query and lets start in the `Query.js` file and add the following:
12 |
13 | ```javascript
14 | const params = {
15 | TableName: process.env.ListingsDB || "dev-listings",
16 | FilterExpression: "listingId = :listingId",
17 | ExpressionAttributeValues: {
18 | ":listingId": args.listingId,
19 | },
20 | }
21 | ```
22 |
23 | 🍕In this params object we are using a `FilterExpression` to get the specific listing from the table, which is the `listingId` in our case.
24 |
25 | 🍕The `ExpressionAttributesValues` object is where we need to assign a value to the property we declared in the `FilterExpression`.
26 |
27 | 🍕 `args` is short for the arguments we take in from query, in this case we are taking in the `listingId`.
28 |
29 | Next we can start sending these params to DynamoDB and return the selected listing:
30 |
31 | ```javascript
32 | try {
33 | const listing = await dynamodb.scan(params)
34 |
35 | if (listing.Items.length === 0) {
36 | return "There is no listing"
37 | } else {
38 | return {
39 | listingName: listing.Items[0].listingName,
40 |
41 | listingId: listing.Items[0].listingId,
42 | coverPhoto: listing.Items[0].coverPhoto,
43 | listingDescription: listing.Items[0].listingDescription,
44 | listingType: listing.Items[0].listingType.map(m => ({
45 | name: m,
46 | })),
47 | listingLocation: listing.Items[0].listingLocation,
48 | listingActivities: listing.Items[0].listingActivities.map(k => ({
49 | name: k,
50 | })),
51 | specialType: listing.Items[0].specialType,
52 | specialAmount: listing.Items[0].specialAmount,
53 | rating: listing.Items[0].rating,
54 | guide: {
55 | Name: listing.Items[0].guide.name,
56 | Bio: listing.Items[0].guide.bio,
57 | Avatar: listing.Items[0].guide.avatar,
58 | },
59 | price: listing.Items[0].price,
60 | numberOfDays: listing.Items[0].numberOfDays,
61 | }
62 | }
63 | } catch (e) {
64 | return {
65 | message: e.message,
66 | code: "500",
67 | }
68 | }
69 | ```
70 |
71 | 🍕In our try-catch we do a scan again to to retrieve the listing
72 |
73 | 🍕 Then we check if the scan returned a listing that exists. If so we return the data associated to that listing.
74 |
75 | 🍕 Since it was a filter scan it will still be an array and we can always assume it will be the first element.
76 |
77 | 🍕Lastly we return a message and code if there is an error.
78 |
79 | Next up we can run this query in Playground:
80 |
81 | ```javascript
82 | {
83 | getAListing(listingId:"a114dded-ddef-4052-a106-bb18b94e6b51"){
84 | listingId
85 | listingName
86 | listingType{
87 | name
88 | }
89 | listingActivities{
90 | name
91 | }
92 | }
93 | }
94 | ```
95 |
96 | The query above should get you the same output as shown in the screenshot:
97 |
98 | 
99 |
100 | Now that we have the query working we can start on the mutation.
101 |
--------------------------------------------------------------------------------
/src/content/complete-listing-query.md:
--------------------------------------------------------------------------------
1 | ---
2 | path: /complete-listings-query
3 | title: Complete Listing Query
4 | tag: backend
5 | date: 2020-05-13T18:27:10.974Z
6 | part: Building backend
7 | chapter: All listings query
8 | postnumber: 12
9 | ---
10 |
11 | In this part we get the data from DynamoDB And return it.
12 |
13 | Back in our `query.js` file we need to import the DynamoDB wrapper at the top of the file:
14 |
15 | ```javascript
16 | import * as dynamoDBLib from "../../libs/dynamodb-lib"
17 | ```
18 |
19 | Next up we can create the parameters for our DynamoDB operation:
20 |
21 | ```javascript
22 | const params = {
23 | TableName: process.env.ListingsDB || "dev-listings",
24 | }
25 | ```
26 |
27 | Just a simple object that has the tablename. reason why we have the or operator is for when we test, Jest cannot pickup the `.env` file.
28 |
29 | Next we have a try catch block that will help us fetch the data and return it:
30 |
31 | ```javascript
32 | try {
33 | const result = await dynamoDBLib.call("scan", params)
34 |
35 | if (result.Items.length === 0) {
36 | return "You have no listings"
37 | } else {
38 | return result.Items.map(i => ({
39 | listingId: i.listingId,
40 | coverPhoto: i.coverPhoto,
41 | listingName: i.listingName,
42 | listingDescription: i.listingDescription,
43 | listingType: i.listingType.map(m => ({
44 | name: m,
45 | })),
46 | listingLocation: i.listingLocation,
47 | listingActivities: i.listingActivities.map(k => ({
48 | name: k,
49 | })),
50 | specialType: i.specialType,
51 | specialAmount: i.specialAmount,
52 | rating: i.rating,
53 | guide: {
54 | Name: i.guide.name,
55 | Bio: i.guide.bio,
56 | Avatar: i.guide.avatar,
57 | },
58 | price: i.price,
59 | numberOfDays: i.numberOfDays,
60 | }))
61 | }
62 |
63 | // return result;
64 | } catch (e) {
65 | return {
66 | message: e.message,
67 | code: "500x",
68 | }
69 | }
70 | ```
71 |
72 | 🔋First we return a promise and execute a scan on Dynamo and pass through our params object.
73 |
74 | 🔋We have an if-else block to check if the is no listings, if they are, return all the listings
75 |
76 | 🔋Because we are returning an array we need to map over each element in the array and returning each field in the object.
77 |
78 | 🔋In the case of the ListingType & ListingActivies these are nested arrays within the listing object itself, which we look over to return each element in the array.
79 |
80 | 🔋 In the case of the guide all we are doing is returning back the properties in the guide object.
81 |
82 | 🔋 Lastly we return an error message and code in the case anything fails.
83 |
84 | Next lets now go to Playground and test the query by executing the following query:
85 |
86 | ```javascript
87 | {
88 | getAllListings{
89 | listingId
90 | listingName
91 | listingLocation
92 | rating
93 | listingType{
94 | name
95 | }
96 | }
97 | }
98 | ```
99 |
100 | Now the function returns our data as we have shaped it:
101 |
102 | 
103 |
--------------------------------------------------------------------------------
/src/content/create-all-listings-pagge.md:
--------------------------------------------------------------------------------
1 | ---
2 | path: /create-all-listings-page
3 | title: Create All Listings Page
4 | tag: frontend
5 | date: 2020-07-20T18:23:15.150Z
6 | part: frontend
7 | postnumber: 43
8 | framework: react
9 | chapter: Get All Listings
10 | ---
11 |
12 | In this chapter we will create the landing page that will get all the listings.
13 |
14 | Open up the `index.js` file under the `pages` directory and add the following:
15 |
16 | ```javascript
17 | import React from "react"
18 | import LandingHeader from "../components/navs/LandingHeader"
19 | import { useQuery } from "@apollo/react-hooks"
20 | import { GET_ALL_LISTINGS } from "../graphql/Queries"
21 | import ListingCard from "../components/cards/ListingCard"
22 | import { Link } from "@reach/router"
23 | import { Skeleton } from "antd"
24 |
25 | const Index = () => {
26 | const { loading, data, error } = useQuery(GET_ALL_LISTINGS)
27 |
28 | if (loading) return
29 | if (error) return
{error}
30 |
31 | return (
32 | <>
33 |
37 |
38 | {data.getAllListings.map(listing => (
39 |
40 |
41 |
48 |
49 |
50 | ))}
51 |
52 | >
53 | )
54 | }
55 |
56 | export default Index
57 | ```
58 |
59 | 🥑 First off we are using the `useQuery` hook from the Apollo library to get all the listings.
60 |
61 | 🥑 Then we have two if statements that will return some HTML for us if `loading` or `error` is true. This is nice because we do not have to do any fancy stuff to determine if the API is fetching data for us or if the QUERY failed.
62 |
63 | 🥑 In our return block, we have the ``component with an `imgURL` and text props. While we loop of over the listings and pass through the data of each listing as props to our `` component.
64 |
65 | Next up we need to create the schema document for the `GET_ALL_LISTINGS` query, so head over to the `Queries.js` file under the `graphql` directory and add the following:
66 |
67 | ```javascript
68 | export const GET_ALL_LISTINGS = gql`
69 | query GetAllListings {
70 | getAllListings {
71 | listingId
72 | coverPhoto
73 | listingName
74 | listingLocation
75 | rating
76 | price
77 | }
78 | }
79 | `
80 | ```
81 |
82 | 🥑 We are calling the `getAllListings` query that will give is the above fields to pass into the listing card. Note we are only telling the API to gives exactly the fields we want.
83 |
84 | If all goes well, you should have this:
85 |
86 | 
87 |
--------------------------------------------------------------------------------
/src/content/create-backend-repo.md:
--------------------------------------------------------------------------------
1 | ---
2 | path: /create-backend-repo
3 | title: Create Backend Repo
4 | tag: backend
5 | date: 2020-04-21T00:00:00.000Z
6 | part: setting up backend
7 | chapter: creating repo
8 | postnumber: 3
9 | ---
10 |
11 | In this chapter, we will initialise the back-end repo and get everything setup.
12 |
13 | ## Create a repo on Github
14 |
15 | Then head over to [Github](https://github.com/) and click on create a repo
16 |
17 | 
18 |
19 | Then create an empty repo and call it lunar-tour-api, and leave the other fields blank.
20 |
21 | 
22 |
23 | Now we need to clone the [servless-graphQL starter](https://github.com/pimp-my-book/serverless-graphql-nodejs-starter), so open your terminal and type in the following:
24 |
25 | ```bash
26 | $ serverless install --url https://github.com/pimp-my-book/serverless-graphql-nodejs-starter --name lunar-tour-api
27 | ```
28 |
29 | Now we need to send the code up to GitHub:
30 |
31 | **Go into the directory of the repo:**
32 |
33 | ```bash
34 | $ cd lunar-tour-api
35 | ```
36 |
37 | Initialise git:
38 |
39 | ```bash
40 | $ git init
41 | ```
42 |
43 | Add all the files
44 |
45 | ```bash
46 | $ git add .
47 | ```
48 |
49 | Create a commit message:
50 |
51 | ```bash
52 | $ git commit -m "initial commit"
53 | ```
54 |
55 | Add the remote URL (Make sure you add the one you created!):
56 |
57 | ```bash
58 | $ git remote add origin https://github.com/name-of-repo.git
59 | ```
60 |
61 | Push all your changes:
62 |
63 | ```bash
64 | git push -u origin master
65 | ```
66 |
67 | Awesome, your repo should be full of code now on GitHub!
68 |
--------------------------------------------------------------------------------
/src/content/create-blue-block-button-1.md:
--------------------------------------------------------------------------------
1 | ---
2 | path: /create-blue-block-button-react
3 | title: Create Blue Block Button
4 | tag: frontend
5 | date: 2020-07-16T18:35:22.849Z
6 | part: frontend
7 | postnumber: 40
8 | framework: react
9 | chapter: Building reusable components
10 | ---
11 |
12 | In this chapter we will build out a Secondary button Blue.
13 |
14 | In the `buttons` folder, create a `BlueBlockButton.js` component and add the following:
15 |
16 | ```javascript
17 | import React from "react"
18 | import PropTypes from "prop-types"
19 | import styled, { keyframes } from "styled-components"
20 | import loading from "../../assets/loop.svg"
21 |
22 | const rotate = keyframes`
23 | from {
24 | transform: rotate(0deg);
25 | }
26 | to {
27 | transform: rotate(360deg);
28 | }
29 | `
30 | const ImageStyles = styled.img`
31 | display: inline-block;
32 | animation: ${rotate} 2s infinite linear;
33 | `
34 |
35 | const BlueBlockButton = ({
36 | className = "",
37 | onClick,
38 | children,
39 | disabled = false,
40 | isLoading,
41 | large,
42 | long,
43 | ...props
44 | }) => {
45 | return (
46 |
55 | )
56 | }
57 | const propTypes = {
58 | className: PropTypes.string,
59 | onClick: PropTypes.func,
60 | children: PropTypes.string,
61 | disabled: PropTypes.bool,
62 | large: PropTypes.bool,
63 | long: PropTypes.bool,
64 | isLoading: PropTypes.bool,
65 | }
66 | BlueBlockButton.propTypes = propTypes
67 | export default BlueBlockButton
68 | ```
69 |
70 | Yet again we are doingg something simalr, only difference is that it is a different colour button.
71 |
72 | Once you're done it should look like this:
73 |
74 | 
75 |
--------------------------------------------------------------------------------
/src/content/create-blue-block-button.md:
--------------------------------------------------------------------------------
1 | ---
2 | path: /create-blue-block-button
3 | title: Create Blue Block Button
4 | tag: frontend
5 | date: 2020-05-21T17:56:23.597Z
6 | part: Setup frontend
7 | chapter: Building reusable components
8 | postnumber: 27
9 | framework: vue
10 | ---
11 |
12 | In this chapter we will build out a Secondary button Blue.
13 |
14 | In the `buttons` folder, create a `BlueBlockButton.vue` component and add the following:
15 |
16 | ```javascript
17 |
18 |
31 |
32 |
45 |
59 |
60 |
61 |
62 | ```
63 |
64 | 🧁In the template part we have a button with a handle click event.
65 |
66 | 🧁 Then if the `v-if` statement is triggered the isLoading prop is set to true. It will show the loading spinner.
67 |
68 | 🧁 We have a method that emits a click event.
69 |
70 | Once you're done it should look like this:
71 |
72 | 
73 |
--------------------------------------------------------------------------------
/src/content/create-booking-forms-pt-1.md:
--------------------------------------------------------------------------------
1 | ---
2 | path: /create-booking-forms-part-one
3 | title: Create Booking Forms Pt.1
4 | tag: frontend
5 | date: 2020-07-29T18:03:50.120Z
6 | part: frontend
7 | postnumber: 47
8 | framework: react
9 | chapter: Make A Booking Mutation
10 | ---
11 |
12 | In this three part section we will create the booking booking forms as well us hook up our local resolvers to the forms.
13 |
14 | First up create a `booking` folder in the pages directory, and create an `index.js` with the following:
15 |
16 | ```javascript
17 | import React, { useState } from "react"
18 | import Tabs from "../../components/navs/Tabs"
19 | import CustomerDetails from "./CustomerDetails"
20 | import Customers from "./Customers"
21 | import Checkout from "./Checkout"
22 | import ConfirmationTab from "./ConfirmationTab"
23 | const BookingIndex = props => {
24 | const [activeTab, setActiveTab] = useState("1")
25 |
26 | return (
27 | <>
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | >
43 | )
44 | }
45 |
46 | export default BookingIndex
47 | ```
48 |
49 | 🌊 Here we have a component with tabs that are hidden to allow the user to navigate between different forms.
50 |
51 | 🌊 Don't worry about some of the other components we will create them. We have an `activeTab` variable that tells the component which tab should be active, we then pass down the `setActiveTab` hook down to each component so that we can change the active tabs when the user is in any component.
52 |
53 | Next go into the `navs` directory and create a file called `Tabs.js`:
54 |
55 | ```javascript
56 | import styled from "styled-components"
57 | import { Tabs } from "antd"
58 | const TabStyles = styled(Tabs)`
59 | .ant-tabs-bar.ant-tabs-top-bar {
60 | display: none;
61 | }
62 | `
63 |
64 | export default TabStyles
65 | ```
66 |
67 | 🌊 Here we are basically overriting the styles from Ant Design for the Tabs so that we can hide the Tab bar with Styled Components
68 |
69 | Next up lets add the route of the Booking index file:
70 |
71 | ```javascript
72 | import React from "react"
73 | import { Router } from "@reach/router"
74 | import Home from "./pages/index"
75 | import ViewListing from "./pages/ViewListing"
76 | import BookingIndex from "./pages/booking"
77 | const Routes = ({ props }) => {
78 | return (
79 |
80 |
81 |
82 |
83 |
84 | )
85 | }
86 |
87 | export default Routes
88 | ```
89 |
90 | Make sure your `routes.js` component now looks like the above.
91 |
--------------------------------------------------------------------------------
/src/content/create-booking-forms-pt-3.md:
--------------------------------------------------------------------------------
1 | ---
2 | path: /create-booking-forms-part-three
3 | title: Create Booking Forms Pt.3
4 | tag: frontend
5 | date: 2020-07-29T18:36:08.870Z
6 | part: frontend
7 | postnumber: 49
8 | framework: react
9 | chapter: Make A Booking Mutation
10 | ---
11 |
12 | In this finale of the three parts we will work on the checkout form and the confirmation tab.
13 |
14 | In the `booking` directory, create a file called `Checkout.js` and add the following:
15 |
16 | ```javascript
17 | import React from "react"
18 | import { loadStripe } from "@stripe/stripe-js"
19 | import {
20 | CardElement,
21 | Elements,
22 | useStripe,
23 | useElements,
24 | } from "@stripe/react-stripe-js"
25 | import HeadingOne from "../../components/typography/HeadingOne"
26 | import BodyOne from "../../components/typography/BodyOne"
27 | import RedBlockButton from "../../components/buttons/RedBlockButton"
28 | import RedOutlineButton from "../../components/buttons/RedOutlineButton"
29 |
30 | const StripeElements = props => {
31 | const stripe = useStripe()
32 | const elements = useElements()
33 |
34 | const pay = async () => {
35 | const result = await stripe.createPaymentMethod({
36 | type: "card",
37 | card: elements.getElement(CardElement),
38 | })
39 | props.setActiveTab("4")
40 | console.log(result)
41 | }
42 | return (
43 | <>
44 |
45 |
63 | Your trip total
64 |
65 | Test using this credit card: 4242 4242 4242 4242, and enter any 5
66 | digits for the zip code
67 |
68 |
69 |
70 |
71 | )
72 | }
73 |
74 | export default Checkout
75 | ```
76 |
77 | 🦚 Here the `StripeElements` is a child component with props passed to it, we make use of the two hooks to allow us to pay and setup the Stripe form
78 |
79 | 🦚 The pay function will trigger Stripe to Pay for us. When it is done we will then send the user to the next tab.
80 |
81 | 🦚 We use Stripe's `cardElement` component and have two buttons with the option to pay or go back to the next tab
82 |
83 | 🦚 `Checkout` is the parent component that will render the form for the Stripe.
84 |
85 | Next up create a file called `ConfirmationTab.js` :
86 |
87 | ```javascript
88 | import React from "react"
89 | import HeadingOne from "../../components/typography/HeadingOne"
90 | import BodyOne from "../../components/typography/BodyOne"
91 | import RedBlockButton from "../../components/buttons/RedBlockButton"
92 | import vector from "../../assets/Vector.svg"
93 | import ticket from "../../assets/confirmation_number.svg"
94 | const ConfirmationTab = props => {
95 | return (
96 | <>
97 |
98 | Thanks for booking with us
99 |
100 |
101 | Your link to your ticket is in the mail. Keep it safe and we will see
102 | you soon on{" "}
103 |
104 |
111 | props.navigate(`/`)} className="mr-5">
112 | Book more
113 |
114 |
115 |
116 | >
117 | )
118 | }
119 |
120 | export default ConfirmationTab
121 | ```
122 |
123 | 🦚 In this last form we thank the user for booking the trip. While allowing them to view a reciept as their ticket from Stripe.
124 |
--------------------------------------------------------------------------------
/src/content/create-home-component.md:
--------------------------------------------------------------------------------
1 | ---
2 | path: /create-home-component
3 | title: Create Home Component
4 | tag: frontend
5 | date: 2020-05-22T17:00:59.770Z
6 | part: Build frontend
7 | chapter: Landing Page
8 | postnumber: 31
9 | framework: vue
10 | ---
11 |
12 | In this part we will create the following landing page.
13 |
14 | First create a `Home.vue` component in the `Views` directory. Then go into the `router.js` and add the following route.
15 |
16 | ```javascript
17 | {
18 | path: "/",
19 | component: Home
20 | }
21 | ```
22 |
23 | Also import the file at the top of the page:
24 |
25 | ```javascript
26 | import Home from "./pages/Home"
27 | ```
28 |
29 | Next up open the `Home.vue` component and copy the following:
30 |
31 | ```javascript
32 |
33 |
34 |
39 |
40 |
41 |
42 |
43 |
44 |
error...
45 |
46 |
47 |
48 |
55 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
81 |
87 |
88 | ```
89 |
90 | ## What is happening here ?
91 |
92 | 🍣 First we are using the `LandingHeader` component to create the header. We passing the `text` and the `image URL`.
93 |
94 | 🍣Next up are querying the API by using the `ApolloQuery` component. We indicate that we are using are a query and reading a query document which we will make soon.
95 |
96 | 🍣In the template under the Apollo Query we pass in a `v-slot` that has an result object with `loading`, `error` and `data` variables.
97 |
98 | 🍣In our `v-if` we check that if the `loading` variable is true and we show a loading component
99 |
100 | 🍣Next we check if the `error` variable is true and tell the user that an error occurred. You can customize this with anything you want.
101 |
102 | 🍣Else if the data is present render all the data. we have a `v-for` that we loop over from the `data.getAllListings` query. Then we give it the key of the `listingId`.
103 |
104 | 🍣We have a `router-link` wrapped around the `TourCard` to redirect the user to the view of the listing.
105 |
106 | 🍣Then we pass the data of the listing into the props of the Tour Card.
107 |
108 | Next we need to create the query schema document. create a folder called `graphql` and create a filed called `getAllListings.gql` and paste the following:
109 |
110 | ```javascript
111 | query GetAllListings {
112 | getAllListings {
113 | listingId
114 | coverPhoto
115 | listingName
116 | listingLocation
117 | rating
118 | price
119 | }
120 | }
121 | ```
122 |
123 | Make sure your `App.vue` looks like this:
124 |
125 | ```javascript
126 |
127 |
128 |
129 |
130 |
131 |
132 |
142 |
143 | ```
144 |
145 | So if we go to the landing page in the browser we should see all the listings load.
146 |
147 | If all goes well, you should have this:
148 |
149 | 
150 |
--------------------------------------------------------------------------------
/src/content/create-input-1.md:
--------------------------------------------------------------------------------
1 | ---
2 | path: /create-react-input
3 | title: Create Input
4 | tag: frontend
5 | date: 2020-07-16T18:05:53.809Z
6 | part: frontend
7 | postnumber: 35
8 | framework: react
9 | chapter: Building reusable components
10 | ---
11 |
12 | In this section we will embark on creating UI components for the application. The reason why I like creating basic components like inputs,buttons etc from scratch is because I will always have control over the styling and not need to overwrite CSS classes.
13 |
14 | Usually I create a component library and package it to NPM and import it into my app, but that would be overkill for such a small project like this.
15 |
16 | First up under the `components` folder, create a folder called `inputs`, then create a file called `input.js` and paste the following:
17 |
18 | ```javascript
19 | import React from "react"
20 | import PropTypes from "prop-types"
21 |
22 | const Input = ({ className, value, onChange, ...props }) => {
23 | return (
24 |
38 | )
39 | }
40 |
41 | const propTypes = {
42 | className: PropTypes.string,
43 | value: PropTypes.string,
44 | onChange: PropTypes.func,
45 | }
46 | Input.propTypes = propTypes
47 | export default Input
48 | ```
49 |
50 | Here we have a basic input component that can accept a couple of props and events. Once you're done it should look like this:
51 |
52 | 
53 |
--------------------------------------------------------------------------------
/src/content/create-input.md:
--------------------------------------------------------------------------------
1 | ---
2 | path: /create-input
3 | title: Create Input
4 | tag: frontend
5 | date: 2020-05-21T18:50:13.817Z
6 | part: Setup frontend
7 | chapter: Building reusable components
8 | postnumber: 22
9 | framework: vue
10 | ---
11 |
12 | In this post we will create an Input component to handle all our input related needs.
13 |
14 | Create an `input` directory and create an `input.vue` file with the following:
15 |
16 | ```javascript
17 |
18 |
32 |
33 |
51 |
52 | ```
53 |
54 | 🥤This input component accepts text and has a `handleInput` method.
55 | 🥤 `this.$emit` helps us store the value of the component if we are calling it from a component higher up the tree.
56 |
57 | Once you're done it should look like this:
58 |
59 | 
60 |
--------------------------------------------------------------------------------
/src/content/create-listing-card.md:
--------------------------------------------------------------------------------
1 | ---
2 | path: /create-listing-card-react
3 | title: Create Listing Card
4 | tag: frontend
5 | date: 2020-07-16T18:47:33.532Z
6 | part: frontend
7 | postnumber: 42
8 | framework: react
9 | chapter: Building reusable components
10 | ---
11 |
12 | In this post we will make a tour card that will allow us to display all our listings.
13 |
14 | Create a `cards` directory and create `ListingCard.js` file:
15 |
16 | ```javascript
17 | import React from "react"
18 | import { Rate } from "antd"
19 | import BodyOne from "../typography/BodyOne"
20 | import PropTypes from "prop-types"
21 | import "antd/dist/antd.css"
22 |
23 | const ListingCard = ({
24 | listingTitle,
25 | listingLocation,
26 | price,
27 | rating,
28 | coverPhoto,
29 | }) => (
30 | <>
31 |
32 |
33 |
34 |
35 | {listingTitle}
36 | {listingLocation}
37 |
38 |
39 | ${price}
40 |
41 |
42 |
43 | >
44 | )
45 |
46 | ListingCard.propTypes = {
47 | listingTitle: PropTypes.string,
48 | listingLocation: PropTypes.string,
49 | price: PropTypes.string,
50 | rating: PropTypes.number,
51 | coverPhoto: PropTypes.string,
52 | }
53 | export default ListingCard
54 | ```
55 |
56 | 🎡 We have a `div` that we have given a `border-radius` and `box-shadow` that has width and height set to auto.
57 |
58 | 🎡 It takes props called `listingTitle`, `listingLocation`, `price`, `rating` and `coverPhoto`.
59 |
60 | Next off we need to add Ant Design because we will use it's tabs and rating components.
61 |
62 | ```bash
63 | $ yarn add antd
64 | ```
65 |
66 | Once you're done, it will look like this:
67 |
68 | 
69 |
--------------------------------------------------------------------------------
/src/content/create-nav-and-header-1.md:
--------------------------------------------------------------------------------
1 | ---
2 | path: /create-nav-and-header-react
3 | title: Create Nav and Header
4 | tag: frontend
5 | date: 2020-07-16T18:12:57.222Z
6 | part: frontend
7 | postnumber: 36
8 | framework: react
9 | chapter: Building reusable components
10 | ---
11 |
12 | In this post we will create a Navigation bar and a Header component for our landing page.
13 |
14 | Go head and create a `navs` folder and create a `Nav.js` file with the following:
15 |
16 | ```javascript
17 | import React from "react"
18 |
19 | const Nav = () => (
20 |
29 | )
30 |
31 | export default Nav
32 | ```
33 |
34 | 🌞 We have an HTML Nav bar that is is responsive and allows the user to redirect to the Home page of the site.
35 |
36 | Next up create a `headers` folder and create a file called `LandingHeader.js` with the following contents:
37 |
38 | ```javascript
39 | import React from "react"
40 | import { HeadingOne } from "../typography"
41 | const LandingHeader = ({ imgURL, text = "" }) => {
42 | return (
43 | <>
44 |
53 | {text}
54 |
55 | >
56 | )
57 | }
58 |
59 | export default LandingHeader
60 | ```
61 |
62 | 🌞 This is a `div` with a custom style to allow the image to sit in the background of the component. Tailwind CSS has directives and plugins for this sort of thing, however I left this out and went the vanilla route. Do not be scared to code!
63 |
64 | 🌞 Then we have Vanilla CSS styles for this component as you are unable to replicate this in TailwindCSS.
65 |
66 | 🌞 Lastly, we have two props for the image URL and whatever text you want to put in the `H1`.
67 |
68 | Once you're done it should look like this:
69 |
70 | 
71 |
--------------------------------------------------------------------------------
/src/content/create-nav-and-header.md:
--------------------------------------------------------------------------------
1 | ---
2 | path: /create-nav-header
3 | title: Create Nav and Header
4 | tag: frontend
5 | date: 2020-05-21T18:36:34.895Z
6 | part: Setup frontend
7 | chapter: Building reusable components
8 | postnumber: 23
9 | framework: vue
10 | ---
11 |
12 | In this post we will create a Navigation bar and a Header component for our landing page.
13 |
14 | Go head and create a `navs` folder and create a `Nav.vue` file with the following:
15 |
16 | ```javascript
17 |
18 |
27 |
28 |
33 |
38 |
39 | ```
40 |
41 | 🌞 We have an HTML Nav bar that is is responsive and allows the user to redirect to the Home page of the site.
42 |
43 | Next up create a `headers` folder and create a file called `LandingHeader.vue` with the following contents:
44 |
45 | ```javascript
46 |
47 |
48 |
49 |
50 | {{ text }}
51 |
52 |
53 |
54 |
55 |
56 |
71 |
79 |
80 | ```
81 |
82 | 🌞In this `div` we have a `v-if` statement that accepts text to be displayed if we need it.
83 |
84 | 🌞 There is also a computed style prop that accepts an image url that will display a picture in the background based on the image URL.
85 |
86 | 🌞Then we have Vanilla CSS styles for this component as you are unable to replicate this in TailwindCSS.
87 |
88 | Once you're done it should look like this:
89 |
90 | 
91 |
--------------------------------------------------------------------------------
/src/content/create-red-block-button-1.md:
--------------------------------------------------------------------------------
1 | ---
2 | path: /create-red-block-button-react
3 | title: Create Red Block Button
4 | tag: frontend
5 | date: 2020-07-16T18:25:31.189Z
6 | part: frontend
7 | postnumber: 38
8 | framework: react
9 | chapter: Building reusable components
10 | ---
11 |
12 | As we continue to build out these components we need a couple of buttons. So we will build our "Primary Button"
13 |
14 | In the components folder create a folder called `buttons`. Then create a `RedBlockButton.js` component and add the following:
15 |
16 | ```javascript
17 | import React from "react"
18 | import PropTypes from "prop-types"
19 | import styled, { keyframes } from "styled-components"
20 | import loading from "../../assets/loop.svg"
21 |
22 | const rotate = keyframes`
23 | from {
24 | transform: rotate(0deg);
25 | }
26 | to {
27 | transform: rotate(360deg);
28 | }
29 | `
30 | const ImageStyles = styled.img`
31 | display: inline-block;
32 | animation: ${rotate} 2s infinite linear;
33 | `
34 |
35 | const BlueBlockButton = ({
36 | className = "",
37 | onClick,
38 | children,
39 | disabled = false,
40 | isLoading,
41 | large,
42 | long,
43 | ...props
44 | }) => {
45 | return (
46 |
55 | )
56 | }
57 | const propTypes = {
58 | className: PropTypes.string,
59 | onClick: PropTypes.func,
60 | children: PropTypes.string,
61 | disabled: PropTypes.bool,
62 | large: PropTypes.bool,
63 | long: PropTypes.bool,
64 | isLoading: PropTypes.bool,
65 | }
66 | BlueBlockButton.propTypes = propTypes
67 | export default BlueBlockButton
68 | ```
69 |
70 | 🧁 Generally buttons have a spinner inside them to allow you to accept a signal that after you click on it that it is loading after making a call to an API or something. That is why we use StyledComponents and a basic CSS animation to get the icon to spin.
71 |
72 | 🧁 Then we have a couple of Props and events passed into the button.
73 |
74 | 🧁 Now we have a ternary operator to see if the `isLoading` prop is true, if so render the Loading spinner. If not render the children in the button.
75 |
76 | Once you're done it should look like this:
77 |
78 | 
79 |
80 | Now it is really easy to pull something out of Bootstrap or your fav library. However, I've found this a lot easier in cases when a designer has custom button button designs. It takes longer to find the necessary CSS class to override than to just write HTML and JavaScript to create things.
81 |
--------------------------------------------------------------------------------
/src/content/create-red-block-button.md:
--------------------------------------------------------------------------------
1 | ---
2 | path: /create-red-block-button
3 | title: Create Red Block Button
4 | tag: frontend
5 | date: 2020-05-21T17:49:41.084Z
6 | part: Setup frontend
7 | chapter: Building reusable components
8 | postnumber: 25
9 | framework: vue
10 | ---
11 |
12 | In this chapter we will build out our button components that we will be using through out the application. First lets go ahead and create a Primary button.
13 |
14 | In the components folder create a folder called `buttons`. Then create a `RedBlockButton.vue` component and add the following:
15 |
16 | ```javascript
17 |
18 |
36 |
37 |
50 |
64 |
65 |
66 |
67 | ```
68 |
69 | 🧁In the template part we have a button with a handle click event.
70 |
71 | 🧁 Then if the `v-if` statement is triggered the isLoading prop is set to true. It will show the loading spinner.
72 |
73 | 🧁 We have a method that emits a click event.
74 |
75 | Once you're done it should look like this:
76 |
77 | 
78 |
--------------------------------------------------------------------------------
/src/content/create-red-outline-button-1.md:
--------------------------------------------------------------------------------
1 | ---
2 | path: /create-red-outline-button-react
3 | title: Create Red Outline Button
4 | tag: frontend
5 | date: 2020-07-16T18:30:51.947Z
6 | part: frontend
7 | postnumber: 39
8 | framework: react
9 | chapter: Building reusable components
10 | ---
11 |
12 | In this chapter we will build out our Secondary button.
13 |
14 | In the `buttons` folder, create a `RedOutlineButton.js` component and add the following:
15 |
16 | ```javascript
17 | import React from "react"
18 | import PropTypes from "prop-types"
19 | import styled, { keyframes } from "styled-components"
20 | import loading from "../../assets/loop.svg"
21 |
22 | const rotate = keyframes`
23 | from {
24 | transform: rotate(0deg);
25 | }
26 | to {
27 | transform: rotate(360deg);
28 | }
29 | `
30 | const ImageStyles = styled.img`
31 | display: inline-block;
32 | animation: ${rotate} 2s infinite linear;
33 | `
34 |
35 | const RedOutlineButton = ({
36 | className = "",
37 | onClick,
38 | children,
39 | disabled = false,
40 | isLoading,
41 | large,
42 | long,
43 | ...props
44 | }) => {
45 | return (
46 |
55 | )
56 | }
57 | const propTypes = {
58 | className: PropTypes.string,
59 | onClick: PropTypes.func,
60 | children: PropTypes.string,
61 | disabled: PropTypes.bool,
62 | large: PropTypes.bool,
63 | long: PropTypes.bool,
64 | isLoading: PropTypes.bool,
65 | }
66 | RedOutlineButton.propTypes = propTypes
67 | export default RedOutlineButton
68 | ```
69 |
70 | We are doing the exact same thing from the `RedBlockButton` we just literally removing the background.
71 |
72 | Once you're done it should look like this:
73 |
74 | 
75 |
--------------------------------------------------------------------------------
/src/content/create-red-outline-button.md:
--------------------------------------------------------------------------------
1 | ---
2 | path: /create-red-outline-button
3 | title: Create Red Outline Button
4 | tag: frontend
5 | date: 2020-05-18T18:58:18.910Z
6 | part: Setup frontend
7 | chapter: Building reusable components
8 | postnumber: 26
9 | framework: vue
10 | ---
11 |
12 | In this chapter we will build out our Secondary button.
13 |
14 | In the `buttons` directroy create a `RedOutlineButton.vue` component and add the following:
15 |
16 | ```javascript
17 |
18 |
36 |
37 |
50 |
64 |
65 |
66 | ```
67 |
68 | 🧁In the template part we have a button with a handle click event.
69 |
70 | 🧁 Then if the `v-if` statement is triggered the isLoading prop is set to true. It will show the loading spinner.
71 |
72 | 🧁 We have a method that emits a click event.
73 |
74 | Once you're done it should look like this:
75 |
76 | 
77 |
--------------------------------------------------------------------------------
/src/content/create-remove-button-1.md:
--------------------------------------------------------------------------------
1 | ---
2 | path: /create-remove-button-react
3 | title: Create Remove Button
4 | tag: frontend
5 | date: 2020-07-16T18:41:11.878Z
6 | part: frontend
7 | postnumber: 41
8 | framework: react
9 | chapter: Building reusable components
10 | ---
11 |
12 | In this chapter we will build out our components that we will be using through out the application. First lets go ahead and create a Remove button.
13 |
14 | In the components folder create a folder called `buttons`. Then create a `RemoveButton.js` component and add the following:
15 |
16 | ```javascript
17 | import React from "react"
18 | import PropTypes from "prop-types"
19 | import remove from "../../assets/remove.svg"
20 |
21 | const RemoveButton = ({
22 | className = "",
23 | onClick,
24 | text,
25 | disabled = false,
26 | isLoading,
27 | large,
28 | long,
29 | ...props
30 | }) => {
31 | return (
32 |
44 | )
45 | }
46 | const propTypes = {
47 | className: PropTypes.string,
48 | onClick: PropTypes.func,
49 | text: PropTypes.string,
50 | disabled: PropTypes.bool,
51 | large: PropTypes.bool,
52 | long: PropTypes.bool,
53 | isLoading: PropTypes.bool,
54 | }
55 | RemoveButton.propTypes = propTypes
56 | export default RemoveButton
57 | ```
58 |
59 | 🧁 This button has an icon that will allow us to remove items in forms that we want to be removed.
60 |
61 | Once you're done, it will look like this:
62 |
63 | 
64 |
--------------------------------------------------------------------------------
/src/content/create-remove-button.md:
--------------------------------------------------------------------------------
1 | ---
2 | path: /create-remove-button
3 | title: Create Remove Button
4 | tag: frontend
5 | date: 2020-05-21T18:03:52.094Z
6 | part: Setup frontend
7 | chapter: Building reusable components
8 | postnumber: 28
9 | framework: vue
10 | ---
11 |
12 | In this chapter we will build out a Remove button.
13 |
14 | In the `buttons` folder, create a `RemoveButton.vue` component and add the following:
15 |
16 | ```javascript
17 |
18 |
27 |
28 |
38 |
39 |
40 | ```
41 |
42 | 🧁In the template part we have a button with a handle click event.
43 |
44 | 🧁 Then if the `v-if` statement is triggered the isLoading prop is set to true. It will show the loading spinner.
45 |
46 | 🧁 We have a method that emits a click event.
47 |
48 | Once you're done, it will look like this:
49 |
50 | 
51 |
--------------------------------------------------------------------------------
/src/content/create-schema-for-a-listings.md:
--------------------------------------------------------------------------------
1 | ---
2 | path: /create-schema-for-a-listings
3 | title: Create Schema For A Listings
4 | tag: backend
5 | date: 2020-05-14T17:42:07.163Z
6 | part: Building backend
7 | chapter: Get A Listing Query
8 | postnumber: 13
9 | ---
10 |
11 | In this chapter we will attempt to get a single a listing from Dynamo when given a `listingID`.
12 |
13 | To do this let's edit our schema and add the following query:
14 |
15 | ```json
16 | getAListing(listingId: String!): Listing!
17 | ```
18 |
19 | Your Query Type should now look like this:
20 |
21 | ```json
22 | type Query {
23 | getAllListings: [Listing]
24 | getAListing(listingId: String!): Listing!
25 | }
26 | ```
27 |
28 | Next lets go ahead and export our `getAListing` query. Go into the `query.js` file and add the following:
29 |
30 | ```javascript
31 | export const getAListing = async (args, context) => {
32 | return null
33 | }
34 | ```
35 |
36 | Then finally import it into the `index` of the resolvers:
37 |
38 | ```javascript
39 | import { getAllListings, getAListing } from "./query"
40 | export const resolvers = {
41 | Query: {
42 | getAllListings: (root, args, context) => getAllListings(args, context),
43 | getAListing: (root, args, context) => getAListing(args, context),
44 | },
45 | }
46 | ```
47 |
48 | Next out will flesh out the function.
49 |
--------------------------------------------------------------------------------
/src/content/create-tour-card.md:
--------------------------------------------------------------------------------
1 | ---
2 | path: /create-tour-card
3 | title: Create Tour Card
4 | tag: frontend
5 | date: 2020-05-21T18:58:35.008Z
6 | part: Setup frontend
7 | chapter: Building reusable components
8 | postnumber: 29
9 | framework: vue
10 | ---
11 |
12 | In this post we will make a tour card that will allow us to display all our listings.
13 |
14 | Create a `cards` directory and create `TourCard.vue` file:
15 |
16 | ```javascript
17 |
18 |
32 |
33 |
50 |
51 | ```
52 |
53 | 🔇 Here we have a `div` that is styled accordingly to look like a card, which will accept props.
54 |
55 | 🔇 It also uses Ant Design's `` component so we can display the rating of the listing.
56 |
57 | Next off we need to add Ant Design because we will use it's tabs and rating components.
58 |
59 | ```bash
60 | $ yarn add ant-design-vue
61 | ```
62 |
63 | Then next head over to the `main.js` file to register it globally:
64 |
65 | ```javascript
66 | import { Tabs, Rate } from "ant-design-vue"
67 | import "ant-design-vue/dist/antd.css"
68 |
69 | Vue.use(Tabs)
70 | Vue.use(Rate)
71 | ```
72 |
73 | Once you're done, it will look like this:
74 |
75 | 
76 |
--------------------------------------------------------------------------------
/src/content/create-typography-components-1.md:
--------------------------------------------------------------------------------
1 | ---
2 | path: /create-typography-components-react
3 | title: Create Typography Components
4 | tag: frontend
5 | date: 2020-07-16T18:18:08.729Z
6 | part: frontend
7 | postnumber: 37
8 | framework: react
9 | chapter: Building reusable components
10 | ---
11 |
12 | In this post we will create a couple of typography components that we will use throughout the App for all of our Typography.
13 |
14 | First create a folder in the components directory called `typography`. And create a file called `HeadingOne.js` and add the following:
15 |
16 | ```javascript
17 | import React from "react"
18 | import PropTypes from "prop-types"
19 | const HeadingOne = ({ className, children }) => {
20 | return (
21 |
22 | {children}
23 |
24 | )
25 | }
26 |
27 | HeadingOne.propTypes = {
28 | className: PropTypes.string,
29 | children: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
30 | }
31 |
32 | HeadingOne.defaultProps = {
33 | className: "",
34 | }
35 | export default HeadingOne
36 | ```
37 |
38 | 🛩️ We are creating a `H1` component that will allow us to add additional content inside it via the children prop.
39 |
40 | 🛩️ Then we've given it some styles.
41 |
42 | Then next up create a `HeadingTwo.js` file with the following:
43 |
44 | ```javascript
45 | import React from "react"
46 | import PropTypes from "prop-types"
47 | const HeadingTwo = ({ className, children }) => {
48 | return (
49 |
100 | )
101 | }
102 |
103 | BodyOne.propTypes = {
104 | className: PropTypes.string,
105 | children: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
106 | }
107 |
108 | BodyOne.defaultProps = {
109 | className: "",
110 | }
111 | export default BodyOne
112 | ```
113 |
114 | Last lets create an `index.js` file so we can import the components in one line through out the app:
115 |
116 | ```javascript
117 | import HeadingOne from "./HeadingOne"
118 | import BodyOne from "./BodyOne"
119 | import HeadingTwo from "./HeadingTwo"
120 | import HeadingThree from "./HeadingThree"
121 |
122 | export { HeadingOne, BodyOne, HeadingTwo, HeadingThree }
123 | ```
124 |
--------------------------------------------------------------------------------
/src/content/create-typography-components.md:
--------------------------------------------------------------------------------
1 | ---
2 | path: /create-typography-components
3 | title: Create Typography Components
4 | tag: frontend
5 | date: 2020-05-21T18:09:47.284Z
6 | part: Setup frontend
7 | chapter: Building reusable components
8 | postnumber: 24
9 | framework: vue
10 | ---
11 |
12 | In this post we will create a couple of typography components that we will use throughout the App for all of our Typography.
13 |
14 | First create a folder in the components directory called `typography`. Then create a file called `HeadingOne.vue`and add the following:
15 |
16 | ```javascript
17 |
18 |
19 |
20 |
21 |
22 |
30 |
31 |
32 | ```
33 |
34 | 🛩️ We are creating a paragraph component with a slot inside it that will allow us to add additional content inside it.
35 |
36 | 🛩️ Then we've given it some styles.
37 |
38 | Next up we are going to create similar files but we are just changing the font size. So in the same directory create a `HeadingTwo.vue` file with the following:
39 |
40 | ```javascript
41 |
42 |
43 |
44 |
45 |
46 |
54 |
55 | ```
56 |
57 | Then create a `HeadingThree.vue` file with the following:
58 |
59 | ```javascript
60 |
61 |
62 |
63 |
64 |
65 |
73 |
74 |
75 | ```
76 |
77 | Next up create a `BodyOne.vue` file and add the following:
78 |
79 | ```javascript
80 |
81 |
82 |
83 |
84 |
85 |
90 |
91 | ```
92 |
93 | Now we need to create an `index.js` file that we can import all these from:
94 |
95 | ```javascript
96 | import Vue from "vue"
97 | import HeadingOne from "./HeadingOne"
98 | import HeadingTwo from "./HeadingTwo"
99 | import HeadingThree from "./HeadingThree"
100 | import BodyOne from "./BodyOne"
101 |
102 | const Typography = {
103 | HeadingOne,
104 | HeadingTwo,
105 | HeadingThree,
106 | BodyOne,
107 | }
108 |
109 | Object.keys(Typography).forEach(name => {
110 | Vue.component(name, Typography[name])
111 | })
112 |
113 | export default Typography
114 | ```
115 |
116 | So all we've done here is imported all those typography files, put them into an object and looped over each to instantate them as Vue components.
117 |
--------------------------------------------------------------------------------
/src/content/creating-our-graphql-schema.md:
--------------------------------------------------------------------------------
1 | ---
2 | path: /create-schema
3 | title: Creating our GraphQL schema
4 | tag: backend
5 | date: 2020-05-13T17:49:01.060Z
6 | part: Building backend
7 | chapter: All listings query
8 | postnumber: 10
9 | ---
10 |
11 | Now we finally get to the core of this course! We will start building our first query that will allow us to grab all the lunar destinations from DynamoDB.
12 |
13 | So let's go into our `schema.js` file and add the necessary types to get a query running:
14 |
15 | ```javascript
16 | type ListingType {
17 | name: String
18 | }
19 |
20 | type ListingActivities {
21 | name: String
22 | }
23 |
24 | type Guide {
25 | Name: String
26 | Bio: String
27 | Avatar: String
28 | }
29 |
30 | type Listing {
31 | listingId: String
32 | coverPhoto: String
33 | listingName: String
34 | listingDescription: String
35 | listingType: [ListingType]
36 | listingLocation: String
37 | listingActivities: [ListingActivities]
38 | specialType: String
39 | specialAmount: Int
40 | rating: Int
41 | guide: Guide
42 | price: String
43 | numberOfDays: Int
44 |
45 | }
46 | ```
47 |
48 | What are we doing here:
49 |
50 | 🔋We are creating three separate types (`ListingType`, `ListingActivities` & `Guide`) to be able to see these types in our `Listing` type.
51 |
52 | 🔋We are creating a `Listing` type that has the various attributes we need for this data type.
53 |
54 | Next up we will set up the query type :
55 |
56 | ```javascript
57 | type Query {
58 | getAllListings: [Listing]
59 | }
60 | ```
61 |
62 | 🔋 We creating a `getAllListings` query that will return an array of the `Listings`.
63 |
64 | So now we went through the process of adding editing our Schema in an SDL manner to create a query. You can also follow a [code-first](https://www.prisma.io/blog/the-problems-of-schema-first-graphql-development-x1mn4cb0tyl3) approach to creating a GraphQL schema.
65 |
--------------------------------------------------------------------------------
/src/content/creating-the-frontend-repo-with-vue-cli.md:
--------------------------------------------------------------------------------
1 | ---
2 | path: /creating-frontend-repo
3 | title: Creating the Frontend Repo with Vue CLI
4 | tag: frontend
5 | date: 2020-05-18T18:06:34.032Z
6 | part: Setup frontend
7 | chapter: Vue cli
8 | postnumber: 21
9 | framework: vue
10 | ---
11 |
12 | In this section we will start building out the frontend of the Lunar Tour app with VueJS. First we will need to create the repo on [Github](https://github.com).
13 |
14 | First go to Github and create new repo:
15 |
16 | 
17 |
18 | Now that we have it created. We need to install the [Vue CLI](https://cli.vuejs.org/). It will allow us to scaffold the project. In your terminal run the following:
19 |
20 | ```bash
21 | $ yarn global add @vue/cli
22 | ```
23 |
24 | Once it is installed run the following:
25 |
26 | ```bash
27 | $ vue create lunar-tour-frontend
28 | ```
29 |
30 | Select the default option:
31 |
32 | 
33 |
34 | Select the Babel, Router & Linter options.
35 |
36 | 
37 |
38 | Select Y for the history:
39 |
40 | 
41 |
42 | Sit back and let the CLI do the work:
43 |
44 | 
45 |
46 | Now change into the directory and start the app:
47 |
48 | ```bash
49 | $ yarn serve
50 | ```
51 |
52 | If you go to `localhost:8080` your app should be visible.
53 |
54 | Now we need to commit this to our repo. Initialize the local directory as a Git repository:
55 |
56 | ```bash
57 | $ git init
58 | ```
59 |
60 | Add the files in your new local repository. This stages them for the first commit:
61 |
62 | ```bash
63 | $ git add .
64 | ```
65 |
66 | Commit the files that you've staged in your local repository:
67 |
68 | ```bash
69 | $ git add .
70 | ```
71 |
72 | In Terminal,[add the URL for the remote repository](https://help.github.com/en/articles/adding-a-remote) where your local repository will be pushed:
73 |
74 | ```bash
75 | $ git remote add origin remote repository URL
76 | # Sets the new remote
77 |
78 | $ git remote -v
79 | # Verifies the new remote URL
80 | ```
81 |
82 | [Push the changes](https://help.github.com/en/articles/pushing-commits-to-a-remote-repository) in your local repository to GitHub:
83 |
84 | ```bash
85 | $ git push -u origin master
86 | ```
87 |
88 | Now all our code is synced with Github.
89 |
90 | Lastly, create a folder `assets` in the `src` directory and copy all the images in this [Google Driver folder](https://drive.google.com/drive/folders/178c-yMzNPj013dpEwkcDAQHArxjTa1HZ?usp=sharing). These are all the icons and images we will need for the project.
91 |
--------------------------------------------------------------------------------
/src/content/creating-the-mutation-in-the-schema.md:
--------------------------------------------------------------------------------
1 | ---
2 | path: /create-mutation-in-schema
3 | title: Creating the Mutation in the Schema
4 | tag: "backend "
5 | date: 2020-05-17T18:21:12.181Z
6 | part: Building backend
7 | chapter: Make a booking mutation
8 | postnumber: 16
9 | ---
10 |
11 | In this chapter we will be building our mutation which will allow users to be able to book for listings. First lets create the necessary Types for our mutations.
12 |
13 | Open up your `schema.js` file and paste the following:
14 |
15 | ```javascript
16 |
17 | type Booking {
18 | ID: String
19 | listingID: String
20 | bookingDate: String
21 | size: Int
22 | bookingTotal: String
23 | customerEmail: String
24 | customers: [Customer]
25 | chargeReciept: String
26 |
27 | }
28 | type Customer {
29 | name: String
30 | Surname: String
31 | country: String
32 | passportNumber: String
33 | physioScore: String
34 | }
35 |
36 | input InputCustomer {
37 | name: String
38 | Surname: String
39 | country: String
40 | passportNumber: String
41 | physioScore: String
42 | }
43 |
44 |
45 | ```
46 |
47 | 🌍 Our first type is a `Booking` Type, with the data we need to capture the booking of the specific listing. If you note there is a `Customers` field.
48 |
49 | 🌍 This `Customers` field is meant to return an array of customers, which we have defined as its own type.
50 |
51 | 🌍 lastly, we have an input called `InputCustomer`. An input in GraphQL is a Type that is used for mutations to pass in large objects. This will make it easier to call our mutation.
52 |
53 | Next up lets create a mutation type in our schema:
54 |
55 | ```javascript
56 | type Mutation {
57 | makeABooking(
58 | listingId: String
59 | size: Int,
60 | bookingDate: String,
61 | customerEmail: String,
62 | customers: [InputCustomer]
63 | ): Booking
64 |
65 | }
66 |
67 | ```
68 |
69 | 🌍 We are saying that our `makeABooking`mutation should take a `listingId`,`size`, `bookingDate` and `customerEmail` arguments. Then `customers` is also an argument but we are assigning it as an array of the `InputCustomer`. This means we can add multiple entires of the customer to our mutation.
70 |
71 | The beauty of GraphQL is that it comes with nifty features that REST does not. Best of all any other developers will be able to know how to call this Mutation easily by looking at it in the schema pane in Playground.
72 |
--------------------------------------------------------------------------------
/src/content/credits.md:
--------------------------------------------------------------------------------
1 | ---
2 | path: /credits
3 | title: Credits
4 | tag: extras
5 | date: 2020-09-14T19:02:08.429Z
6 | part: extras
7 | postnumber: 56
8 | chapter: Extras
9 | ---
10 |
11 | This project is fully licesend under MIT.
12 |
13 | Lunar Tour React is built in ReactJS
14 |
15 | Lunar Tour Vue is built in VueJS.
16 |
17 | Both use TailwindCSS as their primary CSS styling tool.
18 |
19 | Fullstack Serverless GraphQL's docs are written in Markdown powered by a GatsbyJS frontend. The content is created using Netlify CMS.
20 |
21 | All frontend apps are hosted exclusivly on Netlify.
22 |
23 | The Lunar Tour API is a NodeJS project, on a Serverless architecture hosted on AWS.
24 |
25 | DynamoDB is a NoSQL datastore which was used for this project.
26 |
27 | Serverless Framework was used to provision infrastructure.
28 |
29 | VSCode was the IDE of choice to write all code.
30 |
31 | Nimbus chrome extension was used for screenshots
32 |
33 | Seed is the CI/CD provider for the API.
34 |
35 | Github is used for storing the code.
36 |
37 | This project was planned using Miro and Airtable, while being designed in Figma.
38 |
39 | All stock photos are from NASA's flicker, while the images for the guides come from UIFaces. Illustrations are courtesy of Icons8.
40 |
41 | All names of people in this project are purely fictional and are not intended to represent anyone in reality.
42 |
43 | Design was learnt from Refactoring Design.
44 |
45 | Many thanks to folks over at Serverless Stack, your work literally made me into the beast I am today, without your contributions I would not be able to work on the bleeding edge and deliver value.
46 |
47 | Many thanks to Shailen for counsulting on early parts of the Vue content.
48 |
49 | Many thanks to Sitholuhkle for design feedback.
50 |
51 | Many thanks to Brandon Dlamini on grammar checks and motivation.
52 |
53 | Huge thanks to Pimp My Book, which was my first tech job and allowed me learn what is in this course to teach.
54 |
55 | Many thanks to Rob Oswald + Bongani Sithole for helping me grow as an engineer.
56 |
57 | Many thanks to the City of Cape Town and City of Johannesburg where parts of this project were worked on. This project was worked on from February - November 2020.
58 |
59 | Lots of love to my "parents": Mom, Dad, Sindy & Janine. Would not be here today without their scarifieces and love.
60 |
61 | Big shout out to God, the father of Jesus.
62 |
--------------------------------------------------------------------------------
/src/content/deploy-backend-code.md:
--------------------------------------------------------------------------------
1 | ---
2 | path: /deploy-backend-code
3 | title: Deploy Backend Code
4 | tag: "backend "
5 | date: 2020-05-17T19:55:21.315Z
6 | part: Building backend
7 | chapter: "Deploy Backend "
8 | postnumber: 20
9 | ---
10 |
11 | In this chapter we will deploy our backend.
12 |
13 | So make sure you commit all your code. I use Visual Studio code, so I do the following:
14 |
15 | Write a commit message:
16 |
17 | 
18 |
19 | Then I click the tab next to the branch name to commit the code into master:
20 |
21 | 
22 |
23 | Next go into the Seed Console. You should see the build being triggered and in progress:
24 |
25 | 
26 |
27 | When you open up the build logs you should see your Endpoint:
28 |
29 | 
30 |
31 | Navigate to the Endpoints, you should be able execute queries and mutations:
32 |
33 | 
34 |
35 | Congrats! We now have a working GraphQL API!
36 |
--------------------------------------------------------------------------------
/src/content/deploy-react-app.md:
--------------------------------------------------------------------------------
1 | ---
2 | path: /deploy-react-app
3 | title: Deploy React App to Netlify
4 | tag: frontend
5 | date: 2020-09-14T19:02:08.429Z
6 | part: frontend
7 | postnumber: 53
8 | framework: react
9 | chapter: Deploy Frontend
10 | ---
11 |
12 | In this final chapter we will we deploy our frontend to Netlify. Firstly, let's explore why Netlify?
13 |
14 | Variety is the spice of life and when it comes to deploying a React app, you have tonnes of variety. However, I'm yet to find few that gives you a simple process to deploy your site than others.
15 |
16 | The things that stand out for me using Netlify:
17 |
18 | 🦚 The ability to get deploy previews from a specific branch. Very useful if you're trying to test something specfic and want to test it in a somewhat live environment, you can just create a PR and deploy that branch in isolation.
19 |
20 | 🦚 Unlimted sites can be deployed for free, which makes its affordable for everybody.
21 |
22 | First thing you got to do is head over to [Netlify](https://netlify.com/) and login or if you have not created an account simply sign up.
23 |
24 | Once you've done that you should see all of your sites. Click the "add site" button so we can start the process of adding our site:
25 |
26 | 
27 |
28 | Now we come to the screen where it gives options to select our Git provider:
29 |
30 | 
31 |
32 | Please select your prefered Git provider.
33 |
34 | Once you've selected your provider you should be able to select the repo you want to deploy, in my case I'm deploying a repo from an a organistation:
35 |
36 | 
37 |
38 | Next up, our deploy settings will display. In our case the template we used to build our project came with a `Netlify.toml` file that has build commands for us. So all the fields are prepopulated for us. If you're techincally advanced enough you are most welcome to tinker around:
39 |
40 | 
41 |
42 | So by now our site should have deployed by now but it probably won't work, because we need to give the site our environemnt variables. Click the "Overview" tab, then click on the "site settings" button ---> then click the "build and deploy" pane and scoll till you reach the "environment" section and make sure you environment variables names match mine:
43 |
44 | 
45 |
46 | Congratz You have finally deployed the Lunar Tour!
47 |
--------------------------------------------------------------------------------
/src/content/deploy-vue-app.md:
--------------------------------------------------------------------------------
1 | ---
2 | path: /deploy-vue-app
3 | title: Deploy Vue App to Netlify
4 | tag: frontend
5 | date: 2020-09-14T19:02:08.429Z
6 | part: frontend
7 | postnumber: 54
8 | framework: vue
9 | chapter: Deploy Frontend
10 | ---
11 |
12 | In this final chapter we will we deploy our frontend to Netlify. Firstly, let's explore why Netlify?
13 |
14 | Variety is the spice of life and when it comes to deploying a React, you have tonnes of variety. However, I'm yet to find few that gives you a simple process to deploy your site than others.
15 |
16 | The things that stand out for me using Netlify:
17 |
18 | 🦚 The ability to get deploy previews from a specific branch. Very useful if you're trying to test something specfic and want to test it in a somewhat live environment, you can just create a PR and deploy that branch in isolation.
19 |
20 | 🦚 Unlimted sites can be deployed for free, which makes its affordable for everybody.
21 |
22 | First thing you got to do is head over to [Netlify](https://netlify.com/) and login or if you have not created an account simply sign up.
23 |
24 | Once you've done that you should see all or your sites. Click the "add site" button so we can start the process of adding our site:
25 |
26 | 
27 |
28 | Now we come to the screen where it gives options to select our Git provider:
29 |
30 | 
31 |
32 | Please select your prefered Git provider.
33 |
34 | Once you've selected your provider you should be able to select the repo you want to deploy, in my case I'm deploy a repo from an a organistation:
35 |
36 | 
37 |
38 | Next up, our deploy settings will display. In the build command text box add `yarn run build`, while the publish directory textbox add `/dist`:
39 |
40 | 
41 |
42 | This will basically tell Netlify's robots to run the `build` command in our project and put the minified build into our `/dist` directory.
43 |
44 | So by now our site should have deployed by now but it probably won't work, because we need to give the site our environemnt variables. Click the "Overview" tab, then click on the "site settings" button ---> then click the "build and deploy" pane and scoll till you reach the "environment" section and make sure you environment variables names match mine:
45 |
46 | 
47 |
48 | Congratz You have finally deployed the Lunar Tour!
49 |
--------------------------------------------------------------------------------
/src/content/export-listing-functions.md:
--------------------------------------------------------------------------------
1 | ---
2 | path: /export-listings-functions
3 | title: Export listing functions
4 | tag: backend
5 | date: 2020-05-13T18:16:54.234Z
6 | part: Building backend
7 | chapter: All listings query
8 | postnumber: 11
9 | ---
10 |
11 | Here we are just going to set up the function that grabs all our listings and export it so that Apollo Server can pick up the query.
12 |
13 | First lets go into our `query.js` file and create the following function:
14 |
15 | ```javascript
16 | export const getAllListings = async (args, context) => {
17 | return null
18 | }
19 | ```
20 |
21 | All we are doing here is creating an async function that accepts `args` and `context` as properties.
22 |
23 | Next we are going to import this function from above into our `index.js` file:
24 |
25 | ```javascript
26 | import { getAllListings } from "./query"
27 | export const resolvers = {
28 | Query: {
29 | getAllListings: (root, args, context) => getAllListings(args, context),
30 | },
31 | }
32 | ```
33 |
34 | We are creating a resolvers object that has a query property that returns our `getAllListings` function.
35 |
36 | Next we can start writing the code to return data!
37 |
--------------------------------------------------------------------------------
/src/content/export-the-mutation-function.md:
--------------------------------------------------------------------------------
1 | ---
2 | path: /export-the-mutation
3 | title: Export the Mutation Function
4 | tag: backend
5 | date: 2020-05-17T18:39:53.692Z
6 | part: Building backend
7 | chapter: Make a booking mutation
8 | postnumber: 15
9 | ---
10 |
11 | In this chapter we just set up our mutation by exporting it.
12 |
13 | In the `mutation.js` file add the following function:
14 |
15 | ```javascript
16 | export const makeABooking = async (args, context) => {
17 | return null
18 | }
19 | ```
20 |
21 | Then head over the the `index` of the resolvers and import it:
22 |
23 | ```javascript
24 | import { makeABooking } from "./mutation"
25 | export const resolvers = {
26 | Mutation: {
27 | makeABooking: (root, args, context) => makeABooking(args, context),
28 | },
29 | }
30 | ```
31 |
32 | Your resolvers `index.js` should look like this:
33 |
34 | ```javascript
35 | import { getAllListings, getAListing } from "./query"
36 | import { makeABooking } from "./mutation"
37 | export const resolvers = {
38 | Query: {
39 | getAllListings: (root, args, context) => getAllListings(args, context),
40 | getAListing: (root, args, context) => getAListing(args, context),
41 | },
42 | Mutation: {
43 | makeABooking: (root, args, context) => makeABooking(args, context),
44 | },
45 | }
46 | ```
47 |
48 | Next up we will setup a Stripe Account
49 |
--------------------------------------------------------------------------------
/src/content/getting-setup.md:
--------------------------------------------------------------------------------
1 | ---
2 | path: /getting-setup
3 | title: Getting setup
4 | tag: Epilouge
5 | date: 2020-04-13T00:00:00.000Z
6 | part: Epilouge
7 | chapter: "0"
8 | postnumber: 1
9 | ---
10 |
11 | # What you need
12 |
13 | - [An AWS Account](https://portal.aws.amazon.com/billing/signup?nc2=h_ct&src=default&redirect_url=https%3A%2F%2Faws.amazon.com%2Fregistration-confirmation#/start)
14 | - [Node.js](https://nodejs.org/)
15 | - A [Github](https://github.com/)/[Gitlab](http://gitlab.com/) account
16 | - basic knowledge of git
17 | - basic knowledge of HTML/CSS and JavaScript
18 |
19 | ## Setting Up an AWS account
20 |
21 | If this is your first introduction to AWS, I would suggest that you follow the instructions laid out by[ Serverless Stack](https://serverless-stack.com/chapters/create-an-aws-account.html). **Make sure you have an IAM user and the AWS CLI configured as per the instructions.**
22 |
23 | ## Git provider
24 |
25 | All other tools can run directly from Github or Gitlab, whichever you prefer. Make sure you have a free account with either. This guide uses Github.
26 |
27 | ## Git
28 |
29 | You will need basic knowledge of [git](https://git-scm.com/) to save your work and automate deployments. It can be [installed here](https://git-scm.com/), while you use the following resources to get yourself up to speed if needs be:
30 |
31 | - [Git & GitHub Crash Course For Beginners](https://www.youtube.com/watch?v=SWYqp7iY_Tc) 🎥
32 | - [Github help docs](https://help.github.com/en) 📗
33 | - [Getting Git right](https://www.atlassian.com/git) 📗
34 |
35 | ## HTML/CSS/JavaScript
36 |
37 | If you have not used these technologies before, you can still follow along, alternatively these are great places to start:
38 |
39 | - [FreeCodeCamp](https://www.freecodecamp.org/) 🖥️
40 | - [w3Schools](https://www.w3schools.com/) 🖥️
41 | - [HTML Crash Course For Absolute Beginners](https://www.youtube.com/watch?v=UB1O30fR-EE) 🎥
42 | - [CSS Crash Course For Absolute Beginners ](https://www.youtube.com/watch?v=yfoY53QXEnI&t=2891s) 🎥
43 | - [JavaScript Crash Course For Beginners](https://www.youtube.com/watch?v=hdI2bqOjy3c) 🎥
44 |
--------------------------------------------------------------------------------
/src/content/installing-tailwind.md:
--------------------------------------------------------------------------------
1 | ---
2 | path: /installing-tailwind
3 | title: Installing Tailwind
4 | tag: frontend
5 | date: 2020-05-18T18:29:57.643Z
6 | part: Setup frontend
7 | chapter: Tailwind
8 | postnumber: 22
9 | framework: vue
10 | ---
11 |
12 | We will be using [Tailwind](https://tailwindcss.com) for our styling in the App. we just have to run through some installation steps to make sure it is up an running. Tailwind is a utility first framework to help you get your designs highly customised, it can be used in conjuction with UI libraries such as Chakra, Bulma etc if you wish.
13 |
14 | Lets go ahead and install the following:
15 |
16 | ```bash
17 | $ yarn add tailwindcss
18 | ```
19 |
20 | Then create a `tailwind.config.js` file in the root of your project and paste the following config in your file. This config will match the design system for the project that we are building from. You can view it on [Figma](https://www.figma.com/file/wfTuuiWP4TwRRsdcefLp4x/Lunar-Tour-App-v2?node-id=0%3A1) here.
21 |
22 | Next lets go ahead and make a `postcss.config.js` file in the root of the app:
23 |
24 | ```javascript
25 | module.exports = {
26 | plugins: [
27 | require("tailwindcss")("tailwind.config.js"),
28 | require("autoprefixer"),
29 | ],
30 | }
31 | ```
32 |
33 | Next up we need to configure our CSS file. In the `assets` folder create a folder called `css` and create a `tailwind.css` file and copy the following:
34 |
35 | ```css
36 | @tailwind base;
37 |
38 | @tailwind components;
39 |
40 | @tailwind utilities;
41 | @import url("https://fonts.googleapis.com/css?family=Saira&display=swap");
42 | ```
43 |
44 | Lastly import the css file in the `main.js` file:
45 |
46 | ```javascript
47 | import "./assets/css/tailwind.css"
48 | ```
49 |
50 | Create `tailwind.config.js` file and [copy the contents of this link](https://raw.githubusercontent.com/AmoDinho/lunar-tour-v2/master/lunar-tour-client/tailwind.config.js) into the file.
51 |
52 | Now lets test that it is working by adding styles into a component:
53 |
54 | ```javascript
55 |
56 |
57 |
58 | Home |
59 | About
60 |
61 |
62 |
63 |
64 |
65 |
87 | ```
88 |
89 | Now the app is picking up our styles.
90 |
91 | 
92 |
--------------------------------------------------------------------------------
/src/content/introduction.md:
--------------------------------------------------------------------------------
1 | ---
2 | path: /introduction
3 | title: Introduction
4 | tag: Epilouge
5 | date: 2020-03-24T00:00:00.000Z
6 | part: Epilouge
7 | chapter: "0"
8 | postnumber: 0
9 | ---
10 |
11 | Welcome to Fullstack Serverless GraphQL! This resource is meant to be the most up to date way of building a production-ready serverless GraphQL project.
12 |
13 | This is meant for everyone of all skill levels. It follows the Top-Down approach to learning, which is very similar to Fast.ai courses if you have taken them. You are not expected to go learn AWS fundamentals or the basics of REST APIs/JavaScript etc, instead you will learn by doing and explaining what you've done afterwards. This way you focus on what things DO instead of what they ARE.
14 |
15 | Most importantly, after you have completed this guide, you need to reimplement what you've learnt here in your own project. This is the only way you can verify that you understand what you've learnt.
16 |
17 | However, if you're looking to learning AWS, GraphQL, Vue or React specifically you can easily check out the code on GitHub take what you need.
18 |
19 | Everything is built in Node.JS, most of the tools used in the stack can be swapped with something similar like AWS Lambda can be swapped with GCP cloud functions, you just gotta figure out how to adjust your project accordingly. The core concepts of building a GraphQL back-end and consuming it in a front-end are applicable across any provider as long as you understand the fundamentals.
20 |
21 | Lastly, when get to the frontend part of the course, you can choose your own adventure in the form of React or Vue. Both are decent examples of how to tackle advanced frontend implementation.
22 |
--------------------------------------------------------------------------------
/src/content/running-our-lambda-locally.md:
--------------------------------------------------------------------------------
1 | ---
2 | path: /running-locally
3 | title: Running our Lambda locally
4 | tag: backend
5 | date: 2020-05-11T17:46:40.265Z
6 | part: Building backend
7 | chapter: "Adding libraries "
8 | postnumber: 8
9 | ---
10 |
11 | Now we have successfully deployed our function that has a basic Hello World query. We will now go ahead and start testing out how to run it locally.
12 |
13 | In your terminal run the following:
14 |
15 | ```bash
16 | $ sls offline
17 | ```
18 |
19 | This will run your Lambda on port`4000`. So in your browser if you go to `localhost:4000/graphql` you should see an instance of GraphQL Playground.
20 |
21 | GraphQL Playground is where you can test your Queries/Mutations or Subscriptions for your GraphQL API and see the responses that come back from your server. Also it shows you the schema for your API, which makes it self documenting to a certain extent.
22 |
23 | Now we can run the following query :
24 |
25 | ```json
26 | {
27 | hello
28 | }
29 | ```
30 |
31 | It should return the following:
32 |
33 | 
34 |
35 | Now that is working! Let's continue to set up the other libraries we need.
36 |
--------------------------------------------------------------------------------
/src/content/set-up-tables-1.md:
--------------------------------------------------------------------------------
1 | ---
2 | path: /set-up-tables
3 | title: Set up tables
4 | tag: backend
5 | date: 2020-04-21T16:45:08.244Z
6 | part: setting up backend
7 | chapter: Setting Up infrastructure
8 | postnumber: 5
9 | ---
10 |
11 | In this chapter we are going to create our DynamoDB tables. DynamoDB is a NoSQL key-value store serverless native database. Sounds like a mouthful, in a nutshell it does not force your data to adhere to any data model or schema and you do not have to provide any server instances for it to run. However, lately I've found myself using it for a variety of workloads, you should be fully aware of it's downsides.
12 |
13 | The way we will be provisioning our tables will enable us to create tables for any of our environment stages.
14 |
15 | Create a folder called \`resources\` and create a file called \`listing-db.yml\`. Paste the following inside:
16 |
17 | ```YAML
18 | Resources:
19 | ListingsDB:
20 | Type: AWS::DynamoDB::Table
21 | Properties:
22 | TableName: ${self:custom.ListingsDB}
23 | AttributeDefinitions:
24 | - AttributeName: listingId
25 | AttributeType: S
26 | - AttributeName: listingName
27 | AttributeType: S
28 |
29 | KeySchema:
30 | - AttributeName: listingId
31 | KeyType: HASH
32 | - AttributeName: listingName
33 | KeyType: RANGE
34 | # Set the capacity based on the stage
35 | ProvisionedThroughput:
36 | ReadCapacityUnits: ${self:custom.tableThroughput}
37 | WriteCapacityUnits: ${self:custom.tableThroughput}
38 |
39 | ```
40 |
41 | ### What is happening?
42 |
43 | 🎩 We are creating a table with a Primary Key called `listingId` and a Sort Key called `listingName` both of them are strings.
44 |
45 | 🎩 For the ProvisionedThroughput section we are basically tell AWS how many operations per second can be allowed when data is being written or read. \
46 | \
47 | \
48 | In the same directory lets create the table that will store our bookings called `booking-db.yml`:
49 |
50 | ```YAML
51 | Resources:
52 | BookingsDB:
53 | Type: AWS::DynamoDB::Table
54 | Properties:
55 | TableName: ${self:custom.BookingsDB}
56 | AttributeDefinitions:
57 | - AttributeName: bookingId
58 | AttributeType: S
59 | - AttributeName: listingId
60 | AttributeType: S
61 |
62 |
63 | KeySchema:
64 | - AttributeName: bookingId
65 | KeyType: HASH
66 | - AttributeName: listingId
67 | KeyType: RANGE
68 | # Set the capacity based on the stage
69 | ProvisionedThroughput:
70 | ReadCapacityUnits: ${self:custom.tableThroughput}
71 | WriteCapacityUnits: ${self:custom.tableThroughput}
72 | ```
73 |
74 | We are doing the same thing here, only difference is that we made the Sort Key the `listingId`.
75 |
76 | Now we need to reference it in our `serverless.yml`:
77 |
78 | ```YAML
79 | resources:
80 | - ${file(resources/listing-db.yml)}
81 | - ${file(resources/booking-db.yml)}
82 | ```
83 |
84 | In the same file we need to create those tables in the custom section of the file:
85 |
86 | ```YAML
87 | custom:
88 | stage: ${opt:stage, self:provider.stage}
89 | region: ${opt:region, self:provider.region}
90 | ListingsDB: ${self:custom.stage}-listings
91 | BookingsDB: ${self:custom.stage}-bookings
92 | tableThroughputs:
93 | prod: 1
94 | default: 1
95 | tableThroughput: ${self:custom.tableThroughputs.${self:custom.stage}, self:custom.tableThroughputs.default}
96 | ```
97 |
98 | As we well as create them as environment variables and add IAM role statements for this Lambda:
99 |
100 | ```YAML
101 | provider:
102 | name: aws
103 | runtime: nodejs10.x
104 | stage: dev
105 | region: us-east-1
106 | profile: personalAccount
107 | environment:
108 | BookingsDB: ${self:custom.BookingsDB}
109 | ListingsDB: ${self:custom.ListingsDB}
110 |
111 | iamRoleStatements:
112 | - Effect: Allow
113 | Action:
114 | - dynamodb:DescribeTable
115 | - dynamodb:Query
116 | - dynamodb:Scan
117 | - dynamodb:GetItem
118 | - dynamodb:PutItem
119 | - dynamodb:UpdateItem
120 | - dynamodb:DeleteItem
121 | - dynamodb:GetRecords
122 | - dynamodb:GetShardIterator
123 | - dynamodb:DescribeStream
124 | - dynamodb:ListStream
125 |
126 | Resource:
127 | - "Fn::GetAtt": [ListingsDB, Arn]
128 | - "Fn::GetAtt": [BookingsDB, Arn]
129 | ```
130 |
131 | Now we have created our infrastructure for the tables as code. 🗽 Commit this code to master for the tables to be created.
132 |
--------------------------------------------------------------------------------
/src/content/setting-up-advanced-forms.md:
--------------------------------------------------------------------------------
1 | ---
2 | path: /setting-up-advanced-forms
3 | title: Setting Up Advanced Forms
4 | tag: frontend
5 | date: 2020-07-21T18:47:58.754Z
6 | part: frontend
7 | postnumber: 45
8 | framework: react
9 | chapter: Make A Booking Mutation
10 | ---
11 |
12 | Genereally there comes a time when you've got do things in an advanced way in order to write maintainable code that won't compromise the developer experince. Customer experince tends to be loosely coupled to the developer experince. If things are a pain to maintain, the harder it will be to deliver value to your customers.
13 |
14 | So the way the mutation needs to be executed is in a multistep form that needs to be in separte components. The key driver for this is so that the user does not need to do everything on one page. This would be a poor user experince. Also you need to maintain a file that would easliy be 400+ lines of code.
15 |
16 | Our approach will be the following:
17 |
18 | ☎️ Use [Ant design's](https://ant.design/) tabs and target the CSS classes to hide the controls
19 |
20 | ☎️ Use [Apollo client's](https://www.apollographql.com/docs/react/v2.6/data/local-state/) Cache as a central state store to persist the data between the components and use it to trigger our mutation in the penulatmate mutation.
21 |
--------------------------------------------------------------------------------
/src/content/setting-up-seed.md:
--------------------------------------------------------------------------------
1 | ---
2 | path: /set-up-seed
3 | title: Setting Up Seed
4 | tag: backend
5 | date: 2020-05-08T15:48:00.385Z
6 | part: setting up backend
7 | chapter: Setting Up infrastructure
8 | postnumber: 4
9 | ---
10 |
11 | In this part we will set up [Seed](https://seed.run/), which is our Continous Integration & deployment tool that will allow us to deploy our backend whenever there is a change in the master branch of our repo and configure different stages.
12 |
13 | The reason why we are doing this so soon is that for our Apollo Server to work with [Serverless Offline](https://github.com/dherault/serverless-offline) we need the Lambda and the other resources to be provisioned to work with GraphQL playground.
14 |
15 | First, sign up for an account:
16 |
17 | 
18 |
19 | Then click add an App
20 |
21 | 
22 |
23 | Once you've selected the Git provider that your repo is in, it should detect the service automatically.
24 |
25 | Then you need to add your AWS credentials:
26 |
27 | 
28 |
29 | Once that is done click add new app
30 |
31 | Now we need to deploy, so click the arrow in the dev part if the pipeline and click deploy
32 |
33 | 
34 |
35 | After a few minutes you should be able to see the new endpoints and tables we created. first click on the drop down:
36 |
37 | 
38 |
39 | then click view resources:
40 |
41 | 
42 |
43 | We will get back to how we can move stuff to prod later.
44 |
--------------------------------------------------------------------------------
/src/content/setting-up-stripe.md:
--------------------------------------------------------------------------------
1 | ---
2 | path: /setting-up-stripe
3 | title: Setting Up Stripe
4 | tag: backend
5 | date: 2020-05-17T18:49:34.862Z
6 | part: Building backend
7 | chapter: Make a booking mutation
8 | postnumber: 14
9 | ---
10 |
11 | In this section we will get a stripe account setup. Head over to [Stripe](https://stripe.com) and sign up for an account, once you're all setup, you will land on the dashboard. We need to get the API Keys, so click on the "Developers" link on the left hand side:
12 |
13 | 
14 |
15 | Then click on API Keys:
16 |
17 | 
18 |
19 | Then copy the Secret key:
20 |
21 | 
22 |
23 | Next up create a `.env` file in the root of your project and the secret key :
24 |
25 | ```javascript
26 | STRIPE_SECRET_KEY = sk_test_stuff
27 | ```
28 |
29 | Then in your `serverless.yml` file add the key in the environment part in the provider block:
30 |
31 | ```yaml
32 | provider:
33 | name: aws
34 | runtime: nodejs10.x
35 | stage: dev
36 | region: us-east-1
37 | profile: personalAccount
38 | environment:
39 | stripeSecretKey: ${env:STRIPE_SECRET_KEY}
40 | ```
41 |
42 | This should be enough for us to complete the function now.
43 |
--------------------------------------------------------------------------------
/src/content/setting-up-tailwind.md:
--------------------------------------------------------------------------------
1 | ---
2 | path: /setting-up-tailwind
3 | title: Setting Up Tailwind
4 | tag: frontend
5 | date: 2020-07-16T17:56:10.926Z
6 | part: frontend
7 | postnumber: 34
8 | framework: react
9 | chapter: Tailwind
10 | ---
11 |
12 | Tailwind CSS is a utility first library for styling HTML. It is not library with components like Ant Design, Bulma etc. You still need to create those components yourself, however , it gives you a better developer experience for styling your HTML so that you can get a custom feel to your designs, without the headache of overriding styles.
13 |
14 | Since all the necessary installation steps are done via the React Bleeding Edge Kit, all we need to do is replace the default `tailwind.config.js` file with the one that will mirror the design system for the Lunar Tour on [Figma](https://www.figma.com/file/wfTuuiWP4TwRRsdcefLp4x/Lunar-Tour-App-v2?node-id=0%3A1).
15 |
16 | Here is a link to the [file](https://raw.githubusercontent.com/Fullstack-Serverless-GraphQL/lunar-tour-frontend/master/tailwind.config.js), copy its contents and replace it with the contents of the `tailwind.config.js` in the root of the project.
17 |
18 | Pretty simple task. Now we can start creating the UI components.
19 |
--------------------------------------------------------------------------------
/src/content/setup-apollo-cache.md:
--------------------------------------------------------------------------------
1 | ---
2 | path: /setup-apollo-cache
3 | title: Setup Apollo Cache
4 | tag: frontend
5 | date: 2020-07-21T19:02:08.429Z
6 | part: frontend
7 | postnumber: 46
8 | framework: react
9 | chapter: Make A Booking Mutation
10 | ---
11 |
12 | In this post we setup the Apollo cache and our local resolvers to be able to store the form data.
13 |
14 | First in your `index.js` file add the following:
15 |
16 | ```javascript
17 | import { InMemoryCache } from "apollo-cache-inmemory"
18 |
19 | const cache = new InMemoryCache()
20 |
21 | cache.writeData({
22 | data: {
23 | formData: {
24 | date: "",
25 | email: "",
26 | customer: {},
27 | __typename: "formData",
28 | },
29 | },
30 | })
31 | ```
32 |
33 | 🍗 Here we are simply importing the `InMemoryCache` function and then making an instance of a cache object.
34 |
35 | 🍗 Next we use the `writeData` function to create our initial `formData` object that has a date, email and customer fields to store that data.
36 |
37 | 🍗 Very important is the `__typename` field the lets GraphQL know that the `formData` object is the type we will be querying and mutating
38 |
39 | Next up in `src` make a file called `resolvers.js` and add the following:
40 |
41 | ```javascript
42 | import { GET_FORM_DATA } from "./graphql/Queries"
43 |
44 | export const resolvers = {
45 | Mutation: {
46 | updateFormData: (parent, args, context, info) => {
47 | const queryResult = context.cache.readQuery({ query: GET_FORM_DATA })
48 | const { formData } = queryResult
49 | if (queryResult) {
50 | const data = {
51 | formData: {
52 | date: args.date,
53 | email: args.email,
54 | customer: args.customers,
55 | __typename: formData["__typename"],
56 | },
57 | }
58 |
59 | context.cache.writeQuery({ query: GET_FORM_DATA, data })
60 | return data.formData
61 | }
62 | return []
63 | },
64 | },
65 | }
66 | ```
67 |
68 | 🍗 We have a resolvers object that has a mutation which has a function called `updateFormData`.
69 |
70 | 🍗 `updateFormData` reads the cache to get the most update version of the `formData`
71 |
72 | 🍗 Then it creates a new object that takes in new arguments to overwrite the form data with update data.
73 |
74 | 🍗 `formData` is then returned.
75 |
76 | Next up go to the `Queries.js` file and add the following:
77 |
78 | ```javascript
79 | export const GET_FORM_DATA = gql`
80 | query GET_FORM_DATA {
81 | formData @client {
82 | date
83 | email
84 | }
85 | }
86 | `
87 | ```
88 |
89 | We have to create a schema doc to read the `formData` the `@client` directive lets GraphQL know it is directly for the client only.
90 |
91 | In the `mutations.js` add the following:
92 |
93 | ```javascript
94 | import gql from "graphql-tag"
95 |
96 | export const UPDATE_FORM_DATA = gql`
97 | mutation UPDATE_FORM_DATA(
98 | $date: String
99 | $email: String
100 | $customer: CustomerInput
101 | ) {
102 | updateFormData(date: $date, email: $email, customer: $customer) @client
103 | }
104 | `
105 | ```
106 |
107 | Here we are just creating a mutation to update the form data
108 |
109 | This is it for now, next up we will start with building out the forms.
110 |
--------------------------------------------------------------------------------
/src/content/setup-apollo-vue.md:
--------------------------------------------------------------------------------
1 | ---
2 | path: /setup-apollo-vue
3 | title: Setup Vue Apollo
4 | tag: frontend
5 | date: 2020-05-22T16:26:13.725Z
6 | part: Build frontend
7 | chapter: Landing Page
8 | postnumber: 30
9 | framework: vue
10 | ---
11 |
12 | In this part we will integrate [Vue Apollo](https://apollo.vuejs.org/) to be able to content to our GraphQL API.
13 |
14 | Head to your terminal and run the following:
15 |
16 | ```bash
17 | $ vue ui
18 | ```
19 |
20 | This will open the Vue CLI UI:
21 |
22 | 
23 |
24 | This allows us to add packages and plugins to our Vue app.
25 |
26 | So next go to the plugins tab:
27 |
28 | 
29 |
30 | Then click on add plugin:
31 |
32 | 
33 |
34 | Search for vue-cli-plugin-apollo and select it
35 |
36 | 
37 |
38 | Then click install and sit back. You will get a prompt to scaffold other stuff, ignore it and continue:
39 |
40 | 
41 |
42 | Then simply click continue:
43 |
44 | 
45 |
46 | This will setup the Apollo library in your project.
47 |
48 | Everything is set up for us.
49 |
--------------------------------------------------------------------------------
/src/content/setup-mutation-workflow.md:
--------------------------------------------------------------------------------
1 | ---
2 | path: /setup-mutation-workflow
3 | title: Setup Mutation Workflow
4 | tag: frontend
5 | date: 2020-05-25T18:01:10.409Z
6 | part: frontend
7 | chapter: Make A Booking
8 | postnumber: 33
9 | framework: vue
10 | ---
11 |
12 | In this chapter we will eventually create a mutation that allows the user to make a booking for a listing.
13 |
14 | First off lets add a schema document called `makeABooking.gql` in the `graphql` folder:
15 |
16 | ```javascript
17 | mutation MAKE_A_BOOKING(
18 | $size: Int
19 | $customerEmail: String
20 | $bookingDate: String
21 | $listingId: String
22 | $customers: [InputCustomer]
23 | ) {
24 | makeABooking(
25 | size: $size
26 | customerEmail: $customerEmail
27 | bookingDate: $bookingDate
28 | listingId: $listingId
29 | customers: $customers
30 | ) {
31 | ID
32 | listingID
33 | bookingDate
34 | size
35 | bookingTotal
36 | customerEmail
37 | customers {
38 | name
39 | surname
40 | country
41 | passportNumber
42 | physioScore
43 | }
44 | chargeReciept
45 | }
46 | }
47 |
48 | ```
49 |
50 | Lastly, lets add a route for the Booking page in the `router.js` file:
51 |
52 | ```javascript
53 | import Index from "./pages/booking"
54 |
55 | const routes = [
56 | {
57 | path: "/booking/:id",
58 | component: Index,
59 | },
60 | ]
61 | ```
62 |
63 | That should be enough for now, Next up we will set up the component to handle various tabs that will facilitate being able to make a booking.
64 |
--------------------------------------------------------------------------------
/src/content/spawn-react-frontend.md:
--------------------------------------------------------------------------------
1 | ---
2 | path: /spawn-react-frontend
3 | title: "Spawn React Frontend "
4 | tag: frontend
5 | date: 2020-07-16T17:38:13.670Z
6 | part: frontend
7 | postnumber: 33
8 | framework: react
9 | chapter: Setting up React Frontend
10 | ---
11 |
12 | Usually to setup a React app you would use Create React App, unlike Vue it does not come with a CLI where you can configure lots of goodies with it. There are lots of boilerplates out in the wild that suit how their creators work and get things done.
13 |
14 | For this course, we will use the [React Bleeding Edge Kit (TM)](https://github.com/AmoDinho/react-bleeding-edge-kit) however you are most welcome to set up the project however you like and try and plugin different parts of the course where possible.
15 |
16 | However, the React Bleeding Edge Kit comes with the following:
17 |
18 | \* [Apollo Client](https://www.apollographql.com/apollo-client) setup
19 |
20 | \* [Reach Router](https://reach.tech/router/) and routing setup
21 |
22 | \* Tailwind configured
23 |
24 | Setting the above up can easily take 30mins.
25 |
26 | So first thing we got to do is go to Github and use the kit as a templete:
27 |
28 | 
29 |
30 | After that we can now create the repo based on the template.
31 |
32 | 
33 |
34 | Then we can now clone the repo to our local machine.
35 |
36 | ```bash
37 | $ git clone url-of-repo
38 | ```
39 |
40 | Then cd into the repo :
41 |
42 | ```bash
43 | $ cd lunar-tour-client
44 | ```
45 |
46 | Install the node modules:
47 |
48 | ```bash
49 | $ yarn install
50 | ```
51 |
52 | Then we can start the app and go to local host to view the app
53 |
54 | ```bash
55 | $ yarn start
56 | ```
57 |
58 | Now we have our React frontend setup for us!
59 |
60 | 
61 |
62 | Lastly, create a folder `assets` in the `src` directory and copy all the images in this [Google Driver folder](https://drive.google.com/drive/folders/178c-yMzNPj013dpEwkcDAQHArxjTa1HZ?usp=sharing). These are all the icons and images we will need for the project.
63 |
--------------------------------------------------------------------------------
/src/content/unit-testing-mutations.md:
--------------------------------------------------------------------------------
1 | ---
2 | path: /unit-testing-mutations
3 | title: Unit Testing Mutations
4 | tag: "backend "
5 | date: 2020-05-17T19:50:01.039Z
6 | part: Building backend
7 | chapter: Unit testing
8 | postnumber: 19
9 | ---
10 |
11 | In this chapter we will be unit testing our mutation.
12 |
13 | In the `tests` folder, create a `makeABooking.test.js` file and add the following:
14 |
15 | ```javascript
16 | import { makeABooking } from "../src/resolvers/mutation";
17 |
18 |
19 | describe("Make a booking", () => {
20 | test("Successfully able to make a booking", async () => {
21 | const args = {
22 | listingId: "a114dded-ddef-4052-a106-bb18b94e6b51",
23 | bookingDate: "24-Apr-20",
24 | size: 2,
25 | customerEmail: "angela@dundler.com",
26 | customers: [
27 | {
28 | name: "Dwight",
29 | surname: "Shrut",
30 | passportNumber: "3333344",
31 | physioScore: "454",
32 | },
33 | {
34 | name: "Pam",
35 | surname: "Papper",
36 | passportNumber: "34354",
37 | physioScore: "2945",
38 | },
39 | ],
40 | };
41 |
42 | const context = "context";
43 |
44 | const response = await makeABooking(args, context);
45 |
46 | console.log(response);
47 | });
48 |
49 | }
50 | ```
51 |
52 | 💣 We are creating a mutation in the `args` with the necessary data.
53 |
54 | 💣 Next we are making sure we get back a `bookingId` that the charge matches what we expect it to be.
55 |
56 | Then go to your terminal and run the test:
57 |
58 | ```javascript
59 | $ yarn test makeABooking.test.js
60 | ```
61 |
62 | Now all our tests are passing!
63 |
--------------------------------------------------------------------------------
/src/content/unit-testing-queries.md:
--------------------------------------------------------------------------------
1 | ---
2 | path: /unit-testing-queries
3 | title: Unit Testing Queries
4 | tag: backend
5 | date: 2020-05-17T19:38:56.861Z
6 | part: Building backend
7 | chapter: Unit testing
8 | postnumber: 18
9 | ---
10 |
11 | In this part I'm going to show you how to write Unit tests for your functions. In theory We should've done this first, however, I wanted the GraphQL bits to go down first before we nailing down the unit tests.
12 |
13 | In the `tests` folder create a file called `getAllListings.test.js` and add the following:
14 |
15 | ```javascript
16 | import { getAllListings, getAListing } from "../src/resolvers/query";
17 | describe("All Listings", () => {
18 | test("brings back all listings", async () => {
19 | const args = "args";
20 | const context = "context";
21 |
22 | const response = await getAllListings(args, context);
23 | expect(response[0]).toHaveProperty("listingId");
24 | expect(response.length).toBeGreaterThan(1);
25 | });
26 |
27 | }
28 | ```
29 |
30 | 🎯 Jest allows us to first describe the test, then we can call the test function with whatever we want to test.
31 |
32 | 🎯 In our case we are first making sure it brings back the listings. We call the `getAllListings` Query and make sure the response has a `listingId` and it is greater than 1
33 |
34 | Next we can test the `getAListing` function:
35 |
36 | ```javascript
37 | test("brings a listing", async () => {
38 | const args = { listingId: "a114dded-ddef-4052-a106-bb18b94e6b51" }
39 | const context = "context"
40 |
41 | const response = await getAListing(args, context)
42 | expect(response.listingId).toEqual(args.listingId)
43 | })
44 | ```
45 |
46 | 🎯 We are making sure that the listing that comes back matches the `listingId` we supplied to the query.
47 |
48 | Now if you run the following the test should pass:
49 |
50 | ```javascript
51 | $ yarn test getAllListings.test.js
52 | ```
53 |
54 | Next lets test our mutations!
55 |
--------------------------------------------------------------------------------
/src/frameworkContext.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 |
3 | export const frameworks = {
4 | react: "react",
5 | vue: "vue",
6 | }
7 |
8 | export const FrameworkContext = React.createContext({})
9 |
--------------------------------------------------------------------------------
/src/frameworkProvider.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react"
2 | import { FrameworkContext } from "./frameworkContext"
3 |
4 | const FrameworkProvider = ({ children }) => {
5 | const [framework, setFramework] = useState("vue")
6 |
7 | const toggleFramework = selectedFramework => {
8 | setFramework(selectedFramework)
9 | }
10 |
11 | return (
12 |
13 | {children}
14 |
15 | )
16 | }
17 |
18 | export default FrameworkProvider
19 |
--------------------------------------------------------------------------------
/src/html.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import PropTypes from "prop-types"
3 |
4 | export default function HTML(props) {
5 | return (
6 |
7 |
8 |
9 |
10 |
14 | {props.headComponents}
15 |
26 |
27 |
44 |
45 |
46 | {props.preBodyComponents}
47 |
52 | {props.postBodyComponents}
53 |
54 |
55 | )
56 | }
57 |
58 | HTML.propTypes = {
59 | htmlAttributes: PropTypes.object,
60 | headComponents: PropTypes.array,
61 | bodyAttributes: PropTypes.object,
62 | preBodyComponents: PropTypes.array,
63 | body: PropTypes.string,
64 | postBodyComponents: PropTypes.array,
65 | }
66 |
--------------------------------------------------------------------------------
/src/images/Upload.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/images/ant.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Fullstack-Serverless-GraphQL/fullstack-serverless-graphql-docs/a99346a73a7f87fc1ea26839f1f7627989d8cd3e/src/images/ant.jpg
--------------------------------------------------------------------------------
/src/images/ant.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/images/apollo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/images/aws-cognito.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/images/aws-dynamodb.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/images/aws-lambda.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/images/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Fullstack-Serverless-GraphQL/fullstack-serverless-graphql-docs/a99346a73a7f87fc1ea26839f1f7627989d8cd3e/src/images/favicon.png
--------------------------------------------------------------------------------
/src/images/fsgql_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Fullstack-Serverless-GraphQL/fullstack-serverless-graphql-docs/a99346a73a7f87fc1ea26839f1f7627989d8cd3e/src/images/fsgql_logo.png
--------------------------------------------------------------------------------
/src/images/gatsby-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Fullstack-Serverless-GraphQL/fullstack-serverless-graphql-docs/a99346a73a7f87fc1ea26839f1f7627989d8cd3e/src/images/gatsby-icon.png
--------------------------------------------------------------------------------
/src/images/github.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/src/images/graphql.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/images/launch.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/images/postmark.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Fullstack-Serverless-GraphQL/fullstack-serverless-graphql-docs/a99346a73a7f87fc1ea26839f1f7627989d8cd3e/src/images/postmark.jpeg
--------------------------------------------------------------------------------
/src/images/react-icon.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/images/seed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Fullstack-Serverless-GraphQL/fullstack-serverless-graphql-docs/a99346a73a7f87fc1ea26839f1f7627989d8cd3e/src/images/seed.png
--------------------------------------------------------------------------------
/src/images/serverless.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Fullstack-Serverless-GraphQL/fullstack-serverless-graphql-docs/a99346a73a7f87fc1ea26839f1f7627989d8cd3e/src/images/serverless.png
--------------------------------------------------------------------------------
/src/images/serverless.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/images/stripe.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/images/tailwind.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Fullstack-Serverless-GraphQL/fullstack-serverless-graphql-docs/a99346a73a7f87fc1ea26839f1f7627989d8cd3e/src/images/tailwind.png
--------------------------------------------------------------------------------
/src/images/tailwind.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/images/vue.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pages/404.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 |
3 | import Layout from "../components/layout"
4 | import SEO from "../components/seo"
5 |
6 | const NotFoundPage = () => (
7 |
8 |
9 |
NOT FOUND
10 |
You just hit a route that doesn't exist... the sadness.